You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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áľ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 03:14:30