无法在 swiftui 中点击 TextField 向上移动底部视图?

unable to move bottom view up on tap of TextField in swiftui?

我有一个带文本字段的底部视图。这个底部视图将显示在按钮的动作上。但是底部视图不会随着文本字段的点击而向上移动。我已经为此添加了代码。

这是显示按钮的 ContentView。

struct ContentView: View {
    @State var cardShown = false
    @State var cardDismissal = false

    var body: some View {
        NavigationView {
            ZStack {
                Button(action: {
                    cardShown.toggle()
                    cardDismissal.toggle()
                }, label: {
                    Text("Show Card")
                        .bold()
                        .foregroundColor(Color.white)
                        .background(Color.blue)
                        .frame(width: 200, height: 50)
                })
                BottomCard(cardShown: $cardShown, cardDismissal: $cardDismissal, height: 300, content: {
                    CardContent()
                        .padding()
                })
            }
        }
    }
}

这是底部卡片内容视图。这需要点击文本字段。有一个文本字段。我添加了 keyboardAdaptive 修饰符来接收键盘高度但不起作用。

struct CardContent: View {
    
    @State private var text = ""
    
    var body: some View {
        
        VStack {
            
            Text("Photo Collage")
                .bold()
                .font(.system(size: 30))
                .padding()
            
            Text("You can create awesome photo grids and share them with all of your friends")
                .font(.system(size: 18))
                .multilineTextAlignment(.center)
            
            TextField("Enter something", text: $text)
                .textFieldStyle(RoundedBorderTextFieldStyle())
        }
        .padding()
        .keyboardAdaptive() // Apply the modifier
    }
}


struct BottomCard<Content: View>: View {
    let content: Content
    @Binding var cardShown: Bool
    @Binding var cardDismissal: Bool
    let height: CGFloat
    init(cardShown: Binding<Bool>, cardDismissal: Binding<Bool>, height: CGFloat, @ViewBuilder content: () -> Content) {
        _cardShown = cardShown
        _cardDismissal = cardDismissal
        self.height = height
        self.content = content()
    }
    
    var body: some View {
        ZStack {
            // Dimmed
            GeometryReader { _ in
                EmptyView()
            }
            .background(Color.gray.opacity(0.5))
            .opacity(cardShown ? 1: 0)
            .animation(Animation.easeIn, value: 0.9)
            
            .onTapGesture {
                // Dismiss
                dismiss()
            }
            
            // Card
            
            VStack {
                Spacer()
                
                VStack {
                  content
                    
                    Button(action: {
                        // Dismiss
                        dismiss()
                    }, label: {
                        Text("Dismiss")
                            .foregroundColor(Color.white)
                            .frame(width: UIScreen.main.bounds.width/2, height: 50)
                            .background(Color.pink)
                            .cornerRadius(8)

                    })
                     .padding()
                }
                .background(Color(UIColor.secondarySystemBackground))
                .frame(height: height)
                .offset(y: (cardShown && cardShown) ? 0 : 500)
                .animation(Animation.default.delay(0.2), value: 0.2)
            }
        }
        .edgesIgnoringSafeArea(.all)
    }
    
