Wednesday, October 14, 2026
HomeiOS DevelopmentWriting customized property wrappers for SwiftUI – Donny Wals

Writing customized property wrappers for SwiftUI – Donny Wals

[ad_1]

It has been some time since I revealed my publish that helps you wrap your head round Swift’s property wrappers. Since then, I’ve carried out increasingly SwiftUI associated work and one problem that I not too long ago needed to dig into was passing dependencies from SwiftUI’s setting right into a customized property wrapper.

Whereas figuring this out I realized concerning the DynamicProperty protocol which is a protocol that you would be able to conform your property wrappers to. When your property wrapper conforms to the DynamicProperty protocol, your property wrapper will primarily develop into part of your SwiftUI view. Which means your property wrapper can extract values out of your SwiftUI setting, and your SwiftUI view will ask your property wrapper to replace itself at any time when it is about to judge your view’s physique. You may even have @StateObject properties in your property wrapper to set off updates in your views.

This protocol is not restricted to property wrappers, however for the sake of this publish I’ll discover DynamicProperty within the context of a property wrapper.

Understanding the fundamentals of DynamicProperty

Defining a dynamic property is so simple as conforming a property wrapper to the DynamicProperty protocol. The one requirement that this protocol has is that you simply implement an replace methodology that is referred to as by your view at any time when it is about to judge its physique. Defining such a property wrapper seems as follows:

@propertyWrapper
struct MyPropertyWrapper: DynamicProperty {
  var wrappedValue: String

  func replace() {
    // referred to as at any time when the view will consider its physique
  }
}

This instance by itself is not very helpful after all. I will present you a extra helpful customized property wrapper in a second. Let’s go forward and take a second to discover what we have outlined right here first.

The property wrapper that we have outlined right here is fairly easy. The wrapped worth for this wrapper is a string which implies that it will be used as a string in our SwiftUI view. If you wish to be taught extra about how property wrappers work, check out this publish I revealed on the subject.

There’s additionally an replace methodology that is referred to as at any time when SwiftUI is about to judge its physique. This perform permits you to replace state that exists exterior to your property wrapper. You’ll typically discover that you simply don’t must implement this methodology in any respect except you’re doing a bunch of extra advanced work in your property wrapper. I’ll present you an instance of this in direction of the tip of the article.

⚠️ Word that I’ve outlined my property wrapper as a struct and never a category. Defining a DynamicProperty property wrapper as a category is allowed, and works to some extent, however in my expertise it produces very inconsistent outcomes and lots of stuff you would possibly count on to work don’t truly work. I’m not fairly positive precisely why that is the case, and what SwiftUI does to interrupt property wrappers that had been outlined as lessons. I simply know that structs work, and lessons don’t.

One fairly neat factor a couple of DynamicProperty is that SwiftUI will decide it up and make it a part of the SwiftUI setting. What this implies is that you’ve got entry to setting values, and that you would be able to leverage property wrappers like @State and @ObservedObject to set off updates from inside your property wrapper.

Let’s go forward and outline a property wrapper that maintain on to some state and updates the view at any time when this state modifications.

Triggering view updates out of your dynamic property

Dynamic properties on their very own can’t inform a SwiftUI view to replace. Nonetheless, we are able to use @State, @ObservedObject, @StateObject and different SwiftUI property wrappers to set off view updates from inside a customized property wrapper.

A easy instance would look somewhat bit like this:

@propertyWrapper
struct CustomProperty: DynamicProperty {
    @State non-public var worth = 0

    var wrappedValue: Int {
        get {
            return worth
        }

        nonmutating set {
            worth = newValue
        }
    }
}

This property wrapper wraps an Int worth. Each time this worth receives a brand new worth, the @State property worth is mutated, which can set off a view replace. Utilizing this property wrapper in a view seems as follows:

struct ContentView: View {
    @CustomProperty var customProperty

