I am trying to update the focusedField based on user's interaction with the TextField. Everything was working fine until I put the TextFields inside a sheet. Now the focusedField is always null. Maybe because sheet is constantly getting re-rendered. Here is part of the code:
enum TripField: Hashable {
case pickup, destination
}
struct PlanYourRideScreen: View {
@FocusState private var focusedField: TripField?
@State private var trip = Trip()
var body: some View {
VStack {
Map {
}
}
.task(id: trip, {
print("task")
let _ = print(focusedField)
// focusedField is always nil. Even though I tap on the TextField.
})
.sheet(isPresented: .constant(true), content: {
VStack {
VStack(spacing: 12) {
RoundedRectangle(cornerRadius: 8)
.stroke(Color.gray, lineWidth: 1)
.frame(height: 50)
.overlay(
HStack {
Image(systemName: "circle.fill")
.foregroundColor(.blue)
TextField("Pickup location", text: $trip.pickup)
.textFieldStyle(PlainTextFieldStyle())
.padding(.leading, 5)
.focused($focusedField, equals: .pickup)
}
.padding(.horizontal)
)
}
I am trying to update the focusedField based on user's interaction with the TextField. Everything was working fine until I put the TextFields inside a sheet. Now the focusedField is always null. Maybe because sheet is constantly getting re-rendered. Here is part of the code:
enum TripField: Hashable {
case pickup, destination
}
struct PlanYourRideScreen: View {
@FocusState private var focusedField: TripField?
@State private var trip = Trip()
var body: some View {
VStack {
Map {
}
}
.task(id: trip, {
print("task")
let _ = print(focusedField)
// focusedField is always nil. Even though I tap on the TextField.
})
.sheet(isPresented: .constant(true), content: {
VStack {
VStack(spacing: 12) {
RoundedRectangle(cornerRadius: 8)
.stroke(Color.gray, lineWidth: 1)
.frame(height: 50)
.overlay(
HStack {
Image(systemName: "circle.fill")
.foregroundColor(.blue)
TextField("Pickup location", text: $trip.pickup)
.textFieldStyle(PlainTextFieldStyle())
.padding(.leading, 5)
.focused($focusedField, equals: .pickup)
}
.padding(.horizontal)
)
}
Share
Improve this question
asked Mar 5 at 21:36
john doejohn doe
9,72223 gold badges94 silver badges178 bronze badges
2 Answers
Reset to default 1It's not because the sheet is "constantly getting re-rendered", whatever that means. SwiftUI's focus system just doesn't extend across sheets. In addition to FocusState
, FocusedValue
s are also not getting propagated through the "barrier" that is sheet
.
For now, you can move the @FocusState
into a sheet by extracting an extra View
struct just for the sheet contents, then send the focused value up the view hierarchy using a preference value or binding.
Here is a complete example:
struct MySheet: View {
@Binding var text1: String
@Binding var text2: String
@FocusState var focus: FocusedField?
// or
// @Binding var focusBinding: FocusedField?
var body: some View {
VStack {
TextField("Pickup", text: $text1)
.focused($focus, equals: .pickup)
TextField("Destination", text: $text2)
.focused($focus, equals: .destination)
}
.preference(key: FocusedFieldKey.self, value: focus)
// or
// .onChange(of: focus) { _, newValue in
// focusBinding = newValue
// }
}
}
enum FocusedField {
case pickup, destination
}
struct FocusedFieldKey: PreferenceKey {
static var defaultValue: FocusedField? { nil }
static func reduce(value: inout FocusedField?, nextValue: () -> FocusedField?) {
if let next = nextValue() {
value = next
}
}
}
struct ContentView: View {
@State private var text1 = ""
@State private var text2 = ""
var body: some View {
List {}
.sheet(isPresented: .constant(true)) {
MySheet(text1: $text1, text2: $text2)
.onPreferenceChange(FocusedFieldKey.self) {
print($0)
}
}
}
}
You can't access @FocusState
of a parent view inside a child view (or inside a sheet's content closure), because the focus state is passed via the environment and the sheet creates its own separate environment.
But, since the focus state is an enum case, you can pass a binding to a state in parent to the child view and update the binding whenever the focus state in the child view changes.
Here's a complete example:
import SwiftUI
struct FocusSheetContentView: View {
//State values
@State private var pickup = ""
@State private var destination = ""
@State private var showSheet = true
@State private var tripFocusedField: TripFocusedField? // <- State to pass to child in sheet
//Computed properties
private var focusedStatusColor: Color {
tripFocusedField == nil ? .red : .green
}
//Body
var body: some View {
List {
Section("Selected pickup and destination") {
Text("Pickup: \(pickup) ")
Text("Destination: \(destination) ")
}
//Monitor focused field received via binding from child view
HStack {
Text("Focused field is: ")
Text("\(tripFocusedField?.rawValue.capitalized ?? "None")")
.foregroundStyle(focusedStatusColor)
}
}
.sheet(isPresented: $showSheet) {
FocusStateSheet(pickup: $pickup, destination: $destination, focusedField: $tripFocusedField)
//Fancy shmancy sheet styling
.presentationDetents([.height(350)])
.presentationBackground(Color.clear)
.presentationCornerRadius(30)
.clipShape(RoundedRectangle(cornerRadius: 20))
.padding(30)
.scrollDisabled(true)
}
//Sheet toggle
Button {
showSheet.toggle()
} label: {
Text("Open sheet")
}
}
}
struct FocusStateSheet: View {
//Parameters
@Binding var pickup: String
@Binding var destination: String
@Binding var focusedField: TripFocusedField? // <- Binding to state in parent
//Focus states
@FocusState var focused: TripFocusedField?
//Body
var body: some View {
Form {
Section("Enter pickup and destination") {
TextField("Pickup", text: $pickup)
.focused($focused, equals: .pickup)
TextField("Destination", text: $destination)
.focused($focused, equals: .destination)
}
HStack {
Image(systemName: "mappin.and.ellipse")
.font(.system(size: 70))
.foregroundStyle(.tertiary)
}
.frame(maxWidth: .infinity, alignment: .center)
.listRowBackground(Color.clear)
}
.onChange(of: focused) { // <- Update the binding whenever the local focus state changes
focusedField = focused
}
}
}
enum TripFocusedField: String {
case pickup, destination
}
#Preview {
FocusSheetContentView()
}