When you have multiple views in your SwiftUI App, you often need to share data from one view to the next one. If you've been programming for iOS/iPadOS for a while now, you know that this is something we used to do using a
prepareForSegue. However, if you're a newbie, you won't have any problem following this tutorial. There is no need for any previous knowledge of UIKit to pass data between Views using SwiftUI.
This tutorial is the second of a series that explores 4 different solutions for passing data between views:
- Passing Data between Views using a property
- Passing Data between Views using @State and @Binding
- Passing data via the view’s environment
- Passing data via
Last time we have explored how to pass data using a property from a primary view to a modally presented secondary view in SwiftUI. This time we are going to make sure that primary and secondary views have shared data. Changing the data in the second view will mean changing it in the first view and vice versa! Let's go.
To demonstrate how it operates, we will consider an example where navigation from one screen to another is possible using a
NavigationLink (Hierarchical Navigation).
Our goal is to create:
- a first View, called LightBulb, consisting of turned on or off the light bulb and a link to a second View.
- a second View, called ControlRoom, that can control the switching on and off of the light bulb in the first View.
By structuring the
LightBulbView and the
ControlView independently and then creating a hierarchical navigation system using a
NavigationLink you would probably end up with code designed more or less like the following.
LightBulbView.swift consisting of:
NavigationViewfor presenting a stack of views that represents a visible path in a navigation hierarchy.
VStackthat arranges an
NavigationLinkin a vertical line.
Image is a light bulb SF Symbol colored gray if the boolean variable
isOn is false or yellow if the boolean variable
isOn is true. This is obviously intended to simulate turning the light bulb on and off.
NavigationLink allows the navigation to ControlView, using the latter as a destination of the
NavigationLink. A simple
Text is used as a label.
ControlView.swift consisting of:
VStackthat arranges a
Textin a vertical line.
Button toggles the boolean variable
lightIsOn from on to off and vice versa. A circular bolt SF Symbol is used as a label.
Text is used as an explicit description of the action the
Button will perform when tapped.
The code, as just outlined, is formally correct but logically incorrect. Tapping on the
Button in the second screen has no effect on the light bulb on the first screen.
LightBulbView is not responding to the change made in
ControlView. Why? Let’s troubleshoot it together.
Introducing @State and @Binding
The reason for the above misbehavior is straightforward. We are passing a boolean
isOn from the first to the second screen using a property. What we need is to create a stronger bond between the two.
ControlView needs not only to have a copy of the property but to have a stronger link to it.
Basically, when it comes to these cases, you don't pass a boolean to the secondary view using a simple property, but you pass a binding to a boolean to the secondary view.
Let’s see how to fix our code then:
1. As a first step, we use
@Binding instead of
@State to define the boolean variable
isOn in the
2. Immediately after step 1, you will get an error pointing to
LightBulb.swift. This has to do with the fact that we are using the initializer of
ControlView and we have just changed its structure. Xcode is warning us it can't convert
isOn from a
Bool to a
Binding<Bool>. We need a preliminary step before we can solve the issue completely. We need first a property wrapper
@State for the boolean variable
isOn. Not only will this help solve our problem, but it also makes sense because every time that boolean
isOn changes, the view is reloaded, and the light bulb SF Symbol get's colored gray (when
isOn is false) or yellow (when
isOn is true).
3. We can now finally pass a binding to a boolean to
ControlView. We can get the binding we need from the
@State we just added in step 2, just using a
$ in front of
isOn in the initializer pointed by the warning message.
You can bind to property wrappers that expose a binding through their projected value. For example, every property marked with @State provides a binding via $propertyName.
4. In other words,
isOn is now shared between
LightBulbView and the
ControlRoomView view because of the binding. If you compile the code, you should be able to turn on/off the light bulb in the primary view, using the button in the secondary view.
In 4 simple steps, we made sure that a secondary view,
ControlRoomView, not only received data from a primary view,
LightBulbView, but that these two shared this data.
For your convenenience, below you find the code for both files.