Difference between @Binding and Binding Type in Swift
You're correct that @Binding
is a property wrapper. More specifically, it's a DynamicProperty
, which means that it has the ability to notify its parent View
to trigger a render when its value changes.
@Binding
does this while maintaining a fairly transparent interface to the underlying Bool
. For example, in a View
where you had @Binding var hidden: Bool
defined as a property, you could write hidden = false
or Text(hidden ? "hidden" : "visible")
, just as if hidden
were a regular Bool
. If you want to get access to the underlying Binding<Bool>
, you can use $
:
$hidden //<-- Binding<Bool>
In your second example, Binding<Bool>
is not "type casting", but rather "type annotation" -- by writing var hidden: Binding<Bool>
, you're telling the compiler that hidden
is Binding<Bool>
. Because it's Binding<Bool>
and not just a Bool
(and not a @Binding
), you can't treat it like you would if it were just a Bool
. For example, hidden = false
will not work with Binding<Bool>
. Instead, to access the underlying Bool
value, you can use its .wrappedValue
property: hidden.wrappedValue = false
.
The two are are very similar, but different in a couple of important ways (like those detailed above). In practical terms:
- If you're using a binding as a property on a
View
, you'll likely end up using@Binding
. - If you're the binding it outside of a view (and thus don't have use of the DynamicProperty aspect), you'll likely use
Binding<Bool>
(technically, nothing is stopping you from using@Binding
outside of aView
, but it's a semantically odd decision).
SwiftUI – @State vs @Binding
SwiftUI is a declarative Component-Oriented framework. You have to forget about MVC where you have controllers mediating between view and model. SwiftUI uses diffing algorithm to understand changes and update only corresponding views.
@State
- A State property is connected to the view. A State property is permanently being read by the view. That means that every time the @State property gets changed/updated, the view gets re-rendered and eventually displays the content depending on the @State's data.
- State is accessible only to a particular view.
- Simple properties like strings, integers and booleans belongs to a single view - mark as private.
- All the fields marked as State are stored in special separated memory, where only corresponded view can access and update them.
@Binding
- BindableObject protocol, which requires a didChange property. It makes possible to use it inside Environment and rebuild view as soon as it changes.
- The didChange property should be a Publisher, which is a part of a new Apple’s Reactive framework called Combine.
- The main goal of Publisher is to notify all subscribers when something changes. As soon as new values appear, SwiftUI will rebuild Views.
@EnvironmentObject
- It is a part of feature called Environment. You can populate your Environment with all needed service classes and then access them from any view inside that Environment.
- @EnvironmentObject is accessible for every view inside the Environment.
- @EnvironmentObject Properties created elsewhere such as shared data. App crashes if it is missing.
- The Environment is the right way of Dependency Injection with SwiftUI.
SwiftUI: Preventing binding value from passing back up?
In Secondary
use:
@State var secondTime: String
and in ContentView
use:
Secondary(secondTime: time)
not $time
EDIT1:
If you want to click the button in ContentView
to change both views,
but Secondary
only changes itself, then try this approach:
struct Secondary: View {
@Binding var secondTime: String
@State var localTime: String = ""
var body: some View {
Text("Secondary time is \(localTime)") // <--- here
.onChange(of: secondTime) { newval in // <--- here
localTime = newval // <--- here
}
Button("Change time again from Secondary View") {
localTime = "3 oclock " + String(Int.random(in: 1..<100)) // <-- to show changes
}
}
}
struct ContentView: View {
@State var time: String = ""
var body: some View {
VStack (spacing: 55) {
Text("ContentView it is: \(time)")
Secondary(secondTime: $time)
Button("Change time") {
time = "2 oclock " + String(Int.random(in: 1..<100)) // <-- to show changes
}
}
}
}
How to optionally pass in a Binding in SwiftUI?
The main problem with what you want to achieve is that when the index is handled by the parent your View needs a @Binding to it, but when it handles the index itself it needs @State. There are two possible solutions.
If the view can ignore the index property when it doesn't have one:
struct ReusableView: View {
@Binding var index: Int?
init(_ index: Binding<Int?>) {
self._index = index
}
init() {
self._index = .constant(nil)
}
var body: some View {
VStack {
index.map { Text("The index is \($0)") }
}
}
}
The advantage is that it is very straightforward - just two initializers, but you cannot change the value of the index when it is handled by ResusableView itself (its a constant).
If the view cannot ignore the index property when it doesn't have one:
struct ReusableView: View {
private var content: AnyView
init(_ index: Binding<Int>? = nil) {
if let index = index {
self.content = AnyView(DependentView(index: index))
} else {
self.content = AnyView(IndependentView())
}
}
var body: some View {
content
}
private struct DependentView: View {
@Binding var index: Int
var body: some View {
Text("The index is \(index)")
}
}
private struct IndependentView: View {
@State private var index: Int = 0
var body: some View {
Text("The index is \(index)")
}
}
}
The clear advantage is that you have a view that can either be bound to a value or manage it as its own State. As you can see ReusableView is just a wrapper around two different views one managing its own @State and one being bound to @State of its parent view.
How does this SwiftUI binding + state example manage to work without re-invoking body?
On State change SwiftUI rendering engine at first checks for equality of views inside body and, if some of them not equal, calls body to rebuild, but only those non-equal views. In your case no one view depends (as value) on text
value (Binding is like a reference - it is the same), so nothing to rebuild at this level. But inside WrappedText
it is detected that Text
with new text
is not equal to one with old text
, so body of WrappedText
is called to re-render this part.
This is declared rendering optimisation of SwiftUI - by checking & validating exact changed view by equality.
By default this mechanism works by View struct properties, but we can be involved in it by confirming our view to Eqatable
protocol and marking it .equatable()
modifier to give some more complicated logic for detecting if View should be (or not be) re-rendered.
Related Topics
Exclude Swiftui Previews from Code Coverage
What's a Good Way to Iterate Backwards Through the Characters of a String
Swift: How to Expand a Tilde in a Path String
The Correct Way to Override an Initializer in Swift 1.1
How to Set an Ordered Relationship with Nspersistentcloudkitcontainer
Is There No Default(T) in Swift
Swift 3D Touch iOS 10 Home Screen Quick Actions Share Item Missing
Making Nsdecimalnumber Codable
Why Avplayer Downloading First Instead Live Streaming
Closure Containing a Declaration Cannot Be Used with Function Builder 'Viewbuilder'
How to Pass Structure by Reference
Swift Await/Async - How to Wait Synchronously for an Async Task to Complete
Mock Third Party Classes (Firebase) in Swift
Enable + Disable Auto-Layout Constraints
Swiftui - Remove Space Between Cells
Subclass of Skspritenode in .Sks File
Xcode 10 Beta 5 - Clang: Error: Linker Command Failed with Exit Code 1