Last active
July 30, 2025 12:00
-
-
Save bguidolim/28e24c4e259a811440299a267285ceaa to your computer and use it in GitHub Desktop.
A clever way to hide sensitive information from screenshots and screen recordings. Solution by @firatkaratas
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import UIKit | |
import UIUtility | |
extension View { | |
func captureProtected(isBlocked: Bool) -> some View { | |
modifier(BlockCapture(isBlocked: isBlocked)) | |
} | |
} | |
struct BlockCapture: ViewModifier { | |
let isBlocked: Bool | |
func body(content: Content) -> some View { | |
if isBlocked { | |
CaptureBlockingView { | |
content | |
} | |
} else { | |
content | |
} | |
} | |
} | |
struct CaptureBlockingView<Content: View>: UIViewControllerRepresentable { | |
private let content: () -> Content | |
init(@ViewBuilder content: @escaping () -> Content) { | |
self.content = content | |
} | |
func makeUIViewController(context: Context) -> CaptureBlockingHostingController<Content> { | |
CaptureBlockingHostingController(content: content) | |
} | |
func updateUIViewController(_ uiViewController: CaptureBlockingHostingController<Content>, context: Context) { } | |
} | |
final class CaptureBlockingHostingController<Content: View>: UIViewController { | |
private let content: () -> Content | |
private let secureView = CaptureBlockingContainerView() | |
init(@ViewBuilder content: @escaping () -> Content) { | |
self.content = content | |
super.init(nibName: nil, bundle: nil) | |
setupUI() | |
} | |
@available(*, unavailable) | |
required init?(coder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
private func setupUI() { | |
view.addSubview(secureView) | |
secureView.translatesAutoresizingMaskIntoConstraints = false | |
secureView.backgroundColor = .clear | |
let hostingController = addChildView(content()) | |
secureView.setup(contentView: hostingController.view) | |
} | |
} | |
final class CaptureBlockingContainerView: UIView { | |
private var textField = { | |
let textField = UITextField() | |
textField.isSecureTextEntry = true | |
textField.backgroundColor = .clear | |
textField.isUserInteractionEnabled = false | |
return textField | |
}() | |
private lazy var secureViewContainer: UIView? = { | |
let containerPrefix = "UIText" | |
let containerSuffix = "CanvasView" | |
let containerName: String = "Layout" | |
let secureCanvas = textField.subviews.first { subview in | |
// This type has the logic of capture blocking. | |
// It is being extracted so that we don't have to | |
// deal with textField responder logic. | |
let canvasType = "_" + containerPrefix + containerName + containerSuffix | |
return NSStringFromClass(type(of: subview)) == canvasType | |
} | |
guard let secureCanvas else { return nil } | |
addSubview(secureCanvas) | |
secureCanvas.translatesAutoresizingMaskIntoConstraints = false | |
NSLayoutConstraint.activate { | |
secureCanvas.constrain.sameFrame(as: self) | |
} | |
return secureCanvas | |
}() | |
func setup(contentView: UIView) { | |
guard let viewContainer = secureViewContainer else { return } | |
viewContainer.addSubview(contentView) | |
contentView.translatesAutoresizingMaskIntoConstraints = false | |
NSLayoutConstraint.activate { | |
contentView.constrain.sameFrame(as: viewContainer) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment