“SwiftUI Tutorials” are just a few small demos that show how to use “SwiftUI” so its easy to learn and do step by step. After reading it first, you will get a general idea of “SwiftUI”.

3. Handling User Input

SwiftUI Essentials – Handling User Input Handles User Input.

In the Landmarks application, users can mark their favorites and filter the list to show only their favorites. To create this feature, first add a switch to the list so that the user can focus only on their favorite list, and then add a star button on the landmark details page that the user clicks to mark the landmark as a favorite. (Add a switch to the right of the first line of the list, and click to toggle back and forth to show all landmarks or favorite landmarks)

Mark the User’s Favorite Landmarks

To mark Landmarks that the user has collected, enhance the list first (already collected Landmarks are marked with yellow stars on the LandmarkRow) so that the user can see their collection at a glance. Add a property to the Landmark structure to read the initial state of the Landmark as a collection, and then add an asterisk to each LandmarkRow that displays a collection Landmark.

First, modify the data source model and add a var isFavorite: Bool member variable to the Landmark structure to record whether the corresponding Landmark is stored.

struct Landmark: Hashable.Codable.Identifiable {
    .
    
    var isFavorite: Bool

    .
}
Copy the code

Then select LandmarkRow.swift and add a star to the right of the row where our favorite landmark is located.

struct LandmarkRow: View {
    var landmark: Landmark

    var body: some View {
        HStack {
            landmark.image
                .resizable()
                .frame(width: 50, height: 50)
            Text(landmark.name)

            Spacer(a)// Add an Image view based on the data source
            if landmark.isFavorite {
                Image(systemName: "star.fill")
                    // Because system images are vector based, you can change their color with the foregroundColor(_:) modifier.
                    .foregroundColor(.yellow)
            }
        }
    }
}
Copy the code

Add an Image view only if the isFavorite of the landmark member variable of the current LandmarkRow is true and the system-provided Image is used: Image(systemName: “Star.fill “), which is black by default and vector-based, so we can set its color with foregroundColor(_:), for example we changed it to yellow here.

Filter the List View

You can customize the LandmarkList to show all landmarks or just a list of the user’s favorites. To do this, we need to add some state to the LandmarkList. A State is a value or set of values that can change over time and affect the behavior, content, or layout of a view. Add State to a view using an attribute decorated with @state.

Remember the @state keyword? After a property is decorated with @state, SwiftUI stores the property in a special memory area that is isolated from the View struct. When the value of the @state modified property changes, SwiftUI redraws the view based on that property.

Struct LandmarkList add @state property showFavoritesOnly to the struct LandmarkList. ShowFavoritesOnly = false The LandmarkList will be “refreshed”. Because the State property is used to hold information specific to the view and its children, State is always created private.

Add a computed attribute called filteredLandmarks to struct LandmarkList, Filter out the list of favorite landmarks by checking the showFavoritesOnly property and the isFavorite property of each landmark in the Landmarks global array.

When showFavoritesOnly is false, do not show only the favorite landmarks. ShowFavoritesOnly | | landmark. IsFavorite) shall be true, at this time to filter out filteredLandmarks array that contains the landmarks of all elements in the array, Therefore, even if the LandmarkList data source is replaced, all the landmark elements are still displayed. When showFavoritesOnly is set to true, only the favorites are displayed. At this time, landmarks with isFavorite value of true are filtered from the Landmarks array, and only the collected landmarks are displayed in the LandmarkList after replacing the data source.

struct LandmarkList: View {
    @State private var showFavoritesOnly = false
    
    // Calculate the filtered version of the LandmarkList list by checking the showFavoritesOnly attribute and each landmark.isfavorite value
    var filteredLandmarks: [Landmark] {
        landmarks.filter { landmark in
            (!showFavoritesOnly || landmark.isFavorite)
        }
    }
    
    .
}
Copy the code

Use the filtered version of the landmark list in the list. Replace the LandmarkList data source with filteredLandmarks:

struct LandmarkList: View {
    .
    
    var body: some View {
        NavigationView {
            // Replace landmarks with filteredLandmarks
            List(filteredLandmarks) { landmark in
                NavigationLink {
                    LandmarkDetail(landmark: landmark)
                } label: {
                    LandmarkRow(landmark: landmark)
                }
            }
            .navigationTitle("Landmarks")}}}Copy the code

