admin管理员组

文章数量:1221400

I have a view with a @FocusState that I want to pass into a subview. For the subview, I use @FocusState.Binding. This works fine if I don't use a custom initializer ... but I can't figure out the syntax for how it works with a custom view initializer, which I need for other reasons.

Here's the code that works:

struct TestListButtonsAndListFocusView: View {
    
    @FocusState var buttonFocusState: ButtonState?
    @ObservedObject var viewModel: ViewModel = ViewModel()
    
    var body: some View {
        TestListView(viewModel: viewModel, bindedFocusButtonState: $buttonFocusState) // for custom init, replace with following line
        //TestListView(viewModel: viewModel, focusButtonState: $buttonFocusState) // 1. Uncomment this for custom initializer
    }
}

struct TestListView: View {
    @State private var items1: [TimedItem] = [
        TimedItem(number: 1, timestamp: "2024-11-20 10:00"),
        TimedItem(number: 2, timestamp: "2024-11-20 11:00"),
        TimedItem(number: 3, timestamp: "2024-11-20 12:00")
    ]
    
    @ObservedObject var viewModel: ViewModel
    @FocusState.Binding var bindedFocusButtonState: ButtonState?
    
    @State var selectedItem: TimedItem.ID?
    
    var body: some View {
        List(items1, selection: $selectedItem) { item in
            ContentListItemView(item: item)
        }
        .onChange(of: bindedFocusButtonState) {
            if let bindedFocusButtonState {
                print("TestListView - bindedListFocusState has changed to \(bindedFocusButtonState)")
                if bindedFocusButtonState == .one && selectedItem == nil {
                    selectedItem = items1.first?.id
                }
            } else {
                print("ListOneView - bindedListFocusState has changed to nil")
            }
        }
    }
    
    // 2. Uncomment this
    /*init(viewModel: ViewModel, focusButtonState: FocusState<ButtonState?>.Binding) {
        // how to pass in @FocusState.Binding?
        self.viewModel = viewModel
        self.bindedFocusButtonState = focusButtonState
        // Error: Cannot assign value of type 'FocusState<ButtonState?>.Binding' to type 'ButtonState?'
    }*/
}

public class ViewModel: NSObject, ObservableObject {
    
    @Published public var selectedButton: ButtonState? = ButtonState.one
}

public enum ButtonState: Int, CaseIterable, Hashable, Identifiable {
    
    public var id: Self {
        return self
    }
    case one, two, three
}

    
#Preview {
    TestListButtonsAndListFocusView()
}

However, if I uncomment out the line for the custom initializer, and then the line in TestListButtonsAndListFocusView to use the custom initializer, the syntax is wrong and I get the error:

Error: Cannot assign value of type 'FocusState<ButtonState?>.Binding' to type 'ButtonState?'

I'm not sure how to then initialize the @FocusState.Binding this way. I know it works if I use var bindedFocusButtonState: FocusState<ContactTabStyle?>.Binding instead, and then use that in the initializer as well. But I'd really like to figure out how to use the new @FocusState.Binding with the custom initializer, as it avoids having to access wrappedValue and is easier to observe with onChange

I have a view with a @FocusState that I want to pass into a subview. For the subview, I use @FocusState.Binding. This works fine if I don't use a custom initializer ... but I can't figure out the syntax for how it works with a custom view initializer, which I need for other reasons.

Here's the code that works:

struct TestListButtonsAndListFocusView: View {
    
    @FocusState var buttonFocusState: ButtonState?
    @ObservedObject var viewModel: ViewModel = ViewModel()
    
    var body: some View {
        TestListView(viewModel: viewModel, bindedFocusButtonState: $buttonFocusState) // for custom init, replace with following line
        //TestListView(viewModel: viewModel, focusButtonState: $buttonFocusState) // 1. Uncomment this for custom initializer
    }
}

struct TestListView: View {
    @State private var items1: [TimedItem] = [
        TimedItem(number: 1, timestamp: "2024-11-20 10:00"),
        TimedItem(number: 2, timestamp: "2024-11-20 11:00"),
        TimedItem(number: 3, timestamp: "2024-11-20 12:00")
    ]
    
    @ObservedObject var viewModel: ViewModel
    @FocusState.Binding var bindedFocusButtonState: ButtonState?
    
    @State var selectedItem: TimedItem.ID?
    
    var body: some View {
        List(items1, selection: $selectedItem) { item in
            ContentListItemView(item: item)
        }
        .onChange(of: bindedFocusButtonState) {
            if let bindedFocusButtonState {
                print("TestListView - bindedListFocusState has changed to \(bindedFocusButtonState)")
                if bindedFocusButtonState == .one && selectedItem == nil {
                    selectedItem = items1.first?.id
                }
            } else {
                print("ListOneView - bindedListFocusState has changed to nil")
            }
        }
    }
    
    // 2. Uncomment this
    /*init(viewModel: ViewModel, focusButtonState: FocusState<ButtonState?>.Binding) {
        // how to pass in @FocusState.Binding?
        self.viewModel = viewModel
        self.bindedFocusButtonState = focusButtonState
        // Error: Cannot assign value of type 'FocusState<ButtonState?>.Binding' to type 'ButtonState?'
    }*/
}

public class ViewModel: NSObject, ObservableObject {
    
    @Published public var selectedButton: ButtonState? = ButtonState.one
}

public enum ButtonState: Int, CaseIterable, Hashable, Identifiable {
    
    public var id: Self {
        return self
    }
    case one, two, three
}

    
#Preview {
    TestListButtonsAndListFocusView()
}

However, if I uncomment out the line for the custom initializer, and then the line in TestListButtonsAndListFocusView to use the custom initializer, the syntax is wrong and I get the error:

Error: Cannot assign value of type 'FocusState<ButtonState?>.Binding' to type 'ButtonState?'

I'm not sure how to then initialize the @FocusState.Binding this way. I know it works if I use var bindedFocusButtonState: FocusState<ContactTabStyle?>.Binding instead, and then use that in the initializer as well. But I'd really like to figure out how to use the new @FocusState.Binding with the custom initializer, as it avoids having to access wrappedValue and is easier to observe with onChange

Share Improve this question asked Feb 6 at 19:41 Z SZ S 7,51112 gold badges58 silver badges110 bronze badges 3
  • 1 It sounds like you just want to do self._bindedFocusButtonState = focusButtonState? – Sweeper Commented Feb 6 at 19:47
  • That works! What's the significance of this syntax? – Z S Commented Feb 6 at 20:03
  • Note, unrelated, but you should really have @StateObject var viewModel: ViewModel = ViewModel() – workingdog support Ukraine Commented Feb 8 at 2:59
Add a comment  | 

1 Answer 1

Reset to default 0

You should assign to the underscore-prefixed property _bindedFocusButtonState.

self._bindedFocusButtonState = focusButtonState

This is the property actually containing the FocusState.Binding. bindedFocusButtonState on the other hand, is actually a computed property that returns _bindedFocusButtonState.wrappedValue.

In particular, the line

@FocusState.Binding var bindedFocusButtonState: ButtonState?

declares three properties:

private var _bindedFocusButtonState: FocusState<ButtonState?>.Binding

var bindedFocusButtonState: ButtonState? {
    get { _bindedFocusButtonState.wrappedValue }
    set { _bindedFocusButtonState.wrappedValue = newValue }
}

var $bindedFocusButtonState: FocusState<ButtonState?>.Binding {
    get { _bindedFocusButtonState.projectedValue }
}

本文标签: swiftSwiftUI use FocusStateBinding with view initializerStack Overflow