Skip to content

Commit 26c351b

Browse files
authored
Optimize Model part documentation (#548)
1 parent d853dab commit 26c351b

File tree

12 files changed

+275
-34
lines changed

12 files changed

+275
-34
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# Managing user interface state
2+
3+
Encapsulate view-specific data within your app’s view hierarchy to make your
4+
views reusable.
5+
6+
## Overview
7+
8+
Store data as state in the least common ancestor of the views that need the data
9+
to establish a single source of truth that’s shared across views. Provide the
10+
data as read-only through a Swift property, or create a two-way connection to
11+
the state with a binding. OpenSwiftUI watches for changes in the data, and
12+
updates any affected views as needed.
13+
14+
![](https://docs-assets.developer.apple.com/published/c75c698bd113a4ac7c708e178f8294ca/managing-user-interface-state%402x.png)
15+
16+
Don’t use state properties for persistent storage because the life cycle of
17+
state variables mirrors the view life cycle. Instead, use them to manage
18+
transient state that only affects the user interface, like the highlight state
19+
of a button, filter settings, or the currently selected list item. You might
20+
also find this kind of storage convenient while you prototype, before you’re
21+
ready to make changes to your app’s data model.
22+
23+
### Manage mutable values as state
24+
25+
If a view needs to store data that it can modify, declare a variable with the
26+
``State`` property wrapper. For example, you can create an isPlaying Boolean inside
27+
a podcast player view to keep track of when a podcast is running:
28+
29+
```
30+
struct PlayerView: View {
31+
@State private var isPlaying: Bool = false
32+
33+
var body: some View {
34+
// ...
35+
}
36+
}
37+
```
38+
39+
Marking the property as state tells the framework to manage the underlying
40+
storage. Your view reads and writes the data, found in the state’s
41+
``wrappedValue`` property, by using the property name. When you change the
42+
value, OpenSwiftUI updates the affected parts of the view. For example, you can
43+
add a button to the PlayerView that toggles the stored value when tapped, and
44+
that displays a different image depending on the stored value:
45+
46+
```
47+
Button(action: {
48+
self.isPlaying.toggle()
49+
}) {
50+
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
51+
}
52+
```
53+
54+
Limit the scope of state variables by declaring them as private. This ensures
55+
that the variables remain encapsulated in the view hierarchy that declares them.
56+
57+
### Declare Swift properties to store immutable values
58+
59+
To provide a view with data that the view doesn’t modify, declare a standard
60+
Swift property. For example, you can extend the podcast player to have an input
61+
structure that contains strings for the episode title and the show name:
62+
63+
```
64+
struct PlayerView: View {
65+
let episode: Episode // The queued episode.
66+
@State private var isPlaying: Bool = false
67+
68+
var body: some View {
69+
VStack {
70+
// Display information about the episode.
71+
Text(episode.title)
72+
Text(episode.showTitle)
73+
74+
75+
Button(action: {
76+
self.isPlaying.toggle()
77+
}) {
78+
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
79+
}
80+
}
81+
}
82+
}
83+
```
84+
85+
While the value of the episode property is a constant for PlayerView, it doesn’t
86+
need to be constant in this view’s parent view. When the user selects a
87+
different episode in the parent, OpenSwiftUI detects the state change and
88+
recreates the PlayerView with a new input.
89+
90+
### Share access to state with bindings
91+
92+
If a view needs to share control of state with a child view, declare a property
93+
in the child with the ``Binding`` property wrapper. A binding represents a
94+
reference to existing storage, preserving a single source of truth for the
95+
underlying data. For example, if you refactor the podcast player view’s button
96+
into a child view called PlayButton, you can give it a binding to the isPlaying
97+
property:
98+
99+
```
100+
struct PlayButton: View {
101+
@Binding var isPlaying: Bool
102+
103+
var body: some View {
104+
Button(action: {
105+
self.isPlaying.toggle()
106+
}) {
107+
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
108+
}
109+
}
110+
}
111+
```
112+
113+
As shown above, you read and write the binding’s wrapped value by referring
114+
directly to the property, just like state. But unlike a state property, the
115+
binding doesn’t have its own storage. Instead, it references a state property
116+
stored somewhere else, and provides a two-way connection to that storage.
117+
118+
When you instantiate PlayButton, provide a binding to the corresponding state
119+
variable declared in the parent view by prefixing it with the dollar sign ($):
120+
121+
```
122+
struct PlayerView: View {
123+
var episode: Episode
124+
@State private var isPlaying: Bool = false
125+
126+
var body: some View {
127+
VStack {
128+
Text(episode.title)
129+
Text(episode.showTitle)
130+
PlayButton(isPlaying: $isPlaying) // Pass a binding.
131+
}
132+
}
133+
}
134+
```
135+
136+
The $ prefix asks a wrapped property for its projectedValue, which for state is
137+
a binding to the underlying storage. Similarly, you can get a binding from a
138+
binding using the $ prefix, allowing you to pass a binding through an arbitrary
139+
number of levels of view hierarchy.
140+
141+
You can also get a binding to a scoped value within a state variable. For
142+
example, if you declare episode as a state variable in the player’s parent view,
143+
and the episode structure also contains an isFavorite Boolean that you want to
144+
control with a toggle, then you can refer to $episode.isFavorite to get a
145+
binding to the episode’s favorite status:
146+
147+
```
148+
struct Podcaster: View {
149+
@State private var episode = Episode(title: "Some Episode",
150+
showTitle: "Great Show",
151+
isFavorite: false)
152+
var body: some View {
153+
VStack {
154+
Toggle("Favorite", isOn: $episode.isFavorite) // Bind to the Boolean.
155+
PlayerView(episode: episode)
156+
}
157+
}
158+
}
159+
```
160+
161+
### Animate state transitions
162+
163+
When the view state changes, OpenSwiftUI updates affected views right away. If
164+
you want to smooth visual transitions, you can tell SwiftUI to animate them by
165+
wrapping the state change that triggers them in a call to the
166+
``withAnimation(_:_:)`` function. For example, you can animate changes
167+
controlled by the isPlaying Boolean:
168+
169+
```
170+
withAnimation(.easeInOut(duration: 1)) {
171+
self.isPlaying.toggle()
172+
}
173+
```
174+
175+
By changing isPlaying inside the animation function’s trailing closure, you tell
176+
OpenSwiftUI to animate anything that depends on the wrapped value, like a scale
177+
effect on the button’s image:
178+
179+
```
180+
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
181+
.scaleEffect(isPlaying ? 1 : 1.5)
182+
```
183+
184+
OpenSwiftUI transitions the scale effect input over time between the given
185+
values of 1 and 1.5, using the curve and duration that you specify, or
186+
reasonable default values if you provide none. On the other hand, the image
187+
content isn’t affected by the animation, even though the same Boolean dictates
188+
which system image to display. That’s because OpenSwiftUI can’t incrementally
189+
transition in a meaningful way between the two strings `pause.circle` and
190+
`play.circle`.
191+
192+
You can add animation to a state property, or as in the above example, to a
193+
binding. Either way, OpenSwiftUI animates any view changes that happen when the
194+
underlying stored value changes. For example, if you add a background color to
195+
the PlayerView — at a level of view hierarchy above the location of the
196+
animation block — OpenSwiftUI animates that as well:
197+
198+
```
199+
VStack {
200+
Text(episode.title)
201+
Text(episode.showTitle)
202+
PlayButton(isPlaying: $isPlaying)
203+
}
204+
.background(isPlaying ? Color.green : Color.red) // Transitions with animation.
205+
```
206+
207+
When you want to apply animations to specific views, rather than across all
208+
views triggered by a change in state, use the ``View/animation(_:value:)`` view
209+
modifier instead.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# ``Bindable``
2+
3+
## Topics
4+
5+
### Creating a bindable value
6+
7+
- ``init(_:)``
8+
- ``init(wrappedValue:)``
9+
- ``init(projectedValue:)``
10+
11+
### Getting the value
12+
13+
- ``wrappedValue``
14+
- ``projectedValue``
15+
- ``subscript(dynamicMember:)``

Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Binding.md renamed to Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/Binding.md

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,24 @@
44

55
### Creating a binding
66

7-
- ``init(_:)-qgp1``
8-
9-
- ``init(_:)-jtxz``
10-
11-
- ``init(_:)-57cwg``
12-
7+
- ``init(_:)``
138
- ``init(projectedValue:)``
14-
15-
- ``init(get:set:)-x2sw``
16-
17-
- ``init(get:set:)-4fn32``
18-
9+
- ``init(get:set:)``
1910
- ``constant(_:)``
2011

21-
2212
### Getting the value
2313

2414
- ``wrappedValue``
25-
2615
- ``projectedValue``
27-
2816
- ``subscript(dynamicMember:)``
2917

3018
### Managing changes
3119

3220
- ``id``
33-
3421
- ``animation(_:)``
35-
3622
- ``transaction(_:)``
37-
3823
- ``transaction``
24+
25+
### Subscripts
26+
27+
- ``subscripts(_:)``
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# ``Environment``
2+
3+
## Topics
4+
5+
### Creating an environment property
6+
7+
- ``init(_:)``
8+
9+
### Getting the value
10+
11+
- ``wrappedValue``
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# ``EnvironmentObject``
2+
3+
## Topics
4+
5+
### Creating an environment object
6+
7+
- ``init()``
8+
9+
### Getting the value
10+
11+
- ``wrappedValue``
12+
- ``projectedValue``
13+
- ``Wrapper``

Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/ObservedObject.md renamed to Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/ObservedObject.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,10 @@
55
### Creating an observed object
66

77
- ``init(wrappedValue:)``
8-
98
- ``init(initialValue:)``
109

1110
### Getting the value
1211

1312
- ``wrappedValue``
14-
1513
- ``projectedValue``
16-
17-
- ``Wrapper``
14+
- ``Wrapper``

Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/State.md renamed to Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/State.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,10 @@
55
### Creating a state
66

77
- ``init(wrappedValue:)``
8-
98
- ``init(initialValue:)``
10-
119
- ``init()``
1210

1311
### Getting the value
1412

1513
- ``wrappedValue``
16-
1714
- ``projectedValue``

Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/StateObject.md renamed to Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/StateObject.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,4 @@
99
### Getting the value
1010

1111
- ``wrappedValue``
12-
1312
- ``projectedValue``

0 commit comments

Comments
 (0)