ShowFavoritesOnly = true showFavoritesOnly = true showFavoritesOnly = true showFavoritesOnly = true showFavoritesOnly Here we take a look at the List(filteredLandmarks) {… }, which passes the data directly to the List, later changed to a different way.)

Add a Control to Toggle the State

To grant the user control over the list filter, you need to add a control that changes the showFavoritesOnly value. You can do this by passing a Binding to the switch control. Binding acts as a reference to mutable state. When the user clicks the switch from off to on, and then off again, the control uses a Binding to update the view’s state accordingly.

Create nested ForEach groups to convert landmarks to rows. To combine static and dynamic views in a List, or to combine two or more different groups of dynamic views, use the ForEach type instead of passing data collection to a List. What are you talking about?! And the straightforward way to think about it is in a List what if we want to display different rows? By analogy, in UIKit when we use UITableView, the first row we display a Cell of type 1 and the second row we want to display a Cell of type 2. In the example above we use List(filteredLandmarks) {… }, each row of the List is a LandmarkRow. If we want to add a static row to the first row, a row of another type, then we can switch to List {… } to insert any rows you want to display.

struct LandmarkList: View {
    .
    
    var body: some View {
        NavigationView {
// List(filteredLandmarks) { landmark in
// NavigationLink {
// LandmarkDetail(landmark: landmark)
// } label: {
// LandmarkRow(landmark: landmark)
/ /}
/ /}
            
            List {
                ForEach(filteredLandmarks) { landmark in
                    NavigationLink {
                        LandmarkDetail(landmark: landmark)
                    } label: {
                        LandmarkRow(landmark: landmark)
                    }
                }
            }
            .navigationTitle("Landmarks")}}}Copy the code

Add a Toggle as the first subview of the List (line 1) and pass binding to show FavoritesOnly. Use the $prefix to access bindings with a state variable or one of its attributes.

struct LandmarkList: View {
    .
    
    var body: some View {
        NavigationView {
// List(filteredLandmarks) { landmark in
// NavigationLink {
// LandmarkDetail(landmark: landmark)
// } label: {
// LandmarkRow(landmark: landmark)
/ /}
/ /}
            
            List {
                // The first line of the List shows the Toggle and is bidirectionally bound to the showFavoritesOnly (forget the @State and $prefix)
                Toggle(isOn: $showFavoritesOnly) {
                    Text("Favorites only")}// The rest of the List still shows LandmarkRow
                ForEach(filteredLandmarks) { landmark in
                    NavigationLink {
                        LandmarkDetail(landmark: landmark)
                    } label: {
                        LandmarkRow(landmark: landmark)
                    }
                }
            }
            .navigationTitle("Landmarks")}}}Copy the code

Then run the project and click Toggle to display all landmarks/favorites.

Use an Observable Object for Storage

To prepare users to control which specific landmarks are stored, the Landmark data is first stored in an Observable Object.

An Observable object is a custom object for data that can be bound to a view from a SwiftUI’s Environment store. SwiftUI monitors any changes to Observable Objects that might affect the view and displays the correct version of the view after the changes (refresh the page).

Declare new model types that comply with the ObservableObject protocol from the Combine framework (Class ModelData: ObservableObject {… }). SwiftUI subscribes to Observable Objects and updates any views that need to be refreshed when data changes.

import Combine

final class ModelData: ObservableObject {}Copy the code

Then move the Landmarks global array to ModelData:

import Combine

final class ModelData: ObservableObject {
    var landmarks: [Landmark] = load("landmarkData.json")}Copy the code

Observables need to publish any changes to their data so that their subscribers can retrieve the changes. Add the @Published property to the landmark array.

    @Published var landmarks: [Landmark] = load("landmarkData.json")
Copy the code

Adopt the Model Object in Your Views

After you create the ModelData object, you need to update the view to use it as the data store for your application.

In LandmarkList. Swift, add the @environmentobject property declaration and the EnvironmentObject (_:) modifier to the view to LandmarkList_Previews.

struct LandmarkList: View {
    @EnvironmentObject var modelData: ModelData
    @State private var showFavoritesOnly = false
    .
}

struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkList()
            .environmentObject(ModelData()}}Copy the code

When filtering landmarks, use ModelData.landmark as the data.

.
    var filteredLandmarks: [Landmark] {
        modelData.landmarks.filter { landmark in
            (!showFavoritesOnly || landmark.isFavorite)
        }
    }
