86

I need to prevent screen capture by users of my app, for security reasons. The contents I display are confidential and should not be copied onto the device. I saw one answer on Stack Overflow, but for Android.

Is it possible somehow in iOS to prevent screen capture?

While capturing the screenshot into the gallery by the click of few buttons is a very useful feature for the user, there is a limited requirement to prevent it too. Any pointers?

6
  • 3
    It's an OS level action, so I don't think it would be possible, unless you're running on a jailbroken device. I could be wrong though. Commented Sep 8, 2013 at 3:15
  • 3
    I don't think you can prevent it on a non-jailbroken device, but you can detect it - stackoverflow.com/questions/13484516/… Commented Sep 8, 2013 at 4:05
  • 1
    The other problem is screen capture in wetware - as in a person captures the screen with another device such as a camera or other phone. Even if you prevent it in the app it's impossible to prevent someone taking a photo of the screen Commented Oct 12, 2014 at 10:37
  • Is there any way to detect captured image in the block and we can make it obscured? Commented Feb 22, 2019 at 3:04
  • I already saw some security frameworks who do that, but I don't know exactly what they do, but certainly some system call interception. Commented Jan 20, 2020 at 12:18

11 Answers 11

78

I've just wrote simple extension of UIView that allows to hide it from screen-capturing, Airplay mirroring and so on. The solution uses ability of UITextField to hide a password from capturing.

extension UIView { func makeSecure() { DispatchQueue.main.async { let field = UITextField() field.isSecureTextEntry = true field.isUserInteractionEnabled = false self.addSubview(field) field.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true field.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true self.layer.superlayer?.addSublayer(field.layer) field.layer.sublayers?.first?.addSublayer(self.layer) } } } 

using:

class ViewController: UIViewController { var secureView: UIView! override func viewDidLoad() { super.viewDidLoad() secureView.makeSecure() } } 

I'll be grateful if someone explains how Apple does this magic inside.

Sign up to request clarification or add additional context in comments.

26 Comments

It should also be noted that this will only work on iOS 13+
This can cause several issues and I recommend against it. If your layer also has other sublayers (a custom view, for example) then this will most possibly crash depending on your implementation.
@orakull _UITextLayoutCanvasView is responsible for hiding things (see gist.github.com/damian-rzeszot/bbfd92895dfe9400ba9a7cf2a0c1e670)
I'm not longer seeing this approach working on iOS 17. Has anyone identified an alternative approach?
field.layer.sublayers?.last?.addSublayer(self.layer) seems to work on iOS17
|
32

There's no way to prevent taking screenshots entirely. You can do what Snapchat does, which is by requiring the user to be touching the screen to view whatever information you're displaying. This is because the system screenshot event interrupts touches. It's not a perfect method and you can't prevent users from taking screenshots 100% of the time.

More details: iOS Detection of Screenshot?

6 Comments

I don't use Snapchat so I can't test it right now, but What happens if you plug your iPhone to a Mac and take a screenshot through Xcode? (finger on the screen all along). I'm pretty sure there's no programmatic way to detect (let alone prevent) such screen capture...
This also, astoundingly, doesn't prevent anyone from simply taking a photo of the screen with another phone or camera. The whole idea of blocking users from capturing what they're seeing is ridiculously naive.
@SevenSystems is there any system in place that can block a user from taking a photo with another device..?
@AlanS that is physically impossible.
@SevenSystems exactly...
|
10

It's been a while, but I just came across ScreenShieldKit, which is a patent-pending technology used by the messaging app Confide. What it does is that it let's the user take screenshots, but the content is blank on the end picture. They recently released the iOS version.

4 Comments

It requires adopting their custom UI elements because it is protected by converting the view to video with DRM
@CameronLowellPalmer how do you know they are using DRM?
If you dig into their binary you'll see they are doing video transcoding and the whole thing is rather obvious. Apple provides protection for video layers in compliance with copyright law.
But ScreenShieldKit, supports for only iOS 13 and above. Is there any other way for iOS 12 and below?
9

For iOS 17, Swift 4 or Swift 5, those who are getting issue or crash with the previous implementations. Here is a correct way to do it. I just implemented and tested it on iOS 17+.

Add this code in AppDelegate class:

func makeSecure(window: UIWindow) { let field = UITextField() let view = UIView(frame: CGRect(x: 0, y: 0, width: field.frame.self.width, height: field.frame.self.height)) let image = UIImageView(image: UIImage(named: "whiteImage")) image.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) field.isSecureTextEntry = true window.addSubview(field) view.addSubview(image) window.layer.superlayer?.addSublayer(field.layer) field.layer.sublayers?.last!.addSublayer(window.layer) field.leftView = view field.leftViewMode = .always } 

You can call this from AppDelegate's didFinishLaunchingWithOptions function.

if let window = window { makeSecure(window: window) } 

This will prevent screen recording as well as ScreenShot in your app.

14 Comments

