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

iOS开发求助:如何避免截图中显示真实UI内容?

问题原因

你当前使用的UIApplication.userDidTakeScreenshotNotification是截图完成后才触发的通知,此时添加的红色遮挡层不会被包含到已经完成的截图里,这就是保存的图片仍显示真实UI的核心原因。

解决方案

需要改用UIWindowSceneDelegate的windowScene(_:performActionFor:identifier:completionHandler:)方法,这个方法会在系统执行截图动作之前被调用,能让我们提前添加遮挡层,确保截图包含遮挡内容,之后再移除遮挡层不影响用户操作。

修改后的完整代码

class HomeViewController: UIViewController {
    
    private var greetLabel: UILabel!
    private var screenshotBlockerView: ScreenshotBlockerView? // 保存遮挡层引用,方便后续移除
    private let navigator: HomeNavigator
    
    init(navigator: HomeNavigator) {
        self.navigator = navigator
        super.init(nibName: nil, bundle: nil)
        setupTabBar()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupNavigationBar()
        setupViews()
        setupConstraints()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // 绑定当前页面为windowScene的代理,拦截截图动作
        if let windowScene = view.window?.windowScene {
            windowScene.delegate = self
        }
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        // 恢复原有代理,避免影响其他页面逻辑
        if let windowScene = view.window?.windowScene,
           let originalDelegate = UIApplication.shared.connectedScenes.first?.delegate as? UIWindowSceneDelegate {
            windowScene.delegate = originalDelegate
        }
        // 确保遮挡层被移除
        removeScreenshotBlockerView()
    }
    
    // 添加遮挡层(支持全屏或特定视图)
    private func addScreenshotBlockerView() {
        // 方案1:全屏遮挡
        let frame = UIScreen.main.bounds
        
        // 方案2:仅遮挡特定视图(比如greetLabel)
        // let frame = greetLabel.convert(greetLabel.bounds, to: UIApplication.shared.keyWindow)
        
        screenshotBlockerView = ScreenshotBlockerView(frame: frame)
        if let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) {
            keyWindow.addSubview(screenshotBlockerView!)
        }
        // 强制刷新UI,确保遮挡层立即生效
        UIView.performWithoutAnimation {
            screenshotBlockerView?.layoutIfNeeded()
        }
    }
    
    // 移除遮挡层
    func removeScreenshotBlockerView() {
        screenshotBlockerView?.removeFromSuperview()
        screenshotBlockerView = nil
    }
    
    // 原有页面配置方法保持不变
    private func setupNavigationBar() {
        navigationItem.title = "Home"
        self.navigationItem.largeTitleDisplayMode = .never
        navigationController?.navigationBar.prefersLargeTitles = true
        navigationItem.backBarButtonItem = UIBarButtonItem(title: "",style: .plain,target: nil,action: nil)
    }
    
    private func setupTabBar() {
        let tabBarItem = UITabBarItem(title: "Home", image: UIImage(systemName: "house") , tag: 0)
        tabBarItem.selectedImage = UIImage(systemName: "house.fill")
        self.tabBarController?.tabBar.barTintColor =  UIColor.blue
        self.tabBarItem = tabBarItem
    }
    
    private func setupViews() {
        view.backgroundColor = .white
        
        let greetLabel = UILabel()
        greetLabel.numberOfLines = 0
        greetLabel.font = UIFont.systemFont(ofSize: 18 , weight: UIFont.Weight.semibold)
        greetLabel.textColor = UIColor.black
        greetLabel.translatesAutoresizingMaskIntoConstraints = false
        greetLabel.text = AppConstants.shared.greetLabelText(forType: .itemConstant)
        
        self.greetLabel = greetLabel
        
        view.addSubview(greetLabel)
    }
    
    private func setupConstraints() {
        NSLayoutConstraint.activate([
            self.greetLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            self.greetLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        ])
    }
}

// 扩展实现UIWindowSceneDelegate,拦截截图动作
extension HomeViewController: UIWindowSceneDelegate {
    func windowScene(_ windowScene: UIWindowScene, performActionFor session: UISceneSession, identifier: UISceneSession.ActionIdentifier, completionHandler: @escaping (UISceneSession.ActionResponse) -> Void) {
        // 判断是否是截图动作
        if identifier == .takeScreenshot {
            // 截图前添加遮挡层
            addScreenshotBlockerView()
            
            // 截图完成后延迟移除遮挡层(0.1秒足够完成截图)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                self.removeScreenshotBlockerView()
            }
            
            // 允许系统继续完成截图流程
            completionHandler(.continue)
            return
        }
        
        // 其他系统动作交给默认处理逻辑
        completionHandler(.continue)
    }
}

class ScreenshotBlockerView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = UIColor.red
        
        // 可选:添加自定义遮挡内容,比如替换真实文本的占位符
        let placeholderLabel = UILabel(frame: bounds)
        placeholderLabel.text = "截图保护内容"
        placeholderLabel.textColor = .white
        placeholderLabel.textAlignment = .center
        placeholderLabel.font = UIFont.systemFont(ofSize: 18, weight: .semibold)
        addSubview(placeholderLabel)
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
}

关键说明

  1. 拦截时机:通过windowScene(_:performActionFor:identifier:completionHandler:)在截图前触发遮挡层添加,确保截图包含遮挡内容。
  2. 代理恢复:在页面消失时恢复原有windowScene代理,避免影响其他页面的系统交互逻辑。
  3. 灵活遮挡:可以选择全屏遮挡,或者仅遮挡特定视图(比如示例中的greetLabel),只需修改addScreenshotBlockerView中的frame计算逻辑。

内容的提问来源于stack exchange,提问作者Naveen Kumar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 04:25:58