iOS 16键盘安全区域在推送时未更新

14

iOS 16存在一个奇怪的键盘问题,当跳转到新页面时会出现。似乎从跳转回来后,键盘安全区域没有更新。

即使在空项目中使用以下代码也可以重现此问题:

struct ContentView: View {
    
    @State var text = ""
    
    var body: some View {
        NavigationView {
            VStack {
                Spacer()
                NavigationLink {
                    Text("test")
                } label: {
                    Text("Tap me")
                }
                TextField("", text: $text)
                    .textFieldStyle(.roundedBorder)
            }
            .padding()
        }
    }
}

重现步骤:

  • 打开键盘
  • 按下按钮“tap me”并导航到其他屏幕
  • 快速返回到先前的屏幕
  • 键盘被解除显示,但存在一个适合键盘大小的大间隙。

还有其他人遇到过类似的问题吗?


1
修复了XCode 14.1的问题。 - Mixorok
1
@Mixorok,14.1版本没有修复,除了更新Xcode之外,您还需要做些其他的事情吗? - farhad
@farhad 我使用的是iOS 16.1和xcode 14.1版本的iPhone。例如:https://media.giphy.com/media/HwV1P7H8fCpUYN0I6V/giphy.gif - Mixorok
1
在我们的情况下,升级到14.1似乎也不起作用... - Angel G. Olloqui
1
已升级至iOS 16.2和Xcode 14.2,但仍然存在该问题。 - Francojamesfan
显示剩余2条评论
4个回答

2
我找到了两种解决这个问题的方法,但在进入下一个屏幕之前都需要隐藏键盘。
  1. 在导航到另一个视图的按钮中添加隐藏键盘功能。
    @State var isActive: Bool = false
    
    var body: some View {
        NavigationView {
            ZStack {
                NavigationLink(isActive: $isActive, destination: { Text("Hello") }, label: EmptyView.init)
                
                VStack {
                    TextField("Text here", text: .constant(""))
                    Button("Press me") {
                        resignFirstResponder()
                        isActive.toggle()
                    }
                }
            }
        }
    }
  1. 在onChange块中添加隐藏键盘功能
@State var isActive: Bool = false
    
    var body: some View {
        NavigationView {
            ZStack {
                NavigationLink(isActive: $isActive, destination: { Text("Hello") }, label: EmptyView.init)
                    .onChange(of: isActive) { newValue in
                        if newValue {
                            resignFirstResponder()
                        }
                    }
                
                VStack {
                    TextField("Text here", text: .constant(""))
                    Button("Press me") {
                        isActive.toggle()
                    }
                }
            }
        }
    }

隐藏键盘的代码:

public func resignFirstResponder() {
    UIApplication.shared.sendAction(
        #selector(UIResponder.resignFirstResponder),
        to: nil,
        from: nil,
        for: nil
    )
}

1
在父视图中使用.ignoreSafeArea(.keyboard)即可解决此问题。

0

我找到了一个临时解决方案,虽然不太美观,但可以完成删除键盘之前占用的空白空间的工作。解决方案是在onDisappear中从子视图调用父视图,然后在父视图中添加一个隐藏的TextField,使其聚焦并立即失焦。

在父视图中添加属性:

@State private var dummyText = ""
@FocusState private var dummyFocus: Bool

在父视图中的某个位置放置一个TextField,例如在ZStack内:

ZStack {
    TextField("", text: $dummyText)
        .focused($dummyFocus)
        .opacity(0.01)

    ... your other layout ...
}

然后像这样使用完成块调用/导航到子视图:

ChildView(didDismiss: {
    if #available(iOS 16.0, *) {
        dummyFocus = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            dummyFocus = false
        }
    }
})

在子视图中添加属性:
var didDismiss: () -> Void

并在子视图的 onDisappear: 方法中调用完成块:

.onDisappear {
    didDismiss()
}

onDisappeardidDismiss()只有在整个交互式滑动返回动画完成后才会被调用。该代码检查iOS 16,以便它不会在之前的版本上不必要地执行。


0

我基于Frin的解决方案又做了一些修改。在我的情况下,所有的SwiftUI视图都嵌入到某个父级UIViewController中,因为我们的应用程序部分迁移到了SwiftUI。我创建了一个小类(KeyboardLayoutGuideFix),它创建了一个虚拟文本框来捕获焦点,然后观察视图控制器的生命周期来执行以下操作:

  1. 在视图消失时:如果是iOS16,则将焦点放在虚拟文本框上
  2. 在视图出现时:从虚拟文本框中移除焦点

这样,键盘布局似乎按预期工作,尽管下次回到屏幕时键盘将被关闭(这是我们的预期行为)。

以下是代码:

public class KeyboardLayoutGuideFix: Behavior {
    private weak var viewController: UIViewController?
    private lazy var dummyTextField: UITextField = {
        UITextField(frame: .zero).apply { text in
            viewController?.view.addSubview(text)
            text.alpha = 0
        }
    }()
    private var needsEndEditing = false
    private var disposeBag = Set<AnyCancellable>()

    private init(viewController: UIViewController, lifeCycle: ControllerLifeCycle) {
        self.viewController = viewController
        super.init(frame: .zero)
        lifeCycle.$isPresented.sink { [weak self] presented in
            guard let self else { return }
            if presented {
                if self.needsEndEditing {
                    self.needsEndEditing = false
                    DispatchQueue.main.async {
                        self.viewController?.view.endEditing(true)
                    }
                }
            } else {
                self.dummyTextField.becomeFirstResponder()
                self.needsEndEditing = true
            }
        }.store(in: &disposeBag)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    public static func apply(viewController: PlaytomicViewController) {
        apply(viewController: viewController, lifeCycle: viewController.lifecycle)
    }

    public static func apply(viewController: UIViewController, lifeCycle: ControllerLifeCycle) {
        if #available(iOS 16, *) {
            let fix = KeyboardLayoutGuideFix(viewController: viewController, lifeCycle: lifeCycle)
            fix.owner = viewController
        }
    }
}

然后在容器 VC 中使用它,例如:

override func viewDidLoad() {
    super.viewDidLoad()
    KeyboardLayoutGuideFix.apply(viewController: self)
}

请注意,您需要以下对象才能使此项目正常工作,但您可以根据自己的代码库进行调整:
  • Behavior:一个类,允许您将其他对象动态分配给父对象,在这种情况下,它将修复项分配给相关的视图控制器,防止释放。您可以删除它并在您的VC中使用一个包含对修复项引用的局部变量。
  • ControllerLifeCycle:一个公开发布者以跟踪ViewController的呈现状态的类。您可以通过viewWillAppearviewWillDisappear中的显式调用来替换它。
  • PlaytomicViewController:提供生命周期并在出现/消失时更新已发布属性的基类。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接