.
Copy the code

Update the LandmarkDetail preview view to use the ModelData object in the environment.

struct LandmarkDetail_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkDetail(landmark: ModelData().landmarks[0])}}Copy the code

Update the preview view of LandmarkRow to use the ModelData object.

struct LandmarkRow_Previews: PreviewProvider {
    // Static variable landmarks
    static var landmarks = ModelData().landmarks
    
    static var previews: some View {
        Group {
            LandmarkRow(landmark: landmarks[0])
            LandmarkRow(landmark: landmarks[1])
        }
        .previewLayout(.fixed(width: 300, height: 70))}}Copy the code

Update the preview view of the ContentView to add the model object to the environment so that the object can be used by any child view. Preview will fail the modifier if any subviews require model objects in the environment, but the view being previewed does not have environmentObject(_:).

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(ModelData()}}Copy the code

Next, when you run the application in the emulator or device, you update the application instance to place the model objects in the environment. Update the LandmarksApp to create an instance of the model and provide it to ContentView with the environmentObject(_:) modifier.

Use the @StateObject feature to initialize a model object with a given attribute only once during the life of the application. This is true when the property is used in an application instance, as shown below, as well as in a view.

@main
struct LandmarksApp: App {
    @StateObject private var modelData = ModelData(a)var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(modelData)
        }
    }
}
Copy the code

Create a Favorite Button for Each Landmark

The Landmarks application can now switch between filtered and unfiltered views of Landmarks, but the list of favorite Landmarks is still hard-coded. To allow users to add and remove favorite landmarks, add the favorite button to the LandmarkDetail view.

Start by creating a FavoriteButton that you can reuse.

import SwiftUI

struct FavoriteButton: View {
    var body: some View {
        Text("Hello, SwiftUI!")}}struct FavoriteButton_Previews: PreviewProvider {
    static var previews: some View {
        FavoriteButton()}}Copy the code

Add an isSet (@binding) indicating the current state of the button and provide constant values for the preview. Changes made in this view are propagated back to the data source because of the @Binding attribute wrapper.

import SwiftUI

struct FavoriteButton: View {
    @Binding var isSet: Bool
    
    var body: some View {
        Text("Hello, SwiftUI!")}}struct FavoriteButton_Previews: PreviewProvider {
    static var previews: some View {
        FavoriteButton(isSet: .constant(true))}}Copy the code

Create a button that contains an action to toggle isSet state and change its appearance based on the state.

struct FavoriteButton: View {
    @Binding var isSet: Bool
    
    var body: some View {
        Button {
            isSet.toggle()
        } label: {
            Image(systemName: isSet ? "star.fill" : "start")
                .foregroundColor(isSet ? Color.yellow : Color.gray)
        }
    }
}
Copy the code

Next, you add the FavoriteButton to the Land markDetail and bind the isSet attribute of the button to the isFavorite attribute of the given landmark.

Switch to Landmarkdetail.swift and calculate the index of the input landmarks by comparing the input landmarks with the model data. To support this feature, you also need access to the environment’s modelData (@environmentobject var modelData: modelData).

struct LandmarkDetail: View {
    @EnvironmentObject var modelData: ModelData
    var landmark: Landmark
    
    var landmarkIndex: Int {
        modelData.landmarks.firstIndex(where: {$0.id = = landmark.id })!
    }
    
    .
}
Copy the code

Embed the landmark name (Text) and the new FavoriteButton into the HStack. FavoriteButton provides the isFavorite property binding with the dollar symbol ($). Use landmarkIndex with the modelData object to ensure that the button updates the isFavorite property of the landmark stored in the model object. Landmark data source in ModelData. landmarks ensures that the FavoriteButton switch updates the Landmark data source in modelData.landmarks.

.

HStack {
    Text(landmark.name)
        .font(.title)
    FavoriteButton(isSet: $modelData.landmarks[landmarkIndex].isFavorite)
}

.
Copy the code

At this point, all the code for this section is complete. When you navigate from the list to the details of a landmark and click the Favorites button, these changes persist when you return to the list. Because both views access the same model object in the environment, the two views remain consistent. (Previously we might have used block or delegate callbacks to update data. Now Combine + SwiftUI is an elegant batch.)

Although the sample code is very small, the internal hidden information and SwiftUI + Combine are different from the data flow between pages using UIKit + Block + delegate and other methods. SwiftUI + Combine is more flexible and elegant. And a little bit more front-end.