Your method is not working iOS 17.2 simulator.
@TusharLathiya please test it on a real device. It does not work on Simulator
Tested working on iOS 17.3 (real device)
Great Solution? but what if I have to enable the screen recording as well as ScreenShot from the toggle option (some where in the app)? @s.Aulakh
I tried to remove but screen got blackout while removing view from window. Any solution?
|
5

Remove sensitive information from views before moving to the background. When an application transitions to the background, the system takes a snapshot of the application’s main window, which it then presents briefly when transitioning your application back to the foreground. Before returning from your applicationDidEnterBackground: method, you should hide or obscure passwords and other sensitive personal information that might be captured as part of the snapshot.

In swift 4 add this code to your app delegate.

Declare a variable in app delegate

var imageview : UIImageView? func applicationWillResignActive(_ application: UIApplication) { imageview = UIImageView.init(image: UIImage.init(named: "bg_splash")) self.window?.addSubview(imageview!) // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. } func applicationDidBecomeActive(_ application: UIApplication) { if (imageview != nil){ imageview?.removeFromSuperview() imageview = nil } // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } 

8 Comments

yeah @AbdulYasin kindly share your optimal solution
In fact, there is no such way to prevent screenshot in iOS. Though you can use third party library for the same.
yes there is no way but to hide your information you put a layer of image so that your sensitive information should not go to user and there is no mechanism besides this.
This is not relevant to the question
This code is not working in check in the xcode11.3 with iOS version 13.3.1
|
5

You can implement just one function to achieve that:

func disableScreenshots(layer: CALayer) { let textField = UITextField() var secureView: UIView? for subview in textField.subviews { if NSStringFromClass(type(of: subview)).contains("TextLayoutCanvasView") { secureView = subview break } } guard let secureView = secureView else { return } let previousLayer = secureView.layer secureView.setValue(layer, forKey: "layer") textField.isSecureTextEntry = true secureView.setValue(previousLayer, forKey: "layer") } 

Or, if you want a more efficient version:

struct DisableScreenshot { private static let textField = UITextField() private static let secureView = textField.subviews.first { NSStringFromClass(type(of: $0)).contains("TextLayoutCanvasView") } static func disableScreenshots(layer: CALayer) { guard let secureView = Self.secureView else { return } let previousLayer = secureView.layer secureView.setValue(layer, forKey: "layer") Self.textField.isSecureTextEntry = true secureView.setValue(previousLayer, forKey: "layer") } } extension CALayer { func disableScreenshots() { DisableScreenshot.disableScreenshots(layer: self) } } 

1 Comment

you can use this code in viewDidLoad -->>self.view.layer.disableScreenshots() it will work perfect
3

You can use this extension below. Please apply this method to the parent view

 extension UIView { func preventScreenshot(for view: UIView) { let textField = UITextField() textField.isSecureTextEntry = true textField.isUserInteractionEnabled = false guard let hiddenView = textField.layer.sublayers?.first?.delegate as? UIView else { return } hiddenView.subviews.forEach { $0.removeFromSuperview() } hiddenView.translatesAutoresizingMaskIntoConstraints = false self.addSubview(hiddenView) hiddenView.fillSuperview() hiddenView.addSubview(view) } } 

So for instance to be able to prevent screenshots on a scrollView

private weak var scrollView: UIScrollView! (it's an outlet) 

in your viewDidLoad, just do this below

self.view.preventScreenshot(for: self.scrollView) 

Note: fillSuperview is just anchoring your view to its superview so it's like below:

NSLayoutConstraint.activate([ hiddenView.leadingAnchor.constraint(equalTo: self.leadingAnchor), hiddenView.trailingAnchor.constraint(equalTo: self.trailingAnchor), hiddenView.bottomAnchor.constraint(equalTo: self.bottomAnchor), hiddenView.topAnchor.constraint(equalTo: self.topAnchor) ]) 

4 Comments

What's fillSuperview?
Figured it out. However, this does not work for iOS 12.
Not working on iOS 17.
I try implement this but it cause all content inside scrollview (uitextfield, button, etc) can't be interact
1

When using the UITextField.isSecureTextEntry trick to block screenshots / screen recordings for an entire UIWindow, now we noticed that on iOS 26 and in RTL languages (like Arabic), a black strip appears on the left side. This happens because the UITextField internally reserves layout space for its leftView/rightView when system language is RTL, causing misalignment.

You can force the field to render in LTR mode so it doesn’t reserve the space in Arabic:

extension UIWindow { func makeSecure() { DispatchQueue.main.async { let field = UITextField() let view = UIView(frame: CGRect(x: 0, y: 0, width: field.frame.self.width, height: field.frame.self.height)) field.isSecureTextEntry = true self.addSubview(field) self.layer.superlayer?.addSublayer(field.layer) field.layer.sublayers?.last!.addSublayer(self.layer) field.leftView = view field.leftViewMode = .always field.semanticContentAttribute = .forceLeftToRight } } } 

And Call your method in you AppDelegate.swift

 window?.makeSecure() 

This will work because Apple handles secure content by rendering UITextField (with isSecureTextEntry = true) in a protected Core Animation layer that WindowServer excludes from screenshots and recordings.

Comments

1

im using UITextField.isSecureTextEntry trick to block screenshots / screen recordings for an entire UIWindow . i tweak again so i can use it on specific page. so i call it on viewWillAppear and viewWillDisappear

add extension to UIWindow

extension UIWindow { private static var secureFields: [WeakWindowWrapper] = [] // MARK: - Secure Layer func makeSecure() { DispatchQueue.main.async { let field = UITextField() field.isSecureTextEntry = true field.isUserInteractionEnabled = false field.backgroundColor = .clear field.semanticContentAttribute = .forceLeftToRight self.addSubview(field) self.layer.superlayer?.addSublayer(field.layer) field.layer.sublayers?.last?.addSublayer(self.layer) // save field for this window UIWindow.secureFields.removeAll { $0.window == nil } UIWindow.secureFields.append(WeakWindowWrapper(window: self, field: field)) } } // MARK: - Change secure func setSecure(_ isSecure: Bool) { DispatchQueue.main.async { guard let wrapper = UIWindow.secureFields.first(where: { $0.window === self }) else { print("⚠️ The secure field is not set yet. Call makeSecure() first.") return } wrapper.field.isSecureTextEntry = isSecure } } } // MARK: - Helper struct private class WeakWindowWrapper { weak var window: UIWindow? let field: UITextField init(window: UIWindow, field: UITextField) { self.window = window self.field = field } } 

call makeSecure() func on AppDelegate

window?.makeSecure() 

add this extension for UIViewController

extension UIViewController { func setWindowSecure(_ isSecure: Bool) { DispatchQueue.main.async { if let window = self.view.window ?? UIApplication.shared.windows.first { window.setSecure(isSecure) } else { print("No active window was found for this UIViewController.") } } } } 

how to use it on specific view controller

 override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) setWindowSecure(true) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) setWindowSecure(false) } 

