Search code examples
swiftswiftuinotificationcentercapture-list

How to changed focus field in didBecomeKeyNotification closure from SwiftUI view?


I'm writing a multi-platform SwifttUI app, and want to make the Mac version set the current focused field to a specific field when the user swaps to another app and then back to my app. (For completeness, I'll do the same thing on iOS.)

To do that, I tried adding this code to my view's init:

    let      didBecomeKeyNotification: NSNotification.Name =  {
        #if os(macOS)
            return NSWindow.didBecomeKeyNotification
        #else
            return UIApplication.didBecomeKeyNotification
        #endif
    }()

    NotificationCenter.default.addObserver(
        forName: didBecomeKeyNotification,
        object: nil,
        queue: nil) { _ in
            self.pointToFirstInputField()
        }

Where pointToFirstInputField() sets the value of focusedField (an @FocusState var) to the text field I want to be selected when the window becomes the key window again.

However, that gives me an error that Escaping closure captures mutating 'self' parameter.

Ok, I tried to add a [weak self] capture list to my closure. That gives me an error that "'weak' may only be applied to class and class-bound protocol types, not 'WordleView'"

Which makes sense since a SwiftUI view is a struct - a value type.

So what's the solution here?

Edit:

(Note that the linked "duplicate" question is not a duplicate. I am not using the version of notifications that uses a target/action. I am using the newer version that takes a closure. My question is about how to call a mutating function from the body of my closure.)

It won't let me use a capture group to work with a weak variable either.


Solution

  • I got it to work using the environment value controlActiveState rather than with notifications. In my tests this worked both when making the app active and internally between two windows when switching between them.

    Here is a simple implementation

    struct ContentView: View {
        enum Field: Hashable {
            case field1, field2, field3
        }
        @Environment(\.controlActiveState) private var controlActiveState
        @State private var field1 = ""
        @State private var field2 = ""
        @State private var field3 = ""
        @FocusState var field: Field?
    
        var body: some View {
            NavigationSplitView(columnVisibility: $columnVisibilty) {
                VStack {
                    TextField("Field1", text: $field1)
                        .focused($field, equals: .field1)
                    TextField("Field2", text: $field2)
                        .focused($field, equals: .field2)
                    TextField("Field3", text: $field3)
                        .focused($field, equals: .field3)
                }
                .onChange(of: controlActiveState) { old, new in
                    if new == .key {
                        field = .field2
                    }
                }
           }
        }
    }