    var physique: some View {
        Textual content("Depend: (customProperty)")

        Button("Increment") {
            customProperty += 1
        }
    }
}

The SwiftUI view updates the worth of customProperty at any time when a button is tapped. This by itself doesn’t set off a reevaluation of the physique. The rationale our view updates is as a result of the worth that represents our wrapped worth is marked with @State. What’s neat about that is that if worth modifications for any cause, our view will replace.

A property wrapper like this isn’t notably helpful, however we are able to do some fairly neat issues to construct abstractions round totally different varieties of information entry. For instance, you might construct an abstraction round UserDefaults that gives a key path primarily based model of AppStorage:

class SettingKeys: ObservableObject {
    @AppStorage("onboardingCompleted") var onboardingCompleted = false
    @AppStorage("promptedForProVersion") var promptedForProVersion = false
}

@propertyWrapper
struct Setting<T>: DynamicProperty {
    @StateObject non-public var keys = SettingKeys()
    non-public let key: ReferenceWritableKeyPath<SettingKeys, T>

    var wrappedValue: T {
        get {
            keys[keyPath: key]
        }

        nonmutating set {
            keys[keyPath: key] = newValue
        }
    }

    init(_ key: ReferenceWritableKeyPath<SettingKeys, T>) {
        self.key = key
    }
}

Utilizing this property wrapper would look as follows:

struct ContentView: View {
    @Setting(.onboardingCompleted) var didOnboard

    var physique: some View {
        Textual content("Onboarding accomplished: (didOnboard ? "Sure" : "No")")

        Button("Full onboarding") {
            didOnboard = true
        }
    }
}

Anywhere within the app that makes use of @Setting(.onboardingCompleted) var didOnboard will robotically replace when the worth for onboarding accomplished in person defaults up to date, no matter the place / how this occurred. That is precisely the identical as how @AppStorage works. Actually, my customized property wrapper depends closely on @AppStorage below the hood.

My SettingsKeys object wraps up the entire totally different keys I need to write to in UserDefaults, the @AppStorage property wrapper permits for straightforward commentary and makes it in order that I can outline SettingsKeys as an ObservableObject with none troubles.

By implementing a customized get and set on my Setting<T>‘s wrappedValue, I can simply learn values from person defaults, or write a brand new worth just by assigning to the suitable key path in SettingsKeys.

Easy property wrappers like these are very helpful whenever you need to streamline a few of your information entry, or if you wish to add some semantic which means and ease of discovery to your property wrapper.

To see some extra examples of easy property wrappers (together with a person defaults one which impressed the instance you simply noticed), check out this publish from Dave Delong.

Utilizing SwiftUI setting values in your property wrapper

Along with triggering view updates through a few of SwiftUI’s property wrappers, it’s additionally doable to entry the SwiftUI setting for the view that your property wrapper is utilized in. That is extremely helpful when your property wrapper is extra advanced and has a dependency on, for instance, a managed object context, a networking object, or related objects.

Accessing the SwiftUI setting from inside your property wrapper is completed in precisely the identical approach as you do it in your views:

@propertyWrapper
struct CustomFetcher<T>: DynamicProperty {
    @Atmosphere(.managedObjectContext) var managedObjectContext

    // ...
}

Alternatively, you may learn setting objects that had been assigned by your view with the .environmentObject view modifier as follows:

@propertyWrapper
struct UsesEnvironmentObject<T: ObservableObject>: DynamicProperty {
    @EnvironmentObject var envObject: T

    // ...
}

We are able to use the setting to conveniently move dependencies to our property wrappers. For instance, let’s say you’re constructing a property wrapper that fetches information from the community. You may need an object named Networking that may carry out community calls to fetch the info you want. You possibly can inject this object into the property wrapper by the setting:

@propertyWrapper
struct FeedLoader<Feed: FeedType>: DynamicProperty {
    @Atmosphere(.community) var community