Let’s take a look in the direction of data flow at some of the more unfamiliar keywords and the use and meaning of Property Wrappers in the sample code (which is the core of this article). (@stateObject,. EnvironmentObject, @environmentobject, @state, @binding, ObservableObject, @published)

@State

@state is primarily used for the local State of a single view. In the sample code, @state is used in two places:

  1. Struct LandmarkList @State private var showFavoritesOnly = false ShowFavoritesOnly binds the showFavoritesOnly with Toggle(isOn:$showFavoritesOnly) {Text(“Favorites only”)} in the first line of the list to record whether all the landmarks are displayed in the list or only the Favorites.

  2. Struct MapView @State private var region = MKCoordinateRegion() attribute, region and Map(coordinateRegion) : $region).onAppear {setRegion(coordinate)} Map coordinate range is bound together to record the coordinate changes of the current map, Struct MapView also defines a private func setRegion(_ coordinate: CLLocationCoordinate2D) { region = MKCoordinateRegion( center: coordinate, span: MKCoordinateSpan(latitudeDelta: LongitudeDelta: 0.2)} and you can see that the region attribute of the struct MapView structure is modified without the mutating modifier.

The @state attribute allows you to modify attributes of a Struct that are not allowed to be modified in a normal Struct. When @State is placed in front of a property, the property is actually stored outside the Struct, which means SwiftUI can destroy and rebuild the Struct at any time without losing the value of the property.

The @state wrapped properties are usually set to private and not available for external use. If you want external use, you should use @observedobject and @environmentobject, which enable the external to modify the property and change the state. It is recommended that all @state wrapped properties be set to private.

@Binding

Less commonly used, but very important, @binding is a property wrapper that declares that a property is fetched from and shared with the outside world. It’s the same as when it’s passed in from the outside, it’s not passed in.

The struct FavoriteButton structure defines an attribute that uses the @Binding attribute wrapper: isSet.

struct FavoriteButton: View {
    @Binding var isSet: Bool

    var body: some View {
        Button {
            isSet.toggle()
        } label: {
            Label("Toggle Favorite", systemImage: isSet ? "star.fill" : "star")
                .labelStyle(.iconOnly)
                .foregroundColor(isSet ? .yellow : .gray)
        }
    }
}
Copy the code

Toggle isSet values back and forth in Button Button click events.

@frozen public struct Bool : Sendable {
    .

    /// Toggles the Boolean variable's value. Toggles the value of a Boolean variable.
    ///
    /// Use this method to toggle a Boolean value from `true` to `false` or from `false` to `true`. Use this method to switch a Boolean value from "true" to "false" or from "false" to "true".
    ///
    /// var bools = [true, false]
    ///
    /// bools[0].toggle()
    /// // bools == [false, false]
    @inlinable public mutating func toggle(a)
    
    .
}
Copy the code

@binding: Cannot use mutating member on immutable value. Binding: Cannot use mutating member on immutable value ‘self’ is immutable.

Struct FavoriteButton_Previews Binding. Constant (true)

/// A property wrapper type that can read and write a value owned by a source of truth. An attribute wrapper type that reads and writes values owned by the actual source.
///
/// Use a binding to create a two-way connection between a property that stores data, and a view that displays and changes the data. Bindings allow you to create a bidirectional connection between properties that store data and views that display and change the data.
/// A binding connects a property to a source of truth stored elsewhere, instead of storing data directly. Bindings connect properties to fact sources stored elsewhere, rather than storing data directly.
/// For example, a button that toggles between play and pause can create a binding to a property of its parent view using the `Binding` property wrapper. For example, a button that toggles between play and pause can use a "Binding" property wrapper to create a Binding with its parent view property.
///
/// struct PlayButton: View {
/// @Binding var isPlaying: Bool
///
/// var body: some View {
/// Button(action: {
/// self.isPlaying.toggle()
/ / / {})
/// Image(systemName: isPlaying ? "pause.circle" : "play.circle")
/ / /}
/ / /}
/ / /}
///
/// The parent view declares a property to hold the playing state, using the ``State`` property wrapper to indicate that this property is the value's source of truth. The superview declares a property to hold the playback State, using the "State" property wrapper to indicate that this property is the true source of the value.
///
/// 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)
/ / /}
/ / /}
/ / /}
///
/// When `PlayerView` initializes `PlayButton`, it passes a binding of its state property into the button's binding property. When "PlayerView" initializes "PlayButton", it passes the binding of its state property to the button's binding property.
/// Applying the `$` prefix to a property wrapped value returns its ``State/projectedValue``, which for a state property wrapper returns a binding to the value. Applying the "$" prefix to an attribute wrapper value returns its "State/projectedValue", which in the case of a State attribute wrapper returns the binding to that value.
///
/// Whenever the user taps the `PlayButton`, the `PlayerView` updates its `isPlaying` state. Every time the user clicks on" PlayButton", "PlayerView" updates its "isPlaying" state.
@available(iOS 13.0.macOS 10.15.tvOS 13.0.watchOS 6.0.*)
@frozen @propertyWrapper @dynamicMemberLookup public struct Binding<Value> {
    .
    
