HàPhan 河

All new SwiftUI Property Wrappers in iOS 14

There are some new property wrappers added to SwiftUI.
I will explain them by showing you the Old way and the New way to complete the same task.

@StateObject

It's similar to @ObservedObject, when you want to monitor value of Object and reflex the change immediately.
But there's a small different between them.

You should use @StateObject for any observable properties that you initialize in the view that uses it. If the ObservableObject instance is created externally and passed to the view that uses it mark your property with @ObservedObject.

class WrappedModel : ObservableObject {
    @Published var name: String = "Hallo"
}

Old way

struct WrappedView : View {
    //Should store object somewhere not inside the View
    @ObservedObject var model: WrappedModel
    
    var body : some View {
        VStack {
            Button {
                model.name = "Changed"
            } label: {
                Label(model.name, systemImage: "chevron.left")
            }
        }
    }
}

New way

struct WrappedView : View {
    //Can initialized here, the model is kept when View reloaded
    @StateObject var model = WrappedModel()
    
    var body : some View {
        VStack {
            Button {
                model.name = "Changed"
            } label: {
                Label(model.name, systemImage: "hand.point.right")
            }
        }
    }
}

@AppStorage

The wrapper makes usage of UserDefaults easy as pie.

Old Way

class WrappedModel : ObservableObject {
    @Published var name: String
    
    private let nameKey = "app.user.display.name"
    
    init() {
        name = UserDefaults.standard.value(forKey: nameKey) as? String ?? "Anonymous"
    }
    
    func change(_ name: String) {
        UserDefaults.standard.setValue(name, forKey: nameKey)
        loadName()
    }
    
    func loadName() {
        name = UserDefaults.standard.value(forKey: nameKey) as? String ?? "Anonymous"
    }
}

struct WrappedView : View {
    @StateObject var model: WrappedModel = WrappedModel()
    
    var body : some View {
        VStack {
            Button {
                model.change("Kristine")
            } label: {
                Label(model.name, systemImage: "hand.point.right")
            }
        }
    }
}

New Way

struct WrappedView : View {
    @AppStorage("app.user.display.name") var userName = "Anonymous"
    
    //Or @AppStorage("username", store: UserDefaults(suiteName: "group.com.soyo.user.name")) var userName: String = "Anonymous"
    
    var body : some View {
        VStack {
            Button {
                userName = "Sarah"
                
                //Or UserDefaults.standard.set("Sarah", forKey: "app.user.display.name")
            } label: {
                Label(userName, systemImage: "hand.point.right")
            }
        }
    }
}

@ScaledMetric

Before working with this wrapper, let take a research about Dynamic Font type.
https://developer.apple.com/documentation/uikit/uifont/scaling_fonts_automatically

User can change their device's font size dynamictically. It's better UX if our app support that.

s1

@ScaledMetric makes it easy for some hard-code values. Take a look at example:

struct SampleView: View {
    @ScaledMetric var imageSize: CGFloat = 100

    var body: some View {
        Image(systemName: "ant")
            .resizable()
            .frame(width: imageSize, height: imageSize)
            .foregroundColor(.green)
    }
}

Before:

s1 s2

After:

large1l2

Uhm, many more changes will come to SwiftUI soon, like fully support MapView. Just believe our iOS Development will be easier every year.

Happy coding.

Comments