最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

ios - SwiftUI FocusedField is Always Null After Putting in Sheet Modifier - Stack Overflow

programmeradmin5浏览0评论

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
Add a comment  | 

2 Answers 2

Reset to default 1

It'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, FocusedValues 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()
}

发布评论

评论列表(0)

  1. 暂无评论