我有一个带有 textField 的底视图。此底部视图将显示在按钮的操作上。但是底部视图不会在点击文本字段时向上移动。我已经为此添加了代码。
这是显示按钮的 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 { $0.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)
}
}