肘子的 Swift 记事本

@FocusedBinding Overview

Published on

Get weekly handpicked updates on Swift and SwiftUI!

The following code appeared in the Apple WWDC 20 video, introducing a new property wrapper @FocusedBinding. Since it’s still in the testing phase, the current code cannot be executed correctly. There is little information disclosed by Apple about @FocusedBinding, and there is no related information available online. Out of personal interest, I conducted a simple study of it. Despite numerous issues with its operation in the current Xcode Version 12.0 beta 2 (12A6163b), I have gained a basic understanding of it.

Swift
struct BookCommands: Commands {
 @FocusedBinding(\.selectedBook) private var selectedBook: Book?
  var body: some Commands {
    CommandMenu("Book") {
        Section {
            Button("Update Progress...", action: updateProgress)
                .keyboardShortcut("u")
            Button("Mark Completed", action: markCompleted)
                .keyboardShortcut("C")
        }
        .disabled(selectedBook == nil)
    }
  }

   private func updateProgress() {
       selectedBook?.updateProgress()
   }
   private func markCompleted() {
       selectedBook?.markCompleted()
   }
}

Purpose

For data sharing, modification, and binding operations between any views (View).

In SwiftUI 1.0, we could use EnvironmentKey to pass data to child views and PreferenceKey to pass data to parent views. To transfer data between two parallel views not on the same view tree, we usually need to use Single of truth or through NotificationCenter.

In SwiftUI 2.0, Apple introduced @FocusedBinding and @FocusedValue to solve this problem. By defining FocusedValueKey, we can share, modify, and bind data directly between any views, without going through Single of truth.

In the article SwiftUI2.0 —— Commands (macOS menu), we used the Single of truth method at the App level to allow Commnads to share data with other views. From the example provided by WWDC, we can see that Apple wants to provide another solution to perform the above functions. This solution also allows us to exchange data between any views (whether on the same tree or not, whether related or not).

How to Use

Its basic usage is similar to Environment; first, we need to define the specified Key

Swift
struct FocusedMessageKey: FocusedValueKey {
    // Unlike EnvironmentKey, FocusedValueKey has no default value and must be an optional value. For demonstration purposes here, we set the data type to Binding<String>, but it can be set to any value type
    typealias Value = Binding<String>
}

extension FocusedValues {
    
    var message: Binding<String>? {
        get { self[FocusedMessageKey.self] }
        set { self[FocusedMessageKey.self] = newValue }
    }
}

Since it can be used on any view, data does not need to be injected. Unlike EnvironmentKey (which is only effective under the current injected view tree), the data is effective globally.

Swift
struct ShowView: View {
    // The calling method is almost identical to @Environment, using @FocusedValue or @FocusedBinding requires different references
    @FocusedValue(\.message) var focusedMessage
    //@FocusedBinding(\.message) var focusedMessage1
    var body: some View {
        VStack {
            Text("focused:\(focusedMessage?.wrappedValue ?? "")")
            //Text("focused:\(focusedMessage1 ?? "")")
        }
    }
}

Modify the FocusedValueKey data in another view.

Swift
struct InputView1: View {
    @State private var text = ""
    var body: some View {
        VStack {
            TextField("input1:", text: $text)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                // Synchronize message and text
                .focusedValue(\.message, $text)
        }
    }
}

Multiple views can modify the same FocusedValueKey.

Swift
struct InputView2: View {
    @State private var text = ""
    var body: some View {
        TextField("input2:", text: $text)
            .textFieldStyle(RoundedBorderTextFieldStyle())
            .focusedValue(\.message, $text)
    }
}

Finally, assemble them together.

Swift
struct RootView: View {
    var body: some View {
        VStack {
            // The three views are parallel, and using Environment or Preference before, it was impossible to transfer, share data among these three views
            InputView1()
            InputView2()
            ShowView()
        }
        .padding(.all, 20)
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

Currently, FocusedValueKey values cannot be obtained under iOS, although the documentation indicates it is supported on iOS, and this should be resolved in the future.

How to Use It, What to Use It For?

The introduction of FocusedBinding further improves the functionality of data operations between different views in SwiftUI. However, it is still advisable not to overuse this feature.

Since we can modify the value in the key in any view, if overused, it may again lead to difficulties in managing the code.

For some very simple functionality that does not require MVVM logic, or for Single of truth that is too cumbersome (ObservableObject Research - Expressing Love is Not Easy), which may cause app response issues, the above solution can be considered.

I'm really looking forward to hearing your thoughts! Please Leave Your Comments Below to share your views and insights.

Fatbobman(东坡肘子)

I'm passionate about life and sharing knowledge. My blog focuses on Swift, SwiftUI, Core Data, and Swift Data. Follow my social media for the latest updates.

You can support me in the following ways