首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >iOS 全局防截屏原理

iOS 全局防截屏原理

原创
作者头像
莫空9081
发布2026-02-28 17:27:19
发布2026-02-28 17:27:19
410
举报
文章被收录于专栏:iOS 备忘录iOS 备忘录

一、系统背景

  • iOS 对「安全输入」相关视图(例如开启安全输入的文本框及其内部系统私有子视图)在截屏与录屏时会自动排除,这些视图不会出现在截图或录屏画面中。
  • 项目利用这一机制,将整屏应用内容放入系统认定的「安全视图」层级内,从而实现防截屏/防录屏下的内容保护。

<!--more-->

二、实现原理概览

2.1 安全容器从何而来

  • 使用一个透明的、开启安全输入的文本框(系统会为其创建内部私有子视图用于安全渲染)。
  • 取该文本框的第一个子视图(即系统为安全输入创建的内容视图)作为「安全容器」。
  • 将该安全容器从文本框上移除并清空其原有子视图,再作为窗口的唯一直接子视图添加到窗口上,设全屏 frame 与自动布局,使窗口的直接子视图只有一个:安全容器本身。

示意代码(Swift):

代码语言:swift
复制
// 安全窗口内:构建安全容器并挂到窗口上
private func setupSecureContainer() {
    let textField = SecureOverlayTextField(frame: .zero)  // 透明、isSecureTextEntry = true
    guard let secureView = textField.subviews.first else { return }
    secureView.removeFromSuperview()
    secureView.subviews.forEach { $0.removeFromSuperview() }
    secureView.frame = bounds
    secureView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    secureView.isUserInteractionEnabled = true
    super.addSubview(secureView)   // 安全容器成为窗口唯一直接子视图
    secureTextField = textField    // 强引用保留,避免部分系统版本异常
    secureContentView = secureView
}

2.2 内容如何进入安全容器

  • 重写窗口的 addSubview:除「安全容器自身」外,所有通过该窗口添加的视图都不再加在窗口上,而是统一加到安全容器内
  • 这样,无论是根界面还是后续加在「窗口」上的浮层(弹窗、蒙层、设置面板等),最终都在安全容器内,从而在截屏/录屏时被系统整体排除。

示意代码(Swift):

代码语言:swift
复制
// 安全窗口:重写 addSubview,将内容重定向到安全容器
override func addSubview(_ view: UIView) {
    if view === secureContentView {
        super.addSubview(view)
    } else if let content = secureContentView {
        let isRootContent = content.subviews.isEmpty
        if isRootContent {
            view.frame = content.bounds
            view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        }
        content.addSubview(view)   // 根内容与浮层都加在安全容器内
    } else {
        super.addSubview(view)
    }
}

2.3 根内容与浮层的区别对待

  • 根内容:第一个被加入安全容器的视图(通常为根控制器的 view)。对其单独处理:设置 frame = 安全容器.bounds 并设置自动调整大小,保证全屏且随窗口变化。
  • 后续浮层:不再改 frame,保持调用方传入的 frame,避免把弹窗、小视图等误设为全屏。

2.4 文本框的保留

  • 安全容器来自文本框的内部子视图,文本框本身不再挂在视图层级上,但需在窗口侧强引用保留该文本框,避免在部分系统版本上被释放导致异常。

三、注意事项(原理层面)

3.1 按 tag 查找「加在窗口上的浮层」

  • 现象:在启用安全窗口后,窗口的直接子视图只有安全容器这一项,原先加在「窗口」上的浮层实际都在安全容器的子视图里。
  • 注意:凡是通过「遍历窗口的直接子视图」按 tag 查找浮层的逻辑,会找不到目标。
  • 正确做法:若当前为安全窗口,应使用安全窗口提供的「用于 overlay 查找的子视图列表」(即安全容器内的子视图数组)进行遍历与按 tag 查找;非安全窗口时仍使用窗口的 subviews。这样无论是普通窗口还是安全窗口,都能正确找到浮层并执行移除、拖拽等逻辑。

示意代码(Objective-C):

