The doc:
init(wrappedValue:)
Creates the state with an initial wrapped value.
init(initialValue:)
Creates the state with an initial value.
why these two init's, they seem to be the same, but there must be some reason for this?
The doc:
init(wrappedValue:)
Creates the state with an initial wrapped value.
init(initialValue:)
Creates the state with an initial value.
why these two init's, they seem to be the same, but there must be some reason for this?
The documentation for init(wrappedValue:)
indicates that you should never call it. It is used by the declaration of state property:
Discussion
You don’t call this initializer directly. Instead, declare a property with the
@State
attribute, and provide an initial value; for example,@State private var isPlaying: Bool = false
.
I saw the "discussion". I still like to know what's the difference between the two. Despite "don't call this initializer directly", you can call it anyway and I see code that calls this.
Anyway, init(wrappedValue:)
"don't call directly", init(initialValue:)
does not say this. There must be some difference? What is it?
init(initialValue:)
was the first name chosen when the original Property Wrappers proposal was released. It was changed to init(wrappedValue:)
but the original one was left for backwards compatibility.
That's my initial thought, but doesn't the SE-0258 review ends a good while before the announcement of SwiftUI, and most definitely long before the beta test ends?
Yeah, SwiftUI could have removed the original one before shipping. But now that it's shipped, it's ABI and cannot be removed.
Should init(initialValue:)
be "deprecated"?
And the discussion "don't call directly" is not good because you need to call it directly sometimes.
I don't know where you got this from as this isn't correct. You can and must use it if you need to initialize the property wrapper by hand. If your PW has an init(initialValue:)
overload, you should just ignore it and prefer init(wrappedValue:)
, that's all.
In Combine
framework, they had this issue for a whole year. Basically they shipped with only init(initialValue:)
but added init(wrappedValue:)
a year later and also back ported it using @_alwaysEmitIntoClient
.
According to Swift Programming Language Guide, Swift can use init(wrappedValue:)
to initialize a property wrapper when you apply the wrapper to a property. For example, if you created a property wrapper named SmallNumber
, you can do this in code:
struct BigRectangle {
@SmallNumber var width: Int = 1
@SmallNumber var height: Int = 1
}
In the code above, when you write = 1
on a property with a wrapper, that’s translated into a call to the init(wrappedValue:)
initializer by Swift.
According to the documentation, you should use
init(initialValue:)
to create a state with an initial value.
Apple Documentation: Apple Developer Documentation
That is an API contract not a general PW thing. State
has some special framework specific behavior which they communicate through documentation and WWDC videos. If you use the init
to inject value from a parent, you will tap into bugs.
The following example is totally valid:
@propertyWrapper
struct Wrapper {
let wrappedValue: Int // `init(wrappedValue:)` synthesized
}
struct T {
@Wrapper
var a: Int = 0
@Wrapper
var b: Int
init(b: Int) {
self._b = Wrapper(wrappedValue: b * 2)
}
}
Long story short, ignore init(initialValue:)
on property wrapper types that has been already shipped. It's likely you never will see such an init
on future property wrappers unless it provides the user some totally different semantics.
Except we have to use direct initialization if we want to provide an initial value. Initializing just the wrapped value doesn't trigger the wrapper's effects. Is there an alternate solution here?
I don‘t understand the question, do you have a small example of what you want to achieve and how it‘s not possible/working?!
Say you have a View
with a Picker
inside and want to set the initial selection from outside the view. Just passing the selection and setting it on the state property doesn’t work, but initializing the State
wrapper directly does.
I don't understand what you're saying...show sample code would clear things for me.
That‘s an anti pattern anyway because of the re-injection for State
. You should create a model from a different place and pass it down. Again, that‘s not a property wrapper issue.
Whatever model I pass down would have to be translated into a binding for the Picker
, so I'm not sure what you're suggesting here.
struct MyView: View {
class Model: ObservableObject {
@Published
var selection: Int
init(selection: Int) {
self.selection = selection
}
}
@ObservedObject
var model: Model
var body: some View {
Picker(selection: $model.selection, ...)
}
}
Initialize the model from the parent for example using StateObject
to persist it, pass it down and use the property wrappers projected value to obtain a binding.
So to merely set the default value of a Picker
we need to create a separate type, own an instance outside the view that cares about it, inject it into the view, and use that binding? Even with the new @StateObject
, that doesn't seem right.
That‘s the way to do it if you really want to provide a different initial value than a constant default value for MyView
.