    /// Creates a binding with an immutable value. Create a binding with immutable values.
    ///
    /// Use this method to create a binding to a value that cannot change. You can use this method to create bindings to values that cannot be changed.
    /// This can be useful when using a ``PreviewProvider`` to see how a view represents different values. This can be useful when using "PreviewProvider" to see how views represent different values.
    ///
    /// // Example of binding to an immutable value.
    /// PlayButton(isPlaying: Binding.constant(true))
    ///
    /// - Parameter value: An immutable value.
    public static func constant(_ value: Value) -> Binding<Value>
    
    .
}
Copy the code

When FavoriteButton is used in the struct LandmarkDetail structure, the initialization passes the var isFavorite of the struct Landmark structure: Bool Attribute: FavoriteButton(isSet: FavoriteButton) $modeldata.landmarks [landmarkIndex].isfavorite) specifies struct FavoriteButton isSet value and the environment variable modeldata.landmarks data source The isFavorite value of a struct Landmark struct instance is bound together, and the LandmarkList’s list of landmarks is refreshed when isSet changes.

@Published

@Published is a useful property wrapper for SwiftUI that allows us to create an object property that can be automatically observed by SwiftUI. SwiftUI will automatically monitor this property and refresh the interface bound to this property if it changes. The @published wrapper automatically adds a willSet method to monitor property changes. Used in conjunction with Combine’s ObservableObject.

@ObservedObject

@observedobject tells SwiftUI that this object can be observed and contains properties wrapped by @published. The object wrapped by @obServedobject must comply with the ObservableObject protocol. That is, it must be a class object, not a struct. ObservedObject allows external access and modification.

In ModelData.swift, a class ModelData class that follows the ObservableObject protocol is defined, and the Landmarks attributes of the ModelData class are wrapped with @published.

import Combine

final class ModelData: ObservableObject {
    @Published var landmarks: [Landmark] = load("landmarkData.json")}Copy the code

The ObservableObject protocol is inherited from AnyObject protocol. All classes implicitly follow the AnyObject protocol.

/// A type of object with a publisher that emits before the object has changed. An object type whose Publisher is issued before the object changes.
///
/// By default an ``ObservableObject`` synthesizes an ``ObservableObject/objectWillChange-2oa5v`` publisher that emits the changed value before any of its `@Published` properties changes. By default, the "ObservableObject" synthesis of a "ObservableObject/objectWillChange - 2 oa5v" publisher, the publisher in any of its "@ Published" change the value of the property changes before.

     class Contact: ObservableObject {
         @Published var name: String
         @Published var age: Int

         init(name: String.age: Int) {
             self.name = name
             self.age = age
         }

         func haveBirthday(a) -> Int {
             age + = 1
             return age
         }
     }

     let john = Contact(name: "John Appleseed", age: 24)
     cancellable = john.objectWillChange
         .sink { _ in
             print("\(john.age) will change")}print(john.haveBirthday())
     // Prints "24 will change"
     // Prints "25"
     
@available(iOS 13.0.macOS 10.15.tvOS 13.0.watchOS 6.0.*)
public protocol ObservableObject : AnyObject {

    /// The type of publisher that emits before the object has changed. Publisher type issued before the object changes.
    associatedtype ObjectWillChangePublisher : Publisher = ObservableObjectPublisher where Self.ObjectWillChangePublisher.Failure = = Never

    /// A publisher that emits before the object has changed. Publisher issued before the object changes.
    var objectWillChange: Self.ObjectWillChangePublisher { get}}Copy the code

AnyObject