Comments

0

I've heard that you can listen for a screenshot event using UIApplicationUserDidTakeScreenshotNotification

 NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationUserDidTakeScreenshotNotification object:nil queue:mainQueue usingBlock:^(NSNotification *note) { // executes after screenshot NSLog(@"Screenshot Detection : %@", note); UIAlertView *screenshotAlert = [[UIAlertView alloc] initWithTitle:@"Screenshot Detected" message:@"Oh Oh no screenshot bruhh" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [screenshotAlert show]; }]; 

what if you could immediately delete the screenshot file when it was made?

5 Comments

How would you delete the file? That's not possible AFAIK.
Im not very familiar with ios development, but on android if user gives access to gallery you can create and also delete files from there. So i thought that you could immediately delete the taken screenshot.
While this may not help you prevent the screen shot it's a good method to track if anyone is taking screenshots and how prevalent the issue is within your app. If your app uses a login mechanism you can identify the user and restrict app access or provide a notice (if desired) to the user. I implemented this in an app and users were taking screen shots but the amount was small enough to not warrant effort to prevent.
And what if that user will share the taken screenshot?
@BadmintonCat it is possible, but user permission is required
0

You can prevent screen capture and screen recording of a specific view in your app by using this custom view. Also, you can add your own placeholder that will only show when the user tries to screen capture or screen record.

import Foundation import UIKit class SecureView: UIView { // placeholder will become visible when user try to capture screenshot // or try to record the screen private(set) var placeholderView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false return view }() // add your content in this view // it will be secure private(set) var contentView: UIView = { let hiddenView = UIView() hiddenView.makeSecure() hiddenView.translatesAutoresizingMaskIntoConstraints = false return hiddenView }() override init(frame: CGRect) { super.init(frame: frame) setupView() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } fileprivate func setupView() { self.addSubview(placeholderView) self.addSubview(contentView) NSLayoutConstraint.activate([ placeholderView.leadingAnchor.constraint(equalTo: self.leadingAnchor), placeholderView.trailingAnchor.constraint(equalTo: self.trailingAnchor), placeholderView.topAnchor.constraint(equalTo: self.topAnchor), placeholderView.bottomAnchor.constraint(equalTo: self.bottomAnchor), contentView.leadingAnchor.constraint(equalTo: self.leadingAnchor), contentView.trailingAnchor.constraint(equalTo: self.trailingAnchor), contentView.topAnchor.constraint(equalTo: self.topAnchor), contentView.bottomAnchor.constraint(equalTo: self.bottomAnchor), ]) } } extension UIView { func makeSecure() { DispatchQueue.main.async { let field = UITextField() field.isSecureTextEntry = true self.addSubview(field) field.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true field.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true self.layer.superlayer?.addSublayer(field.layer) field.layer.sublayers?.first?.addSublayer(self.layer) } } } 

Sample project

Demo.gif

3 Comments

This does not work for iOS 17.0
I never tried it with iOS 17.0, I will verify that.
I tried and not working on iOS 17

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.