    func dismiss() {
        cardDismissal.toggle()
        DispatchQueue.main.asyncAfter(deadline: .now()+0.25) {
            cardShown.toggle()
        }

    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
extension Publishers {
    // 1.
    static var keyboardHeight: AnyPublisher<CGFloat, Never> {
        // 2.
        let willShow = NotificationCenter.default.publisher(for: UIApplication.keyboardWillShowNotification)
            .map { [=11=].keyboardHeight }
        
        let willHide = NotificationCenter.default.publisher(for: UIApplication.keyboardWillHideNotification)
            .map { _ in CGFloat(0) }
        
        // 3.
        return MergeMany(willShow, willHide)
            .eraseToAnyPublisher()
    }
}
extension Notification {
    var keyboardHeight: CGFloat {
        return (userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0
    }
}

struct KeyboardAdaptive: ViewModifier {
    @State private var bottomPadding: CGFloat = 0
    
    func body(content: Content) -> some View {
        // 1.
        GeometryReader { geometry in
            content
                .padding(.bottom, self.bottomPadding)
                // 2.
                .onReceive(Publishers.keyboardHeight) { keyboardHeight in
                    // 3.
                    let keyboardTop = geometry.frame(in: .global).height - keyboardHeight
                    // 4.
                    let focusedTextInputBottom = UIResponder.currentFirstResponder?.globalFrame?.maxY ?? 0
                    // 5.
                    self.bottomPadding = max(0, focusedTextInputBottom - keyboardTop - geometry.safeAreaInsets.bottom)
            }
            // 6.
                .animation(.easeOut, value: 0.16)
        }
    }
}
extension View {
    func keyboardAdaptive() -> some View {
        ModifiedContent(content: self, modifier: KeyboardAdaptive())
    }
}
extension UIResponder {
    static var currentFirstResponder: UIResponder? {
        _currentFirstResponder = nil
        UIApplication.shared.sendAction(#selector(UIResponder.findFirstResponder(_:)), to: nil, from: nil, for: nil)
        return _currentFirstResponder
    }

    private static weak var _currentFirstResponder: UIResponder?

    @objc private func findFirstResponder(_ sender: Any) {
        UIResponder._currentFirstResponder = self
    }

    var globalFrame: CGRect? {
        guard let view = self as? UIView else { return nil }
        return view.superview?.convert(view.frame, to: nil)
    }
}

我一直在玩这个。首先你的目标是什么系统。如果是 14 或更高版本,则键盘回避已内置,您不需要 .keyboardAdaptive() 代码。但这不是你的问题。当键盘显示时,它会更改安全区域。因此,无论您是使用 .keyboardAdaptive() 代码还是内置代码,您实际上都是在告诉视图忽略键盘在屏幕上这一事实。我还删除了您的 cardDismissal 代码,因为不需要它。

struct CardKeyboardView: View {
    @State var cardShown = false

    var body: some View {
        NavigationView {
            ZStack {
                Button(action: {
                    cardShown.toggle()
                }, label: {
                    Text("Show Card")
                        .bold()
                        .foregroundColor(Color.white)
                        .background(Color.blue)
                        .frame(width: 200, height: 50)
                })
                BottomCard(cardShown: $cardShown, height: 300, content: {
                    CardContent()
                        .padding()
                })
            }
        }
    }
}

struct CardContent: View {
    
    @State private var text = ""
    
    var body: some View {
        
        VStack {
            
            Text("Photo Collage")
                .bold()
                .font(.system(size: 30))
                .padding()
            
            Text("You can create awesome photo grids and share them with all of your friends")
                .font(.system(size: 18))
                .multilineTextAlignment(.center)
            
            TextField("Enter something", text: $text)
                .textFieldStyle(RoundedBorderTextFieldStyle())
        }
        .padding()
    }
}


struct BottomCard<Content: View>: View {
    let content: Content
    @Binding var cardShown: Bool
    let height: CGFloat
    init(cardShown: Binding<Bool>, height: CGFloat, @ViewBuilder content: () -> Content) {
        _cardShown = cardShown
        self.height = height
        self.content = content()
    }
    
    var body: some View {
        ZStack {
            // Dimmed
            GeometryReader { _ in
                EmptyView()
            }
            .background(Color.gray.opacity(0.5))
            .opacity(cardShown ? 1: 0)
            .animation(Animation.easeIn, value: 0.9)
            
            .onTapGesture {
                // Dismiss
                dismiss()
            }
            
            // Card
            
            VStack {
                Spacer()
                
                VStack {
                  content
                    
                    Button(action: {
                        // Dismiss
                        dismiss()
                    }, label: {
                        Text("Dismiss")
                            .foregroundColor(Color.white)
                            .frame(width: UIScreen.main.bounds.width/2, height: 50)
                            .background(Color.pink)
                            .cornerRadius(8)

                    })
                     .padding()
                }
                .background(Color(UIColor.secondarySystemBackground))
                .frame(height: height)
                .offset(y: (cardShown && cardShown) ? 0 : 500)
                .animation(Animation.default.delay(0.2), value: 0.2)
            }
        }
    }
    
    func dismiss() {
            cardShown.toggle()
    }
}

编辑:我重新粘贴了代码并添加了它工作的 gif。