// The protocol to which all classes implicitly conform.

/// You use `AnyObject` when you need the flexibility of an untyped object or when you use bridged Objective-C methods and properties that return an untyped result. Use "AnyObject" when you need the flexibility of untyped objects, or when you use bridged Objective-C methods and properties that return untyped results.
/// `AnyObject` can be used as the concrete type for an instance of any class, class type, Or class-only protocol. For example: "AnyObject" can be used as the concrete type of any class, class type, or instance of class-only protocol. Such as:

     class FloatRef {
         let value: Float
         init(_ value: Float) {
             self.value = value
         }
     }

     let x = FloatRef(2.3)
     let y: AnyObject = x
     let z: AnyObject = FloatRef.self

/// `AnyObject` can also be used as the concrete type for an instance of a type that bridges to an Objective-C class. Many value types in Swift bridge to Objective-C counterparts, Like 'String' and 'Int'. "AnyObject" can also be used as a concrete type to bridge an instance of a type in objective-C. Many value types in Swift are linked to their Objective-C counterparts, such as "String" and "Int".

     let s: AnyObject = "This is a bridged string." as NSString
     print(s is NSString)
     // Prints "true"

     let v: AnyObject = 100 as NSNumber
     print(type(of: v))
     // Prints "__NSCFNumber"

/// The flexible behavior of the `AnyObject` protocol is similar to Objective-C's `id` type. For this reason, imported Objective-C types frequently use `AnyObject` as the type for properties, method parameters, And return values. The flexible behavior of the "AnyObject" protocol is similar to the "ID" type of Objective-C. Therefore, imported Objective-C types often use "AnyObject" as the type for properties, method parameters, and return values.

/// Casting AnyObject Instances to a Known Type Casts AnyObject Instances to Known types
/ / / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

/// Objects with a concrete type of `AnyObject` maintain a specific dynamic type and can be cast to that type using one of the type-cast operators (`as`, `as? `, or `as! '). Objects of type "AnyObject" retain a specific dynamic type and can use one of the conversion operators ("as", "as?"). Or as "!" ) cast to this type.

/// This example uses the conditional downcast operator (`as? `) to conditionally cast the `s` constant declared above to an instance of Swift's `String` type. This example uses the conditional downconversion operator ('as? ') conditionally converts the 's' constant declared above to an instance of Swift 's' String' type.

     if let message = s as? String {
         print("Successful cast to String: \(message)")}// Prints "Successful cast to String: This is a bridged string."

/// If you have prior knowledge that an `AnyObject` instance has a particular type, you can use the unconditional downcast operator (`as! `). Performing an invalid cast triggers a runtime error. If you know in advance that an instance of "AnyObject" has a specific type, you can use the unconditional lower conversion operator ("as!"). ). Performing an invalid cast triggers a runtime error.

     let message = s as! String
     print("Successful cast to String: \(message)")
     // Prints "Successful cast to String: This is a bridged string."

     let badCase = v as! String
     // Runtime error

/// Casting is always safe in the context of a `switch` statement. A cast is always safe in the context of a "switch" statement.

     let mixedArray: [AnyObject] = [s, v]
     for object in mixedArray {
         switch object {
         case let x as String:
             print("'\(x)' is a String")
         default:
             print("'\(object)' is not a String")}}// Prints "'This is a bridged string.' is a String"
     // Prints "'100' is not a String"

/// Accessing Objective-C Methods and Properties
/ / / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

/// When you use `AnyObject` as a concrete type, you have at your disposal every `@objc` method and property---that is, methods and properties imported from Objective-C or marked with the `@objc` attribute. Because Swift can't guarantee at compile time that these methods and properties are actually available on an `AnyObject` instance's underlying type, these `@objc` symbols are available as implicitly unwrapped optional methods and properties, respectively. When you use "AnyObject" as a concrete type, you can use all "@objc" methods and properties - that is, methods and properties imported from Objective-C or labeled with an "@objc" property. Since Swift cannot guarantee at compile time that these methods and properties are actually available on the underlying type of an "AnyObject" instance, these "@objc" symbols can be used as optional methods and properties for implicit unpacking, respectively.

