Reputation: 4718
I'm using SwfitUI with macOS. I have two text fields in a sheet, a Picker and a TextField. I want the second TextField to have focus when a boolean is set if the Picker only has a single element (because there's nothing to choose). Below, please find simplified code where I'm always setting focusRoadNameField
to true
. I'm then using this @FocusState
variable with focused()
on the TextField—but the picker is still always being focused when the sheet opens.
import AppKit
import SwiftUI
struct ContentView: View {
@State private var sheetOpen: Bool = false
@State private var roadName: String = ""
@State private var noFocus: String = ""
@FocusState private var focusRoadNameField: Bool
var body: some View {
Button(action: {
sheetOpen = true
roadName = ""
// And yet the field isn't focused
focusRoadNameField = true
}) {
Text("Open Sheet")
}
.sheet(isPresented: $sheetOpen, content: {
VStack {
TextField("No focus", text: $noFocus)
TextField("Road Name", text: $roadName)
.focused($focusRoadNameField)
HStack {
Button("Cancel", action: {
sheetOpen = false
}).keyboardShortcut(.cancelAction)
.padding()
Spacer()
Button("Add Road", action: {
print("=> Add road '\(roadName)'")
sheetOpen = false
}).keyboardShortcut(.defaultAction)
.padding()
}
}
.frame(width: 260)
.padding()
})
.frame(width: 100, height: 50)
}
}
#Preview {
ContentView()
}
Update: Thanks to Sweeper for the comment. I forgot that I am using Full Keyboard Access, so widgets like the Picker get keyboard focus with a focus ring. I've adjusted the example to use two text fields to show the buggy behavior even on systems which don't have Full Keyboard Access enabled, which is the default state.
Now, you will see the sheet always open with the first, "No focus" field selected, despite the second field having the .focused()
modifier.
Upvotes: 1
Views: 111
Reputation: 4718
Thanks to workingdog for the answer. Here's the code I used to solve the problem:
struct ContentView: View {
@State private var sheetOpen: Bool = false
@State private var field1: String = ""
@State private var roadName: String = ""
@State private var addNewRoad: Bool = false
var body: some View {
Button(action: {
addNewRoad = false
sheetOpen = true
field1 = "test" // <-- Remove text here to focus field1. Otherwise, roadName will be focused
roadName = ""
}) {
Text("Open Sheet")
}
.sheet(isPresented: $sheetOpen, onDismiss: {
if addNewRoad {
print("Add road with name \(roadName)")
}
}) {
AddRoadSheetView(sheetOpen: $sheetOpen, field1: $field1, roadName: $roadName, addNewRoad: $addNewRoad)
}
.frame(width: 100, height: 50)
}
}
struct AddRoadSheetView: View {
@Binding var sheetOpen: Bool
@Binding var field1: String
@Binding var roadName: String
@Binding var addNewRoad: Bool
@FocusState private var focusRoadNameField
var body: some View {
VStack {
TextField("Conditional focus", text: $field1)
TextField("Road Name", text: $roadName)
.focused($focusRoadNameField)
HStack {
Button("Cancel", action: {
sheetOpen = false
})
.keyboardShortcut(.cancelAction)
.padding()
Spacer()
Button("Add Road", action: {
addNewRoad = true
sheetOpen = false
})
.keyboardShortcut(.defaultAction)
.disabled(roadName.isEmpty)
.padding()
}
}
.frame(width: 260)
.padding()
.onAppear {
focusRoadNameField = !field1.isEmpty // Choose whether the Road Name field has focus
}
}
}
Upvotes: 0
Reputation: 30746
From what I’ve read and my own experiments @FocusState
is currently broken however for a TextField
to be initially focused we are supposed to use .defaultFocus()
:
https://developer.apple.com/documentation/swiftui/view/defaultfocus(_:_:priority:)
Example: https://developer.apple.com/videos/play/wwdc2023/10162?time=773
struct GroceryListView: View {
@State private var list = GroceryList.examples
@FocusState private var focusedItem: GroceryList.Item.ID?
var body: some View {
NavigationStack {
List($list.items) { $item in
HStack {
Toggle("Obtained", isOn: $item.isObtained)
TextField("Item Name", text: $item.name)
.onSubmit { addEmptyItem() }
.focused($focusedItem, equals: item.id)
}
}
.defaultFocus($focusedItem, list.items.last?.id)
.toggleStyle(.checklist)
}
.toolbar {
Button(action: addEmptyItem) {
Label("New Item", systemImage: "plus")
}
}
}
private func addEmptyItem() {
let newItem = list.addItem()
focusedItem = newItem.id
}
}
Upvotes: 0
Reputation: 36782
Sheets are modal, and I found that passing @FocusState
can be tricky in iOS18+.
You could try this simple approach using two local @FocusState
and
a basic @State private var focus: Field?
to pass the focus
info to the sheet view. Note capturing
the vars for use in the sheet,
and the .onAppear
.
Example code:
enum Field {
case noFocus
case roadName
}
struct ContentView: View {
@State private var sheetOpen: Bool = false
@State private var roadName: String = "some road"
@State private var noFocus: String = "something"
@State private var text: String = "test"
@State private var focus: Field? // <--- to pass to sheet
@FocusState private var focusField: Field? // <--- local to this view
var body: some View {
VStack {
Button("focus on roadName"){
focus = .roadName // <--- here
sheetOpen = true
}
Button("focus on noFocus"){
focus = .noFocus // <--- here
sheetOpen = true
}
.sheet(isPresented: $sheetOpen) { [$roadName, $noFocus, focus] in // <--- here
MidView(sheetOpen: $sheetOpen,
roadName: $roadName,
noFocus: $noFocus,
focus: focus)
}
}
}
}
struct MidView: View {
@Binding var sheetOpen: Bool
@Binding var roadName: String
@Binding var noFocus: String
var focus: Field? // <--- here
@FocusState private var focusField: Field? // <--- local to this view
var body: some View {
VStack {
TextField("No focus", text: $noFocus)
.focused($focusField, equals: .noFocus)
TextField("Road Name", text: $roadName)
.focused($focusField, equals: .roadName)
HStack {
Button("Cancel", action: {
sheetOpen = false
}).keyboardShortcut(.cancelAction)
.padding()
Spacer()
Button("Add Road", action: {
print("----> Add road '\(roadName)'")
sheetOpen = false
}).keyboardShortcut(.defaultAction)
.padding()
}
}
.frame(width: 260)
.padding()
.onAppear {
focusField = focus // <--- here
}
}
}
Upvotes: 0