Skip to content

Commit ac80b64

Browse files
committed
Initial commit
1 parent 3f9c607 commit ac80b64

File tree

4 files changed

+147
-5
lines changed

4 files changed

+147
-5
lines changed

Package.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
// swift-tools-version: 6.2
2-
// The swift-tools-version declares the minimum version of Swift required to build this package.
3-
42
import PackageDescription
53

64
let package = Package(
75
name: "SnapShield",
6+
platforms: [
7+
.iOS(.v16),
8+
.tvOS(.v16)
9+
],
810
products: [
9-
// Products define the executables and libraries a package produces, making them visible to other packages.
1011
.library(
1112
name: "SnapShield",
1213
targets: ["SnapShield"]
1314
),
1415
],
1516
targets: [
16-
// Targets are the basic building blocks of a package, defining a module or a test suite.
17-
// Targets can depend on other targets in this package and products from dependencies.
1817
.target(
1918
name: "SnapShield"
2019
),
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//
2+
// SecureView.swift
3+
// SnapShield
4+
//
5+
// Created by Emad on 2025-10-12.
6+
//
7+
8+
import Foundation
9+
import UIKit
10+
11+
// Internal to the package (not public)
12+
final class SecureView: UIView {
13+
14+
fileprivate var secureField: UITextField = UITextField()
15+
16+
// toggle this value to enable or disable
17+
// the secure behaviour
18+
var isSecure: Bool = true {
19+
didSet {
20+
secureField.isSecureTextEntry = isSecure
21+
}
22+
}
23+
24+
// placeholder will become visible when user try to capture screenshot
25+
// or try to record the screen
26+
private(set) var placeholderView: UIView = {
27+
let view = UIView()
28+
view.translatesAutoresizingMaskIntoConstraints = false
29+
return view
30+
}()
31+
32+
// add your content in this view
33+
// it will be secure
34+
private(set) var contentView: UIView!
35+
36+
override init(frame: CGRect) {
37+
super.init(frame: frame)
38+
setupView()
39+
}
40+
41+
required init?(coder: NSCoder) {
42+
fatalError("init(coder:) has not been implemented")
43+
}
44+
45+
fileprivate func setupView() {
46+
contentView = secureField.subviews.first ?? UIView()
47+
contentView.translatesAutoresizingMaskIntoConstraints = false
48+
contentView.removeFromSuperview()
49+
50+
isSecure = true
51+
52+
addSubview(placeholderView)
53+
addSubview(contentView)
54+
55+
NSLayoutConstraint.activate([
56+
placeholderView.leadingAnchor.constraint(equalTo: leadingAnchor),
57+
placeholderView.trailingAnchor.constraint(equalTo: trailingAnchor),
58+
placeholderView.topAnchor.constraint(equalTo: topAnchor),
59+
placeholderView.bottomAnchor.constraint(equalTo: bottomAnchor),
60+
61+
contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
62+
contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
63+
contentView.topAnchor.constraint(equalTo: topAnchor),
64+
contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
65+
])
66+
}
67+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// SnapShieldModifier.swift
3+
// SnapShield
4+
//
5+
// Created by Emad on 2025-10-12.
6+
//
7+
import SwiftUI
8+
9+
public extension View {
10+
@ViewBuilder
11+
func snapShield(_ shouldHide: Bool = true) -> some View {
12+
if shouldHide {
13+
SnapShieldView { self }
14+
} else {
15+
self
16+
}
17+
}
18+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//
2+
// SnapShieldView.swift
3+
// SnapShield
4+
//
5+
// Created by Emad on 2025-10-12.
6+
//
7+
8+
import SwiftUI
9+
import UIKit
10+
11+
public struct SnapShieldView<Content: View>: UIViewRepresentable {
12+
13+
private let content: () -> Content
14+
private let hostingController: UIHostingController<Content>
15+
16+
public init(@ViewBuilder content: @escaping () -> Content) {
17+
self.content = content
18+
self.hostingController = UIHostingController(rootView: content())
19+
}
20+
21+
public func makeUIView(context: Context) -> UIView {
22+
let secureTextField = UITextField()
23+
secureTextField.isSecureTextEntry = true
24+
secureTextField.isUserInteractionEnabled = false
25+
26+
guard let secureView = secureTextField.subviews.first else {
27+
return UIView()
28+
}
29+
30+
secureView.removeFromSuperview()
31+
secureView.subviews.forEach { subview in
32+
subview.removeFromSuperview()
33+
}
34+
35+
secureView.addSubview(hostingController.view)
36+
37+
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
38+
39+
NSLayoutConstraint.activate([
40+
hostingController.view.topAnchor.constraint(equalTo: secureView.topAnchor),
41+
hostingController.view.bottomAnchor.constraint(equalTo: secureView.bottomAnchor),
42+
hostingController.view.leadingAnchor.constraint(equalTo: secureView.leadingAnchor),
43+
hostingController.view.trailingAnchor.constraint(equalTo: secureView.trailingAnchor)
44+
])
45+
46+
return secureView
47+
}
48+
49+
public func updateUIView(_ uiView: UIView, context: Context) { }
50+
51+
public func sizeThatFits(_ proposal: ProposedViewSize, uiView: UIView, context: Context) -> CGSize? {
52+
let targetSize = CGSize(
53+
width: proposal.width ?? .infinity,
54+
height: proposal.height ?? .infinity
55+
)
56+
return hostingController.sizeThatFits(in: targetSize)
57+
}
58+
}

0 commit comments

Comments
 (0)