SwiftUI中为偏移后空白区域添加点击手势以收起键盘
问题:偏移产生的空白区域无法响应点击收起键盘
我给TextField实现了键盘显示时的Y轴偏移机制,触发时整个屏幕会上移。现在希望点击任意空白区域即可收起键盘,但偏移产生的空白区域无法响应点击手势,试过在各处添加点击手势识别器都没有效果。
原代码如下:
struct LoginView: View { @State private var email = "" @State private var password = "" @State private var keyboardYOffset = CGFloat(0) var body: some View { VStack { VStack(spacing: 32) { Rectangle() .frame(width: 187, height: 208) .padding(.top, 56) .padding(.bottom, 94) .background(Color.red) TextField("Email", text: $email) .frame(maxWidth: .infinity) .frame(height: 50) // inset of the placeholder text .padding(.horizontal) .border(Color.red) // inset of the entire textfield .padding(.horizontal) SecureField("Password", text: $email) .frame(maxWidth: .infinity) .frame(height: 50) // inset of the placeholder text .padding(.horizontal) .border(Color.red) // inset of the entire textfield .padding(.horizontal) Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity) .padding(.top, 32) .background(Color.white.ignoresSafeArea()) } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .background(Theme.BrandColor.primary.ignoresSafeArea()) .offset(y: -keyboardYOffset) .animation(.easeInOut(duration: 1), value: keyboardYOffset) .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { notification in let value = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect let keyboardHeight = value?.height ?? 0 // for our purposes we only need to offset by half the keyboard height self.keyboardYOffset = keyboardHeight / 2 } .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in self.keyboardYOffset = 0 } .onTapGesture { UIApplication.shared.endEditing() } } } struct LoginView_Previews: PreviewProvider { static var previews: some View { LoginView() } } extension UIApplication { func endEditing() { sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } }
解决方案
原因分析
当前的onTapGesture绑定在最外层VStack上,当屏幕上移后,VStack下方露出的空白区域并不属于这个VStack的点击范围,所以点击该区域不会触发手势。
方法一:用ZStack分层处理全屏点击
把全屏背景和内容视图分层,让底层的全屏背景接收所有点击事件,这样不管内容怎么偏移,空白区域的点击都能被捕获:
struct LoginView: View { @State private var email = "" @State private var password = "" @State private var keyboardYOffset = CGFloat(0) var body: some View { ZStack { // 底层全屏背景,接收所有空白区域点击 Theme.BrandColor.primary .ignoresSafeArea() .onTapGesture { UIApplication.shared.endEditing() } // 原有内容视图 VStack { VStack(spacing: 32) { Rectangle() .frame(width: 187, height: 208) .padding(.top, 56) .padding(.bottom, 94) .background(Color.red) TextField("Email", text: $email) .frame(maxWidth: .infinity) .frame(height: 50) .padding(.horizontal) .border(Color.red) .padding(.horizontal) // 修复原代码的绑定错误:SecureField应该绑定password SecureField("Password", text: $password) .frame(maxWidth: .infinity) .frame(height: 50) .padding(.horizontal) .border(Color.red) .padding(.horizontal) Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity) .padding(.top, 32) .background(Color.white.ignoresSafeArea()) } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .offset(y: -keyboardYOffset) .animation(.easeInOut(duration: 1), value: keyboardYOffset) } .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { notification in let value = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect let keyboardHeight = value?.height ?? 0 self.keyboardYOffset = keyboardHeight / 2 } .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in self.keyboardYOffset = 0 } } } struct LoginView_Previews: PreviewProvider { static var previews: some View { LoginView() } } extension UIApplication { func endEditing() { sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } }
方法二:扩展视图的点击区域
给最外层VStack添加contentShape(Rectangle()),强制让视图的点击区域扩展到整个屏幕范围,即使偏移后,空白区域也会被视为视图的一部分:
struct LoginView: View { @State private var email = "" @State private var password = "" @State private var keyboardYOffset = CGFloat(0) var body: some View { VStack { // 原有内容视图 VStack(spacing: 32) { Rectangle() .frame(width: 187, height: 208) .padding(.top, 56) .padding(.bottom, 94) .background(Color.red) TextField("Email", text: $email) .frame(maxWidth: .infinity) .frame(height: 50) .padding(.horizontal) .border(Color.red) .padding(.horizontal) // 修复绑定错误 SecureField("Password", text: $password) .frame(maxWidth: .infinity) .frame(height: 50) .padding(.horizontal) .border(Color.red) .padding(.horizontal) Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity) .padding(.top, 32) .background(Color.white.ignoresSafeArea()) } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .background(Theme.BrandColor.primary.ignoresSafeArea()) .offset(y: -keyboardYOffset) .animation(.easeInOut(duration: 1), value: keyboardYOffset) // 添加这一行,扩展点击区域到整个屏幕 .contentShape(Rectangle()) .onTapGesture { UIApplication.shared.endEditing() } .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { notification in let value = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect let keyboardHeight = value?.height ?? 0 self.keyboardYOffset = keyboardHeight / 2 } .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in self.keyboardYOffset = 0 } } } struct LoginView_Previews: PreviewProvider { static var previews: some View { LoginView() } } extension UIApplication { func endEditing() { sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } }
额外修复
原代码中SecureField错误绑定了email状态变量,已在上述代码中修正为绑定password,避免输入密码时同步修改邮箱内容。
内容的提问来源于stack exchange,提问作者meowmeowmeow
相关产品推荐
相关产品推荐

