New Property Wrappers in SwiftUI

Hello, Khabrovites! At the end of August, a new group of professional basic course starts at OTUS “IOS Developer”… As always, we share a useful translation and invite you to free online events: “Open Day” and “Quick start in IOS development”


WWDC20 brings a lot of new features to SwiftUI, which I’m going to share with you on my blog over the next weeks. Today I want to start with the main additions to SwiftUI data streams in the form of new Property Wrappers. @StateObject, @AppStorage, @SceneStorage and @ScaledMetric

If you are not familiar with the existing property wrappers that SwiftUI provides, I advise you to start by posting “Understanding Property Wrappers in SwiftUI”

StateObject

As you remember, SwiftUI provides us with a property wrapper @ObservedObjectwhich allows us to observe changes in the data model, which is outside the SwiftUI framework. For example, this could be data that you retrieve from a web service or local database. The main problem @ObservedObject there was a life cycle. You have to store it somewhere outside of SwiftUI in order to keep it during view updates, like in SceneDelegate or AppDelegate… Otherwise, under certain circumstances, you may lose the data for which the @ObservedObject

New property wrapper StateObject fixes the most significant gap in SwiftUI data flow management. SwiftUI only creates one instance StateObject for each container instance you declare and stores it in the framework’s internal memory, which stores it when the view is updated. StateObject works very much like a property wrapper State, but instead of value types, it is designed to work with reference types.

struct CalendarContainerView: View {
    @StateObject var viewModel = ViewModel()

    var body: some View {
        CalendarView(viewModel.dates)
            .onAppear(perform: viewModel.fetch)
    }
}

AppStorage

AppStorage another new property wrapper that we got this year. This is an ideal way to store a small amount of key-value data associated with UserDefaultsAppStorageDynamicPropertywhich means that SwiftUI will update your views as soon as the value of a given key is updated in UserDefaultsAppStorage ideal for storing application settings. Let’s see how we can use it.

enum Settings {
    static let notifications = "notifications"
    static let sleepGoal = "sleepGoal"
}

struct SettingsView: View {
    @AppStorage(Settings.notifications) var notifications: Bool = false
    @AppStorage(Settings.sleepGoal) var sleepGoal: Double = 8.0

    var body: some View {
        Form {
            Section {
                Toggle("Notifications", isOn: $notifications)
            }

            Section {
                Stepper(value: $sleepGoal, in: 6...12) {
                    Text("Sleep goal is (sleepGoal, specifier: "%.f") hr")
                }
            }
        }
    }
}

In the above example, we create a settings screen using a property wrapper AppStorage and presentation Form… We can now access our settings anywhere in the application with AppStorageand as soon as we change any values ​​there, SwiftUI will update the views.

struct ContentView: View {
    @AppStorage(Settings.sleepGoal) var sleepGoal = 8
    @StateObject var store = SleepStore()

    var body: some View {
        WeeklySleepChart(store.sleeps, goal: sleepGoal)
            .onAppear(perform: store.fetch)
    }
}

SceneStorage

This year we got a lot of possibilities for managing scenes in SwiftUI without UIKit. As a result, we have a new property wrapper SceneStoragewhich allows us to implement correct state restoration for our scenes. SceneStorage works similarly AppStoragebut instead of UserDefaults uses separate storage for each scene. This means that each scene has its own storage that other scenes cannot access. The system is completely responsible for managing the storage for each scene and you don’t have access to the data without wrapping the property SceneStorage

struct ContentView: View {
    @SceneStorage("selectedTab") var selection = 0

    var body: some View {
        TabView(selection: $selection) {
            Text("Tab 1").tag(0)
            Text("Tab 2").tag(1)
        }
    }
}

I recommend using SceneStorage to store scene-specific data, such as a set of tabs or the index of a selected book, in a reading application. IOS is currently very aggressive in terms of killing apps in the background, and state restoration is a key solution to ensure a positive user experience.

ScaledMetric

Another new property wrapper is ScaledMetricScaledMetric allows us to scale any binary float relative to the size category Dynamic Type… For example, in your application, it is very easy to change the spacing according to the size category Dynamic Type… Let’s take a look at a small example.

struct ContentView: View {
    @ScaledMetric(relativeTo: .body) var spacing: CGFloat = 8

    var body: some View {
        VStack(spacing: spacing) {
            ForEach(0...10, id: .self) { number in
                Text(String(number))
            }
        }
    }
}

As soon as the user changes the settings Dynamic TypeSwiftUI will scale the spacing value and update the view.

Conclusion

Today we learned about the new property wrappers in SwiftUI. I believe we now have enough data flow property wrappers to cover whatever logic we need to implement our applications. Feel free to follow me on Twitter and ask your questions related to this article. Thank you for your attention and have a nice week!


Learn more about the basic course “IOS Developer”


Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *