Reputation: 1431
This is an interesting scenario. While I'm not going to get into designing the viewHandler, assume a parent view has NavigationStack setup where you can append to the navigationPath variable.
Let's assume a simple view model like this
extension View_1 {
@MainActor
class ViewModel: ObservableObject {
@Published var name: String = ""
enum Field: Hashable {
case myTextField
}
}
}
If you have two views that make up a single screen of content, you can write
struct View_1 {
@FocusState var myFocus: View_1.ViewModel.Field?
@StateObject var viewModel: ViewModel
var body: some View {
TextField("", $name)
.focused($myFocus, equals: .name)
Continue_Button(myFocus: $myFocus)
}
}
struct Continue_Button {
var myFocus: FocusState<View_1.ViewModel.Field?>.Binding
var body: some View {
VStack {
Button {
myFocus.wrappedValue = nil
} label: {
Image(systemName: "chevron.right.circle">
}
}
}
}
That works fine, but if you try to pass FocusState to a second view through a navigationDestination like this:
struct View_1: View {
@EnvironmentObject var viewHandler: ViewHandler
@FocusState var myFocus: View_1.ViewModel.Field?
@StateObject var viewModel: ViewModel
var body: some View {
Button {
viewHandler.navigationPath.append(MyViews.secondView)
} label: {
Text("Go to second view")
}
.navigationDestination(for: MyViews) { navigation in
if navigation == MyViews.secondView {
SecondView(myFocus: $myFocus, viewModel: viewModel)
}
}
}
}
struct View_2: View {
var myFocus: FocusState<View_1.ViewModel.Field?>.Binding
@ObservedObject var viewModel: View_1.ViewModel
var body: some View {
VStack {
TextField("", $viewModel.name)
.focused(myFocus, equals: .name)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Button {
myFocus.wrappedValue = nil
} label: {
Image(systemName: "keyboard")
}
}
}
}
}
}
It doesn't work at all. The second view recognizes .name from the view model's enum properly and you get a keyboard, but if you try to close that keyboard using myFocus.wrappedValue = nil nothing happens because something is getting confused or reset.
Any idea how to pass FocusState through a navigationDestination (or is it even possible)?
Upvotes: 0
Views: 41
Reputation: 1431
The answer seems to be that with the new Swift 6, NavigationStack, and the latest iOS 18, you can no longer pass FocusState nor AccessibilityFocusState between views that navigate, only those that appear in the same view as described above.
In order to make this work, you now have to do it like this:
extension View_1 {
@MainActor
class ViewModel: ObservableObject {
@Published var myFocusOptions: FieldOptions?
@Published var name: String = ""
@Published var email: String = ""
enum Field: Hashable {
case myName
}
enum FieldOptions: Hashable {
case myEmail
}
}
}
struct View_1: View {
@EnvironmentObject var viewHandler: ViewHandler
@FocusState var myFocus: View_1.ViewModel.Field?
@StateObject var viewModel: ViewModel
var body: some View {
TextField("", $viewModel.name)
.focused($myFocus, equals: .myName)
Button {
viewModel.myFocusOptions = .myEmail
viewHandler.navigationPath.append(MyViews.secondView)
} label: {
Text("Go to second view")
}
.navigationDestination(for: MyViews) { navigation in
if navigation == MyViews.secondView {
SecondView(viewModel: viewModel)
}
}
}
}
struct View_2: View {
@FocusState var myFocus: View_1.ViewModel.FieldOptions?
@ObservedObject var viewModel: View_1.ViewModel
var body: some View {
VStack {
TextField("", $viewModel.email)
.focused($myFocus, equals: .myEmail)
.onAppear {
self.myFocus = viewModel.myFocusOptions
}
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Button {
self.myFocus = nil
} label: {
Image(systemName: "keyboard")
}
}
}
}
}
}
If you need to monitor the change of the FocusState within the view and keep the view model variable in sync you would additionally write in View_2:
.onChange(of: self.myFocus) {
viewModel.myFocusOptions = self.myFocus
}
.onChange(of: viewModel.myFocusOptions) {
self.myFocus = viewModel.myFocusOptions
}
Upvotes: 0