iOS中如何在键盘上方叠加视图?含高亮式帮助屏需求
我来帮你解决这个带透明孔洞的模糊帮助屏问题,尤其是要兼容带自定义输入附件的键盘场景——这个需求其实挺常见的,我给你两个靠谱的方案:
实现带透明孔洞的模糊帮助屏(兼容自定义键盘附件)
你的核心需求是:在键盘弹出时,添加一层模糊遮罩,仅保留指定区域的透明孔洞来高亮目标组件,同时让键盘的自定义输入附件视图保持可见且可交互。直接用UIWindow容易因为层级问题把键盘(包括附件)完全覆盖,这里推荐更灵活的实现方式:
方案一:基于应用主窗口添加遮罩层
这个方案更轻量,利用UIApplication.shared.keyWindow来承载遮罩层,能很好地适配键盘层级,同时保证附件视图的可见性。
1. 先写带模糊+孔洞的遮罩视图类
class HelpOverlayView: UIView { // 存储需要高亮的区域frame(全局坐标系) var highlightFrames: [CGRect] = [] { didSet { setNeedsDisplay() } } override init(frame: CGRect) { super.init(frame: frame) setupBlur() } required init?(coder: NSCoder) { super.init(coder: coder) setupBlur() } private func setupBlur() { // 添加深色模糊效果 let blurEffect = UIBlurEffect(style: .dark) let blurView = UIVisualEffectView(effect: blurEffect) blurView.frame = bounds blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight] addSubview(blurView) // 叠加半透明黑色,增强遮罩的沉浸感 backgroundColor = UIColor.black.withAlphaComponent(0.5) } override func draw(_ rect: CGRect) { super.draw(rect) guard let context = UIGraphicsGetCurrentContext() else { return } // 先填充整个遮罩区域 context.setFillColor(UIColor.black.withAlphaComponent(0.5).cgColor) context.fill(rect) // 裁剪出透明孔洞 context.setBlendMode(.clear) highlightFrames.forEach { frame in context.fill(frame) } context.setBlendMode(.normal) } }
2. 在ViewController中监听键盘并管理遮罩层
class ViewController: UIViewController { var textField: UITextField = CustomTextField() private var helpOverlay: HelpOverlayView? override func viewDidLoad() { super.viewDidLoad() setupTextField() setupKeyboardListeners() } private func setupTextField() { textField.frame = CGRect(x: 50, y: 200, width: 300, height: 50) textField.borderStyle = .roundedRect textField.placeholder = "点击弹出键盘" // 设置自定义输入附件视图 let accessoryView = UIView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 60)) accessoryView.backgroundColor = .lightGray let doneButton = UIButton(type: .system) doneButton.setTitle("完成", for: .normal) doneButton.frame = CGRect(x: view.frame.width - 80, y: 10, width: 60, height: 40) accessoryView.addSubview(doneButton) textField.inputAccessoryView = accessoryView view.addSubview(textField) } private func setupKeyboardListeners() { NotificationCenter.default.addObserver( self, selector: #selector(onKeyboardShown(_:)), name: UIResponder.keyboardDidShowNotification, object: nil ) NotificationCenter.default.addObserver( self, selector: #selector(onKeyboardHidden(_:)), name: UIResponder.keyboardWillHideNotification, object: nil ) } @objc private func onKeyboardShown(_ notification: Notification) { guard let keyWindow = UIApplication.shared.keyWindow else { return } // 创建并配置遮罩层 helpOverlay = HelpOverlayView(frame: keyWindow.bounds) helpOverlay?.autoresizingMask = [.flexibleWidth, .flexibleHeight] // 转换目标视图的坐标为全局坐标系 let textFieldGlobalFrame = textField.convert(textField.bounds, to: keyWindow) if let doneButton = textField.inputAccessoryView?.subviews.first(where: { $0 is UIButton }) as? UIButton { let buttonGlobalFrame = doneButton.convert(doneButton.bounds, to: keyWindow) helpOverlay?.highlightFrames = [textFieldGlobalFrame, buttonGlobalFrame] } else { helpOverlay?.highlightFrames = [textFieldGlobalFrame] } // 添加遮罩层到主窗口 keyWindow.addSubview(helpOverlay!) // 可选:添加帮助说明文本 addHelpInstructions() } @objc private func onKeyboardHidden(_ notification: Notification) { helpOverlay?.removeFromSuperview() helpOverlay = nil } private func addHelpInstructions() { guard let overlay = helpOverlay else { return } let helpLabel = UILabel() helpLabel.text = "点击输入框填写内容,或者点击「完成」结束输入" helpLabel.textColor = .white helpLabel.numberOfLines = 0 helpLabel.frame = CGRect(x: 50, y: 100, width: overlay.frame.width - 100, height: 80) overlay.addSubview(helpLabel) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) view.backgroundColor = .white textField.becomeFirstResponder() } deinit { NotificationCenter.default.removeObserver(self) } } class CustomTextField: UITextField { override var canResignFirstResponder: Bool { return false } }
方案二:优化版UIWindow方案
如果你坚持用UIWindow实现,需要调整窗口层级,确保它覆盖在键盘上方,同时处理好遮罩的孔洞逻辑:
class HelpWindow: UIWindow { var highlightFrames: [CGRect] = [] { didSet { setNeedsDisplay() } } override init(frame: CGRect) { super.init(frame: frame) // 键盘窗口的层级是UIWindow.Level.alert,这里设得更高一点 windowLevel = UIWindow.Level.alert + 1 backgroundColor = .clear isUserInteractionEnabled = true } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func draw(_ rect: CGRect) { super.draw(rect) guard let context = UIGraphicsGetCurrentContext() else { return } // 添加模糊视图 let blurEffect = UIBlurEffect(style: .dark) let blurView = UIVisualEffectView(effect: blurEffect) blurView.frame = rect addSubview(blurView) // 绘制遮罩蒙版 UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.main.scale) guard let imageContext = UIGraphicsGetCurrentContext() else { return } imageContext.setFillColor(UIColor.black.withAlphaComponent(0.5).cgColor) imageContext.fill(rect) imageContext.setBlendMode(.clear) highlightFrames.forEach { frame in imageContext.fill(frame) } imageContext.setBlendMode(.normal) let maskImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() // 给模糊视图添加蒙版 let maskLayer = CALayer() maskLayer.contents = maskImage?.cgImage maskLayer.frame = rect blurView.layer.mask = maskLayer } }
然后在onKeyboardShown中初始化这个窗口:
@objc private func onKeyboardShown(_ notification: Notification) { let helpWindow = HelpWindow(frame: UIScreen.main.bounds) let textFieldGlobalFrame = textField.convert(textField.bounds, to: nil) helpWindow.highlightFrames = [textFieldGlobalFrame] helpWindow.makeKeyAndVisible() // 保存引用避免被提前释放 self.helpWindow = helpWindow }
关键注意事项
- 坐标转换:一定要把目标视图的本地坐标转换成全局坐标系(相对于屏幕或主窗口),否则孔洞位置会完全错位。
- 层级控制:键盘的窗口层级是
UIWindow.Level.alert,所以你的遮罩层/window层级必须≥这个值才能覆盖在键盘上方。 - 交互保留:如果需要让高亮区域和键盘附件保持可交互,确保遮罩层的
isUserInteractionEnabled设为false,或者在孔洞区域添加点击穿透逻辑。 - 内存管理:记得在键盘隐藏时移除遮罩层或释放window,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Milan Nosáľ
相关产品推荐
相关产品推荐

