-
[iOS] 화면 스크린샷 방지 기능 개발에 대한 회고/iOS 📱 2022. 7. 6. 11:51반응형
개요
iOS 단말에서 특정 화면을 스크린샷을 하지 못하게 해달라는 요구사항이 있었는데요 !
구글링과 Github를 돌아다니며 가볍게 훑어보니 관련한 레포지토리들이 검색 결과가 있던 편이었습니다.
단순하게 오 ~ 구현이 쉬운가보다하고 넘어갔었는데
그것은 저에게 큰 시련으로 다가오게됩니다....! (회고를 작성하게된 계기...!)
요구사항 정리
요구사항은 보안상의 이유로 특정화면의 캡쳐를 방지하는 것이었습니다.
특정 화면을 막았더라도 사진으로 저장하는 방법을 넷플릭스 우회법으로 검색을 해보니 아래와 같이 정리할 수 있었습니다.
스크린샷 기능 사용
화면 기록를 통해 기록한 영상을 스크린샷
App Switcher 를 통해 보이는 화면을 스크린샷주어진 시간은 2일, 요구사항, 그리고 우회 방법까지 막는 것은 크게 어려워보이지않았다.
구현 1
iOS 화면캡쳐방지에 대한 내용을 검색해보면 NotifiactionCenter를 통해 화면 녹화, 캡쳐의 이벤트가 발생시 처리하는 내용이다.
예시 코드
더보기import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. NotificationCenter.default.addObserver(self, selector: #selector(didDetectRecording), name: UIScreen.capturedDidChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(didDetectScreenShot), name: UIApplication.userDidTakeScreenshotNotification, object: nil) return true } } extension AppDelegate { @objc private func didDetectRecording() { DispatchQueue.main.async { if UIScreen.main.isCaptured { window?.isHidden = true } else { window?.isHidden = false } } } @objc private func didDetectScreenshot() { let alert = UIAlertController(title: "스크린캡처가 감지되었습니다.", message: "", preferredStyle: .alert) let confirmAction = UIAlertAction(title: "확인", style: .cancel, handler: nil) alert.addAction(cancelAction) DispatchQueue.main.async { if var topController = self.window?.rootViewController { while let presentedViewController = topController.presentedViewController { topController = presentedViewController } topController.present(alert, animated: false, completion: nil) } } } }
구현 1 에서 생각 다시하기
.userDidTakeScreenshotNotification 는 유저가 ScreenShot을 통해 사진 앱에 저장한 후에 노티가 오기때문에
이미 화면이 캡쳐된 이후에 옵저버를 통해서 노티를 받은 후에는 어떤 작업을 해도 근본적으로 화면 캡쳐 방지를 할 수 없었습니다.
.capturedDidChangeNotification 는화면 녹화가 시작되었을 때 노티가 오기 때문에
UIScreen.main.isCaptured get 프로퍼티를 통해 화면녹화가 진행되었을 때 처리를 해줄 수 있습니다.
구현 1을 통해 저는 요구사항을 처리할 수 없었습니다.
그리고 전역적으로 적용하는게 아닌, 화면 캡쳐방지 화면에서만 화면 캡쳐 방지를 했어야했습니다. (이건 addObserver 위치를 통해 해결 가능한 부분이었습니다.)
이때 사전조사의 부족함을 깨달았습니다.
사전조사의 미흡함으로 기간산정에 대해서도 오류를 범하게되었습니다..! (일정은 정해져있었기에 야근을 통해서 .. 맞췄다는 후일담이. ..)
구현 2
다시 사전 조사 단계로 돌아왔습니다.
그리고 참고하게 된 자료입니다.
https://stackoverflow.com/questions/18680028/prevent-screen-capture-in-an-ios-apphttps://stackoverflow.com/questions/18680028/prevent-screen-capture-in-an-ios-app
https://joonhyoung.github.io/swift/PreventScreenCapture/
TextField의 .isSecureTextEntry 프로퍼티를 이용하는 내용이었습니다.
공식문서를 참고하게되면 recording/broadcasting 예방할 수 있다고되어있습니다.
TextField의 프로퍼티를 활용해 UIView의 화면 캡쳐방지 기능을 활성화할 수 있다는 점이 신기했습니다. (프로그래밍의 세계는 대단,.,)
화면 캡쳐방지 주요기능 예제코드는 아래와 같습니다.
전체코드 펼쳐서 보기
더보기import UIKit class ViewController: UIViewController { let customView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false view.backgroundColor = .purple return view }() let button: UIButton = { let button = UIButton(frame: CGRect(x: 10, y: 100, width: 200, height: 30)) button.setTitle("Pay Button", for: .normal) button.setTitleColor(.white, for: .normal) button.layer.borderWidth = 1 button.layer.borderColor = CGColor(red: 255, green: 255, blue: 255, alpha: 1) button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside) return button }() let secureView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false view.backgroundColor = .blue return view }() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. NotificationCenter.default.addObserver(self, selector: #selector(didDetectRecording), name: UIScreen.capturedDidChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(didDetectScreenshot), name: UIApplication.userDidTakeScreenshotNotification, object: nil) addView() secureView.makeSecure() } func addView() { view.addSubview(customView) view.addSubview(secureView) NSLayoutConstraint.activate([ customView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor), customView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor), customView.widthAnchor.constraint(equalToConstant: 240), customView.heightAnchor.constraint(equalToConstant: 240), secureView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor), secureView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor), secureView.widthAnchor.constraint(equalToConstant: 240), secureView.heightAnchor.constraint(equalToConstant: 240) ]) secureView.addSubview(button) NSLayoutConstraint.activate([ button.widthAnchor.constraint(equalToConstant: 150), button.heightAnchor.constraint(equalToConstant: 20), ]) } @objc func buttonAction() { print("touchUpInside") } } 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) } } } extension ViewController { @objc private func didDetectScreenshot() { let alert = UIAlertController(title: "스크린캡처가 감지되었습니다.", message: "", preferredStyle: .alert) let cancelAction = UIAlertAction(title: "확인", style: .cancel, handler: nil) alert.addAction(cancelAction) DispatchQueue.main.async { self.present(alert, animated: false, completion: nil) } } @objc private func didDetectRecording() { if UIScreen.main.isCaptured { self.view?.isHidden = true } else { self.view?.isHidden = false } } }
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 //여기를 주석하면 Runtime Exception self.layer.superlayer?.addSublayer(field.layer) // 여기를 주석하면 Trigger ScreenShot 하여도 prevent recording/broadcasting이 되지않음 field.layer.sublayers?.first?.addSublayer(self.layer) } } }
layer에 대한 얘기는 다음 포스팅으로 나눠서 CALayer에 대해서 자세히 기술해야할 것 같습니다...!
구현 1과 구현 2를 합쳐놓은 모습입니다.
드디어 !! Trigger ScreenShot을 하게 되면 상위에 있는 Blue View가 가려집니다 !!
요구사항 정리했던게 기억나시나요 ?
스크린샷 기능 사용
화면 기록를 통해 기록한 영상을 스크린샷
App Switcher 를 통해 보이는 화면을 스크린샷스크린샷 기능 사용, 화면기록을 통해 기록한 영상을 스크린샷, App Switcher를 통해 보이는 화면 스크린샷
세 가지 요구사항 전부 Pass 했네요
화면 기록 같은 경우는 검은색 화면만 나오게 처리를 해둬서 따로 gif 로 어떤식으로 prevent 되는지 첨부했습니다 !!!
' > iOS 📱' 카테고리의 다른 글
[Firebase] Crashlytics 에서 .dSYM 파일 수동 추가하기 (0) 2022.08.25 로컬라이징(Localization, Localizable) 적용하기 / info.plist (0) 2022.08.21 [iOS] 디바이스의 정보를 알아보자 UIDevice - UUID (1) (0) 2022.05.14 [iOS] 동적 테이블 뷰 셀(UITableView Dynamic cell height) 만들기 (estimatedRowHeight, autoDimension) (0) 2022.05.01 [iOS] Core Loaction 위치 권한 설정하기 (0) 2022.04.26