├── .github
└── workflows
│ └── CI.yml
├── .gitignore
├── Documentation
├── Flutter.md
├── React-Native.md
└── Swift.md
├── LICENSE
├── Package.swift
├── README.md
├── Resources
├── AddingFiles.png
├── AddingFiles1.png
├── AddingFiles2.png
├── AddingViaSPM.png
├── AddingViaSPM1.png
└── flutterExample.mov
├── SnapshotSafeView.podspec
├── SnapshotSafeView.xcodeproj
├── SnapshotSafeViewTests_Info.plist
├── SnapshotSafeView_Info.plist
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
└── xcshareddata
│ └── xcschemes
│ └── SnapshotSafeView-Package.xcscheme
├── Sources
└── SnapshotSafeView
│ ├── Core
│ ├── HiddenContainerRecognizer.swift
│ ├── ScreenshotInvincibleContainer.swift
│ └── ScreenshotProtectController.swift
│ ├── Library
│ ├── UIView+SnapshotSafeController.swift
│ └── ViewWithDisabledPointInsideCheck.swift
│ ├── Multiplatform
│ └── Flutter
│ │ └── SnapshotSafeViewFlutterInterceptor.swift
│ ├── MultiplatformBridgeView
│ ├── MultiplatformBridgeView.swift
│ └── MultiplatformContainer.swift
│ ├── Protocols
│ ├── ScreenshotInvincibleContainerProtocol.swift
│ └── ScreenshotProtectControllerProtocol.swift
│ ├── RootControllerInterceptor
│ ├── RootControllerExpectation
│ │ ├── Flutter
│ │ │ └── FlutterExpectation.swift
│ │ └── RootControllerExpectation.swift
│ ├── RootControllerInterceptor.swift
│ └── RootControllerInterceptorProtocol.swift
│ └── SwiftUIBridge
│ ├── SnaphotSafeViewSwiftUIBridge.swift
│ ├── SnaphotSafeViewSwiftUIBridgeProtocol.swift
│ └── View+SnaphotSafeViewSwiftUIBridge.swift
└── Tests
└── SnapshotSafeViewTests
└── RootControllerInterceptorTests
└── RootControllerInterceptorTests.swift
/.github/workflows/CI.yml:
--------------------------------------------------------------------------------
1 | name: Swift
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: macos-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Build
13 | run: |
14 | sudo xcodebuild -scheme SnapshotSafeView-Package\
15 | -project SnapshotSafeView.xcodeproj\
16 | -derivedDataPath ./buildData\
17 | -destination "platform=iOS Simulator,name=iPhone SE (3rd generation),OS=16.2"\
18 | build-for-testing
19 |
20 | # Removed, because pipeline always fail in Github macOS node :(
21 | #
22 | # - name: Tests
23 | # run: |
24 | # sudo xcodebuild test -scheme SnapshotSafeView-Package\
25 | # -project SnapshotSafeView.xcodeproj\
26 | # -destination "platform=iOS Simulator,name=iPhone SE (3rd generation),OS=16.2"\
27 | # -derivedDataPath ./buildData\
28 | # test-without-building
29 |
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | xcuserdata/
5 | DerivedData/
6 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
7 |
--------------------------------------------------------------------------------
/Documentation/Flutter.md:
--------------------------------------------------------------------------------
1 | ## Flutter
2 | >**Warning**: Experimental feature
3 |
4 | 1. In `Xcode`, go to `File` → `Add Packages...` OR select your project in the `Project Editor`, go to the `Package Dependencies` tab, and press the `+`.
5 |
6 | 
7 |
8 | 2. Enter a `Package URL` (e.g. a `GitHub` repository `URL`) or a search term in the search field in the upper right.
9 |
10 | 
11 |
12 | 3. Select the package you want to add. Select a `Dependency Rule`. In most cases, you probably want to set this to `Up to Next Major Version`.
13 | Click `Add Package`.
14 |
15 | 4. Instantiate `SnapshotSafeViewFlutterInterceptor` and call `performSwitchView` for intercepting root view:
16 |
17 | ```swift
18 | @UIApplicationMain
19 | @objc class AppDelegate: FlutterAppDelegate {
20 |
21 | private let snapshotSafeViewInterceptor = SnapshotSafeViewFlutterInterceptor()
22 |
23 | override func application(
24 | _ application: UIApplication,
25 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
26 | ) -> Bool {
27 | GeneratedPluginRegistrant.register(with: self)
28 | snapshotSafeViewInterceptor.performSwitchView(in: window)
29 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
30 | }
31 |
32 | }
33 | ```
34 |
35 | ## Example:
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Documentation/React-Native.md:
--------------------------------------------------------------------------------
1 | ## React-Native
2 |
3 | 1. In `Xcode`, go to `File` → `Add Packages...` OR select your project in the `Project Editor`, go to the `Package Dependencies` tab, and press the `+`.
4 |
5 | 
6 |
7 | 2. Enter a `Package URL` (e.g. a `GitHub` repository `URL`) or a search term in the search field in the upper right.
8 |
9 | 
10 |
11 | 3. Select the package you want to add. Select a `Dependency Rule`. In most cases, you probably want to set this to `Up to Next Major Version`.
12 | Click `Add Package`.
13 |
14 | 4. Add three files for bridging package to `React-Native`.
15 |
16 | 
17 |
18 | for `Swift` files:
19 | 
20 |
21 | for `Obj-c` files:
22 | 
23 |
24 | `Bridge`:
25 | ```swift
26 | import UIKit
27 | import SnapshotSafeView
28 |
29 | final class ReactNativeBridgeSnapshotSafeView: MultiplatformBridgeView { }
30 | ```
31 |
32 | `Obj-c bridge`:
33 | ```objc
34 | #import
35 |
36 | @interface RCT_EXTERN_MODULE(RCTReactNativeBridgeSnapshotSafeViewManager, RCTViewManager)
37 | @end
38 | ```
39 |
40 | `React-native fabric`:
41 | ```swift
42 | #if canImport(React)
43 | import React
44 |
45 | @objc (RCTReactNativeBridgeSnapshotSafeViewManager)
46 | class ReactNativeBridgeSnapshotSafeViewManager: RCTViewManager {
47 |
48 | override static func requiresMainQueueSetup() -> Bool {
49 | return true
50 | }
51 |
52 | override func view() -> UIView! {
53 | let view = ReactNativeBridgeSnapshotSafeView()
54 | return view
55 | }
56 |
57 | }
58 | #endif
59 | ```
60 |
61 | 5. After build you can use the view in `React-Native`
62 |
63 | ```javascript
64 | import React, { Component } from 'react';
65 | import { StyleSheet, View, Text, requireNativeComponent } from 'react-native';
66 |
67 | const SnapshotSafeView = requireNativeComponent('RCTReactNativeBridgeSnapshotSafeView');
68 |
69 | const ViewBoxesWithColorAndText = () => {
70 | return (
71 |
72 |
78 |
79 |
80 | Hello World!
81 |
82 |
83 | );
84 | };
85 |
86 | export default ViewBoxesWithColorAndText;
87 | ```
--------------------------------------------------------------------------------
/Documentation/Swift.md:
--------------------------------------------------------------------------------
1 | ## Swift
2 |
3 | 1. In `Xcode`, go to `File` → `Add Packages...` OR select your project in the `Project Editor`, go to the `Package Dependencies` tab, and press the `+`.
4 |
5 | 
6 |
7 | 2. Enter a `Package URL` (e.g. a `GitHub` repository `URL`) or a search term in the search field in the upper right.
8 |
9 | 
10 |
11 | 3. Select the package you want to add. Select a `Dependency Rule`. In most cases, you probably want to set this to `Up to Next Major Version`.
12 | Click `Add Package`.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Ilya knyazkov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.5
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "SnapshotSafeView",
8 | platforms: [
9 | .iOS(.v12)
10 | ],
11 | products: [
12 | .library(name: "SnapshotSafeView", targets: ["SnapshotSafeView"])
13 | ],
14 | targets: [
15 | .target(name: "SnapshotSafeView", dependencies: []),
16 | .testTarget(name: "SnapshotSafeViewTests", dependencies: ["SnapshotSafeView"]),
17 | ]
18 | )
19 |
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SnapshotSafeView
2 |
3 | Used for hide view from system screenshots and video recording.
4 |
5 | ## Categories
6 |
7 | 1. [How install](#how-install)
8 | 2. [Requirements](#requirements)
9 | 3. [Progress](#progress)
10 | 4. [How usage](#how-usage)
11 |
12 | 4.1. [SwiftUI](#swiftui)
13 |
14 | 4.2. [UIKit](#uikit)
15 |
16 | -----------------------
17 |
18 | ## How install
19 |
20 | - [React-Native](Documentation/React-Native.md)
21 | - [Flutter](Documentation/Flutter.md)(experimental)
22 | - [Swift](Documentation/Swift.md)
23 |
24 | -----------------------
25 |
26 | ## Requirements
27 |
28 | - Xcode version: `12.5.1` or higher
29 | - Deployment target `iOS v12` or higher
30 | - Swift toolchain `5.5`
31 |
32 | -----------------------
33 |
34 | ## Progress:
35 |
36 | - ~~Dinamicaly enable/disable hiding from system screenshots and videorecordings(disabling not working yet)~~ Done
37 | - Run from `init(coder:)`
38 | - ~~position inside content with autolayout(now frame based only)~~ Done
39 | - ~~Support SwiftUI~~
40 | - Improve API
41 | - ~~Support `React-Native`~~ Done
42 | - Support `Flutter`
43 |
44 | -----------------------
45 |
46 | ## How usage:
47 | ### `SwiftUI`
48 | ```swift
49 | import SwiftUI
50 | import SnapshotSafeView
51 |
52 | struct ContentView: View {
53 |
54 | @State var isNeedHiddenContentFromScreenshots: Bool = false
55 |
56 | var body: some View {
57 | Text("Hello, world!")
58 | .padding()
59 | .background(Color.brown)
60 | Text("Hello, world!")
61 | .padding()
62 | .background(Color.blue)
63 | .hiddenFromSystemSnaphotWithDefaultPadding(when: isNeedHiddenContentFromScreenshots)
64 | Text("Hello, world!")
65 | .padding()
66 | .background(Color.orange)
67 | Button("Toggle hide from screenshots condition") {
68 | isNeedHiddenContentFromScreenshots.toggle()
69 | }
70 |
71 | Spacer()
72 |
73 | Text(isNeedHiddenContentFromScreenshots ? "Will be hidden from snapshots" : "Will be appear in snapshots")
74 | .padding()
75 | .background(isNeedHiddenContentFromScreenshots ? Color.green : Color.red)
76 |
77 | }
78 | }
79 |
80 | struct ContentView_Previews: PreviewProvider {
81 | static var previews: some View {
82 | ContentView()
83 | }
84 | }
85 | ```
86 |
87 | ### Example:
88 |
89 |
90 |
91 |
92 |
93 |
94 | ### `UIKit`
95 |
96 | >**Warning**: This is experimental API, be careful
97 | ```swift
98 | let someView = UIView()
99 |
100 | someView.translatesAutoresizingMaskIntoConstraints = false
101 | someView.topAnchor.constraint(superView.topAnchor, constant: 12).isActive = true
102 | someView.setupAsHiddenFromScreenshot()
103 | ```
104 |
105 | or:
106 |
107 | ```swift
108 | final class ExampleViewController: UIViewController {
109 |
110 | var hiddenFromScreenshotButtonController = ScreenshotProtectController(content: UIButton())
111 |
112 | override func viewDidLoad() {
113 | super.viewDidLoad()
114 | hiddenFromScreenshotButtonController.content.backgroundColor = .systemRed // UI customization apply to content
115 | hiddenFromScreenshotButtonController.content.layer.cornerRadius = 20
116 |
117 | view.addSubview(hiddenFromScreenshotButtonController.container)
118 | hiddenFromScreenshotButtonController.container.translatesAutoresizingMaskIntoConstraints = false
119 |
120 | [
121 | hiddenFromScreenshotButtonController.container.topAnchor.constraint(equalTo: view.topAnchor, constant: 65),
122 | hiddenFromScreenshotButtonController.container.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 65),
123 | hiddenFromScreenshotButtonController.container.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -65),
124 | hiddenFromScreenshotButtonController.container.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -65)
125 | ].forEach { $0.isActive = true } // Layout control apply to container
126 |
127 | hiddenFromScreenshotButtonController.setupContentAsHiddenInScreenshotMode() // apply hidden mode
128 | // content will be removed from system screenshots and screen recording
129 | }
130 |
131 | }
132 | ```
133 |
134 | ### Example:
135 |
136 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/Resources/AddingFiles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stampoo/SnapshotSafeView/82825e5cab6c581070e50ca6a1d42d5e4f253ebc/Resources/AddingFiles.png
--------------------------------------------------------------------------------
/Resources/AddingFiles1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stampoo/SnapshotSafeView/82825e5cab6c581070e50ca6a1d42d5e4f253ebc/Resources/AddingFiles1.png
--------------------------------------------------------------------------------
/Resources/AddingFiles2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stampoo/SnapshotSafeView/82825e5cab6c581070e50ca6a1d42d5e4f253ebc/Resources/AddingFiles2.png
--------------------------------------------------------------------------------
/Resources/AddingViaSPM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stampoo/SnapshotSafeView/82825e5cab6c581070e50ca6a1d42d5e4f253ebc/Resources/AddingViaSPM.png
--------------------------------------------------------------------------------
/Resources/AddingViaSPM1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stampoo/SnapshotSafeView/82825e5cab6c581070e50ca6a1d42d5e4f253ebc/Resources/AddingViaSPM1.png
--------------------------------------------------------------------------------
/Resources/flutterExample.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stampoo/SnapshotSafeView/82825e5cab6c581070e50ca6a1d42d5e4f253ebc/Resources/flutterExample.mov
--------------------------------------------------------------------------------
/SnapshotSafeView.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod spec lint SnapshotSafeView.podspec' to ensure this is a
3 | # valid spec and to remove all comments including this before submitting the spec.
4 | #
5 | # To learn more about Podspec attributes see https://guides.cocoapods.org/syntax/podspec.html
6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/
7 | #
8 |
9 | Pod::Spec.new do |spec|
10 |
11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
12 | #
13 | # These will help people to find your library, and whilst it
14 | # can feel like a chore to fill in it's definitely to your advantage. The
15 | # summary should be tweet-length, and the description more in depth.
16 | #
17 |
18 | spec.name = "SnapshotSafeView"
19 | spec.version = "0.2.1"
20 | spec.summary = "Used for hide view from system screenshots and video recording "
21 |
22 | # This description is used to generate tags and improve search results.
23 | # * Think: What does it do? Why did you write it? What is the focus?
24 | # * Try to keep it short, snappy and to the point.
25 | # * Write the description between the DESC delimiters below.
26 | # * Finally, don't worry about the indent, CocoaPods strips it!
27 | spec.description = <<-DESC
28 | DESC
29 |
30 | spec.homepage = "https://github.com/Stampoo/SnapshotSafeView"
31 | # spec.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif"
32 |
33 |
34 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
35 | #
36 | # Licensing your code is important. See https://choosealicense.com for more info.
37 | # CocoaPods will detect a license file if there is a named LICENSE*
38 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'.
39 | #
40 |
41 | spec.license = "MIT"
42 | # spec.license = { :type => "MIT", :file => "FILE_LICENSE" }
43 |
44 |
45 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
46 | #
47 | # Specify the authors of the library, with email addresses. Email addresses
48 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also
49 | # accepts just a name if you'd rather not provide an email address.
50 | #
51 | # Specify a social_media_url where others can refer to, for example a twitter
52 | # profile URL.
53 | #
54 |
55 | spec.author = { "Ilya knyazkov" => "fivecoil@gmail.com" }
56 | # Or just: spec.author = "Ilya knyazkov"
57 | # spec.authors = { "Ilya knyazkov" => "email@address.com" }
58 | # spec.social_media_url = "https://twitter.com/Ilya knyazkov"
59 |
60 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
61 | #
62 | # If this Pod runs only on iOS or OS X, then specify the platform and
63 | # the deployment target. You can optionally include the target after the platform.
64 | #
65 |
66 | # spec.platform = :ios
67 | # spec.platform = :ios, "5.0"
68 |
69 | # When using multiple platforms
70 | # spec.ios.deployment_target = "5.0"
71 | # spec.osx.deployment_target = "10.7"
72 | # spec.watchos.deployment_target = "2.0"
73 | # spec.tvos.deployment_target = "9.0"
74 |
75 |
76 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
77 | #
78 | # Specify the location from where the source should be retrieved.
79 | # Supports git, hg, bzr, svn and HTTP.
80 | #
81 |
82 | spec.source = { :git => "https://github.com/Stampoo/SnapshotSafeView.git", :tag => "#{spec.version}" }
83 |
84 |
85 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
86 | #
87 | # CocoaPods is smart about how it includes source code. For source files
88 | # giving a folder will include any swift, h, m, mm, c & cpp files.
89 | # For header files it will include any header in the folder.
90 | # Not including the public_header_files will make all headers public.
91 | #
92 |
93 | spec.source_files = "Source/*.swift"
94 |
95 | # spec.public_header_files = "Classes/**/*.h"
96 |
97 |
98 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
99 | #
100 | # A list of resources included with the Pod. These are copied into the
101 | # target bundle with a build phase script. Anything else will be cleaned.
102 | # You can preserve files from being cleaned, please don't preserve
103 | # non-essential files like tests, examples and documentation.
104 | #
105 |
106 | # spec.resource = "icon.png"
107 | # spec.resources = "Resources/*.png"
108 |
109 | # spec.preserve_paths = "FilesToSave", "MoreFilesToSave"
110 |
111 |
112 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
113 | #
114 | # Link your library with frameworks, or libraries. Libraries do not include
115 | # the lib prefix of their name.
116 | #
117 |
118 | # spec.framework = "SomeFramework"
119 | # spec.frameworks = "SomeFramework", "AnotherFramework"
120 |
121 | # spec.library = "iconv"
122 | # spec.libraries = "iconv", "xml2"
123 |
124 |
125 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
126 | #
127 | # If your library depends on compiler flags you can set them in the xcconfig hash
128 | # where they will only apply to your library. If you depend on other Podspecs
129 | # you can include multiple dependencies to ensure it works.
130 |
131 | # spec.requires_arc = true
132 |
133 | # spec.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }
134 | # spec.dependency "JSONKit", "~> 1.4"
135 |
136 | end
137 |
--------------------------------------------------------------------------------
/SnapshotSafeView.xcodeproj/SnapshotSafeViewTests_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | BNDL
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/SnapshotSafeView.xcodeproj/SnapshotSafeView_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | FMWK
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/SnapshotSafeView.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = "1";
4 | objectVersion = "46";
5 | objects = {
6 | "OBJ_1" = {
7 | isa = "PBXProject";
8 | attributes = {
9 | LastSwiftMigration = "9999";
10 | LastUpgradeCheck = "9999";
11 | };
12 | buildConfigurationList = "OBJ_2";
13 | compatibilityVersion = "Xcode 3.2";
14 | developmentRegion = "en";
15 | hasScannedForEncodings = "0";
16 | knownRegions = (
17 | "en"
18 | );
19 | mainGroup = "OBJ_5";
20 | productRefGroup = "OBJ_38";
21 | projectDirPath = ".";
22 | targets = (
23 | "snapshotsafeview::SnapshotSafeView",
24 | "snapshotsafeview::SwiftPMPackageDescription",
25 | "snapshotsafeview::SnapshotSafeViewPackageTests::ProductTarget",
26 | "snapshotsafeview::SnapshotSafeViewTests"
27 | );
28 | };
29 | "OBJ_10" = {
30 | isa = "PBXFileReference";
31 | path = "HiddenContainerRecognizer.swift";
32 | sourceTree = "";
33 | };
34 | "OBJ_11" = {
35 | isa = "PBXFileReference";
36 | path = "ScreenshotInvincibleContainer.swift";
37 | sourceTree = "";
38 | };
39 | "OBJ_12" = {
40 | isa = "PBXFileReference";
41 | path = "ScreenshotProtectController.swift";
42 | sourceTree = "";
43 | };
44 | "OBJ_13" = {
45 | isa = "PBXGroup";
46 | children = (
47 | "OBJ_14"
48 | );
49 | name = "Library";
50 | path = "Library";
51 | sourceTree = "";
52 | };
53 | "OBJ_14" = {
54 | isa = "PBXFileReference";
55 | path = "UIView+SnapshotSafeController.swift";
56 | sourceTree = "";
57 | };
58 | "OBJ_15" = {
59 | isa = "PBXGroup";
60 | children = (
61 | "OBJ_16"
62 | );
63 | name = "Multiplatform";
64 | path = "Multiplatform";
65 | sourceTree = "";
66 | };
67 | "OBJ_16" = {
68 | isa = "PBXGroup";
69 | children = (
70 | "OBJ_17"
71 | );
72 | name = "Flutter";
73 | path = "Flutter";
74 | sourceTree = "";
75 | };
76 | "OBJ_17" = {
77 | isa = "PBXFileReference";
78 | path = "SnapshotSafeViewFlutterInterceptor.swift";
79 | sourceTree = "";
80 | };
81 | "OBJ_18" = {
82 | isa = "PBXGroup";
83 | children = (
84 | "OBJ_19"
85 | );
86 | name = "MultiplatformBridgeView";
87 | path = "MultiplatformBridgeView";
88 | sourceTree = "";
89 | };
90 | "OBJ_19" = {
91 | isa = "PBXFileReference";
92 | path = "MultiplatformBridgeView.swift";
93 | sourceTree = "";
94 | };
95 | "OBJ_2" = {
96 | isa = "XCConfigurationList";
97 | buildConfigurations = (
98 | "OBJ_3",
99 | "OBJ_4"
100 | );
101 | defaultConfigurationIsVisible = "0";
102 | defaultConfigurationName = "Release";
103 | };
104 | "OBJ_20" = {
105 | isa = "PBXGroup";
106 | children = (
107 | "OBJ_21",
108 | "OBJ_22"
109 | );
110 | name = "Protocols";
111 | path = "Protocols";
112 | sourceTree = "";
113 | };
114 | "OBJ_21" = {
115 | isa = "PBXFileReference";
116 | path = "ScreenshotInvincibleContainerProtocol.swift";
117 | sourceTree = "";
118 | };
119 | "OBJ_22" = {
120 | isa = "PBXFileReference";
121 | path = "ScreenshotProtectControllerProtocol.swift";
122 | sourceTree = "";
123 | };
124 | "OBJ_23" = {
125 | isa = "PBXGroup";
126 | children = (
127 | "OBJ_24",
128 | "OBJ_28",
129 | "OBJ_29"
130 | );
131 | name = "RootControllerInterceptor";
132 | path = "RootControllerInterceptor";
133 | sourceTree = "";
134 | };
135 | "OBJ_24" = {
136 | isa = "PBXGroup";
137 | children = (
138 | "OBJ_25",
139 | "OBJ_27"
140 | );
141 | name = "RootControllerExpectation";
142 | path = "RootControllerExpectation";
143 | sourceTree = "";
144 | };
145 | "OBJ_25" = {
146 | isa = "PBXGroup";
147 | children = (
148 | "OBJ_26"
149 | );
150 | name = "Flutter";
151 | path = "Flutter";
152 | sourceTree = "";
153 | };
154 | "OBJ_26" = {
155 | isa = "PBXFileReference";
156 | path = "FlutterExpectation.swift";
157 | sourceTree = "";
158 | };
159 | "OBJ_27" = {
160 | isa = "PBXFileReference";
161 | path = "RootControllerExpectation.swift";
162 | sourceTree = "";
163 | };
164 | "OBJ_28" = {
165 | isa = "PBXFileReference";
166 | path = "RootControllerInterceptor.swift";
167 | sourceTree = "";
168 | };
169 | "OBJ_29" = {
170 | isa = "PBXFileReference";
171 | path = "RootControllerInterceptorProtocol.swift";
172 | sourceTree = "";
173 | };
174 | "OBJ_3" = {
175 | isa = "XCBuildConfiguration";
176 | buildSettings = {
177 | CLANG_ENABLE_OBJC_ARC = "YES";
178 | COMBINE_HIDPI_IMAGES = "YES";
179 | COPY_PHASE_STRIP = "NO";
180 | DEBUG_INFORMATION_FORMAT = "dwarf";
181 | DYLIB_INSTALL_NAME_BASE = "@rpath";
182 | ENABLE_NS_ASSERTIONS = "YES";
183 | GCC_OPTIMIZATION_LEVEL = "0";
184 | GCC_PREPROCESSOR_DEFINITIONS = (
185 | "$(inherited)",
186 | "SWIFT_PACKAGE=1",
187 | "DEBUG=1"
188 | );
189 | MACOSX_DEPLOYMENT_TARGET = "10.10";
190 | ONLY_ACTIVE_ARCH = "YES";
191 | OTHER_SWIFT_FLAGS = (
192 | "$(inherited)",
193 | "-DXcode"
194 | );
195 | PRODUCT_NAME = "$(TARGET_NAME)";
196 | SDKROOT = "macosx";
197 | SUPPORTED_PLATFORMS = (
198 | "$(AVAILABLE_PLATFORMS)"
199 | );
200 | SUPPORTS_MACCATALYST = "YES";
201 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = (
202 | "$(inherited)",
203 | "SWIFT_PACKAGE",
204 | "DEBUG"
205 | );
206 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
207 | USE_HEADERMAP = "NO";
208 | };
209 | name = "Debug";
210 | };
211 | "OBJ_30" = {
212 | isa = "PBXGroup";
213 | children = (
214 | "OBJ_31",
215 | "OBJ_32",
216 | "OBJ_33"
217 | );
218 | name = "SwiftUIBridge";
219 | path = "SwiftUIBridge";
220 | sourceTree = "";
221 | };
222 | "OBJ_31" = {
223 | isa = "PBXFileReference";
224 | path = "SnaphotSafeViewSwiftUIBridge.swift";
225 | sourceTree = "";
226 | };
227 | "OBJ_32" = {
228 | isa = "PBXFileReference";
229 | path = "SnaphotSafeViewSwiftUIBridgeProtocol.swift";
230 | sourceTree = "";
231 | };
232 | "OBJ_33" = {
233 | isa = "PBXFileReference";
234 | path = "View+SnaphotSafeViewSwiftUIBridge.swift";
235 | sourceTree = "";
236 | };
237 | "OBJ_34" = {
238 | isa = "PBXGroup";
239 | children = (
240 | "OBJ_35"
241 | );
242 | name = "Tests";
243 | path = "";
244 | sourceTree = "SOURCE_ROOT";
245 | };
246 | "OBJ_35" = {
247 | isa = "PBXGroup";
248 | children = (
249 | "OBJ_36"
250 | );
251 | name = "SnapshotSafeViewTests";
252 | path = "Tests/SnapshotSafeViewTests";
253 | sourceTree = "SOURCE_ROOT";
254 | };
255 | "OBJ_36" = {
256 | isa = "PBXGroup";
257 | children = (
258 | "OBJ_37"
259 | );
260 | name = "RootControllerInterceptorTests";
261 | path = "RootControllerInterceptorTests";
262 | sourceTree = "";
263 | };
264 | "OBJ_37" = {
265 | isa = "PBXFileReference";
266 | path = "RootControllerInterceptorTests.swift";
267 | sourceTree = "";
268 | };
269 | "OBJ_38" = {
270 | isa = "PBXGroup";
271 | children = (
272 | "snapshotsafeview::SnapshotSafeView::Product",
273 | "snapshotsafeview::SnapshotSafeViewTests::Product"
274 | );
275 | name = "Products";
276 | path = "";
277 | sourceTree = "BUILT_PRODUCTS_DIR";
278 | };
279 | "OBJ_4" = {
280 | isa = "XCBuildConfiguration";
281 | buildSettings = {
282 | CLANG_ENABLE_OBJC_ARC = "YES";
283 | COMBINE_HIDPI_IMAGES = "YES";
284 | COPY_PHASE_STRIP = "YES";
285 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
286 | DYLIB_INSTALL_NAME_BASE = "@rpath";
287 | GCC_OPTIMIZATION_LEVEL = "s";
288 | GCC_PREPROCESSOR_DEFINITIONS = (
289 | "$(inherited)",
290 | "SWIFT_PACKAGE=1"
291 | );
292 | MACOSX_DEPLOYMENT_TARGET = "10.10";
293 | OTHER_SWIFT_FLAGS = (
294 | "$(inherited)",
295 | "-DXcode"
296 | );
297 | PRODUCT_NAME = "$(TARGET_NAME)";
298 | SDKROOT = "macosx";
299 | SUPPORTED_PLATFORMS = (
300 | "$(AVAILABLE_PLATFORMS)"
301 | );
302 | SUPPORTS_MACCATALYST = "YES";
303 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = (
304 | "$(inherited)",
305 | "SWIFT_PACKAGE"
306 | );
307 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
308 | USE_HEADERMAP = "NO";
309 | };
310 | name = "Release";
311 | };
312 | "OBJ_41" = {
313 | isa = "PBXFileReference";
314 | path = "Documentation";
315 | sourceTree = "SOURCE_ROOT";
316 | };
317 | "OBJ_42" = {
318 | isa = "PBXFileReference";
319 | path = "Crossplatform";
320 | sourceTree = "SOURCE_ROOT";
321 | };
322 | "OBJ_43" = {
323 | isa = "PBXFileReference";
324 | path = "Resources";
325 | sourceTree = "SOURCE_ROOT";
326 | };
327 | "OBJ_44" = {
328 | isa = "PBXFileReference";
329 | path = "SwiftGen";
330 | sourceTree = "SOURCE_ROOT";
331 | };
332 | "OBJ_45" = {
333 | isa = "PBXFileReference";
334 | path = "LICENSE";
335 | sourceTree = "";
336 | };
337 | "OBJ_46" = {
338 | isa = "PBXFileReference";
339 | path = "SnapshotSafeView.podspec";
340 | sourceTree = "";
341 | };
342 | "OBJ_47" = {
343 | isa = "PBXFileReference";
344 | path = "README.md";
345 | sourceTree = "";
346 | };
347 | "OBJ_49" = {
348 | isa = "XCConfigurationList";
349 | buildConfigurations = (
350 | "OBJ_50",
351 | "OBJ_51"
352 | );
353 | defaultConfigurationIsVisible = "0";
354 | defaultConfigurationName = "Release";
355 | };
356 | "OBJ_5" = {
357 | isa = "PBXGroup";
358 | children = (
359 | "OBJ_6",
360 | "OBJ_7",
361 | "OBJ_34",
362 | "OBJ_38",
363 | "OBJ_41",
364 | "OBJ_42",
365 | "OBJ_43",
366 | "OBJ_44",
367 | "OBJ_45",
368 | "OBJ_46",
369 | "OBJ_47"
370 | );
371 | path = "";
372 | sourceTree = "";
373 | };
374 | "OBJ_50" = {
375 | isa = "XCBuildConfiguration";
376 | buildSettings = {
377 | CURRENT_PROJECT_VERSION = "1";
378 | DRIVERKIT_DEPLOYMENT_TARGET = "19.0";
379 | ENABLE_TESTABILITY = "YES";
380 | FRAMEWORK_SEARCH_PATHS = (
381 | "$(inherited)",
382 | "$(PLATFORM_DIR)/Developer/Library/Frameworks"
383 | );
384 | HEADER_SEARCH_PATHS = (
385 | "$(inherited)"
386 | );
387 | INFOPLIST_FILE = "SnapshotSafeView.xcodeproj/SnapshotSafeView_Info.plist";
388 | IPHONEOS_DEPLOYMENT_TARGET = "12.0";
389 | LD_RUNPATH_SEARCH_PATHS = (
390 | "$(inherited)",
391 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"
392 | );
393 | MACOSX_DEPLOYMENT_TARGET = "10.13";
394 | OTHER_CFLAGS = (
395 | "$(inherited)"
396 | );
397 | OTHER_LDFLAGS = (
398 | "$(inherited)"
399 | );
400 | OTHER_SWIFT_FLAGS = (
401 | "$(inherited)"
402 | );
403 | PRODUCT_BUNDLE_IDENTIFIER = "SnapshotSafeView";
404 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
405 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
406 | SKIP_INSTALL = "YES";
407 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = (
408 | "$(inherited)"
409 | );
410 | SWIFT_VERSION = "5.0";
411 | TARGET_NAME = "SnapshotSafeView";
412 | TVOS_DEPLOYMENT_TARGET = "11.0";
413 | WATCHOS_DEPLOYMENT_TARGET = "4.0";
414 | };
415 | name = "Debug";
416 | };
417 | "OBJ_51" = {
418 | isa = "XCBuildConfiguration";
419 | buildSettings = {
420 | CURRENT_PROJECT_VERSION = "1";
421 | DRIVERKIT_DEPLOYMENT_TARGET = "19.0";
422 | ENABLE_TESTABILITY = "YES";
423 | FRAMEWORK_SEARCH_PATHS = (
424 | "$(inherited)",
425 | "$(PLATFORM_DIR)/Developer/Library/Frameworks"
426 | );
427 | HEADER_SEARCH_PATHS = (
428 | "$(inherited)"
429 | );
430 | INFOPLIST_FILE = "SnapshotSafeView.xcodeproj/SnapshotSafeView_Info.plist";
431 | IPHONEOS_DEPLOYMENT_TARGET = "12.0";
432 | LD_RUNPATH_SEARCH_PATHS = (
433 | "$(inherited)",
434 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"
435 | );
436 | MACOSX_DEPLOYMENT_TARGET = "10.13";
437 | OTHER_CFLAGS = (
438 | "$(inherited)"
439 | );
440 | OTHER_LDFLAGS = (
441 | "$(inherited)"
442 | );
443 | OTHER_SWIFT_FLAGS = (
444 | "$(inherited)"
445 | );
446 | PRODUCT_BUNDLE_IDENTIFIER = "SnapshotSafeView";
447 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
448 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
449 | SKIP_INSTALL = "YES";
450 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = (
451 | "$(inherited)"
452 | );
453 | SWIFT_VERSION = "5.0";
454 | TARGET_NAME = "SnapshotSafeView";
455 | TVOS_DEPLOYMENT_TARGET = "11.0";
456 | WATCHOS_DEPLOYMENT_TARGET = "4.0";
457 | };
458 | name = "Release";
459 | };
460 | "OBJ_52" = {
461 | isa = "PBXSourcesBuildPhase";
462 | files = (
463 | "OBJ_53",
464 | "OBJ_54",
465 | "OBJ_55",
466 | "OBJ_56",
467 | "OBJ_57",
468 | "OBJ_58",
469 | "OBJ_59",
470 | "OBJ_60",
471 | "OBJ_61",
472 | "OBJ_62",
473 | "OBJ_63",
474 | "OBJ_64",
475 | "OBJ_65",
476 | "OBJ_66",
477 | "OBJ_67"
478 | );
479 | };
480 | "OBJ_53" = {
481 | isa = "PBXBuildFile";
482 | fileRef = "OBJ_10";
483 | };
484 | "OBJ_54" = {
485 | isa = "PBXBuildFile";
486 | fileRef = "OBJ_11";
487 | };
488 | "OBJ_55" = {
489 | isa = "PBXBuildFile";
490 | fileRef = "OBJ_12";
491 | };
492 | "OBJ_56" = {
493 | isa = "PBXBuildFile";
494 | fileRef = "OBJ_14";
495 | };
496 | "OBJ_57" = {
497 | isa = "PBXBuildFile";
498 | fileRef = "OBJ_17";
499 | };
500 | "OBJ_58" = {
501 | isa = "PBXBuildFile";
502 | fileRef = "OBJ_19";
503 | };
504 | "OBJ_59" = {
505 | isa = "PBXBuildFile";
506 | fileRef = "OBJ_21";
507 | };
508 | "OBJ_6" = {
509 | isa = "PBXFileReference";
510 | explicitFileType = "sourcecode.swift";
511 | path = "Package.swift";
512 | sourceTree = "";
513 | };
514 | "OBJ_60" = {
515 | isa = "PBXBuildFile";
516 | fileRef = "OBJ_22";
517 | };
518 | "OBJ_61" = {
519 | isa = "PBXBuildFile";
520 | fileRef = "OBJ_26";
521 | };
522 | "OBJ_62" = {
523 | isa = "PBXBuildFile";
524 | fileRef = "OBJ_27";
525 | };
526 | "OBJ_63" = {
527 | isa = "PBXBuildFile";
528 | fileRef = "OBJ_28";
529 | };
530 | "OBJ_64" = {
531 | isa = "PBXBuildFile";
532 | fileRef = "OBJ_29";
533 | };
534 | "OBJ_65" = {
535 | isa = "PBXBuildFile";
536 | fileRef = "OBJ_31";
537 | };
538 | "OBJ_66" = {
539 | isa = "PBXBuildFile";
540 | fileRef = "OBJ_32";
541 | };
542 | "OBJ_67" = {
543 | isa = "PBXBuildFile";
544 | fileRef = "OBJ_33";
545 | };
546 | "OBJ_68" = {
547 | isa = "PBXFrameworksBuildPhase";
548 | files = (
549 | );
550 | };
551 | "OBJ_7" = {
552 | isa = "PBXGroup";
553 | children = (
554 | "OBJ_8"
555 | );
556 | name = "Sources";
557 | path = "";
558 | sourceTree = "SOURCE_ROOT";
559 | };
560 | "OBJ_70" = {
561 | isa = "XCConfigurationList";
562 | buildConfigurations = (
563 | "OBJ_71",
564 | "OBJ_72"
565 | );
566 | defaultConfigurationIsVisible = "0";
567 | defaultConfigurationName = "Release";
568 | };
569 | "OBJ_71" = {
570 | isa = "XCBuildConfiguration";
571 | buildSettings = {
572 | LD = "/usr/bin/true";
573 | OTHER_SWIFT_FLAGS = (
574 | "-swift-version",
575 | "5",
576 | "-I",
577 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/ManifestAPI",
578 | "-sdk",
579 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.0.sdk",
580 | "-package-description-version",
581 | "5.5.0"
582 | );
583 | SWIFT_VERSION = "5.0";
584 | };
585 | name = "Debug";
586 | };
587 | "OBJ_72" = {
588 | isa = "XCBuildConfiguration";
589 | buildSettings = {
590 | LD = "/usr/bin/true";
591 | OTHER_SWIFT_FLAGS = (
592 | "-swift-version",
593 | "5",
594 | "-I",
595 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/ManifestAPI",
596 | "-sdk",
597 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.0.sdk",
598 | "-package-description-version",
599 | "5.5.0"
600 | );
601 | SWIFT_VERSION = "5.0";
602 | };
603 | name = "Release";
604 | };
605 | "OBJ_73" = {
606 | isa = "PBXSourcesBuildPhase";
607 | files = (
608 | "OBJ_74"
609 | );
610 | };
611 | "OBJ_74" = {
612 | isa = "PBXBuildFile";
613 | fileRef = "OBJ_6";
614 | };
615 | "OBJ_76" = {
616 | isa = "XCConfigurationList";
617 | buildConfigurations = (
618 | "OBJ_77",
619 | "OBJ_78"
620 | );
621 | defaultConfigurationIsVisible = "0";
622 | defaultConfigurationName = "Release";
623 | };
624 | "OBJ_77" = {
625 | isa = "XCBuildConfiguration";
626 | buildSettings = {
627 | };
628 | name = "Debug";
629 | };
630 | "OBJ_78" = {
631 | isa = "XCBuildConfiguration";
632 | buildSettings = {
633 | };
634 | name = "Release";
635 | };
636 | "OBJ_79" = {
637 | isa = "PBXTargetDependency";
638 | target = "snapshotsafeview::SnapshotSafeViewTests";
639 | };
640 | "OBJ_8" = {
641 | isa = "PBXGroup";
642 | children = (
643 | "OBJ_9",
644 | "OBJ_13",
645 | "OBJ_15",
646 | "OBJ_18",
647 | "OBJ_20",
648 | "OBJ_23",
649 | "OBJ_30"
650 | );
651 | name = "SnapshotSafeView";
652 | path = "Sources/SnapshotSafeView";
653 | sourceTree = "SOURCE_ROOT";
654 | };
655 | "OBJ_81" = {
656 | isa = "XCConfigurationList";
657 | buildConfigurations = (
658 | "OBJ_82",
659 | "OBJ_83"
660 | );
661 | defaultConfigurationIsVisible = "0";
662 | defaultConfigurationName = "Release";
663 | };
664 | "OBJ_82" = {
665 | isa = "XCBuildConfiguration";
666 | buildSettings = {
667 | CLANG_ENABLE_MODULES = "YES";
668 | CURRENT_PROJECT_VERSION = "1";
669 | DRIVERKIT_DEPLOYMENT_TARGET = "19.0";
670 | EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES";
671 | FRAMEWORK_SEARCH_PATHS = (
672 | "$(inherited)",
673 | "$(PLATFORM_DIR)/Developer/Library/Frameworks"
674 | );
675 | HEADER_SEARCH_PATHS = (
676 | "$(inherited)"
677 | );
678 | INFOPLIST_FILE = "SnapshotSafeView.xcodeproj/SnapshotSafeViewTests_Info.plist";
679 | IPHONEOS_DEPLOYMENT_TARGET = "14.0";
680 | LD_RUNPATH_SEARCH_PATHS = (
681 | "$(inherited)",
682 | "@loader_path/../Frameworks",
683 | "@loader_path/Frameworks"
684 | );
685 | MACOSX_DEPLOYMENT_TARGET = "11.0";
686 | OTHER_CFLAGS = (
687 | "$(inherited)"
688 | );
689 | OTHER_LDFLAGS = (
690 | "$(inherited)"
691 | );
692 | OTHER_SWIFT_FLAGS = (
693 | "$(inherited)"
694 | );
695 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = (
696 | "$(inherited)"
697 | );
698 | SWIFT_VERSION = "5.0";
699 | TARGET_NAME = "SnapshotSafeViewTests";
700 | TVOS_DEPLOYMENT_TARGET = "14.0";
701 | WATCHOS_DEPLOYMENT_TARGET = "7.0";
702 | };
703 | name = "Debug";
704 | };
705 | "OBJ_83" = {
706 | isa = "XCBuildConfiguration";
707 | buildSettings = {
708 | CLANG_ENABLE_MODULES = "YES";
709 | CURRENT_PROJECT_VERSION = "1";
710 | DRIVERKIT_DEPLOYMENT_TARGET = "19.0";
711 | EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES";
712 | FRAMEWORK_SEARCH_PATHS = (
713 | "$(inherited)",
714 | "$(PLATFORM_DIR)/Developer/Library/Frameworks"
715 | );
716 | HEADER_SEARCH_PATHS = (
717 | "$(inherited)"
718 | );
719 | INFOPLIST_FILE = "SnapshotSafeView.xcodeproj/SnapshotSafeViewTests_Info.plist";
720 | IPHONEOS_DEPLOYMENT_TARGET = "14.0";
721 | LD_RUNPATH_SEARCH_PATHS = (
722 | "$(inherited)",
723 | "@loader_path/../Frameworks",
724 | "@loader_path/Frameworks"
725 | );
726 | MACOSX_DEPLOYMENT_TARGET = "11.0";
727 | OTHER_CFLAGS = (
728 | "$(inherited)"
729 | );
730 | OTHER_LDFLAGS = (
731 | "$(inherited)"
732 | );
733 | OTHER_SWIFT_FLAGS = (
734 | "$(inherited)"
735 | );
736 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = (
737 | "$(inherited)"
738 | );
739 | SWIFT_VERSION = "5.0";
740 | TARGET_NAME = "SnapshotSafeViewTests";
741 | TVOS_DEPLOYMENT_TARGET = "14.0";
742 | WATCHOS_DEPLOYMENT_TARGET = "7.0";
743 | };
744 | name = "Release";
745 | };
746 | "OBJ_84" = {
747 | isa = "PBXSourcesBuildPhase";
748 | files = (
749 | "OBJ_85"
750 | );
751 | };
752 | "OBJ_85" = {
753 | isa = "PBXBuildFile";
754 | fileRef = "OBJ_37";
755 | };
756 | "OBJ_86" = {
757 | isa = "PBXFrameworksBuildPhase";
758 | files = (
759 | "OBJ_87"
760 | );
761 | };
762 | "OBJ_87" = {
763 | isa = "PBXBuildFile";
764 | fileRef = "snapshotsafeview::SnapshotSafeView::Product";
765 | };
766 | "OBJ_88" = {
767 | isa = "PBXTargetDependency";
768 | target = "snapshotsafeview::SnapshotSafeView";
769 | };
770 | "OBJ_9" = {
771 | isa = "PBXGroup";
772 | children = (
773 | "OBJ_10",
774 | "OBJ_11",
775 | "OBJ_12"
776 | );
777 | name = "Core";
778 | path = "Core";
779 | sourceTree = "";
780 | };
781 | "snapshotsafeview::SnapshotSafeView" = {
782 | isa = "PBXNativeTarget";
783 | buildConfigurationList = "OBJ_49";
784 | buildPhases = (
785 | "OBJ_52",
786 | "OBJ_68"
787 | );
788 | dependencies = (
789 | );
790 | name = "SnapshotSafeView";
791 | productName = "SnapshotSafeView";
792 | productReference = "snapshotsafeview::SnapshotSafeView::Product";
793 | productType = "com.apple.product-type.framework";
794 | };
795 | "snapshotsafeview::SnapshotSafeView::Product" = {
796 | isa = "PBXFileReference";
797 | path = "SnapshotSafeView.framework";
798 | sourceTree = "BUILT_PRODUCTS_DIR";
799 | };
800 | "snapshotsafeview::SnapshotSafeViewPackageTests::ProductTarget" = {
801 | isa = "PBXAggregateTarget";
802 | buildConfigurationList = "OBJ_76";
803 | buildPhases = (
804 | );
805 | dependencies = (
806 | "OBJ_79"
807 | );
808 | name = "SnapshotSafeViewPackageTests";
809 | productName = "SnapshotSafeViewPackageTests";
810 | };
811 | "snapshotsafeview::SnapshotSafeViewTests" = {
812 | isa = "PBXNativeTarget";
813 | buildConfigurationList = "OBJ_81";
814 | buildPhases = (
815 | "OBJ_84",
816 | "OBJ_86"
817 | );
818 | dependencies = (
819 | "OBJ_88"
820 | );
821 | name = "SnapshotSafeViewTests";
822 | productName = "SnapshotSafeViewTests";
823 | productReference = "snapshotsafeview::SnapshotSafeViewTests::Product";
824 | productType = "com.apple.product-type.bundle.unit-test";
825 | };
826 | "snapshotsafeview::SnapshotSafeViewTests::Product" = {
827 | isa = "PBXFileReference";
828 | path = "SnapshotSafeViewTests.xctest";
829 | sourceTree = "BUILT_PRODUCTS_DIR";
830 | };
831 | "snapshotsafeview::SwiftPMPackageDescription" = {
832 | isa = "PBXNativeTarget";
833 | buildConfigurationList = "OBJ_70";
834 | buildPhases = (
835 | "OBJ_73"
836 | );
837 | dependencies = (
838 | );
839 | name = "SnapshotSafeViewPackageDescription";
840 | productName = "SnapshotSafeViewPackageDescription";
841 | productType = "com.apple.product-type.framework";
842 | };
843 | };
844 | rootObject = "OBJ_1";
845 | }
846 |
--------------------------------------------------------------------------------
/SnapshotSafeView.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
--------------------------------------------------------------------------------
/SnapshotSafeView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SnapshotSafeView.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SnapshotSafeView.xcodeproj/xcshareddata/xcschemes/SnapshotSafeView-Package.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
54 |
60 |
61 |
63 |
64 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/Sources/SnapshotSafeView/Core/HiddenContainerRecognizer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HiddenContainerRecognizer.swift
3 | //
4 | //
5 | // Created by Князьков Илья on 23.07.2022.
6 | //
7 |
8 | import UIKit
9 |
10 | /// Recognize view, which can be hidden before system screenshot event did triggered, depend on `iOS version`
11 | struct HiddenContainerRecognizer {
12 |
13 | // MARK: - Nested Types
14 |
15 | private enum Error: Swift.Error {
16 | case unsupportedIosVersion(version: Float)
17 | case desiredContainerWasNotFound(_ containerName: String)
18 | }
19 |
20 | // MARK: - Internal Methods
21 |
22 | func getHiddenContainer(from view: UIView) throws -> UIView {
23 | let containerName = try getHiddenContainerTypeInStringRepresentation()
24 | let containers = view.subviews.filter { subview in
25 | type(of: subview).description() == containerName
26 | }
27 |
28 | guard let container = containers.first else {
29 | throw Error.desiredContainerWasNotFound(containerName)
30 | }
31 |
32 | return container
33 | }
34 |
35 | func getHiddenContainerTypeInStringRepresentation() throws -> String {
36 |
37 | if #available(iOS 15, *) {
38 | return "_UITextLayoutCanvasView"
39 | }
40 |
41 | if #available(iOS 14, *) {
42 | return "_UITextFieldCanvasView"
43 | }
44 |
45 | if #available(iOS 13, *) {
46 | return "_UITextFieldCanvasView"
47 | }
48 |
49 | if #available(iOS 12, *) {
50 | return "_UITextFieldContentView"
51 | }
52 |
53 | let currentIOSVersion = (UIDevice.current.systemVersion as NSString).floatValue
54 | throw Error.unsupportedIosVersion(version: currentIOSVersion)
55 | }
56 |
57 | func viewIsAlreadyInHiddenContainer(_ view: UIView) -> Bool {
58 | guard
59 | let containerClassName = try? getHiddenContainerTypeInStringRepresentation(),
60 | let superViewInspectableView = view.superview
61 | else {
62 | return false
63 | }
64 |
65 | let typeOfClassContainer = type(of: superViewInspectableView)
66 | let stringRepresentationOfClassContainer = String(describing: typeOfClassContainer.self)
67 |
68 | return stringRepresentationOfClassContainer == containerClassName
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/Sources/SnapshotSafeView/Core/ScreenshotInvincibleContainer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScreenshotInvincibleContainer.swift
3 | //
4 | //
5 | // Created by Князьков Илья on 01.03.2022.
6 | //
7 |
8 | import UIKit
9 |
10 | class ScreenshotInvincibleContainer: UITextField {
11 |
12 | // MARK: - Private Properties
13 |
14 | private let hiddenContainerRecognizer = HiddenContainerRecognizer()
15 | private var container: UIView? {
16 | try? hiddenContainerRecognizer.getHiddenContainer(from: self)
17 | }
18 |
19 | // MARK: - Internal Properties
20 |
21 | /// - View, which will be hidden on screenshots and screen recording
22 | private(set) var content: UIView
23 |
24 | // MARK: - Initialization
25 |
26 | public init(content: UIView) {
27 | self.content = content
28 | super.init(frame: .zero)
29 | setupInitialState()
30 | }
31 |
32 | public required init?(coder: NSCoder) {
33 | self.content = UIView()
34 | super.init(coder: coder)
35 | setupInitialState()
36 | }
37 |
38 | // MARK: - UIView
39 |
40 | override var canBecomeFocused: Bool {
41 | false
42 | }
43 |
44 | override var canBecomeFirstResponder: Bool {
45 | false
46 | }
47 |
48 | override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
49 | return container?.hitTest(point, with: event)
50 | }
51 |
52 | override func layoutSubviews() {
53 | super.layoutSubviews()
54 | isUserInteractionEnabled = content.isUserInteractionEnabled
55 | }
56 |
57 | // MARK: - Private methods
58 |
59 | private func setupInitialState() {
60 | appendContent(to: container)
61 |
62 | backgroundColor = .clear
63 | isUserInteractionEnabled = content.isUserInteractionEnabled
64 | }
65 |
66 | private func activateLayoutConstraintsOfContent(to view: UIView) {
67 | [
68 | content.topAnchor.constraint(equalTo: view.topAnchor),
69 | content.bottomAnchor.constraint(equalTo: view.bottomAnchor),
70 | content.leftAnchor.constraint(equalTo: view.leftAnchor),
71 | content.rightAnchor.constraint(equalTo: view.rightAnchor)
72 | ].forEach { $0.isActive = true }
73 | }
74 |
75 | private func appendContent(to view: UIView?) {
76 | guard let view = view else {
77 | return
78 | }
79 | view.addSubview(content)
80 | view.isUserInteractionEnabled = true
81 | content.translatesAutoresizingMaskIntoConstraints = false
82 | activateLayoutConstraintsOfContent(to: view)
83 | }
84 |
85 | }
86 |
87 | // MARK: - ScreenshotInvincibleContainerProtocol
88 |
89 | extension ScreenshotInvincibleContainer: ScreenshotInvincibleContainerProtocol {
90 |
91 | func eraseOldAndAddnewContent(_ newContent: UIView) {
92 | content.removeFromSuperview()
93 | content = newContent
94 | appendContent(to: container)
95 | }
96 |
97 | func setupContanerAsHideContentInScreenshots() {
98 | isSecureTextEntry = true
99 | }
100 |
101 | func setupContanerAsDisplayContentInScreenshots() {
102 | isSecureTextEntry = false
103 | }
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/Sources/SnapshotSafeView/Core/ScreenshotProtectController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScreenshotProtectController.swift
3 | //
4 | //
5 | // Created by Князьков Илья on 02.03.2022.
6 | //
7 |
8 | import class UIKit.UIView
9 |
10 | /**
11 | Controller on hide from screenshot and screen recording states of content
12 |
13 | Example usage:
14 | ```
15 | final class ExampleSecureViewController: UIViewController {
16 |
17 | let hiddenFromScreenshotButtonController = ScreenshotProtectController(content: UIButton())
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 | hiddenFromScreenshotButtonController.content.backgroundColor = .red // UI customization apply to content
22 | hiddenFromScreenshotButtonController.content.layer.cornerRadius = 16
23 |
24 | view.addSubview(hiddenFromScreenshotButtonController.container)
25 | hiddenFromScreenshotButtonController.container // Layout control apply to container
26 | .position
27 | .pin(to: view.safeAreaLayoutGuide, const: 65)
28 |
29 | hiddenFromScreenshotButtonController.setupContentAsHiddenInScreenshotMode() // apply hidden mode
30 | // content will be removed from system screenshots and screen recording
31 | }
32 |
33 | }
34 |
35 | ```
36 | */
37 | open class ScreenshotProtectController: ScreenshotProtectControllerProtocol {
38 |
39 | public typealias ProtectiveContainer = ScreenshotInvincibleContainerProtocol
40 |
41 | /// - View, which will be hidden on screenshots and screen recording
42 | /// - All operation with UI customization need perform at content
43 | public var content: Content
44 |
45 | /// - Container view, all operation with layout need perform at container
46 | public lazy var container: ProtectiveContainer = ScreenshotInvincibleContainer(content: content)
47 |
48 | public init(content: Content) {
49 | self.content = content
50 | }
51 |
52 | public func eraseOldAndAddnewContent(_ newContent: Content) {
53 | container.eraseOldAndAddnewContent(newContent)
54 | }
55 |
56 | public func setupContentAsHiddenInScreenshotMode() {
57 | container.setupContanerAsHideContentInScreenshots()
58 | }
59 |
60 | public func setupContentAsDisplayedInScreenshotMode() {
61 | container.setupContanerAsDisplayContentInScreenshots()
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/Sources/SnapshotSafeView/Library/UIView+SnapshotSafeController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+SnapshotSafeController.swift
3 | //
4 | //
5 | // Created by Илья Князьков on 10.11.2022.
6 | //
7 |
8 | import UIKit
9 | import Foundation
10 |
11 | public extension UIView {
12 |
13 | /// Wraps view in ``SnpashotSafeController`` with saving ``Auto Layout`` properties.
14 | /// But this is irreversible process and for much control use directly ``SnapshotSaveController`` instead.
15 | /// - Warning: May destroy layout of view!
16 | /**
17 | # Example
18 | ```swift
19 | let someView = UIView()
20 |
21 | someView.translatesAutoresizingMaskIntoConstraints = false
22 | someView.topAnchor.constraint(superView.topAnchor, constant: 12).isActive = true
23 | someView.setupAsHiddenFromScreenshot()
24 | ```
25 | */
26 | func setupAsHiddenFromSystemScreenshotsAndVideoRecordings() {
27 | guard
28 | let superview = self.superview,
29 | !HiddenContainerRecognizer().viewIsAlreadyInHiddenContainer(self)
30 | else {
31 | return
32 | }
33 |
34 | let alreadyUsedConstraints = copiedConstraints(from: self)
35 | let snapshotSafeController = ScreenshotProtectController(content: self)
36 |
37 | snapshotSafeController.setupContentAsHiddenInScreenshotMode()
38 |
39 | superview.addSubview(snapshotSafeController.container)
40 | snapshotSafeController.container.translatesAutoresizingMaskIntoConstraints = false
41 |
42 | alreadyUsedConstraints
43 | .fromRelationShip
44 | .map { constraint in
45 | generateConstraint(for: constraint.firstItem, to: snapshotSafeController.container, from: constraint)
46 | }
47 | .forEach(superview.addConstraint)
48 |
49 | alreadyUsedConstraints
50 | .toRelationship
51 | .map { constraint in
52 | generateConstraint(for: snapshotSafeController.container, to: constraint.secondItem, from: constraint)
53 | }
54 | .forEach(superview.addConstraint)
55 | }
56 |
57 | }
58 |
59 | private extension UIView {
60 |
61 | // MARK: - Nested Types
62 |
63 | struct Constraints {
64 |
65 | let toRelationship: [NSLayoutConstraint]
66 | let fromRelationShip: [NSLayoutConstraint]
67 |
68 | func insertedToRelatinshipConstraint(_ constraint: NSLayoutConstraint) -> Self {
69 | return Constraints(toRelationship: toRelationship + [constraint], fromRelationShip: fromRelationShip)
70 | }
71 |
72 | func insertedfromRelatinshipConstraint(_ constraint: NSLayoutConstraint) -> Self {
73 | return Constraints(toRelationship: toRelationship, fromRelationShip: fromRelationShip + [constraint])
74 | }
75 |
76 | }
77 |
78 | // MARK: - Private Methods
79 |
80 | func copiedConstraints(from view: UIView) -> Constraints {
81 | let constraints = Constraints(toRelationship: [], fromRelationShip: [])
82 | guard let superView = view.superview else {
83 | return constraints
84 | }
85 |
86 | return superView
87 | .constraints
88 | .reduce(constraints) { partialResult, constraint in
89 | if let firstItem = constraint.firstItem as? UIView, firstItem == view {
90 | return partialResult.insertedToRelatinshipConstraint(constraint)
91 | } else if let secondItem = constraint.secondItem as? UIView, secondItem == view {
92 | return partialResult.insertedfromRelatinshipConstraint(constraint)
93 | }
94 | return partialResult
95 | }
96 | }
97 |
98 | func generateConstraint(
99 | for forItem: Any?,
100 | to toItem: Any?,
101 | from constraint: NSLayoutConstraint
102 | ) -> NSLayoutConstraint {
103 | return NSLayoutConstraint(
104 | item: forItem ?? Void(),
105 | attribute: constraint.firstAttribute,
106 | relatedBy: constraint.relation,
107 | toItem: toItem,
108 | attribute: constraint.secondAttribute,
109 | multiplier: constraint.multiplier,
110 | constant: constraint.constant
111 | )
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/Sources/SnapshotSafeView/Library/ViewWithDisabledPointInsideCheck.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewWithDisabledPointInsideCheck.swift
3 | //
4 | //
5 | // Created by Илья Князьков on 21.02.2023.
6 | //
7 |
8 | import UIKit
9 |
10 | final class ViewWithDisabledPointInsideCheck: UIView {
11 |
12 | override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
13 | return true
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/SnapshotSafeView/Multiplatform/Flutter/SnapshotSafeViewFlutterInterceptor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnapshotSafeViewFlutterInterceptor.swift
3 | //
4 | //
5 | // Created by Илья Князьков on 30.01.2023.
6 | //
7 |
8 | import UIKit
9 |
10 | /** - Note: Should be retained, because the desired controller may not appear immediately
11 |
12 | **Usage:**
13 | ```swift
14 | import UIKit
15 | import Flutter
16 | import SnapshotSafeView
17 |
18 | @UIApplicationMain
19 | @objc class AppDelegate: FlutterAppDelegate {
20 |
21 | private let snapshotSafeViewInterceptor = SnapshotSafeViewFlutterInterceptor()
22 |
23 | override func application(
24 | _ application: UIApplication,
25 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
26 | ) -> Bool {
27 | GeneratedPluginRegistrant.register(with: self)
28 | snapshotSafeViewInterceptor.performSwitchView(in: window) /// Wait for ``FlutterViewController``
29 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
30 | }
31 |
32 | }
33 | ```
34 | */
35 | public final class SnapshotSafeViewFlutterInterceptor {
36 |
37 | // MARK: - Private Properties
38 |
39 | private let multiplatformSnapshotSafeView: UIView
40 | private let rootControllerInterceptor: RootControllerInterceptorProtocol
41 |
42 | // MARK: - Initialization
43 |
44 | public init() {
45 | self.multiplatformSnapshotSafeView = MultiplatformBridgeView()
46 | self.rootControllerInterceptor = RootControllerInterceptor()
47 | }
48 |
49 | // MARK: - Public Methods
50 |
51 | public func performSwitchView(in window: UIWindow) {
52 | rootControllerInterceptor.performSwitchView(
53 | in: window,
54 | expectedContainer: multiplatformSnapshotSafeView,
55 | waitWhenRootBecome: FlutterExpectation()
56 | )
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/Sources/SnapshotSafeView/MultiplatformBridgeView/MultiplatformBridgeView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MultiplatformBridgeView.swift
3 | //
4 | //
5 | // Created by Илья Князьков on 19.01.2023.
6 | //
7 |
8 | import UIKit
9 |
10 | open class MultiplatformBridgeView: UIView {
11 |
12 | // MARK: - Private Properties
13 |
14 | private let screenshotSafeContainer: MultiplatformContainer
15 | private var setupInitialStateIsPerformed: Bool = false
16 |
17 | // MARK: - Initialization
18 |
19 | public init() {
20 | self.screenshotSafeContainer = MultiplatformContainer(content: ViewWithDisabledPointInsideCheck())
21 | super.init(frame: .zero)
22 |
23 | configureScreenshotSafeContainer()
24 | }
25 |
26 | public required init?(coder: NSCoder) {
27 | fatalError("init(coder:) has not been implemented")
28 | }
29 |
30 | // MARK: - UIView
31 |
32 | override open func addSubview(_ view: UIView) {
33 | guard setupInitialStateIsPerformed else {
34 | return super.addSubview(view)
35 | }
36 | screenshotSafeContainer.content.addSubview(view)
37 | }
38 |
39 | }
40 |
41 | // MARK: - Private Methods
42 |
43 | private extension MultiplatformBridgeView {
44 |
45 | func configureScreenshotSafeContainer() {
46 | screenshotSafeContainer.setupContanerAsHideContentInScreenshots()
47 | addSubview(screenshotSafeContainer)
48 | [
49 | screenshotSafeContainer.topAnchor.constraint(equalTo: topAnchor),
50 | screenshotSafeContainer.bottomAnchor.constraint(equalTo: bottomAnchor),
51 | screenshotSafeContainer.leftAnchor.constraint(equalTo: leftAnchor),
52 | screenshotSafeContainer.rightAnchor.constraint(equalTo: rightAnchor)
53 | ].forEach { $0.isActive = true }
54 | setupInitialStateIsPerformed = true
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/Sources/SnapshotSafeView/MultiplatformBridgeView/MultiplatformContainer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MultiplatformContainer.swift
3 | //
4 | //
5 | // Created by Илья Князьков on 21.02.2023.
6 | //
7 |
8 | import UIKit
9 |
10 | final class MultiplatformContainer: ScreenshotInvincibleContainer {
11 |
12 | override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
13 | return content.hitTest(point, with: event)
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/SnapshotSafeView/Protocols/ScreenshotInvincibleContainerProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScreenshotInvincibleContainerProtocol.swift.swift
3 | //
4 | //
5 | // Created by Князьков Илья on 02.03.2022.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | public protocol ScreenshotInvincibleContainerProtocol: UIView {
12 |
13 | func eraseOldAndAddnewContent(_ newContent: UIView)
14 | func setupContanerAsHideContentInScreenshots()
15 | func setupContanerAsDisplayContentInScreenshots()
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/SnapshotSafeView/Protocols/ScreenshotProtectControllerProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScreenshotProtectControllerProtocol.swift
3 | //
4 | //
5 | // Created by Князьков Илья on 02.03.2022.
6 | //
7 |
8 | import UIKit
9 |
10 | public protocol ScreenshotProtectControllerProtocol {
11 |
12 | associatedtype Content
13 | associatedtype ProtectiveContainer
14 |
15 | var content: Content { get set }
16 | var container: ProtectiveContainer { get set }
17 |
18 | func eraseOldAndAddnewContent(_ newContent: Content)
19 | func setupContentAsHiddenInScreenshotMode()
20 | func setupContentAsDisplayedInScreenshotMode()
21 |
22 | }
23 |
24 | extension ScreenshotProtectControllerProtocol where Self: ScreenshotInvincibleContainerProtocol {
25 |
26 | func setupContentAsHiddenInScreenshotMode() {
27 | setupContentAsDisplayedInScreenshotMode()
28 | }
29 |
30 | func setupContentAsDisplayedInScreenshotMode() {
31 | setupContentAsDisplayedInScreenshotMode()
32 | }
33 |
34 | }
35 |
36 |
37 |
--------------------------------------------------------------------------------
/Sources/SnapshotSafeView/RootControllerInterceptor/RootControllerExpectation/Flutter/FlutterExpectation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FlutterExpectation.swift
3 | //
4 | //
5 | // Created by Илья Князьков on 30.01.2023.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct FlutterExpectation: RootControllerExpectation {
11 |
12 | public var typeInStringRepresentation: String = "FlutterViewController"
13 |
14 | public init() { }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/SnapshotSafeView/RootControllerInterceptor/RootControllerExpectation/RootControllerExpectation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RootControllerExpectation.swift
3 | //
4 | //
5 | // Created by Илья Князьков on 30.01.2023.
6 | //
7 |
8 | import Foundation
9 |
10 | public protocol RootControllerExpectation {
11 |
12 | var typeInStringRepresentation: String { get }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/Sources/SnapshotSafeView/RootControllerInterceptor/RootControllerInterceptor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RootControllerInterceptor.swift
3 | //
4 | //
5 | // Created by Илья Князьков on 30.01.2023.
6 | //
7 |
8 | import UIKit
9 |
10 | public final class RootControllerInterceptor: RootControllerInterceptorProtocol {
11 |
12 | // MARK: - Private Properties
13 |
14 | private var timerWhichListenWindowsRoot: DispatchSourceTimer?
15 |
16 | // MARK: - Deinitialization
17 |
18 | deinit {
19 | stopWaitingRootInWindow()
20 | }
21 |
22 | // MARK: - RootControllerInterceptorProtocol
23 |
24 | public func performSwitchView(
25 | in window: UIWindow,
26 | expectedContainer view: UIView,
27 | waitWhenRootBecome rootExpectation: RootControllerExpectation
28 | ) {
29 | guard
30 | isNeedPerformSwitching(window.rootViewController, expected: rootExpectation),
31 | let viewController = window.rootViewController
32 | else {
33 | return startWaitingExpectedController(in: window, expectedContainer: view, waitWhenRootBecome: rootExpectation)
34 | }
35 | moveContainerBehindDesiredView(view, desired: viewController.view, in: viewController)
36 | }
37 |
38 | }
39 |
40 |
41 | // MARK: - Private Methods
42 |
43 | private extension RootControllerInterceptor {
44 |
45 | func startWaitingExpectedController(
46 | in window: UIWindow,
47 | expectedContainer view: UIView,
48 | waitWhenRootBecome rootExpectation: RootControllerExpectation
49 | ) {
50 | stopWaitingRootInWindow()
51 |
52 | timerWhichListenWindowsRoot = DispatchSource.makeTimerSource(queue: .main)
53 | timerWhichListenWindowsRoot?.setEventHandler { [weak self] in
54 | guard
55 | self?.isNeedPerformSwitching(window.rootViewController, expected: rootExpectation) == .some(true),
56 | let viewController = window.rootViewController
57 | else {
58 | return
59 | }
60 | self?.moveContainerBehindDesiredView(view, desired: viewController.view, in: viewController)
61 | self?.stopWaitingRootInWindow()
62 | }
63 |
64 | timerWhichListenWindowsRoot?.schedule(deadline: .now(), repeating: 0.005)
65 | timerWhichListenWindowsRoot?.resume()
66 | }
67 |
68 | func isNeedPerformSwitching(_ controller: UIViewController?, expected rootExpectation: RootControllerExpectation) -> Bool {
69 | guard let rootViewController = controller else {
70 | return false
71 | }
72 | let typeOfRootInString = String(describing: type(of: rootViewController))
73 | return typeOfRootInString == rootExpectation.typeInStringRepresentation
74 | }
75 |
76 | func moveContainerBehindDesiredView(_ container: UIView, desired view: UIView, in controller: UIViewController) {
77 | DispatchQueue.main.async {
78 | container.autoresizingMask = [.flexibleWidth, .flexibleHeight]
79 | controller.view = container
80 |
81 | container.addSubview(view)
82 |
83 | view.translatesAutoresizingMaskIntoConstraints = false
84 | [
85 | view.topAnchor.constraint(equalTo: container.topAnchor),
86 | view.bottomAnchor.constraint(equalTo: container.bottomAnchor),
87 | view.leadingAnchor.constraint(equalTo: container.leadingAnchor),
88 | view.trailingAnchor.constraint(equalTo: container.trailingAnchor)
89 | ].forEach { constraint in
90 | constraint.isActive = true
91 | }
92 | }
93 | }
94 |
95 | func stopWaitingRootInWindow() {
96 | timerWhichListenWindowsRoot?.cancel()
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/Sources/SnapshotSafeView/RootControllerInterceptor/RootControllerInterceptorProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RootControllerInterceptorProtocol.swift
3 | //
4 | //
5 | // Created by Илья Князьков on 30.01.2023.
6 | //
7 |
8 | import UIKit
9 | import Foundation
10 |
11 | /// - Note: Should be retained, because the desired controller may not appear immediately
12 | public protocol RootControllerInterceptorProtocol {
13 |
14 | /// Method waiting desired controller in ``UIWindow.rootViewController`` and will try to switch him at `view`
15 | ///
16 | /// - Parameters:
17 | /// - window: ``UIWindow``
18 | /// - view: ``UIView`` for switching
19 | /// - rootExpectation: Swappable view ``RootControllerExpectation``
20 | func performSwitchView(
21 | in window: UIWindow,
22 | expectedContainer view: UIView,
23 | waitWhenRootBecome rootExpectation: RootControllerExpectation
24 | )
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/SnapshotSafeView/SwiftUIBridge/SnaphotSafeViewSwiftUIBridge.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnaphotSafeViewSwiftUIBridge.swift
3 | //
4 | //
5 | // Created by Илья Князьков on 10.09.2022.
6 | //
7 |
8 |
9 | #if canImport(SwiftUI)
10 | import SwiftUI
11 |
12 | @available(iOS 13, *)
13 | struct SnaphotSafeViewSwiftUIBridge: SnaphotSafeViewSwiftUIBridgeProtocol {
14 |
15 | // MARK: - Private Properties
16 |
17 | private let content: () -> Content
18 |
19 | // MARK: - Initialization
20 |
21 | init(content: @autoclosure @escaping () -> Content) {
22 | self.content = content
23 | }
24 |
25 | // MARK: - SnaphotSafeViewSwiftUIBridgeProtocol
26 |
27 | typealias ProtectedView = ProtectedViewRepresentable
28 |
29 | func hiddenFromSystemSnaphot() -> ProtectedView {
30 | let hostingController = UIHostingController(rootView: content())
31 | return ProtectedViewRepresentable(content: hostingController.view)
32 | }
33 |
34 | }
35 |
36 | // MARK: - Nested Types
37 |
38 | @available(iOS 13, *)
39 | extension SnaphotSafeViewSwiftUIBridge {
40 |
41 | struct ProtectedViewRepresentable: UIViewRepresentable {
42 |
43 | // MARK: - Internal Properties
44 |
45 | let viewContent: UIView
46 |
47 | // MARK: - Initialization
48 |
49 | init(content: UIView) {
50 | self.viewContent = content
51 | }
52 |
53 | // MARK: - UIViewRepresentable
54 |
55 | func makeUIView(context: Context) -> UIView {
56 | let snapshotController = ScreenshotProtectController(content: viewContent)
57 | snapshotController.setupContentAsHiddenInScreenshotMode()
58 | snapshotController.container.setContentHuggingPriority(.required, for: .vertical)
59 | snapshotController.container.setContentHuggingPriority(.fittingSizeLevel, for: .horizontal)
60 | return snapshotController.container
61 | }
62 |
63 | func updateUIView(_ uiView: UIView, context: Context) { }
64 |
65 | }
66 |
67 | }
68 | #endif
69 |
--------------------------------------------------------------------------------
/Sources/SnapshotSafeView/SwiftUIBridge/SnaphotSafeViewSwiftUIBridgeProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnaphotSafeViewSwiftUIBridgeProtocol.swift
3 | //
4 | //
5 | // Created by Илья Князьков on 10.09.2022.
6 | //
7 |
8 | #if canImport(SwiftUI)
9 | import SwiftUI
10 |
11 | @available(iOS 13, *)
12 | protocol SnaphotSafeViewSwiftUIBridgeProtocol {
13 |
14 | associatedtype ProtectedView: View
15 |
16 | func hiddenFromSystemSnaphot() -> ProtectedView
17 |
18 | }
19 | #endif
20 |
--------------------------------------------------------------------------------
/Sources/SnapshotSafeView/SwiftUIBridge/View+SnaphotSafeViewSwiftUIBridge.swift:
--------------------------------------------------------------------------------
1 | //
2 | // View+SnaphotSafeViewSwiftUIBridge.swift
3 | //
4 | //
5 | // Created by Илья Князьков on 10.09.2022.
6 | //
7 |
8 | #if canImport(SwiftUI)
9 | import SwiftUI
10 |
11 | @available(iOS 13, *)
12 | public extension View {
13 |
14 | // MARK: - Private Properties
15 |
16 | private var snapshotBridge: SnaphotSafeViewSwiftUIBridge {
17 | SnaphotSafeViewSwiftUIBridge(content: self)
18 | }
19 |
20 | // MARK: - Public Methods
21 |
22 | /// Wrapped `View` in`ScreenshotProtectController`, will be hidden from system screenshots, during ``condition`` is true
23 | /// - Parameter condition: During condittion is true, return wrapped `View`
24 | /// - Returns: Wrapped `View` or just `Self`, depend at condition
25 | @ViewBuilder
26 | func hiddenFromSystemSnaphot(when condition: @autoclosure () -> Bool) -> some View {
27 | if condition() {
28 | snapshotBridge.hiddenFromSystemSnaphot()
29 | } else {
30 | self
31 | }
32 | }
33 |
34 | /// Wrapped `View` in`ScreenshotProtectController` with default padding, will be hidden from system screenshots, during ``condition`` is true
35 | /// - Parameter condition: During condittion is true, return wrapped `View`
36 | /// - Returns: Wrapped `View` or just `Self`, depend at condition
37 | @ViewBuilder
38 | func hiddenFromSystemSnaphotWithDefaultPadding(when condition: @autoclosure () -> Bool) -> some View {
39 | if condition() {
40 | snapshotBridge.hiddenFromSystemSnaphot()
41 | .padding(.vertical)
42 | } else {
43 | self
44 | }
45 | }
46 |
47 | }
48 | #endif
49 |
--------------------------------------------------------------------------------
/Tests/SnapshotSafeViewTests/RootControllerInterceptorTests/RootControllerInterceptorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RootControllerInterceptorTests.swift
3 | //
4 | //
5 | // Created by Илья Князьков on 31.01.2023.
6 | //
7 |
8 | import XCTest
9 | @testable import SnapshotSafeView
10 |
11 | final class RootControllerInterceptorTests: XCTestCase {
12 |
13 | var interceptor: RootControllerInterceptorProtocol {
14 | RootControllerInterceptor()
15 | }
16 |
17 | func testOnCorrectInterceptingAndSwitchingViews() {
18 | let expectation = expectation(description: "Wait for switching")
19 |
20 | let expectedView = TestableView()
21 | let controller = TestableController()
22 | let frontView = controller.view
23 |
24 | let window = UIWindow()
25 | window.rootViewController = controller
26 |
27 | interceptor.performSwitchView(in: window, expectedContainer: expectedView, waitWhenRootBecome: TestableExpectation())
28 |
29 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
30 | XCTAssertEqual(controller.view, expectedView)
31 | XCTAssertEqual(controller.view.subviews.first, frontView)
32 | expectation.fulfill()
33 | }
34 |
35 | wait(for: [expectation], timeout: 0.1)
36 | }
37 |
38 | func testOnCorrectWaitingForInterceptingAndSwitchingViews() {
39 | let expectation = expectation(description: "Wait for switching")
40 |
41 | let interceptor = self.interceptor
42 | let expectedView = TestableView()
43 | let controller = TestableController()
44 | let frontView = controller.view
45 |
46 | let window = UIWindow()
47 |
48 | interceptor.performSwitchView(in: window, expectedContainer: expectedView, waitWhenRootBecome: TestableExpectation())
49 |
50 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
51 | window.rootViewController = controller
52 | }
53 |
54 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
55 | XCTAssertEqual(controller.view, expectedView)
56 | XCTAssertEqual(controller.view.subviews.first, frontView)
57 | expectation.fulfill()
58 | }
59 |
60 | wait(for: [expectation], timeout: 2)
61 | }
62 |
63 | }
64 |
65 | // MARK: - Mocks
66 |
67 | fileprivate final class TestableController: UIViewController { }
68 |
69 | fileprivate struct TestableExpectation: RootControllerExpectation {
70 |
71 | var typeInStringRepresentation: String {
72 | String(describing: type(of: TestableController()))
73 | }
74 |
75 | }
76 |
77 | fileprivate final class TestableView: UIView { }
78 |
--------------------------------------------------------------------------------