From 07ee11a2033deebd5a712197513347a66d9c1f46 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 5 Oct 2025 21:00:16 +0800 Subject: [PATCH] Optimize Model part documentation --- .../managing-model-data-in-your-app.md} | 0 .../Articles/managing-user-interface-state.md | 209 ++++++++++++++++++ .../ModelData/Extensions/Bindable.md | 15 ++ .../ModelData/{ => Extensions}/Binding.md | 23 +- .../{ => Extensions}/DynamicProperty.md | 0 .../ModelData/Extensions/Environment.md | 11 + .../ModelData/Extensions/EnvironmentObject.md | 13 ++ .../{ => Extensions}/ObservedObject.md | 5 +- .../ModelData/{ => Extensions}/State.md | 3 - .../ModelData/{ => Extensions}/StateObject.md | 1 - .../{Model-Data.md => model-data.md} | 23 +- .../OpenSwiftUI.docc/OpenSwiftUI.md | 6 +- 12 files changed, 275 insertions(+), 34 deletions(-) rename Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/{Managing-model-data-in-your-app.md => Articles/managing-model-data-in-your-app.md} (100%) create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Articles/managing-user-interface-state.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/Bindable.md rename Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/{ => Extensions}/Binding.md (69%) rename Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/{ => Extensions}/DynamicProperty.md (100%) create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/Environment.md create mode 100644 Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/EnvironmentObject.md rename Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/{ => Extensions}/ObservedObject.md (91%) rename Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/{ => Extensions}/State.md (98%) rename Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/{ => Extensions}/StateObject.md (99%) rename Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/{Model-Data.md => model-data.md} (84%) diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Managing-model-data-in-your-app.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Articles/managing-model-data-in-your-app.md similarity index 100% rename from Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Managing-model-data-in-your-app.md rename to Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Articles/managing-model-data-in-your-app.md diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Articles/managing-user-interface-state.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Articles/managing-user-interface-state.md new file mode 100644 index 000000000..b14ebc92f --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Articles/managing-user-interface-state.md @@ -0,0 +1,209 @@ +# Managing user interface state + +Encapsulate view-specific data within your app’s view hierarchy to make your +views reusable. + +## Overview + +Store data as state in the least common ancestor of the views that need the data +to establish a single source of truth that’s shared across views. Provide the +data as read-only through a Swift property, or create a two-way connection to +the state with a binding. OpenSwiftUI watches for changes in the data, and +updates any affected views as needed. + +![](https://docs-assets.developer.apple.com/published/c75c698bd113a4ac7c708e178f8294ca/managing-user-interface-state%402x.png) + +Don’t use state properties for persistent storage because the life cycle of +state variables mirrors the view life cycle. Instead, use them to manage +transient state that only affects the user interface, like the highlight state +of a button, filter settings, or the currently selected list item. You might +also find this kind of storage convenient while you prototype, before you’re +ready to make changes to your app’s data model. + +### Manage mutable values as state + +If a view needs to store data that it can modify, declare a variable with the +``State`` property wrapper. For example, you can create an isPlaying Boolean inside +a podcast player view to keep track of when a podcast is running: + +``` +struct PlayerView: View { + @State private var isPlaying: Bool = false + + var body: some View { + // ... + } +} +``` + +Marking the property as state tells the framework to manage the underlying +storage. Your view reads and writes the data, found in the state’s +``wrappedValue`` property, by using the property name. When you change the +value, OpenSwiftUI updates the affected parts of the view. For example, you can +add a button to the PlayerView that toggles the stored value when tapped, and +that displays a different image depending on the stored value: + +``` +Button(action: { + self.isPlaying.toggle() +}) { + Image(systemName: isPlaying ? "pause.circle" : "play.circle") +} +``` + +Limit the scope of state variables by declaring them as private. This ensures +that the variables remain encapsulated in the view hierarchy that declares them. + +### Declare Swift properties to store immutable values + +To provide a view with data that the view doesn’t modify, declare a standard +Swift property. For example, you can extend the podcast player to have an input +structure that contains strings for the episode title and the show name: + +``` +struct PlayerView: View { + let episode: Episode // The queued episode. + @State private var isPlaying: Bool = false + + var body: some View { + VStack { + // Display information about the episode. + Text(episode.title) + Text(episode.showTitle) + + + Button(action: { + self.isPlaying.toggle() + }) { + Image(systemName: isPlaying ? "pause.circle" : "play.circle") + } + } + } +} +``` + +While the value of the episode property is a constant for PlayerView, it doesn’t +need to be constant in this view’s parent view. When the user selects a +different episode in the parent, OpenSwiftUI detects the state change and +recreates the PlayerView with a new input. + +### Share access to state with bindings + +If a view needs to share control of state with a child view, declare a property +in the child with the ``Binding`` property wrapper. A binding represents a +reference to existing storage, preserving a single source of truth for the +underlying data. For example, if you refactor the podcast player view’s button +into a child view called PlayButton, you can give it a binding to the isPlaying +property: + +``` +struct PlayButton: View { + @Binding var isPlaying: Bool + + var body: some View { + Button(action: { + self.isPlaying.toggle() + }) { + Image(systemName: isPlaying ? "pause.circle" : "play.circle") + } + } +} +``` + +As shown above, you read and write the binding’s wrapped value by referring +directly to the property, just like state. But unlike a state property, the +binding doesn’t have its own storage. Instead, it references a state property +stored somewhere else, and provides a two-way connection to that storage. + +When you instantiate PlayButton, provide a binding to the corresponding state +variable declared in the parent view by prefixing it with the dollar sign ($): + +``` +struct PlayerView: View { + var episode: Episode + @State private var isPlaying: Bool = false + + var body: some View { + VStack { + Text(episode.title) + Text(episode.showTitle) + PlayButton(isPlaying: $isPlaying) // Pass a binding. + } + } +} +``` + +The $ prefix asks a wrapped property for its projectedValue, which for state is +a binding to the underlying storage. Similarly, you can get a binding from a +binding using the $ prefix, allowing you to pass a binding through an arbitrary +number of levels of view hierarchy. + +You can also get a binding to a scoped value within a state variable. For +example, if you declare episode as a state variable in the player’s parent view, +and the episode structure also contains an isFavorite Boolean that you want to +control with a toggle, then you can refer to $episode.isFavorite to get a +binding to the episode’s favorite status: + +``` +struct Podcaster: View { + @State private var episode = Episode(title: "Some Episode", + showTitle: "Great Show", + isFavorite: false) + var body: some View { + VStack { + Toggle("Favorite", isOn: $episode.isFavorite) // Bind to the Boolean. + PlayerView(episode: episode) + } + } +} +``` + +### Animate state transitions + +When the view state changes, OpenSwiftUI updates affected views right away. If +you want to smooth visual transitions, you can tell SwiftUI to animate them by +wrapping the state change that triggers them in a call to the +``withAnimation(_:_:)`` function. For example, you can animate changes +controlled by the isPlaying Boolean: + +``` +withAnimation(.easeInOut(duration: 1)) { + self.isPlaying.toggle() +} +``` + +By changing isPlaying inside the animation function’s trailing closure, you tell +OpenSwiftUI to animate anything that depends on the wrapped value, like a scale +effect on the button’s image: + +``` +Image(systemName: isPlaying ? "pause.circle" : "play.circle") + .scaleEffect(isPlaying ? 1 : 1.5) +``` + +OpenSwiftUI transitions the scale effect input over time between the given +values of 1 and 1.5, using the curve and duration that you specify, or +reasonable default values if you provide none. On the other hand, the image +content isn’t affected by the animation, even though the same Boolean dictates +which system image to display. That’s because OpenSwiftUI can’t incrementally +transition in a meaningful way between the two strings `pause.circle` and +`play.circle`. + +You can add animation to a state property, or as in the above example, to a +binding. Either way, OpenSwiftUI animates any view changes that happen when the +underlying stored value changes. For example, if you add a background color to +the PlayerView — at a level of view hierarchy above the location of the +animation block — OpenSwiftUI animates that as well: + +``` +VStack { + Text(episode.title) + Text(episode.showTitle) + PlayButton(isPlaying: $isPlaying) +} +.background(isPlaying ? Color.green : Color.red) // Transitions with animation. +``` + +When you want to apply animations to specific views, rather than across all +views triggered by a change in state, use the ``View/animation(_:value:)`` view +modifier instead. diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/Bindable.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/Bindable.md new file mode 100644 index 000000000..07afffa77 --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/Bindable.md @@ -0,0 +1,15 @@ +# ``Bindable`` + +## Topics + +### Creating a bindable value + +- ``init(_:)`` +- ``init(wrappedValue:)`` +- ``init(projectedValue:)`` + +### Getting the value + +- ``wrappedValue`` +- ``projectedValue`` +- ``subscript(dynamicMember:)`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Binding.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/Binding.md similarity index 69% rename from Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Binding.md rename to Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/Binding.md index ebe54087a..b84098420 100644 --- a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Binding.md +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/Binding.md @@ -4,35 +4,24 @@ ### Creating a binding -- ``init(_:)-qgp1`` - -- ``init(_:)-jtxz`` - -- ``init(_:)-57cwg`` - +- ``init(_:)`` - ``init(projectedValue:)`` - -- ``init(get:set:)-x2sw`` - -- ``init(get:set:)-4fn32`` - +- ``init(get:set:)`` - ``constant(_:)`` - ### Getting the value - ``wrappedValue`` - - ``projectedValue`` - - ``subscript(dynamicMember:)`` ### Managing changes - ``id`` - - ``animation(_:)`` - - ``transaction(_:)`` - - ``transaction`` + +### Subscripts + +- ``subscripts(_:)`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/DynamicProperty.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/DynamicProperty.md similarity index 100% rename from Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/DynamicProperty.md rename to Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/DynamicProperty.md diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/Environment.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/Environment.md new file mode 100644 index 000000000..af18b0d33 --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/Environment.md @@ -0,0 +1,11 @@ +# ``Environment`` + +## Topics + +### Creating an environment property + +- ``init(_:)`` + +### Getting the value + +- ``wrappedValue`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/EnvironmentObject.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/EnvironmentObject.md new file mode 100644 index 000000000..e8e16a717 --- /dev/null +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/EnvironmentObject.md @@ -0,0 +1,13 @@ +# ``EnvironmentObject`` + +## Topics + +### Creating an environment object + +- ``init()`` + +### Getting the value + +- ``wrappedValue`` +- ``projectedValue`` +- ``Wrapper`` \ No newline at end of file diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/ObservedObject.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/ObservedObject.md similarity index 91% rename from Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/ObservedObject.md rename to Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/ObservedObject.md index 460c2a263..7eea052b4 100644 --- a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/ObservedObject.md +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/ObservedObject.md @@ -5,13 +5,10 @@ ### Creating an observed object - ``init(wrappedValue:)`` - - ``init(initialValue:)`` ### Getting the value - ``wrappedValue`` - - ``projectedValue`` - -- ``Wrapper`` +- ``Wrapper`` \ No newline at end of file diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/State.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/State.md similarity index 98% rename from Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/State.md rename to Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/State.md index 8e894e300..94da83b71 100644 --- a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/State.md +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/State.md @@ -5,13 +5,10 @@ ### Creating a state - ``init(wrappedValue:)`` - - ``init(initialValue:)`` - - ``init()`` ### Getting the value - ``wrappedValue`` - - ``projectedValue`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/StateObject.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/StateObject.md similarity index 99% rename from Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/StateObject.md rename to Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/StateObject.md index 45d0b0940..f9011b072 100644 --- a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/StateObject.md +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Extensions/StateObject.md @@ -9,5 +9,4 @@ ### Getting the value - ``wrappedValue`` - - ``projectedValue`` diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Model-Data.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/model-data.md similarity index 84% rename from Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Model-Data.md rename to Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/model-data.md index fbe7a0040..76cf62a88 100644 --- a/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/Model-Data.md +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/DataAndStorage/ModelData/model-data.md @@ -11,6 +11,8 @@ that the user performs, OpenSwiftUI automatically updates the affected parts of the interface. As a result, the framework automatically performs most of the work that view controllers traditionally do. +![](https://docs-assets.developer.apple.com/published/7a8488351b0c9f662b694bc1153162a5/model-data-hero%402x.png) + The framework provides tools, like state variables and bindings, for connecting your app’s data to the user interface. These tools help you maintain a single source of truth for every piece of data in your app, in part by reducing the @@ -69,17 +71,30 @@ in [The Swift Programming Language](https://www.swift.org/documentation/#the-swi ### Creating and sharing view state +- - ``State`` - +- ``Bindable`` - ``Binding`` ### Creating model data -- - +- +- +- ``Observable()`` - ``StateObject`` - - ``ObservedObject`` +- ``ObservableObject`` + +### Responding to data changes + +- ``View/onChange(of:initial:_:)`` +- ``View/onReceive(_:perform:)`` + +### Distributing model data throughout your app + +- ``View/environmentObject(_:)`` +- ``Scene/environmentObject(_:)`` +- ``EnvironmentObject`` ### Managing dynamic data diff --git a/Sources/OpenSwiftUI/OpenSwiftUI.docc/OpenSwiftUI.md b/Sources/OpenSwiftUI/OpenSwiftUI.docc/OpenSwiftUI.md index 03740cbff..4ace7aaa6 100644 --- a/Sources/OpenSwiftUI/OpenSwiftUI.docc/OpenSwiftUI.md +++ b/Sources/OpenSwiftUI/OpenSwiftUI.docc/OpenSwiftUI.md @@ -22,22 +22,18 @@ You can integrate OpenSwiftUI views with objects from the [UIKit](https://develo ### Data and storage -- - +- - ### Views - - - - - ### View Layout - - - ### Event Handling