Swift UIKit에서 화면 캡쳐 방지하기

화면 캡쳐 방지

  • iOS 앱에서 콘텐츠 보호를 위해 화면 캡쳐를 방지해야 하는 경우가 있습니다.
  • 스크린샷(이미지)과 화면 녹화(영상)를 통틀어 화면 캡쳐라고 표현을 해봤는데요.
  • 애플의 isSecureTextEntry를 활용한 트릭을 사용해 화면 캡쳐 방지를 구현해보겠습니다.
  • 더불어, 스크린샷화면 녹화를 구분해 방지하는 방법도 알아보겠습니다.

isSecureTextEntry 트릭

  • UITextFieldisSecureTextEntry 속성이 true면, 해당 TextField의 내용은 캡쳐했을때 가려집니다.
  • 이는 원래 비밀번호 등 민감한 정보를 캡쳐하는 것을 방지하기 위한 것으로 알려져있는데요.
  • 이를 활용해 화면 캡쳐를 방지하고 싶은 ViewTextField를 추가하면 캡쳐를 막을 수 있습니다.

UIView 확장

  • Swift 파일을 추가해 아래와 같이 UIView를 확장합니다.
  • 코드에 대한 설명은 코드내 주석으로 추가했습니다.
import UIKit

private var AssociatedObjectHandle = 0

extension UIView {
  // 트릭을 위한 임의의 UITextField
  private var secureTextField: UITextField {
    if let textField = objc_getAssociatedObject(self, &AssociatedObjectHandle) as? UITextField {
      // 추가된 TextField가 있다면 해당 객체 반환
      return textField
    } else {
      // 추가된 TextField가 없다면 생성해서
      let textField = UITextField()
      // 캡쳐하려는 UIView에 추가하고
      addSubview(textField)
      textField.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
      textField.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
      textField.layer.removeFromSuperlayer()
      // 캡쳐하려는 UIView의 Layer를 TextField의 Layer 사이에 끼워 넣고
      layer.superlayer?.insertSublayer(textField.layer, at: 0)
      textField.layer.sublayers?.last?.addSublayer(layer)
      // 마지막으로 set 해서 return
      objc_setAssociatedObject(self, &AssociatedObjectHandle, textField, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
      return textField
    }
  }

  // 트릭의 활성화를 제어하는 메소드
  func secureMode(enable: Bool) {
    secureTextField.isSecureTextEntry = enable
  }
}

사용 방법

조금 더 유연한 화면 캡쳐 대응을 위해 스크린샷화면 녹화를 구분하는 방법도 추가해봤습니다. 해당 코드는 스크린샷은 허용하고 화면 녹화는 방지하도록 작성했습니다.

  • 구현한 Extension을 실제 ViewViewController에서 사용해보겠습니다.
  • 코드에 대한 설명은 코드내 주석으로 추가했습니다.
import UIKit

class ViewController: UIViewController {
  // 예시 콘텐츠
  private lazy var label = {
    let label = UILabel()
    label.text = "캡쳐 보호가 필요한 콘텐츠"
    return label
  }()

  override func viewDidLoad() {
    super.viewDidLoad()

    // 캡쳐 감지용 노티피케이션 센터 추가
    NotificationCenter.default.addObserver(
      self,
      selector: #selector(captureAction),
      name: UIScreen.capturedDidChangeNotification,
      object: nil
    )

    // 예시 콘텐츠 추가
    view.addSubview(label)
    label.translatesAutoresizingMaskIntoConstraints = false
    label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
  }

  // 캡쳐시 실행되는 액션 메소드
  @objc private func captureAction() {
    if UIScreen.main.isCaptured {
      // 캡쳐 중이면 = 화면 녹화 중이면
      view.secureMode(enable: true)
    } else {
      // 캡쳐 중이 아니면 = 화면 녹화가 끝났으면
      view.secureMode(enable: false)
    }
  }
}
  • 핵심적인 사용 코드는 결국 아래 부분입니다.
if UIScreen.main.isCaptured {
  view.secureMode(enable: true)
} else {
  view.secureMode(enable: false)
}

적용한 결과

  • 스크린샷
ios-limit-capture
  • 화면 녹화
ios-limit-capture

주의사항

  • 스크린샷과 화면 녹화와 같은 화면 캡쳐는 실기기에서만 정상적으로 테스트가 가능합니다.