/// This example defines an `IntegerRef` type with an `@objc` method named `getIntegerValue()`. This example defines an 'IntegerRef' type using the '@objc' method called 'getIntegerValue()'.

     class IntegerRef {
         let value: Int
         init(_ value: Int) {
             self.value = value
         }

         @objc func getIntegerValue(a) -> Int {
             return value
         }
     }

     func getObject(a) -> AnyObject {
         return IntegerRef(100)}let obj: AnyObject = getObject()

/// In the example, `obj` has a static type of `AnyObject` and a dynamic type of `IntegerRef`. You can use optional chaining to call the `@objc` method `getIntegerValue()` on `obj` safely. If you're sure of the dynamic type of `obj`, you can call `getIntegerValue()` directly. In the example, "obj" has statically typed "AnyObject" and dynamically typed "IntegerRef". You can safely call the '@objc' method 'getIntegerValue()' on 'obj' using the optional link. If you determine the dynamic type of "obj", you can simply call "getIntegerValue()".

     let possibleValue = obj.getIntegerValue?(a)print(possibleValue)
     // Prints "Optional(100)"

     let certainValue = obj.getIntegerValue()
     print(certainValue)
     // Prints "100"

/// If the dynamic type of `obj` doesn't implement a `getIntegerValue()` method, the system returns a runtime error when you initializeCertainValue. If dynamically typed"obj"Do not implement"getIntegerValue()"Method, the system returns a runtime error during initialization"certainValue".

/// Alternatively, if you need to test whether `obj.getIntegerValue()` exists, use optional binding before calling the method. Alternatively, if you need to test for the presence of 'obj.getIntegerValue()', use the optional binding before calling the method.

     if let f = obj.getIntegerValue {
         print("The value of 'obj' is \(f())")}else {
         print("'obj' does not have a 'getIntegerValue()' method")}// Prints "The value of 'obj' is 100"
     
public typealias AnyObject
Copy the code

@EnvironmentObject

As the name suggests, this property wrapper is specific to the global environment. With this, we can avoid creating an ObservableObject in the initial View and instead get an ObservableObject from the environment. In this way, we can easily share data across complex applications.

In the example code, the struct LandmarksApp defines a @StateObject private var modelData = modelData () property, The body uses the modelData instance as the environment variable of the ContentView view via ContentView().environmentobject (modelData). Can read an instance of modelData, and when modelData changes, ContentView subviews that read using modelData will be refreshed. Struct LandmarkList and struct LandmarkDetail pass @environmentobject var modelData ModelData is used to obtain ModelData from the environment. When ModelData changes, struct LandmarkList and struct LandmarkDetail are refreshed.

The View protocol extends a function called environmentObject that limits T to comply with the ObservableObject protocol, which “provides” environment variables to the sub-views of the View.

@available(iOS 13.0.macOS 10.15.tvOS 13.0.watchOS 6.0.*)
extension View {

    /// Supplies an `ObservableObject` to a view subhierarchy. Provide "ObservableObject" to the view subhierarchy.
    ///
    /// The object can be read by any child by using `EnvironmentObject`. Any child object can be read by using "EnvironmentObject".
    ///
    /// - Parameter object: the object to store and make available to the view's subhierarchy. Objects to store and contribute to the view's child hierarchy.
    @inlinable public func environmentObject<T> (_ object: T) -> some View where T : ObservableObject

}
Copy the code

@StateObject

In the WWDC video, Apple makes it clear that @StateObject is held by the View that created it. That is, the life cycle of the instance is completely controllable, as is the life cycle of the View that created it. The difference between @StateObject and @Observedobject is whether the instance is held by the View that created it and whether its life cycle is fully controllable. SwiftUI 2.0 — @stateObject Research

The modelData (modelData class instance) property wrapped with @StateObject in the struct LandmarksApp is passed as an environment variable to be used in the child view of the ContentView.

@main
struct LandmarksApp: App {
    @StateObject private var modelData = ModelData(a)var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(modelData)
        }
    }
}
Copy the code

Now that we’ve covered the Handling User Input, we’re all going to focus on the property wrapper and get some insight into the flow of data in SwiftUI. Let’s move on to the next section.

Refer to the link

Reference link :🔗

  • SwiftUI Status binding: @state
  • Understand the attribute decorators @state, @Binding, @observedobject, @environmentobje in SwiftUI
  • [SwiftUI 100 days] use @environmentobject to read values from the environment
  • SwiftUI 2.0 — @stateObject Research
  • New features for Swift 5.5
  • SwiftUI attribute packaging
  • Property Wrappers in Swift – Property Wrappers