    var wrappedValue: [Feed.ObjectType] = []
}

The community setting secret is a customized key that I’ve added to my SwiftUI setting. To be taught extra about including customized values to the SwiftUI setting, check out this tip I posted earlier.

Now that we’ve got this property wrapper outlined, we’d like a solution to fetch information from the community and assign it to one thing so as to replace our wrapped worth. To do that, we are able to implement the replace methodology that enables us to replace information that’s referenced by our property wrapper if wanted.

Implementing the replace methodology in your property wrapper

The replace methodology that’s a part of the DynamicProperty protocol permits you to reply to SwiftUI’s physique evaluations. Each time SwiftUI is about to judge a view’s physique, it should name your dynamic property’s replace methodology.

💡 To be taught extra about how and when SwiftUI evaluates your view’s physique, check out this publish the place I discover physique analysis in depth.

As talked about earlier, you received’t typically have a must implement the replace methodology. I’ve personally discovered this methodology to be helpful at any time when I wanted to kick off some type of information fetching operation. For instance, to fetch information from Core Information in a customized implementation of @FetchRequest, or to experiment with fetching information from a server. Let’s develop the FeedLoader property wrapper from earlier a bit to see what an information loading property wrapper would possibly appear to be:

@propertyWrapper
struct FeedLoader<Feed: FeedType>: DynamicProperty {
    @Atmosphere(.community) var community
    @State non-public var feed: [Feed.ObjectType] = []
    non-public let feedType: Feed
    @State var isLoading = false

    var wrappedValue: [Feed.ObjectType] {
        return feed
    }

    init(feedType: Feed) {
        self.feedType = feedType
    }

    func replace() {
        Activity {
            if feed.isEmpty && !isLoading {
                self.isLoading = true
                self.feed = attempt await community.load(feedType.endpoint)
                self.isLoading = false
            }
        }
    }
}

It is a very, quite simple implementation of a property wrapper that makes use of the replace methodology to go to the community, load some information, and assign it to the feed property. We now have to make it possible for we solely load information if we’re not already loading, and we additionally must examine whether or not or not the feed is presently empty to keep away from loading the identical information each time SwiftUI decides to re-evaluate our view’s physique.

This after all begs the query, must you use a customized property wrapper to load information from the community? And the reply is, I’m undecided. I’m nonetheless closely experimenting with the replace methodology, its limitations, and its advantages. One factor that’s essential to appreciate is that replace is known as each time SwiftUI is about to judge a view’s physique. So synchronously assigning a brand new worth to an @State property from inside that methodology might be not one of the best concept; Xcode even reveals a runtime warning once I do that.

At this level I’m pretty positive Apple supposed replace for use for updating or studying state exterior to the property wrapper fairly than it getting used to synchronously replace state that’s inner to the property wrapper.

Alternatively, I’m optimistic that @FetchRequest makes heavy use of replace to refetch information at any time when its predicate or type descriptors have modified, most likely in a considerably related approach that I’m fetching information from the community right here.

Abstract

Writing customized property wrappers in your SwiftUI views is a enjoyable train and it’s doable to put in writing some very handy little helpers to enhance your expertise whereas writing SwiftUI views. The undeniable fact that your dynamic properties are linked to the SwiftUI view lifecycle and setting is tremendous handy as a result of it permits you to set off view updates, and browse values from SwiftUI’s setting.

That stated, documentation on a few of the particulars surrounding DynamicProperty is severely missing which implies that we are able to solely guess how mechanisms like replace() are alleged to be leverages, and why structs work completely high-quality as dynamic properties however lessons don’t.

These are some factors that I hope to have the ability to develop on sooner or later, however for now, there are positively nonetheless some mysteries for me to unravel. And that is the place you are available! If in case you have any additions for this posts, or if in case you have a strong understanding of how replace() was meant for use, be happy to ship me a Tweet. I’d love to listen to from you.



[ad_2]

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments