iOS设备旋转且键盘弹出时,自定义动画时长被系统覆盖如何解决?
解决iOS键盘旋转时自定义动画时长被系统覆盖的问题
这个坑我之前踩过!系统在UIKeyboardWillChangeFrame通知的回调里,其实已经处于一个自带的动画上下文当中了,咱们直接写的动画会被这个上下文的参数(比如时长、曲线)强制覆盖,所以自定义时长根本不生效。下面给你几个经过验证的解决方案:
方案1:把自定义动画放到下一个RunLoop周期
系统的键盘动画是在当前RunLoop执行的,咱们用DispatchQueue.main.async把自己的动画代码推迟到下一个RunLoop,就能避开系统的动画上下文,完全用自己的参数控制动画。
代码示例:
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: .main) { [weak self] notification in guard let userInfo = notification.userInfo, let endFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { return } // 计算需要调整的约束值(这里以底部约束为例) let bottomInset = UIScreen.main.bounds.height - endFrame.origin.y self?.contentBottomConstraint.constant = bottomInset // 推迟到下一个RunLoop执行自定义动画 DispatchQueue.main.async { UIView.animate(withDuration: 0.8, // 这里写你想要的自定义时长 delay: 0, options: .curveEaseInOut) { self?.view.layoutIfNeeded() } } }
方案2:临时禁用系统动画,再执行自定义动画
先强制关闭系统的动画开关,立即更新约束(无动画),然后再开启动画开关,执行咱们自己的过渡动画。这种方式可以完全摆脱系统动画的影响。
代码示例:
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: .main) { [weak self] notification in guard let userInfo = notification.userInfo, let endFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { return } let bottomInset = UIScreen.main.bounds.height - endFrame.origin.y self?.contentBottomConstraint.constant = bottomInset // 临时禁用系统动画,立即更新布局 UIView.setAnimationsEnabled(false) self?.view.layoutIfNeeded() UIView.setAnimationsEnabled(true) // 执行自定义时长的动画 UIView.animate(withDuration: 0.8) { self?.view.layoutIfNeeded() } }
方案3:使用UIViewPropertyAnimator(更灵活的控制)
UIViewPropertyAnimator是苹果推出的更强大的动画控制类,它不受系统默认动画上下文的影响,还能支持暂停、倒播等高级操作,适合需要精细控制动画的场景。
代码示例:
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: .main) { [weak self] notification in guard let self = self, let userInfo = notification.userInfo, let endFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { return } let bottomInset = UIScreen.main.bounds.height - endFrame.origin.y self.contentBottomConstraint.constant = bottomInset // 初始化自定义时长的动画器 let animator = UIViewPropertyAnimator(duration: 0.8, curve: .easeInOut) { self.view.layoutIfNeeded() } animator.startAnimation() }
额外注意事项
- 一定要在闭包里用
weak self,避免因为通知持有导致的内存泄漏。 - 如果同时处理设备旋转和键盘状态,建议提前记录当前键盘的显示状态,旋转完成后再重新触发动画,避免状态混乱。
- 不同iOS版本可能有细微差异,建议在目标版本上多做测试,比如iOS 15+和iOS 13的动画上下文处理可能略有不同。
内容的提问来源于stack exchange,提问作者Sphingobium
相关产品推荐
相关产品推荐

