Clarification on Using Secure UITextField to Prevent Screen Capture

Hello Developer Forums Team,

I’ve seen that some banking apps prevent screenshots on certain sensitive screens. I’m working on a similar feature in my SDK and want to confirm if my implementation complies with App Store guidelines.

Since there’s no public API to block screenshots, I’m using a workaround based on the secure rendering behavior of UITextField (isSecureTextEntry = true). I embed my custom content (e.g., a UITableView) inside the internal secure container of a UITextField, which results in blank content being captured during screenshots—similar to what some banking apps do.

Approach Summary

  1. I create a UITextField
  2. I detect its internal secure container by matching UIKit internal class names as strings
  3. I embed my real UI content into that container
  4. I do not use or call any private APIs, just match view class names via strings.

ScreenshotPreventingView.swift

final class ScreenshotPreventingView: UIView {
    private let textField = UITextField()
    private let recognizer = HiddenContainerRecognizer()
    private var contentView: UIView?

    public var preventScreenCapture = true {
        didSet {
            textField.isSecureTextEntry = preventScreenCapture
        }
    }

    public init(contentView: UIView? = nil) {
        super.init(frame: .zero)
        self.contentView = contentView
        setupUI()
    }

    private func setupUI() {
        guard let container = try? recognizer.getHiddenContainer(from: textField) else { return }
        addSubview(container)
        NSLayoutConstraint.activate([
            container.topAnchor.constraint(equalTo: topAnchor),
            container.bottomAnchor.constraint(equalTo: bottomAnchor),
            container.leadingAnchor.constraint(equalTo: leadingAnchor),
            container.trailingAnchor.constraint(equalTo: trailingAnchor)
        ])
        if let contentView = contentView {
            setup(contentView: contentView, in: container)
        }
        DispatchQueue.main.async {
            self.preventScreenCapture = true
        }
    }

    private func setup(contentView: UIView) {
        self.contentView?.removeFromSuperview()
        self.contentView = contentView

        guard let container = hiddenContentContainer else { return }

        container.addSubview(contentView)
        container.isUserInteractionEnabled = isUserInteractionEnabled
        contentView.translatesAutoresizingMaskIntoConstraints = false
        

        let bottomConstraint = contentView.bottomAnchor.constraint(equalTo: container.bottomAnchor)
        bottomConstraint.priority = .required - 1

        NSLayoutConstraint.activate([
            contentView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
            contentView.topAnchor.constraint(equalTo: container.topAnchor),
            bottomConstraint
        ])
    }
}

HiddenContainerRecognizer.swift

struct HiddenContainerRecognizer {

    private enum Error: Swift.Error {
        case unsupportedOSVersion(version: Float)
        case desiredContainerNotFound(_ containerName: String)
    }

    func getHiddenContainer(from view: UIView) throws -> UIView {
        let containerName = try getHiddenContainerTypeInStringRepresentation()
        let containers = view.subviews.filter { subview in
            type(of: subview).description() == containerName
        }

        guard let container = containers.first else {
            throw Error.desiredContainerNotFound(containerName)
        }

        return container
    }

    private func getHiddenContainerTypeInStringRepresentation() throws -> String {

        if #available(iOS 15, *) {
            return "_UITextLayoutCanvasView"
        }

        if #available(iOS 14, *) {
            return "_UITextFieldCanvasView"
        }

        if #available(iOS 13, *) {
            return "_UITextFieldCanvasView"
        }

        if #available(iOS 12, *) {
            return "_UITextFieldContentView"
        }

        let currentIOSVersion = (UIDevice.current.systemVersion as NSString).floatValue
        throw Error.unsupportedOSVersion(version: currentIOSVersion)
    }
}

How I use it in my Screen

let container = ScreenshotPreventingView()

override func viewDidLoad() {
 super.viewDidLoad()
 container.preventScreenCapture = true
 container.setup(contentView: viewContainer) //viewContainer is UIView in storyboard, in which all other UI elements are placed in e.g. UITableView
 self.view.addSubview(container)
container.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            container.topAnchor.constraint(equalTo: self.view.topAnchor),
            container.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
            container.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            container.trailingAnchor.constraint(equalTo: self.view.trailingAnchor)
        ])
}

What I’d Like to Confirm

  1. Is this approach acceptable for App Store submission?
  2. Is there a more Apple-recommended approach to prevent screen capture of arbitrary UI?

Thank you for your help in ensuring compliance.

Just checking if anyone has insight on this. Would really appreciate any guidance from someone who has tried this approach or knows if it's App Store-safe or has any other approach. Thanks!

Clarification on Using Secure UITextField to Prevent Screen Capture
 
 
Q