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

wkftcu5l  于 2022-11-19  发布在  iOS
关注(0)|答案(3)|浏览(185)

iOS16有一个奇怪的键盘问题,在推送新屏幕时。似乎键盘安全区没有更新,当你从推送的屏幕回来。
在一个空的项目中,甚至可以用下面的代码块来重现:

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()
        }
    }
}

重现步骤:

  • 打开键盘
  • 按下“点击我”按钮并导航至其他屏幕
  • 快速返回上一个屏幕
  • 键盘被驳回,但有一个很大的差距,适合键盘的大小。

其他人也有类似的问题吗?

qnyhuwrf

qnyhuwrf1#

我找到了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
    )
}
kwvwclae

kwvwclae2#

我找到了一个临时的解决方案,它不漂亮,但它做的工作,删除空白的空间,以前是由键盘占用.解决方案是从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

并调用child的onDisappear中的完成块:

.onDisappear {
    didDismiss()
}

onDisappeardidDismiss()只会在整个交互式向后滑动动画完成后被调用。代码会检查iOS 16,这样就不会在以前的版本上执行不必要的操作。

lstz6jyr

lstz6jyr3#

基于Frin的解决方案,我找到了另一个修复方法。在我的例子中,我们所有的SwiftUI视图都嵌入到某个父UIViewController中,因为我们有一个部分迁移到SwiftUI的应用程序。我所做的是创建一个小类(KeyboardLayoutGuideFix),它创建了一个伪文本字段来捕获焦点,然后观察视图控制器的生命周期来执行以下操作:
1.视图消失时:如果是iOS 16,则将焦点放在虚拟文本字段上
1.视图显示时:从虚拟文本字段中删除焦点
这样,键盘布局看起来就像预期的那样工作了,尽管下次回到屏幕时键盘将被取消(在我们的示例中这是预期的行为)。
代码如下:

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:提供生命周期并在出现/消失时更新已发布属性的基类

相关问题