代码语言:objc
复制
// 按 tag 查找加在「窗口」上的浮层时,兼容安全窗口
id window = [AppDelegate shared].window;
NSArray<UIView *> *subviewsToSearch = [window isKindOfClass:[SecureWindow class]]
    ? [(SecureWindow *)window subviewsForOverlayTagLookup]
    : [window subviews];

UIView *overlay = nil;
for (UIView *view in subviewsToSearch) {
    if (view.tag == kOverlayTag) {
        overlay = view;
        break;
    }
}
if (overlay) {
    // 执行移除、更新 frame、拖拽逻辑等
}

3.2 Toast、Loading 等「以窗口为父视图」的展示

  • 现象:安全容器对应的系统私有视图,对后加在其上的部分子视图可能存在渲染或命中测试上的差异,导致 Toast、Loading 等加在窗口上时显示异常或尺寸被错误修改(若曾对「所有加在窗口的视图」统一设全屏 frame,会加剧该问题)。
  • 注意:若 Toast / Loading 在「未指定父视图」时默认加在窗口上,在安全窗口下可改为加在顶层控制器视图等稳定容器上,避免依赖安全容器对后加子视图的渲染行为;同时仅对「根内容」设全屏 frame,不对后续 overlay 改 frame。

示意代码(选择展示容器):

代码语言:swift
复制
// 当 inView 为空时,决定 Toast/Loading 加在哪个视图上
func resolveToastContainerView() -> UIView? {
    guard let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) else { return nil }
    if window is SecureWindow, let topVC = topViewController(for: window), let v = topVC.view {
        return v   // 安全窗口下用顶层 VC.view,避免加在安全容器内导致渲染异常
    }
    return window
}

3.3 浮层点击、拖拽「不响应」

  • 可能原因一:事件与查找无关,而是查找失败。拖拽/点击的处理逻辑里若仍通过「窗口的直接子视图」按 tag 查找浮层,在安全窗口下会找不到,逻辑提前 return,表现为「不响应」。
  • 可能原因二:安全容器为系统私有视图,其子视图的 hitTest / 事件传递在个别场景下可能与预期不符,需结合具体视图层级与手势配置排查。
  • 建议:先统一将「在窗口上按 tag 找浮层」改为使用上述 overlay 查找方式,再视情况排查 hitTest、手势冲突、userInteractionEnabled 等。

3.4 坐标转换、只读使用窗口

  • 仅读取窗口、做坐标转换(如 convertRect:fromView:)、访问 rootViewController 等与 addSubview 规则无关的用法,无需因防截屏而修改

3.5 防截屏的边界

  • 防截屏仅影响被放入安全容器的内容在截屏/录屏画面中的可见性(被系统排除)。
  • 应用内仍可监听系统截屏/录屏通知(如 UIScreenCapturedDidChangeNotificationUIScreen.isCaptured)做业务逻辑:例如提示用户、显示自定义遮挡层等,与「安全容器内不参与截屏」是互补关系。

四、小结

要点

说明

核心思路

用系统「安全输入」视图的私有子视图作为安全容器,重写窗口 addSubview 使所有内容进该容器,从而被截屏/录屏排除。

根内容

仅第一个加入安全容器的视图设全屏 frame;其余 overlay 保持原 frame。

查找浮层

安全窗口下必须用「overlay 查找接口」返回的列表(安全容器子视图),不能再用窗口的直接 subviews。

Toast/Loading

可考虑以顶层 VC.view 等为展示容器,避免安全容器对后加子视图的渲染/尺寸问题。

不响应交互

先检查是否因「仍用窗口 subviews 查找」导致找不到视图;再排查 hitTest、手势与层级。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、系统背景
  • 二、实现原理概览
    • 2.1 安全容器从何而来
    • 2.2 内容如何进入安全容器
    • 2.3 根内容与浮层的区别对待
    • 2.4 文本框的保留
  • 三、注意事项(原理层面)
    • 3.1 按 tag 查找「加在窗口上的浮层」
    • 3.2 Toast、Loading 等「以窗口为父视图」的展示
    • 3.3 浮层点击、拖拽「不响应」
    • 3.4 坐标转换、只读使用窗口
    • 3.5 防截屏的边界
  • 四、小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档