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
- I create a UITextField
- I detect its internal secure container by matching UIKit internal class names as strings
- I embed my real UI content into that container
- 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
- Is this approach acceptable for App Store submission?
- Is there a more Apple-recommended approach to prevent screen capture of arbitrary UI?
Thank you for your help in ensuring compliance.