- Published on
Preventing Screenshot Capture in SwiftUI Views
- Authors

- Name
- Mick MacCallum
- @0x7fs
If you're building apps that handle sensitive information like banking details, medical records, or private communications, you may need to prevent users from capturing screenshots of certain screens. While iOS doesn't provide a built-in way to completely block screenshots, you can use the secure text entry mechanism and field redaction to hide sensitive content when screenshots are taken.
SwiftUI doesn't expose direct screenshot prevention APIs, but we can bridge to UIKit's secure field functionality to protect sensitive views. This approach is commonly used in banking apps, password managers, and healthcare applications.
The Challenge
Unlike Android, iOS doesn't allow apps to completely prevent screenshots. However, iOS does provide a mechanism originally designed for password fields that makes content appear blank in screenshots, screen recordings, and the app switcher. This same mechanism can be applied to any view containing sensitive information.
The key is using UITextField's isSecureTextEntry property, but applying it at the view level rather than just for text input.
Solution: Secure Field View Wrapper
We'll create a SwiftUI view that wraps your sensitive content and uses UIKit's secure text entry mechanism to hide it from screenshots:
import SwiftUI
import UIKit
struct SecureView<Content: View>: UIViewRepresentable {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
func makeUIView(context: Context) -> SecureUIView {
let secureView = SecureUIView()
// Add SwiftUI content as a child
let hostingController = UIHostingController(rootView: content)
hostingController.view.backgroundColor = .clear
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
secureView.addSubview(hostingController.view)
NSLayoutConstraint.activate([
hostingController.view.leadingAnchor.constraint(equalTo: secureView.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: secureView.trailingAnchor),
hostingController.view.topAnchor.constraint(equalTo: secureView.topAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: secureView.bottomAnchor)
])
return secureView
}
func updateUIView(_ uiView: SecureUIView, context: Context) {
// No update needed
}
}
class SecureUIView: UIView {
private let secureTextField = UITextField()
override init(frame: CGRect) {
super.init(frame: frame)
setupSecureField()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupSecureField()
}
private func setupSecureField() {
// Create a secure text field to trigger screenshot protection
secureTextField.isSecureTextEntry = true
// Make it invisible but part of the view hierarchy
secureTextField.isUserInteractionEnabled = false
secureTextField.alpha = 0
secureTextField.translatesAutoresizingMaskIntoConstraints = false
addSubview(secureTextField)
// Pin it to a corner (it won't be visible)
NSLayoutConstraint.activate([
secureTextField.leadingAnchor.constraint(equalTo: leadingAnchor),
secureTextField.topAnchor.constraint(equalTo: topAnchor),
secureTextField.widthAnchor.constraint(equalToConstant: 1),
secureTextField.heightAnchor.constraint(equalToConstant: 1)
])
// Bring content to front
sendSubviewToBack(secureTextField)
}
}
How It Works
The solution relies on a clever iOS behavior: when a view hierarchy contains a secure text field (isSecureTextEntry = true), the entire view subtree appears blank in screenshots and screen recordings.
Here's what's happening:
- SecureUIView: Creates an invisible
UITextFieldwithisSecureTextEntry = true - View Hierarchy: The secure text field is added to the view but made completely transparent and non-interactive
- Screenshot Protection: iOS detects the secure field and blanks out the entire view in screenshots
- SwiftUI Integration:
UIViewRepresentablebridges this UIKit solution into SwiftUI
Using the Secure View
Wrap any sensitive SwiftUI content in the SecureView:
struct BankingDetailsView: View {
@State private var accountNumber = "1234567890"
@State private var balance = "$10,453.21"
var body: some View {
VStack(spacing: 20) {
// Regular content - can be screenshot
Text("Account Summary")
.font(.title)
// Sensitive content - protected from screenshots
SecureView {
VStack(spacing: 12) {
HStack {
Text("Account Number:")
Spacer()
Text(accountNumber)
.fontWeight(.bold)
}
HStack {
Text("Available Balance:")
Spacer()
Text(balance)
.font(.title2)
.foregroundColor(.green)
}
}
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(12)
}
Spacer()
}
.padding()
}
}
Protecting the Entire Screen
For maximum security, you can wrap your entire screen:
struct SecureScreenView: View {
var body: some View {
SecureView {
VStack {
Text("Confidential Information")
.font(.title)
// All content here is protected
List {
Section("Patient Records") {
Text("Medical History: ...")
Text("Diagnosis: ...")
Text("Prescription: ...")
}
}
}
}
}
}
Also Protects App Switcher
A bonus benefit: this approach also hides content in the iOS app switcher (when users double-tap the home button or swipe up). This prevents sensitive information from being visible when switching between apps.
Important Considerations
Not Foolproof: Users can still photograph the screen with another device. This technique prevents digital screenshots but not physical photos.
User Experience: Consider showing a placeholder message like "Screenshot not allowed for security reasons" when content is hidden.
Accessibility: Ensure VoiceOver and other accessibility features still work properly with wrapped content.
Performance: The UIKit bridge adds minimal overhead, but avoid wrapping unnecessarily large view hierarchies.
Alternative: Privacy Sensitive Modifier (iOS 15+)
For less critical content, consider the built-in .privacySensitive() modifier:
Text("Sensitive data")
.privacySensitive()
However, this only affects screenshots in some contexts and isn't as comprehensive as the secure field approach.
When to Use This Technique
Use screenshot prevention for:
- Banking and financial information
- Medical records and health data
- Password managers and authentication screens
- Private messaging content
- Personal identification numbers
- Credit card details
This approach gives you fine-grained control over what content is protected, allowing you to balance security with user experience.
Continue Learning
Detect Successful Share Sheet Completion in SwiftUI
Learn how to detect when a user successfully shares content using a share sheet in SwiftUI by wrapping UIActivityViewController.
Adding an App Delegate to a SwiftUI App
Learn how to integrate UIKit's App Delegate into your SwiftUI app for handling app lifecycle events and system callbacks.
Implementing Pull-to-Refresh Without List in SwiftUI
Learn how to add pull-to-refresh functionality to custom ScrollViews in SwiftUI without using List, perfect for custom layouts and complex scroll content.
