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.

Clarification on Using Secure UITextField to Prevent Screen Capture
 
 
Q