├── .gitignore
├── CardPCDemo.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ └── CardModal.xcscheme
├── CardPresentationController.mp4
├── CardPresentationController.podspec
├── CardPresentationController.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ └── CardPresentationController.xcscheme
├── CardPresentationController
├── CardAnimator.swift
├── CardConfiguration.swift
├── CardPresentationController.swift
├── CardTransitionManager.swift
└── UIKit-CardPresentationExtensions.swift
├── EmbeddedNCExample
├── EmbeddedNCExample.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── EmbeddedNCExample
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-20x20@1x.png
│ │ ├── Icon-App-20x20@2x.png
│ │ ├── Icon-App-20x20@3x.png
│ │ ├── Icon-App-29x29@1x.png
│ │ ├── Icon-App-29x29@2x.png
│ │ ├── Icon-App-29x29@3x.png
│ │ ├── Icon-App-40x40@1x.png
│ │ ├── Icon-App-40x40@2x.png
│ │ ├── Icon-App-40x40@3x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-76x76@1x.png
│ │ ├── Icon-App-76x76@2x.png
│ │ ├── Icon-App-83.5x83.5@2x.png
│ │ └── ItunesArtwork@2x.png
│ ├── Contents.json
│ └── WonderWomanPoster.imageset
│ │ ├── Contents.json
│ │ └── WonderWomanPoster.jpg
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── ContentController.storyboard
│ ├── ContentController.swift
│ ├── Info.plist
│ └── ViewController.swift
├── Example
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-20x20@1x.png
│ │ ├── Icon-App-20x20@2x.png
│ │ ├── Icon-App-20x20@3x.png
│ │ ├── Icon-App-29x29@1x.png
│ │ ├── Icon-App-29x29@2x.png
│ │ ├── Icon-App-29x29@3x.png
│ │ ├── Icon-App-40x40@1x.png
│ │ ├── Icon-App-40x40@2x.png
│ │ ├── Icon-App-40x40@3x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-76x76@1x.png
│ │ ├── Icon-App-76x76@2x.png
│ │ ├── Icon-App-83.5x83.5@2x.png
│ │ └── ItunesArtwork@2x.png
│ ├── Contents.json
│ └── WonderWomanPoster.imageset
│ │ ├── Contents.json
│ │ └── WonderWomanPoster.jpg
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── ContentController.storyboard
├── ContentController.swift
├── CustomContainerController.storyboard
├── CustomContainerController.swift
├── GridCell.swift
├── GridCell.xib
├── GridController.swift
├── GridLayout.swift
├── Info.plist
├── PopupNavigationController.swift
├── SecondController.storyboard
├── SecondController.swift
└── ViewController.swift
├── Framework
├── CardPresentationController.h
└── Info.plist
├── LICENSE
├── README.md
├── Vendor
├── BaseGridLayout.swift
├── Controllers
│ ├── Embeddable.swift
│ └── StoryboardLoadable.swift
├── GradientView.swift
└── Views
│ ├── DequeableView.swift
│ ├── NibLoadable.swift
│ └── ReusableView.swift
└── resources
├── apple-music-iphone-xs.acorn
├── apple-music-iphone-xs.png
├── cardpc.png
├── presentedNC-top.png
├── presentedNC.png
├── presentedVC-top.png
└── presentedVC.png
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xcuserstate
23 |
24 | ## Obj-C/Swift specific
25 | *.hmap
26 | *.ipa
27 |
28 | ## Playgrounds
29 | timeline.xctimeline
30 | playground.xcworkspace
31 |
32 | # Swift Package Manager
33 | #
34 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
35 | # Packages/
36 | .build/
37 |
38 | # CocoaPods
39 | #
40 | # We recommend against adding the Pods directory to your .gitignore. However
41 | # you should judge for yourself, the pros and cons are mentioned at:
42 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
43 | #
44 | # Pods/
45 |
46 | # Carthage
47 | #
48 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
49 | # Carthage/Checkouts
50 |
51 | Carthage/Build
52 |
53 | # fastlane
54 | #
55 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
56 | # screenshots whenever they are needed.
57 | # For more information about the recommended setup visit:
58 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md
59 |
60 | fastlane/report.xml
61 | fastlane/screenshots
62 |
63 | # big files
64 | *.mov
65 | *.mp4
--------------------------------------------------------------------------------
/CardPCDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CardPCDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/CardPCDemo.xcodeproj/xcshareddata/xcschemes/CardModal.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/CardPresentationController.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/CardPresentationController.mp4
--------------------------------------------------------------------------------
/CardPresentationController.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'CardPresentationController'
3 | s.version = '1.6'
4 | s.summary = 'Custom UIPresentationController which mimics the behavior of Apple Music UI'
5 | s.description = 'Configurable, supports interactive dismissal and really easy to integrate. Fallbacks to system look & behavior on iOS 13.'
6 | s.homepage = 'https://github.com/radianttap/CardPresentationController'
7 | s.license = { :type => "MIT", :file => "LICENSE" }
8 | s.author = { 'Aleksandar Vacić' => 'radianttap.com' }
9 | s.social_media_url = "https://twitter.com/radiantav"
10 | s.ios.deployment_target = "10.0"
11 | s.source = { :git => "https://github.com/radianttap/CardPresentationController.git" }
12 | s.source_files = 'CardPresentationController/*.swift'
13 | s.frameworks = 'UIKit'
14 |
15 | s.swift_version = '5.0'
16 | end
17 |
18 |
--------------------------------------------------------------------------------
/CardPresentationController.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | D0ECC50721FF7B72006D5ED1 /* CardPresentationController.h in Headers */ = {isa = PBXBuildFile; fileRef = D0ECC50521FF7B72006D5ED1 /* CardPresentationController.h */; settings = {ATTRIBUTES = (Public, ); }; };
11 | D0ECC51021FF7B9D006D5ED1 /* CardPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05FF1E8214D54B50017A263 /* CardPresentationController.swift */; };
12 | D0ECC51121FF7B9D006D5ED1 /* CardTransitionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D051930B214D6C690047AB2D /* CardTransitionManager.swift */; };
13 | D0ECC51221FF7B9D006D5ED1 /* CardAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D051930D214D6DD10047AB2D /* CardAnimator.swift */; };
14 | D0ECC51321FF7B9D006D5ED1 /* CardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AB35EF21C56F040038C6C6 /* CardConfiguration.swift */; };
15 | D0ECC51421FF7B9D006D5ED1 /* UIKit-CardPresentationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AC738021BC61B800054FC3 /* UIKit-CardPresentationExtensions.swift */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXFileReference section */
19 | D051930B214D6C690047AB2D /* CardTransitionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardTransitionManager.swift; sourceTree = ""; };
20 | D051930D214D6DD10047AB2D /* CardAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardAnimator.swift; sourceTree = ""; };
21 | D05FF1E8214D54B50017A263 /* CardPresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentationController.swift; sourceTree = ""; };
22 | D0AB35EF21C56F040038C6C6 /* CardConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardConfiguration.swift; sourceTree = ""; };
23 | D0AC738021BC61B800054FC3 /* UIKit-CardPresentationExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIKit-CardPresentationExtensions.swift"; sourceTree = ""; };
24 | D0CE0567220442E300012062 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
25 | D0CE05682204458A00012062 /* CardPresentationController.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = CardPresentationController.podspec; sourceTree = ""; };
26 | D0ECC50321FF7B71006D5ED1 /* CardPresentationController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CardPresentationController.framework; sourceTree = BUILT_PRODUCTS_DIR; };
27 | D0ECC50521FF7B72006D5ED1 /* CardPresentationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CardPresentationController.h; sourceTree = ""; };
28 | D0ECC50621FF7B72006D5ED1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
29 | /* End PBXFileReference section */
30 |
31 | /* Begin PBXFrameworksBuildPhase section */
32 | D0ECC50021FF7B71006D5ED1 /* Frameworks */ = {
33 | isa = PBXFrameworksBuildPhase;
34 | buildActionMask = 2147483647;
35 | files = (
36 | );
37 | runOnlyForDeploymentPostprocessing = 0;
38 | };
39 | /* End PBXFrameworksBuildPhase section */
40 |
41 | /* Begin PBXGroup section */
42 | D01ADC72222BE2EF0007ACD8 /* Framework */ = {
43 | isa = PBXGroup;
44 | children = (
45 | D0ECC50521FF7B72006D5ED1 /* CardPresentationController.h */,
46 | D0ECC50621FF7B72006D5ED1 /* Info.plist */,
47 | );
48 | path = Framework;
49 | sourceTree = "";
50 | };
51 | D05FF1C9214D53520017A263 = {
52 | isa = PBXGroup;
53 | children = (
54 | D0CE05682204458A00012062 /* CardPresentationController.podspec */,
55 | D0CE0567220442E300012062 /* README.md */,
56 | D05FF1E7214D54970017A263 /* CardPresentationController */,
57 | D01ADC72222BE2EF0007ACD8 /* Framework */,
58 | D05FF1D3214D53520017A263 /* Products */,
59 | );
60 | sourceTree = "";
61 | };
62 | D05FF1D3214D53520017A263 /* Products */ = {
63 | isa = PBXGroup;
64 | children = (
65 | D0ECC50321FF7B71006D5ED1 /* CardPresentationController.framework */,
66 | );
67 | name = Products;
68 | sourceTree = "";
69 | };
70 | D05FF1E7214D54970017A263 /* CardPresentationController */ = {
71 | isa = PBXGroup;
72 | children = (
73 | D05FF1E8214D54B50017A263 /* CardPresentationController.swift */,
74 | D051930B214D6C690047AB2D /* CardTransitionManager.swift */,
75 | D051930D214D6DD10047AB2D /* CardAnimator.swift */,
76 | D0AB35EF21C56F040038C6C6 /* CardConfiguration.swift */,
77 | D0AC738021BC61B800054FC3 /* UIKit-CardPresentationExtensions.swift */,
78 | );
79 | path = CardPresentationController;
80 | sourceTree = "";
81 | };
82 | /* End PBXGroup section */
83 |
84 | /* Begin PBXHeadersBuildPhase section */
85 | D0ECC4FE21FF7B71006D5ED1 /* Headers */ = {
86 | isa = PBXHeadersBuildPhase;
87 | buildActionMask = 2147483647;
88 | files = (
89 | D0ECC50721FF7B72006D5ED1 /* CardPresentationController.h in Headers */,
90 | );
91 | runOnlyForDeploymentPostprocessing = 0;
92 | };
93 | /* End PBXHeadersBuildPhase section */
94 |
95 | /* Begin PBXNativeTarget section */
96 | D0ECC50221FF7B71006D5ED1 /* CardPresentationController */ = {
97 | isa = PBXNativeTarget;
98 | buildConfigurationList = D0ECC50C21FF7B72006D5ED1 /* Build configuration list for PBXNativeTarget "CardPresentationController" */;
99 | buildPhases = (
100 | D0ECC4FE21FF7B71006D5ED1 /* Headers */,
101 | D0ECC4FF21FF7B71006D5ED1 /* Sources */,
102 | D0ECC50021FF7B71006D5ED1 /* Frameworks */,
103 | D0ECC50121FF7B71006D5ED1 /* Resources */,
104 | );
105 | buildRules = (
106 | );
107 | dependencies = (
108 | );
109 | name = CardPresentationController;
110 | productName = CardPresentationController;
111 | productReference = D0ECC50321FF7B71006D5ED1 /* CardPresentationController.framework */;
112 | productType = "com.apple.product-type.framework";
113 | };
114 | /* End PBXNativeTarget section */
115 |
116 | /* Begin PBXProject section */
117 | D05FF1CA214D53520017A263 /* Project object */ = {
118 | isa = PBXProject;
119 | attributes = {
120 | LastSwiftUpdateCheck = 1000;
121 | LastUpgradeCheck = 1000;
122 | ORGANIZATIONNAME = "Aleksandar Vacić";
123 | TargetAttributes = {
124 | D0ECC50221FF7B71006D5ED1 = {
125 | CreatedOnToolsVersion = 10.1;
126 | LastSwiftMigration = 1020;
127 | };
128 | };
129 | };
130 | buildConfigurationList = D05FF1CD214D53520017A263 /* Build configuration list for PBXProject "CardPresentationController" */;
131 | compatibilityVersion = "Xcode 9.3";
132 | developmentRegion = en;
133 | hasScannedForEncodings = 0;
134 | knownRegions = (
135 | en,
136 | Base,
137 | );
138 | mainGroup = D05FF1C9214D53520017A263;
139 | productRefGroup = D05FF1D3214D53520017A263 /* Products */;
140 | projectDirPath = "";
141 | projectRoot = "";
142 | targets = (
143 | D0ECC50221FF7B71006D5ED1 /* CardPresentationController */,
144 | );
145 | };
146 | /* End PBXProject section */
147 |
148 | /* Begin PBXResourcesBuildPhase section */
149 | D0ECC50121FF7B71006D5ED1 /* Resources */ = {
150 | isa = PBXResourcesBuildPhase;
151 | buildActionMask = 2147483647;
152 | files = (
153 | );
154 | runOnlyForDeploymentPostprocessing = 0;
155 | };
156 | /* End PBXResourcesBuildPhase section */
157 |
158 | /* Begin PBXSourcesBuildPhase section */
159 | D0ECC4FF21FF7B71006D5ED1 /* Sources */ = {
160 | isa = PBXSourcesBuildPhase;
161 | buildActionMask = 2147483647;
162 | files = (
163 | D0ECC51321FF7B9D006D5ED1 /* CardConfiguration.swift in Sources */,
164 | D0ECC51121FF7B9D006D5ED1 /* CardTransitionManager.swift in Sources */,
165 | D0ECC51021FF7B9D006D5ED1 /* CardPresentationController.swift in Sources */,
166 | D0ECC51221FF7B9D006D5ED1 /* CardAnimator.swift in Sources */,
167 | D0ECC51421FF7B9D006D5ED1 /* UIKit-CardPresentationExtensions.swift in Sources */,
168 | );
169 | runOnlyForDeploymentPostprocessing = 0;
170 | };
171 | /* End PBXSourcesBuildPhase section */
172 |
173 | /* Begin XCBuildConfiguration section */
174 | D05FF1E2214D53540017A263 /* Debug */ = {
175 | isa = XCBuildConfiguration;
176 | buildSettings = {
177 | ALWAYS_SEARCH_USER_PATHS = NO;
178 | CLANG_ANALYZER_NONNULL = YES;
179 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
180 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
181 | CLANG_CXX_LIBRARY = "libc++";
182 | CLANG_ENABLE_MODULES = YES;
183 | CLANG_ENABLE_OBJC_ARC = YES;
184 | CLANG_ENABLE_OBJC_WEAK = YES;
185 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
186 | CLANG_WARN_BOOL_CONVERSION = YES;
187 | CLANG_WARN_COMMA = YES;
188 | CLANG_WARN_CONSTANT_CONVERSION = YES;
189 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
190 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
191 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
192 | CLANG_WARN_EMPTY_BODY = YES;
193 | CLANG_WARN_ENUM_CONVERSION = YES;
194 | CLANG_WARN_INFINITE_RECURSION = YES;
195 | CLANG_WARN_INT_CONVERSION = YES;
196 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
197 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
198 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
199 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
200 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
201 | CLANG_WARN_STRICT_PROTOTYPES = YES;
202 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
203 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
204 | CLANG_WARN_UNREACHABLE_CODE = YES;
205 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
206 | CODE_SIGN_IDENTITY = "iPhone Developer";
207 | COPY_PHASE_STRIP = NO;
208 | CURRENT_PROJECT_VERSION = 24;
209 | DEBUG_INFORMATION_FORMAT = dwarf;
210 | DYLIB_CURRENT_VERSION = "$(CURRENT_PROJECT_VERSION)";
211 | ENABLE_STRICT_OBJC_MSGSEND = YES;
212 | ENABLE_TESTABILITY = YES;
213 | GCC_C_LANGUAGE_STANDARD = gnu11;
214 | GCC_DYNAMIC_NO_PIC = NO;
215 | GCC_NO_COMMON_BLOCKS = YES;
216 | GCC_OPTIMIZATION_LEVEL = 0;
217 | GCC_PREPROCESSOR_DEFINITIONS = (
218 | "DEBUG=1",
219 | "$(inherited)",
220 | );
221 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
222 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
223 | GCC_WARN_UNDECLARED_SELECTOR = YES;
224 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
225 | GCC_WARN_UNUSED_FUNCTION = YES;
226 | GCC_WARN_UNUSED_VARIABLE = YES;
227 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
228 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
229 | MTL_FAST_MATH = YES;
230 | ONLY_ACTIVE_ARCH = YES;
231 | SDKROOT = iphoneos;
232 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
233 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
234 | SWIFT_VERSION = 5.0;
235 | VERSIONING_SYSTEM = "apple-generic";
236 | };
237 | name = Debug;
238 | };
239 | D05FF1E3214D53540017A263 /* Release */ = {
240 | isa = XCBuildConfiguration;
241 | buildSettings = {
242 | ALWAYS_SEARCH_USER_PATHS = NO;
243 | CLANG_ANALYZER_NONNULL = YES;
244 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
245 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
246 | CLANG_CXX_LIBRARY = "libc++";
247 | CLANG_ENABLE_MODULES = YES;
248 | CLANG_ENABLE_OBJC_ARC = YES;
249 | CLANG_ENABLE_OBJC_WEAK = YES;
250 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
251 | CLANG_WARN_BOOL_CONVERSION = YES;
252 | CLANG_WARN_COMMA = YES;
253 | CLANG_WARN_CONSTANT_CONVERSION = YES;
254 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
255 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
256 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
257 | CLANG_WARN_EMPTY_BODY = YES;
258 | CLANG_WARN_ENUM_CONVERSION = YES;
259 | CLANG_WARN_INFINITE_RECURSION = YES;
260 | CLANG_WARN_INT_CONVERSION = YES;
261 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
262 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
263 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
264 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
265 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
266 | CLANG_WARN_STRICT_PROTOTYPES = YES;
267 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
268 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
269 | CLANG_WARN_UNREACHABLE_CODE = YES;
270 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
271 | CODE_SIGN_IDENTITY = "iPhone Developer";
272 | COPY_PHASE_STRIP = NO;
273 | CURRENT_PROJECT_VERSION = 24;
274 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
275 | DYLIB_CURRENT_VERSION = "$(CURRENT_PROJECT_VERSION)";
276 | ENABLE_NS_ASSERTIONS = NO;
277 | ENABLE_STRICT_OBJC_MSGSEND = YES;
278 | GCC_C_LANGUAGE_STANDARD = gnu11;
279 | GCC_NO_COMMON_BLOCKS = YES;
280 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
281 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
282 | GCC_WARN_UNDECLARED_SELECTOR = YES;
283 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
284 | GCC_WARN_UNUSED_FUNCTION = YES;
285 | GCC_WARN_UNUSED_VARIABLE = YES;
286 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
287 | MTL_ENABLE_DEBUG_INFO = NO;
288 | MTL_FAST_MATH = YES;
289 | SDKROOT = iphoneos;
290 | SWIFT_COMPILATION_MODE = wholemodule;
291 | SWIFT_OPTIMIZATION_LEVEL = "-O";
292 | SWIFT_VERSION = 5.0;
293 | VALIDATE_PRODUCT = YES;
294 | VERSIONING_SYSTEM = "apple-generic";
295 | };
296 | name = Release;
297 | };
298 | D0ECC50D21FF7B72006D5ED1 /* Debug */ = {
299 | isa = XCBuildConfiguration;
300 | buildSettings = {
301 | CODE_SIGN_IDENTITY = "";
302 | CODE_SIGN_STYLE = Automatic;
303 | CURRENT_PROJECT_VERSION = 30;
304 | DEFINES_MODULE = YES;
305 | DEVELOPMENT_TEAM = "";
306 | DYLIB_COMPATIBILITY_VERSION = 1;
307 | DYLIB_INSTALL_NAME_BASE = "@rpath";
308 | INFOPLIST_FILE = "$(SRCROOT)/Framework/Info.plist";
309 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
310 | LD_RUNPATH_SEARCH_PATHS = (
311 | "$(inherited)",
312 | "@executable_path/Frameworks",
313 | "@loader_path/Frameworks",
314 | );
315 | MARKETING_VERSION = 1.6;
316 | PRODUCT_BUNDLE_IDENTIFIER = com.radianttap.CardPresentationController;
317 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
318 | SKIP_INSTALL = YES;
319 | TARGETED_DEVICE_FAMILY = "1,2";
320 | VERSIONING_SYSTEM = "apple-generic";
321 | VERSION_INFO_PREFIX = "";
322 | };
323 | name = Debug;
324 | };
325 | D0ECC50E21FF7B72006D5ED1 /* Release */ = {
326 | isa = XCBuildConfiguration;
327 | buildSettings = {
328 | CODE_SIGN_IDENTITY = "";
329 | CODE_SIGN_STYLE = Automatic;
330 | CURRENT_PROJECT_VERSION = 30;
331 | DEFINES_MODULE = YES;
332 | DEVELOPMENT_TEAM = "";
333 | DYLIB_COMPATIBILITY_VERSION = 1;
334 | DYLIB_INSTALL_NAME_BASE = "@rpath";
335 | INFOPLIST_FILE = "$(SRCROOT)/Framework/Info.plist";
336 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
337 | LD_RUNPATH_SEARCH_PATHS = (
338 | "$(inherited)",
339 | "@executable_path/Frameworks",
340 | "@loader_path/Frameworks",
341 | );
342 | MARKETING_VERSION = 1.6;
343 | PRODUCT_BUNDLE_IDENTIFIER = com.radianttap.CardPresentationController;
344 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
345 | SKIP_INSTALL = YES;
346 | TARGETED_DEVICE_FAMILY = "1,2";
347 | VERSIONING_SYSTEM = "apple-generic";
348 | VERSION_INFO_PREFIX = "";
349 | };
350 | name = Release;
351 | };
352 | /* End XCBuildConfiguration section */
353 |
354 | /* Begin XCConfigurationList section */
355 | D05FF1CD214D53520017A263 /* Build configuration list for PBXProject "CardPresentationController" */ = {
356 | isa = XCConfigurationList;
357 | buildConfigurations = (
358 | D05FF1E2214D53540017A263 /* Debug */,
359 | D05FF1E3214D53540017A263 /* Release */,
360 | );
361 | defaultConfigurationIsVisible = 0;
362 | defaultConfigurationName = Release;
363 | };
364 | D0ECC50C21FF7B72006D5ED1 /* Build configuration list for PBXNativeTarget "CardPresentationController" */ = {
365 | isa = XCConfigurationList;
366 | buildConfigurations = (
367 | D0ECC50D21FF7B72006D5ED1 /* Debug */,
368 | D0ECC50E21FF7B72006D5ED1 /* Release */,
369 | );
370 | defaultConfigurationIsVisible = 0;
371 | defaultConfigurationName = Release;
372 | };
373 | /* End XCConfigurationList section */
374 | };
375 | rootObject = D05FF1CA214D53520017A263 /* Project object */;
376 | }
377 |
--------------------------------------------------------------------------------
/CardPresentationController.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CardPresentationController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/CardPresentationController.xcodeproj/xcshareddata/xcschemes/CardPresentationController.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/CardPresentationController/CardAnimator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CardAnimator.swift
3 | // CardPresentationController
4 | //
5 | // Copyright © 2018 Aleksandar Vacić, Radiant Tap
6 | // MIT License · http://choosealicense.com/licenses/mit/
7 | //
8 |
9 | import UIKit
10 |
11 | @available(iOS 10.0, *)
12 | final class CardAnimator: NSObject, UIViewControllerAnimatedTransitioning {
13 | enum Direction {
14 | case presentation
15 | case dismissal
16 | }
17 |
18 | // Init
19 |
20 | var direction: Direction
21 | var configuration: CardConfiguration
22 | var isInteractive = false
23 |
24 | init(direction: Direction = .presentation, configuration: CardConfiguration) {
25 | self.direction = direction
26 | self.configuration = configuration
27 | super.init()
28 | }
29 |
30 | // Animators
31 |
32 | private(set) lazy var presentationAnimator: UIViewPropertyAnimator = setupAnimator(.presentation)
33 | private(set) lazy var dismissAnimator: UIViewPropertyAnimator = setupAnimator(.dismissal)
34 |
35 | private weak var transitionContext: UIViewControllerContextTransitioning?
36 |
37 | private var interactiveAnimator: UIViewPropertyAnimator {
38 | switch direction {
39 | case .presentation:
40 | return presentationAnimator
41 | case .dismissal:
42 | return dismissAnimator
43 | }
44 | }
45 |
46 | // Local configuration
47 |
48 | private var verticalSpacing: CGFloat { return configuration.verticalSpacing }
49 | private var verticalInset: CGFloat { return configuration.verticalInset }
50 | private var horizontalInset: CGFloat { return configuration.horizontalInset }
51 | private var cornerRadius: CGFloat { return configuration.cornerRadius }
52 | private var backFadeAlpha: CGFloat { return configuration.backFadeAlpha }
53 | private var initialTransitionFrame: CGRect? { return configuration.initialTransitionFrame }
54 |
55 | // MARK:- UIViewControllerAnimatedTransitioning
56 |
57 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
58 | let interval: TimeInterval
59 | switch direction {
60 | case .presentation:
61 | interval = 0.65
62 | case .dismissal:
63 | interval = 0.55
64 | }
65 | return interval
66 | }
67 |
68 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
69 | guard let pa = buildAnimator(for: transitionContext) else {
70 | return
71 | }
72 | pa.startAnimation()
73 | }
74 |
75 | func animationEnded(_ transitionCompleted: Bool) {
76 | isInteractive = false
77 | }
78 | }
79 |
80 | extension CardAnimator: UIViewControllerInteractiveTransitioning {
81 | func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
82 | guard let _ = buildAnimator(for: transitionContext) else {
83 | return
84 | }
85 | self.transitionContext = transitionContext
86 | }
87 |
88 | var wantsInteractiveStart: Bool {
89 | return isInteractive
90 | }
91 |
92 | func updateInteractiveTransition(_ percentComplete: CGFloat) {
93 | let pa = interactiveAnimator
94 |
95 | pa.fractionComplete = percentComplete
96 | transitionContext?.updateInteractiveTransition(percentComplete)
97 | }
98 |
99 | func cancelInteractiveTransition(with velocity: CGVector = .zero) {
100 | guard isInteractive else {
101 | return
102 | }
103 |
104 | transitionContext?.cancelInteractiveTransition()
105 |
106 | interactiveAnimator.isReversed = true // animate back to starting position
107 |
108 | let pct = interactiveAnimator.fractionComplete
109 | endInteraction(from: pct,
110 | withVelocity: velocity,
111 | durationFactor: 1 - pct)
112 | }
113 |
114 | func finishInteractiveTransition(with velocity: CGVector = .zero) {
115 | guard isInteractive else {
116 | return
117 | }
118 |
119 | transitionContext?.finishInteractiveTransition()
120 |
121 | let pct = interactiveAnimator.fractionComplete
122 | endInteraction(from: pct,
123 | withVelocity: velocity,
124 | durationFactor: pct)
125 | }
126 | }
127 |
128 |
129 | // UIViewPropertyAnimator
130 |
131 | private extension CardAnimator {
132 | func endInteraction(from percentComplete: CGFloat, withVelocity velocity: CGVector, durationFactor: CGFloat) {
133 | switch interactiveAnimator.state {
134 | case .inactive:
135 | interactiveAnimator.startAnimation()
136 | default: // case .active, .stopped, @unknown-futures
137 | interactiveAnimator.continueAnimation(withTimingParameters: nil, durationFactor: durationFactor)
138 | }
139 | }
140 |
141 | func setupAnimator(_ direction: Direction) -> UIViewPropertyAnimator {
142 | let params: SpringParameters
143 |
144 | switch direction {
145 | case .presentation:
146 | params = .tap
147 | case .dismissal:
148 | params = .momentum
149 | }
150 |
151 | // entire spring animation should not last more than transitionDuration
152 | let damping = params.damping
153 | let response = params.response
154 | let timingParameters = UISpringTimingParameters(damping: damping, response: response)
155 |
156 | let pa = UIViewPropertyAnimator(duration: 0, timingParameters: timingParameters)
157 |
158 | return pa
159 | }
160 |
161 | func buildAnimator(for transitionContext: UIViewControllerContextTransitioning) -> UIViewPropertyAnimator? {
162 | guard
163 | let fromVC = transitionContext.viewController(forKey: .from),
164 | let toVC = transitionContext.viewController(forKey: .to),
165 | let fromView = fromVC.view,
166 | let toView = toVC.view
167 | else {
168 | return nil
169 | }
170 | let containerView = transitionContext.containerView
171 |
172 | switch direction {
173 | case .presentation:
174 | let sourceCardPresentationController = fromVC.presentationController as? CardPresentationController
175 |
176 | let fromEndFrame: CGRect
177 | let toEndFrame: CGRect
178 |
179 | if let sourceCardPresentationController = sourceCardPresentationController {
180 | sourceCardPresentationController.fadeoutHandle()
181 |
182 | let fromBeginFrame: CGRect
183 | if #available(iOS 13, *) {
184 | fromBeginFrame = fromView.frame
185 | } else {
186 | // on iOS 13, origin.y for this seem to always be 0
187 | fromBeginFrame = transitionContext.initialFrame(for: fromVC)
188 | }
189 | fromEndFrame = fromBeginFrame.inset(by: UIEdgeInsets(top: 0, left: horizontalInset, bottom: 0, right: horizontalInset))
190 |
191 | toEndFrame = fromBeginFrame.inset(by: UIEdgeInsets(top: verticalSpacing, left: 0, bottom: 0, right: 0))
192 |
193 | } else {
194 | let fromBeginFrame: CGRect
195 | if #available(iOS 13, *) {
196 | fromBeginFrame = fromView.frame
197 | } else {
198 | // on iOS 13, origin.y for this seem to always be 0
199 | fromBeginFrame = transitionContext.initialFrame(for: fromVC)
200 | }
201 | fromEndFrame = fromBeginFrame.inset(by: UIEdgeInsets(top: verticalInset, left: horizontalInset, bottom: 0, right: horizontalInset))
202 |
203 | let toBaseFinalFrame = transitionContext.finalFrame(for: toVC)
204 | toEndFrame = toBaseFinalFrame.inset(by: UIEdgeInsets(top: verticalInset + verticalSpacing, left: 0, bottom: 0, right: 0))
205 | }
206 | let toStartFrame = offscreenFrame(inside: containerView)
207 |
208 | toView.clipsToBounds = true
209 | toView.frame = toStartFrame
210 | toView.layoutIfNeeded()
211 | containerView.addSubview(toView)
212 |
213 | let pa = presentationAnimator
214 | pa.addAnimations() {
215 | [weak self] in
216 | guard let self = self else { return }
217 |
218 | self.insetBackCards(of: sourceCardPresentationController)
219 | fromView.frame = fromEndFrame
220 | toView.frame = toEndFrame
221 | fromView.cardMaskTopCorners(using: self.cornerRadius)
222 | toView.cardMaskTopCorners(using: self.cornerRadius)
223 |
224 | fromView.alpha = self.backFadeAlpha
225 | }
226 |
227 | pa.addCompletion() {
228 | [weak self] animatingPosition in
229 |
230 | switch animatingPosition {
231 | case .start:
232 | self?.direction = .presentation
233 | fromView.isUserInteractionEnabled = true
234 | transitionContext.completeTransition(false)
235 | default: // case .end, .current (which should not be possible), @unknown-futures
236 | self?.direction = .dismissal
237 | fromView.isUserInteractionEnabled = false
238 | transitionContext.completeTransition(true)
239 | }
240 | }
241 |
242 | return pa
243 |
244 |
245 | case .dismissal:
246 | let targetCardPresentationController = toVC.presentationController as? CardPresentationController
247 | let isTargetAlreadyCard = (targetCardPresentationController != nil)
248 |
249 | let toBeginFrame = toView.frame
250 | let toEndFrame: CGRect
251 |
252 | if let _ = targetCardPresentationController {
253 | toEndFrame = toBeginFrame.inset(by: UIEdgeInsets(top: 0, left: -horizontalInset, bottom: 0, right: -horizontalInset))
254 | } else {
255 | toEndFrame = transitionContext.finalFrame(for: toVC)
256 | }
257 |
258 | let fromEndFrame = offscreenFrame(inside: containerView)
259 |
260 | let pa = dismissAnimator
261 | pa.addAnimations() {
262 | [weak self] in
263 | guard let self = self else { return }
264 |
265 | fromView.cardUnmask()
266 | if !isTargetAlreadyCard {
267 | toView.cardUnmask()
268 | }
269 | self.outsetBackCards(of: targetCardPresentationController)
270 |
271 | fromView.frame = fromEndFrame
272 | toView.frame = toEndFrame
273 | toView.alpha = 1
274 | fromView.alpha = 1
275 | }
276 |
277 | pa.addCompletion() {
278 | [weak self] animatingPosition in
279 | guard let self = self else { return }
280 |
281 | switch animatingPosition {
282 | case .start:
283 | self.direction = .dismissal
284 | self.dismissAnimator = self.setupAnimator(.dismissal)
285 |
286 | toView.isUserInteractionEnabled = false
287 |
288 | transitionContext.completeTransition(false)
289 |
290 | default: // case .end, .current (which should not be possible), @unknown-futures
291 | self.direction = .presentation
292 | self.presentationAnimator = self.setupAnimator(.presentation)
293 |
294 | toView.isUserInteractionEnabled = true
295 | fromView.removeFromSuperview()
296 |
297 | if let targetCardPresentationController = targetCardPresentationController {
298 | targetCardPresentationController.fadeinHandle()
299 | }
300 |
301 | transitionContext.completeTransition(true)
302 | }
303 | }
304 |
305 | return pa
306 | }
307 | }
308 | }
309 |
310 |
311 |
312 | // Helper methods
313 |
314 | private extension CardAnimator {
315 | private func insetBackCards(of pc: CardPresentationController?) {
316 | guard
317 | let pc = pc,
318 | let pcView = pc.presentingViewController.view
319 | else { return }
320 |
321 | let frame = pcView.frame
322 | pcView.frame = frame.inset(by: UIEdgeInsets(top: 0, left: horizontalInset, bottom: 0, right: horizontalInset))
323 | pcView.alpha *= backFadeAlpha
324 |
325 | insetBackCards(of: pc.presentingViewController.presentationController as? CardPresentationController)
326 | }
327 |
328 | private func outsetBackCards(of pc: CardPresentationController?) {
329 | guard
330 | let pc = pc,
331 | let pcView = pc.presentingViewController.view
332 | else { return }
333 |
334 | let frame = pcView.frame
335 | pcView.frame = frame.inset(by: UIEdgeInsets(top: 0, left: -horizontalInset, bottom: 0, right: -horizontalInset))
336 | pcView.alpha /= backFadeAlpha
337 |
338 | outsetBackCards(of: pc.presentingViewController.presentationController as? CardPresentationController)
339 | }
340 |
341 | func offscreenFrame(inside containerView: UIView) -> CGRect {
342 | if let initialTransitionFrame = initialTransitionFrame {
343 | return initialTransitionFrame
344 | } else {
345 | var f = containerView.frame
346 | f.origin.y = f.height
347 | return f
348 | }
349 | }
350 |
351 | struct SpringParameters {
352 | let damping: CGFloat
353 | let response: CGFloat
354 |
355 | // From amazing session 803 at WWDC 2018: https://developer.apple.com/videos/play/wwdc2018-803/?time=2238
356 | // > Because the tap doesn't have any momentum in the direction of the presentation of Now Playing,
357 | // > we use 100% damping to make sure it doesn't overshoot.
358 | //
359 | static let tap = SpringParameters(damping: 1, response: 0.44)
360 |
361 | // > But, if you swipe to dismiss Now Playing,
362 | // > there is momentum in the direction of the dismissal,
363 | // > and so we use 80% damping to have a little bit of bounce and squish,
364 | // > making the gesture a lot more satisfying.
365 | //
366 | // (note that they use momentum even when tapping to dismiss)
367 | static let momentum = SpringParameters(damping: 0.8, response: 0.44)
368 | }
369 | }
370 |
371 |
372 | private extension UISpringTimingParameters {
373 | /// A design-friendly way to create a spring timing curve.
374 | /// See: https://medium.com/@nathangitter/building-fluid-interfaces-ios-swift-9732bb934bf5
375 | ///
376 | /// - Parameters:
377 | /// - damping: The 'bounciness' of the animation. Value must be between 0 and 1.
378 | /// - response: The 'speed' of the animation.
379 | /// - initialVelocity: The vector describing the starting motion of the property. Optional, default is `.zero`.
380 | convenience init(damping: CGFloat, response: CGFloat, initialVelocity: CGVector = .zero) {
381 | let stiffness = pow(2 * .pi / response, 2)
382 | let damp = 4 * .pi * damping / response
383 | self.init(mass: 1, stiffness: stiffness, damping: damp, initialVelocity: initialVelocity)
384 | }
385 | }
386 |
387 |
--------------------------------------------------------------------------------
/CardPresentationController/CardConfiguration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CardConfiguration.swift
3 | // CardPresentationController
4 | //
5 | // Copyright © 2018 Aleksandar Vacić, Radiant Tap
6 | // MIT License · http://choosealicense.com/licenses/mit/
7 | //
8 |
9 | import UIKit
10 |
11 | /// Simple package for various values defining transition's look & feel.
12 | ///
13 | /// Supply it as optional parameter to `presentCard(...)`.
14 | public struct CardConfiguration {
15 | /// Vertical inset from the top or already shown card.
16 | public var verticalSpacing: CGFloat = 16
17 |
18 | /// Top vertical inset for the existing (presenting) view when it's being pushed further back.
19 | public var verticalInset: CGFloat = UIApplication.shared.statusBarFrame.size.height
20 |
21 | /// Leading and trailing inset for the existing (presenting) view when it's being pushed further back.
22 | public var horizontalInset: CGFloat = 16
23 |
24 | /// Height of the "empty" area at the top of the card where dismiss handle glyph will be centered.
25 | public var dismissAreaHeight: CGFloat = 16
26 |
27 | /// Cards have rounded corners, right?
28 | public var cornerRadius: CGFloat = 12
29 |
30 | /// The starting frame for the presented card.
31 | public var initialTransitionFrame: CGRect?
32 |
33 | /// How much to fade the back card.
34 | public var backFadeAlpha: CGFloat = 0.8
35 |
36 | /// Set to false to disable interactive dismissal
37 | public var allowInteractiveDismissal = true
38 |
39 | /// Default initializer, with most suitable values
40 | init() {}
41 |
42 | /// Common instance of the Configuration, applied to all cards
43 | /// unless a specific instance is supplied in `presentCard()` call.
44 | ///
45 | /// Set this value early in your app lifecycle to adjust the look of all cards in your app.
46 | public static var shared = CardConfiguration()
47 | }
48 |
49 | extension CardConfiguration {
50 | /// Very convenient initializer; supply only those params which are different from default ones.
51 | public init(verticalSpacing: CGFloat? = nil,
52 | verticalInset: CGFloat? = nil,
53 | horizontalInset: CGFloat? = nil,
54 | dismissAreaHeight: CGFloat? = nil,
55 | cornerRadius: CGFloat? = nil,
56 | backFadeAlpha: CGFloat? = nil,
57 | initialTransitionFrame: CGRect? = nil,
58 | allowInteractiveDismissal: Bool? = nil)
59 | {
60 | if let verticalSpacing = verticalSpacing {
61 | self.verticalSpacing = verticalSpacing
62 | }
63 |
64 | if let verticalInset = verticalInset {
65 | self.verticalInset = verticalInset
66 | }
67 |
68 | if let horizontalInset = horizontalInset {
69 | self.horizontalInset = horizontalInset
70 | }
71 |
72 | if let dismissAreaHeight = dismissAreaHeight {
73 | self.dismissAreaHeight = dismissAreaHeight
74 | }
75 |
76 | if let cornerRadius = cornerRadius {
77 | self.cornerRadius = cornerRadius
78 | }
79 |
80 | if let backFadeAlpha = backFadeAlpha {
81 | self.backFadeAlpha = backFadeAlpha
82 | }
83 |
84 | if let initialTransitionFrame = initialTransitionFrame {
85 | self.initialTransitionFrame = initialTransitionFrame
86 | }
87 |
88 | if let allowInteractiveDismissal = allowInteractiveDismissal {
89 | self.allowInteractiveDismissal = allowInteractiveDismissal
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/CardPresentationController/CardPresentationController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CardPresentationController.swift
3 | // CardPresentationController
4 | //
5 | // Copyright © 2018 Aleksandar Vacić, Radiant Tap
6 | // MIT License · http://choosealicense.com/licenses/mit/
7 | //
8 |
9 | import UIKit
10 |
11 | @available(iOS 10.0, *)
12 | public class CardPresentationController: UIPresentationController {
13 | public static var useSystemPresentationOniOS13 = true
14 |
15 | /// This is a link to the original UIVC on which presentCard() was called.
16 | /// (this is populated by CardTransitionManager)
17 | /// It's used in this file to clean-up CTM instance once dismissal happens.
18 | weak var sourceController: UIViewController?
19 |
20 | /// Required link to the actual animator,
21 | /// so that pan gesture handler can drive the animation
22 | weak var cardAnimator: CardAnimator!
23 |
24 | /// How much space is available at the top of the presentedView (card) to draw the dismiss glyph (handle).
25 | ///
26 | /// By default, it's assumed it's 16pt and handle will be 5pt tall, centered in the middle of that area
27 | /// (thus effectivelly its center is 8pt from the top of the card).
28 | var dismissAreaHeight: CGFloat = 16
29 |
30 |
31 |
32 | // Init
33 |
34 | public private(set) var configuration: CardConfiguration
35 |
36 | public override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
37 | fatalError("Use init(configuration:presentedViewController:presenting:)")
38 | }
39 |
40 | public init(configuration: CardConfiguration, presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
41 | self.configuration = configuration
42 | super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
43 | }
44 |
45 | // Private stuff
46 |
47 | private lazy var handleView: UIView = {
48 | let view = UIView()
49 | view.translatesAutoresizingMaskIntoConstraints = false
50 | view.backgroundColor = UIColor(white: 1, alpha: 0.5)
51 | view.layer.cornerRadius = 3
52 | view.alpha = 0
53 | return view
54 | }()
55 |
56 | private lazy var handleButton: UIButton = {
57 | let view = UIButton(frame: .zero)
58 | view.translatesAutoresizingMaskIntoConstraints = false
59 | view.backgroundColor = .clear
60 | return view
61 | }()
62 |
63 | private var handleTopConstraint: NSLayoutConstraint!
64 |
65 | private var usesDismissHandle: Bool {
66 | return !(presentedViewController is UINavigationController)
67 | }
68 |
69 | // MARK:- PresentationController
70 |
71 | public override func presentationTransitionWillBegin() {
72 | setupDismissHandle()
73 |
74 | super.presentationTransitionWillBegin()
75 | }
76 |
77 | public override func presentationTransitionDidEnd(_ completed: Bool) {
78 | super.presentationTransitionDidEnd(completed)
79 |
80 | if !completed {
81 | return
82 | }
83 | showDismissHandle()
84 | setupPanToDismiss()
85 | }
86 |
87 | public override func dismissalTransitionWillBegin() {
88 | fadeoutHandle()
89 | super.dismissalTransitionWillBegin()
90 | }
91 |
92 | public override func dismissalTransitionDidEnd(_ completed: Bool) {
93 | super.dismissalTransitionDidEnd(completed)
94 | if !completed {
95 | return
96 | }
97 | sourceController?.removeCardTransitionManager()
98 | }
99 |
100 | // MARK:- Public
101 |
102 | func fadeinHandle() {
103 | UIView.animate(withDuration: 0.15) {
104 | [weak self] in
105 | guard let self = self else { return }
106 | self.handleView.alpha = 1
107 | }
108 | }
109 |
110 | func fadeoutHandle() {
111 | UIView.animate(withDuration: 0.15) {
112 | [weak self] in
113 | guard let self = self else { return }
114 | self.handleView.alpha = 0
115 | }
116 | }
117 |
118 | // MARK:- Internal
119 |
120 | @objc private func handleTapped(_ sender: UIButton) {
121 | handleView.alpha = 0
122 | presentedViewController.dismiss(animated: true)
123 | }
124 |
125 | private func setupDismissHandle() {
126 | guard
127 | usesDismissHandle,
128 | let containerView = containerView
129 | else { return }
130 |
131 | containerView.addSubview(handleView)
132 | handleView.widthAnchor.constraint(equalToConstant: 50).isActive = true
133 | handleView.heightAnchor.constraint(equalToConstant: 5).isActive = true
134 | handleView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
135 |
136 | handleTopConstraint = handleView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 10)
137 | handleTopConstraint.isActive = true
138 |
139 | containerView.addSubview(handleButton)
140 | handleButton.widthAnchor.constraint(equalTo: handleView.widthAnchor).isActive = true
141 | handleButton.heightAnchor.constraint(equalTo: handleView.widthAnchor).isActive = true
142 | handleButton.centerYAnchor.constraint(equalTo: handleView.centerYAnchor).isActive = true
143 | handleButton.centerXAnchor.constraint(equalTo: handleView.centerXAnchor).isActive = true
144 |
145 | handleButton.addTarget(self, action: #selector(handleTapped), for: .touchUpInside)
146 | }
147 |
148 | private func showDismissHandle() {
149 | guard
150 | usesDismissHandle,
151 | let containerView = containerView
152 | else { return }
153 |
154 | containerView.bringSubviewToFront(handleView)
155 | containerView.bringSubviewToFront(handleButton)
156 |
157 | // Center dismiss handle in the (hopefully empty) area at the top of the presented card.
158 | // Place in the middle of that space.
159 | if let v = presentedViewController.view {
160 | let handleCenterY = v.frame.minY + dismissAreaHeight / 2
161 | handleTopConstraint.constant = handleCenterY - handleView.frame.height / 2
162 | }
163 |
164 | self.handleView.superview?.layoutIfNeeded()
165 | self.fadeinHandle()
166 | }
167 |
168 | // MARK:- Pan to dismiss
169 |
170 | private var panGR: UIPanGestureRecognizer?
171 | private var hasStartedPan = false
172 |
173 | private func setupPanToDismiss() {
174 | if !configuration.allowInteractiveDismissal { return }
175 |
176 | let gr = UIPanGestureRecognizer(target: self, action: #selector(panned))
177 | gr.delegate = self
178 |
179 | containerView?.addGestureRecognizer(gr)
180 | panGR = gr
181 | }
182 |
183 | @objc private func panned(_ gr: UIPanGestureRecognizer) {
184 | guard let containerView = containerView else { return }
185 |
186 | let verticalMove = gr.translation(in: containerView).y
187 | let pct = verticalMove / containerView.bounds.height
188 | let verticalVelocity = gr.velocity(in: containerView)
189 |
190 | switch gr.state {
191 | case .began:
192 | // do not start dismiss until pan goes down
193 | if verticalMove <= 0 { return }
194 | // setup flag that pan has finally started in the correct direction
195 | hasStartedPan = true
196 | // and reset the movement so far
197 | gr.setTranslation(.zero, in: containerView)
198 |
199 | // tell Animator that this will be interactive
200 | cardAnimator.isInteractive = true
201 |
202 | // and then initiate dismissal
203 | presentedViewController.dismiss(animated: true)
204 |
205 | case .changed:
206 | if !hasStartedPan { return }
207 | cardAnimator.updateInteractiveTransition(pct)
208 | // handleView.alpha = max(0, 1 - pct * 4) // handle disappears 4x faster
209 |
210 | case .ended, .cancelled:
211 | if !hasStartedPan { return }
212 | let vector = verticalVelocity.vector
213 |
214 | if verticalVelocity.y < 0 {
215 | cardAnimator.cancelInteractiveTransition(with: vector)
216 | handleView.alpha = 1
217 |
218 | } else if verticalVelocity.y > 0 {
219 | cardAnimator.finishInteractiveTransition(with: vector)
220 | handleView.alpha = 0
221 |
222 | } else {
223 | if pct < 0.5 {
224 | cardAnimator.cancelInteractiveTransition(with: vector)
225 | handleView.alpha = 1
226 | } else {
227 | cardAnimator.finishInteractiveTransition(with: vector)
228 | handleView.alpha = 0
229 | }
230 | }
231 | hasStartedPan = false
232 |
233 | default:
234 | break
235 | }
236 | }
237 | }
238 |
239 | extension CardPresentationController: UIGestureRecognizerDelegate {
240 | public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
241 | shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
242 | {
243 | if gestureRecognizer != panGR { return true }
244 |
245 | let otherView = otherGestureRecognizer.view
246 |
247 | // allow unconditional panning if that other view is not `UIScrollView`
248 | guard let scrollView = otherView as? UIScrollView else {
249 | return true
250 | }
251 |
252 | // if it is `UIScrollView`,
253 | // allow panning only if its content is at the very top
254 | if (scrollView.contentOffset.y + scrollView.contentInset.top) == 0 {
255 | return true
256 | }
257 |
258 | // otherwise, disallow pan to dismiss
259 | return false
260 | }
261 | }
262 |
263 | private extension CGPoint {
264 | var vector: CGVector {
265 | return CGVector(dx: x, dy: y)
266 | }
267 | }
268 |
269 |
--------------------------------------------------------------------------------
/CardPresentationController/CardTransitionManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CardTransitionManager.swift
3 | // CardPresentationController
4 | //
5 | // Copyright © 2018 Aleksandar Vacić, Radiant Tap
6 | // MIT License · http://choosealicense.com/licenses/mit/
7 | //
8 |
9 | import UIKit
10 |
11 | @available(iOS 10.0, *)
12 | final class CardTransitionManager: NSObject, UIViewControllerTransitioningDelegate {
13 | private(set) var configuration: CardConfiguration
14 |
15 | init(configuration: CardConfiguration) {
16 | self.configuration = configuration
17 | super.init()
18 | }
19 |
20 | private lazy var cardAnimator = CardAnimator(configuration: configuration)
21 |
22 | func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
23 | let pc = CardPresentationController(configuration: configuration, presentedViewController: presented, presenting: presenting)
24 | pc.sourceController = source
25 | pc.cardAnimator = cardAnimator
26 | pc.dismissAreaHeight = configuration.dismissAreaHeight
27 | return pc
28 | }
29 |
30 | func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
31 | cardAnimator.direction = .presentation
32 | return cardAnimator
33 | }
34 |
35 | func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
36 | cardAnimator.direction = .dismissal
37 | return cardAnimator
38 | }
39 |
40 | func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
41 | if cardAnimator.isInteractive { return cardAnimator }
42 | return nil
43 | }
44 |
45 | func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
46 | if cardAnimator.isInteractive { return cardAnimator }
47 | return nil
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/CardPresentationController/UIKit-CardPresentationExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIKit-CardPresentationExtensions.swift
3 | // CardPresentationController
4 | //
5 | // Created by Aleksandar Vacić on 12/8/18.
6 | // Copyright © 2018 Aleksandar Vacić. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIViewController {
12 | private struct AssociatedKeys {
13 | static var cardTransitionManager = "CardTransitionManager"
14 | }
15 | private(set) var cardTransitionManager: CardTransitionManager? {
16 | get {
17 | return objc_getAssociatedObject(self, &AssociatedKeys.cardTransitionManager) as? CardTransitionManager
18 | }
19 | set {
20 | objc_setAssociatedObject(self, &AssociatedKeys.cardTransitionManager, newValue, .OBJC_ASSOCIATION_RETAIN)
21 | }
22 | }
23 |
24 |
25 | @available(iOS 10.0, *)
26 | /// Presents given View Controller using custom Card-like modal transition. Think like Apple‘s Music or Wallet apps.
27 | ///
28 | /// Existing view will slide down and fade a bit and top corners would be rounded.
29 | /// Presented controller will slide up, over it, slightly below and also with rounder corners.
30 | ///
31 | /// - Parameters:
32 | /// - viewControllerToPresent: `UIViewController` instance to present.
33 | /// - configuration: an instance of `CardConfiguration`. By default it's `nil` which means that defaults will be used.
34 | /// - flag: Pass `true` to animate the presentation; otherwise, `pass` false.
35 | /// - completion: The closure to execute after the presentation finishes. This closure has no return value and takes no parameters. You may specify `nil` for this parameter or omit it entirely.
36 | public func presentCard(_ viewControllerToPresent: UIViewController,
37 | configuration: CardConfiguration? = nil,
38 | animated flag: Bool,
39 | completion: (() -> Void)? = nil)
40 | {
41 | if #available(iOS 13, *), CardPresentationController.useSystemPresentationOniOS13 {
42 | // if we are on iOS 13, fallback to default system look & behavior
43 | present(viewControllerToPresent, animated: flag, completion: completion)
44 | } else {
45 | // make it custom
46 | viewControllerToPresent.modalPresentationStyle = .custom
47 |
48 | // enforce statusBarStyle preferred by presented UIVC
49 | viewControllerToPresent.modalPresentationCapturesStatusBarAppearance = true
50 |
51 | // card config, using supplied or default
52 | let config = configuration ?? CardConfiguration.shared
53 |
54 | // then build transition manager
55 | let tm = CardTransitionManager(configuration: config)
56 | self.cardTransitionManager = tm
57 | viewControllerToPresent.transitioningDelegate = tm
58 |
59 | present(viewControllerToPresent,
60 | animated: flag,
61 | completion: completion)
62 | }
63 | }
64 |
65 | func removeCardTransitionManager() {
66 | cardTransitionManager = nil
67 | }
68 | }
69 |
70 | extension UIView {
71 | func cardMaskTopCorners(using cornerRadius: CGFloat = 24) {
72 | clipsToBounds = true
73 |
74 | // from iOS 11, it's possible to choose which corners are rounded
75 | if #available(iOS 11.0, *) {
76 | layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
77 | }
78 |
79 | layer.cornerRadius = cornerRadius
80 | }
81 |
82 | func cardUnmask() {
83 | layer.cornerRadius = 0
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | D021C72721C3FDB0004CFA5A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021C72621C3FDB0004CFA5A /* AppDelegate.swift */; };
11 | D021C72921C3FDB0004CFA5A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021C72821C3FDB0004CFA5A /* ViewController.swift */; };
12 | D021C72C21C3FDB0004CFA5A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D021C72A21C3FDB0004CFA5A /* Main.storyboard */; };
13 | D021C72E21C3FDB2004CFA5A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D021C72D21C3FDB2004CFA5A /* Assets.xcassets */; };
14 | D021C73121C3FDB2004CFA5A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D021C72F21C3FDB2004CFA5A /* LaunchScreen.storyboard */; };
15 | D021C73D21C3FDEB004CFA5A /* UIKit-CardPresentationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021C73921C3FDEB004CFA5A /* UIKit-CardPresentationExtensions.swift */; };
16 | D021C73E21C3FDEB004CFA5A /* CardPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021C73A21C3FDEB004CFA5A /* CardPresentationController.swift */; };
17 | D021C73F21C3FDEB004CFA5A /* CardAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021C73B21C3FDEB004CFA5A /* CardAnimator.swift */; };
18 | D021C74021C3FDEB004CFA5A /* CardTransitionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021C73C21C3FDEB004CFA5A /* CardTransitionManager.swift */; };
19 | D021C74921C3FE0B004CFA5A /* StoryboardLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021C74321C3FE0B004CFA5A /* StoryboardLoadable.swift */; };
20 | D021C74A21C3FE0B004CFA5A /* Embeddable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021C74421C3FE0B004CFA5A /* Embeddable.swift */; };
21 | D021C74B21C3FE0B004CFA5A /* DequeableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021C74621C3FE0B004CFA5A /* DequeableView.swift */; };
22 | D021C74C21C3FE0B004CFA5A /* ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021C74721C3FE0B004CFA5A /* ReusableView.swift */; };
23 | D021C74D21C3FE0B004CFA5A /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021C74821C3FE0B004CFA5A /* NibLoadable.swift */; };
24 | D021C75021C3FE68004CFA5A /* ContentController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D021C74E21C3FE68004CFA5A /* ContentController.storyboard */; };
25 | D021C75121C3FE68004CFA5A /* ContentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021C74F21C3FE68004CFA5A /* ContentController.swift */; };
26 | D0AB35F221C5AB920038C6C6 /* CardConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AB35F121C5AB920038C6C6 /* CardConfiguration.swift */; };
27 | /* End PBXBuildFile section */
28 |
29 | /* Begin PBXFileReference section */
30 | D021C72321C3FDB0004CFA5A /* EmbeddedNCExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EmbeddedNCExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
31 | D021C72621C3FDB0004CFA5A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
32 | D021C72821C3FDB0004CFA5A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
33 | D021C72B21C3FDB0004CFA5A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
34 | D021C72D21C3FDB2004CFA5A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
35 | D021C73021C3FDB2004CFA5A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
36 | D021C73221C3FDB2004CFA5A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
37 | D021C73921C3FDEB004CFA5A /* UIKit-CardPresentationExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIKit-CardPresentationExtensions.swift"; sourceTree = ""; };
38 | D021C73A21C3FDEB004CFA5A /* CardPresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardPresentationController.swift; sourceTree = ""; };
39 | D021C73B21C3FDEB004CFA5A /* CardAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardAnimator.swift; sourceTree = ""; };
40 | D021C73C21C3FDEB004CFA5A /* CardTransitionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardTransitionManager.swift; sourceTree = ""; };
41 | D021C74321C3FE0B004CFA5A /* StoryboardLoadable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardLoadable.swift; sourceTree = ""; };
42 | D021C74421C3FE0B004CFA5A /* Embeddable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Embeddable.swift; sourceTree = ""; };
43 | D021C74621C3FE0B004CFA5A /* DequeableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DequeableView.swift; sourceTree = ""; };
44 | D021C74721C3FE0B004CFA5A /* ReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReusableView.swift; sourceTree = ""; };
45 | D021C74821C3FE0B004CFA5A /* NibLoadable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; };
46 | D021C74E21C3FE68004CFA5A /* ContentController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = ContentController.storyboard; sourceTree = ""; };
47 | D021C74F21C3FE68004CFA5A /* ContentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentController.swift; sourceTree = ""; };
48 | D0AB35F121C5AB920038C6C6 /* CardConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardConfiguration.swift; sourceTree = ""; };
49 | /* End PBXFileReference section */
50 |
51 | /* Begin PBXFrameworksBuildPhase section */
52 | D021C72021C3FDB0004CFA5A /* Frameworks */ = {
53 | isa = PBXFrameworksBuildPhase;
54 | buildActionMask = 2147483647;
55 | files = (
56 | );
57 | runOnlyForDeploymentPostprocessing = 0;
58 | };
59 | /* End PBXFrameworksBuildPhase section */
60 |
61 | /* Begin PBXGroup section */
62 | D021C71A21C3FDB0004CFA5A = {
63 | isa = PBXGroup;
64 | children = (
65 | D021C73821C3FDEB004CFA5A /* CardPresentationController */,
66 | D021C72521C3FDB0004CFA5A /* EmbeddedNCExample */,
67 | D021C72421C3FDB0004CFA5A /* Products */,
68 | D021C74121C3FE0B004CFA5A /* Vendor */,
69 | );
70 | sourceTree = "";
71 | };
72 | D021C72421C3FDB0004CFA5A /* Products */ = {
73 | isa = PBXGroup;
74 | children = (
75 | D021C72321C3FDB0004CFA5A /* EmbeddedNCExample.app */,
76 | );
77 | name = Products;
78 | sourceTree = "";
79 | };
80 | D021C72521C3FDB0004CFA5A /* EmbeddedNCExample */ = {
81 | isa = PBXGroup;
82 | children = (
83 | D021C72621C3FDB0004CFA5A /* AppDelegate.swift */,
84 | D021C72821C3FDB0004CFA5A /* ViewController.swift */,
85 | D021C72A21C3FDB0004CFA5A /* Main.storyboard */,
86 | D021C72D21C3FDB2004CFA5A /* Assets.xcassets */,
87 | D021C72F21C3FDB2004CFA5A /* LaunchScreen.storyboard */,
88 | D021C73221C3FDB2004CFA5A /* Info.plist */,
89 | D021C74F21C3FE68004CFA5A /* ContentController.swift */,
90 | D021C74E21C3FE68004CFA5A /* ContentController.storyboard */,
91 | );
92 | path = EmbeddedNCExample;
93 | sourceTree = "";
94 | };
95 | D021C73821C3FDEB004CFA5A /* CardPresentationController */ = {
96 | isa = PBXGroup;
97 | children = (
98 | D0AB35F121C5AB920038C6C6 /* CardConfiguration.swift */,
99 | D021C73921C3FDEB004CFA5A /* UIKit-CardPresentationExtensions.swift */,
100 | D021C73A21C3FDEB004CFA5A /* CardPresentationController.swift */,
101 | D021C73B21C3FDEB004CFA5A /* CardAnimator.swift */,
102 | D021C73C21C3FDEB004CFA5A /* CardTransitionManager.swift */,
103 | );
104 | name = CardPresentationController;
105 | path = ../CardPresentationController;
106 | sourceTree = "";
107 | };
108 | D021C74121C3FE0B004CFA5A /* Vendor */ = {
109 | isa = PBXGroup;
110 | children = (
111 | D021C74221C3FE0B004CFA5A /* Controllers */,
112 | D021C74521C3FE0B004CFA5A /* Views */,
113 | );
114 | name = Vendor;
115 | path = ../Vendor;
116 | sourceTree = "";
117 | };
118 | D021C74221C3FE0B004CFA5A /* Controllers */ = {
119 | isa = PBXGroup;
120 | children = (
121 | D021C74321C3FE0B004CFA5A /* StoryboardLoadable.swift */,
122 | D021C74421C3FE0B004CFA5A /* Embeddable.swift */,
123 | );
124 | path = Controllers;
125 | sourceTree = "";
126 | };
127 | D021C74521C3FE0B004CFA5A /* Views */ = {
128 | isa = PBXGroup;
129 | children = (
130 | D021C74621C3FE0B004CFA5A /* DequeableView.swift */,
131 | D021C74721C3FE0B004CFA5A /* ReusableView.swift */,
132 | D021C74821C3FE0B004CFA5A /* NibLoadable.swift */,
133 | );
134 | path = Views;
135 | sourceTree = "";
136 | };
137 | /* End PBXGroup section */
138 |
139 | /* Begin PBXNativeTarget section */
140 | D021C72221C3FDB0004CFA5A /* EmbeddedNCExample */ = {
141 | isa = PBXNativeTarget;
142 | buildConfigurationList = D021C73521C3FDB2004CFA5A /* Build configuration list for PBXNativeTarget "EmbeddedNCExample" */;
143 | buildPhases = (
144 | D021C71F21C3FDB0004CFA5A /* Sources */,
145 | D021C72021C3FDB0004CFA5A /* Frameworks */,
146 | D021C72121C3FDB0004CFA5A /* Resources */,
147 | );
148 | buildRules = (
149 | );
150 | dependencies = (
151 | );
152 | name = EmbeddedNCExample;
153 | productName = EmbeddedNCExample;
154 | productReference = D021C72321C3FDB0004CFA5A /* EmbeddedNCExample.app */;
155 | productType = "com.apple.product-type.application";
156 | };
157 | /* End PBXNativeTarget section */
158 |
159 | /* Begin PBXProject section */
160 | D021C71B21C3FDB0004CFA5A /* Project object */ = {
161 | isa = PBXProject;
162 | attributes = {
163 | LastSwiftUpdateCheck = 1010;
164 | LastUpgradeCheck = 1010;
165 | ORGANIZATIONNAME = "Radiant Tap";
166 | TargetAttributes = {
167 | D021C72221C3FDB0004CFA5A = {
168 | CreatedOnToolsVersion = 10.1;
169 | LastSwiftMigration = 1020;
170 | };
171 | };
172 | };
173 | buildConfigurationList = D021C71E21C3FDB0004CFA5A /* Build configuration list for PBXProject "EmbeddedNCExample" */;
174 | compatibilityVersion = "Xcode 9.3";
175 | developmentRegion = en;
176 | hasScannedForEncodings = 0;
177 | knownRegions = (
178 | en,
179 | Base,
180 | );
181 | mainGroup = D021C71A21C3FDB0004CFA5A;
182 | productRefGroup = D021C72421C3FDB0004CFA5A /* Products */;
183 | projectDirPath = "";
184 | projectRoot = "";
185 | targets = (
186 | D021C72221C3FDB0004CFA5A /* EmbeddedNCExample */,
187 | );
188 | };
189 | /* End PBXProject section */
190 |
191 | /* Begin PBXResourcesBuildPhase section */
192 | D021C72121C3FDB0004CFA5A /* Resources */ = {
193 | isa = PBXResourcesBuildPhase;
194 | buildActionMask = 2147483647;
195 | files = (
196 | D021C73121C3FDB2004CFA5A /* LaunchScreen.storyboard in Resources */,
197 | D021C72E21C3FDB2004CFA5A /* Assets.xcassets in Resources */,
198 | D021C75021C3FE68004CFA5A /* ContentController.storyboard in Resources */,
199 | D021C72C21C3FDB0004CFA5A /* Main.storyboard in Resources */,
200 | );
201 | runOnlyForDeploymentPostprocessing = 0;
202 | };
203 | /* End PBXResourcesBuildPhase section */
204 |
205 | /* Begin PBXSourcesBuildPhase section */
206 | D021C71F21C3FDB0004CFA5A /* Sources */ = {
207 | isa = PBXSourcesBuildPhase;
208 | buildActionMask = 2147483647;
209 | files = (
210 | D021C74A21C3FE0B004CFA5A /* Embeddable.swift in Sources */,
211 | D021C75121C3FE68004CFA5A /* ContentController.swift in Sources */,
212 | D021C74C21C3FE0B004CFA5A /* ReusableView.swift in Sources */,
213 | D021C74021C3FDEB004CFA5A /* CardTransitionManager.swift in Sources */,
214 | D021C72921C3FDB0004CFA5A /* ViewController.swift in Sources */,
215 | D021C74921C3FE0B004CFA5A /* StoryboardLoadable.swift in Sources */,
216 | D021C74D21C3FE0B004CFA5A /* NibLoadable.swift in Sources */,
217 | D021C74B21C3FE0B004CFA5A /* DequeableView.swift in Sources */,
218 | D021C73D21C3FDEB004CFA5A /* UIKit-CardPresentationExtensions.swift in Sources */,
219 | D021C73F21C3FDEB004CFA5A /* CardAnimator.swift in Sources */,
220 | D0AB35F221C5AB920038C6C6 /* CardConfiguration.swift in Sources */,
221 | D021C73E21C3FDEB004CFA5A /* CardPresentationController.swift in Sources */,
222 | D021C72721C3FDB0004CFA5A /* AppDelegate.swift in Sources */,
223 | );
224 | runOnlyForDeploymentPostprocessing = 0;
225 | };
226 | /* End PBXSourcesBuildPhase section */
227 |
228 | /* Begin PBXVariantGroup section */
229 | D021C72A21C3FDB0004CFA5A /* Main.storyboard */ = {
230 | isa = PBXVariantGroup;
231 | children = (
232 | D021C72B21C3FDB0004CFA5A /* Base */,
233 | );
234 | name = Main.storyboard;
235 | sourceTree = "";
236 | };
237 | D021C72F21C3FDB2004CFA5A /* LaunchScreen.storyboard */ = {
238 | isa = PBXVariantGroup;
239 | children = (
240 | D021C73021C3FDB2004CFA5A /* Base */,
241 | );
242 | name = LaunchScreen.storyboard;
243 | sourceTree = "";
244 | };
245 | /* End PBXVariantGroup section */
246 |
247 | /* Begin XCBuildConfiguration section */
248 | D021C73321C3FDB2004CFA5A /* Debug */ = {
249 | isa = XCBuildConfiguration;
250 | buildSettings = {
251 | ALWAYS_SEARCH_USER_PATHS = NO;
252 | CLANG_ANALYZER_NONNULL = YES;
253 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
254 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
255 | CLANG_CXX_LIBRARY = "libc++";
256 | CLANG_ENABLE_MODULES = YES;
257 | CLANG_ENABLE_OBJC_ARC = YES;
258 | CLANG_ENABLE_OBJC_WEAK = YES;
259 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
260 | CLANG_WARN_BOOL_CONVERSION = YES;
261 | CLANG_WARN_COMMA = YES;
262 | CLANG_WARN_CONSTANT_CONVERSION = YES;
263 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
264 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
265 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
266 | CLANG_WARN_EMPTY_BODY = YES;
267 | CLANG_WARN_ENUM_CONVERSION = YES;
268 | CLANG_WARN_INFINITE_RECURSION = YES;
269 | CLANG_WARN_INT_CONVERSION = YES;
270 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
271 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
272 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
273 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
274 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
275 | CLANG_WARN_STRICT_PROTOTYPES = YES;
276 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
277 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
278 | CLANG_WARN_UNREACHABLE_CODE = YES;
279 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
280 | CODE_SIGN_IDENTITY = "iPhone Developer";
281 | COPY_PHASE_STRIP = NO;
282 | DEBUG_INFORMATION_FORMAT = dwarf;
283 | ENABLE_STRICT_OBJC_MSGSEND = YES;
284 | ENABLE_TESTABILITY = YES;
285 | GCC_C_LANGUAGE_STANDARD = gnu11;
286 | GCC_DYNAMIC_NO_PIC = NO;
287 | GCC_NO_COMMON_BLOCKS = YES;
288 | GCC_OPTIMIZATION_LEVEL = 0;
289 | GCC_PREPROCESSOR_DEFINITIONS = (
290 | "DEBUG=1",
291 | "$(inherited)",
292 | );
293 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
294 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
295 | GCC_WARN_UNDECLARED_SELECTOR = YES;
296 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
297 | GCC_WARN_UNUSED_FUNCTION = YES;
298 | GCC_WARN_UNUSED_VARIABLE = YES;
299 | IPHONEOS_DEPLOYMENT_TARGET = 12.1;
300 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
301 | MTL_FAST_MATH = YES;
302 | ONLY_ACTIVE_ARCH = YES;
303 | SDKROOT = iphoneos;
304 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
305 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
306 | SWIFT_VERSION = 5.0;
307 | };
308 | name = Debug;
309 | };
310 | D021C73421C3FDB2004CFA5A /* Release */ = {
311 | isa = XCBuildConfiguration;
312 | buildSettings = {
313 | ALWAYS_SEARCH_USER_PATHS = NO;
314 | CLANG_ANALYZER_NONNULL = YES;
315 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
316 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
317 | CLANG_CXX_LIBRARY = "libc++";
318 | CLANG_ENABLE_MODULES = YES;
319 | CLANG_ENABLE_OBJC_ARC = YES;
320 | CLANG_ENABLE_OBJC_WEAK = YES;
321 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
322 | CLANG_WARN_BOOL_CONVERSION = YES;
323 | CLANG_WARN_COMMA = YES;
324 | CLANG_WARN_CONSTANT_CONVERSION = YES;
325 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
326 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
327 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
328 | CLANG_WARN_EMPTY_BODY = YES;
329 | CLANG_WARN_ENUM_CONVERSION = YES;
330 | CLANG_WARN_INFINITE_RECURSION = YES;
331 | CLANG_WARN_INT_CONVERSION = YES;
332 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
333 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
334 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
335 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
336 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
337 | CLANG_WARN_STRICT_PROTOTYPES = YES;
338 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
339 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
340 | CLANG_WARN_UNREACHABLE_CODE = YES;
341 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
342 | CODE_SIGN_IDENTITY = "iPhone Developer";
343 | COPY_PHASE_STRIP = NO;
344 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
345 | ENABLE_NS_ASSERTIONS = NO;
346 | ENABLE_STRICT_OBJC_MSGSEND = YES;
347 | GCC_C_LANGUAGE_STANDARD = gnu11;
348 | GCC_NO_COMMON_BLOCKS = YES;
349 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
350 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
351 | GCC_WARN_UNDECLARED_SELECTOR = YES;
352 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
353 | GCC_WARN_UNUSED_FUNCTION = YES;
354 | GCC_WARN_UNUSED_VARIABLE = YES;
355 | IPHONEOS_DEPLOYMENT_TARGET = 12.1;
356 | MTL_ENABLE_DEBUG_INFO = NO;
357 | MTL_FAST_MATH = YES;
358 | SDKROOT = iphoneos;
359 | SWIFT_COMPILATION_MODE = wholemodule;
360 | SWIFT_OPTIMIZATION_LEVEL = "-O";
361 | SWIFT_VERSION = 5.0;
362 | VALIDATE_PRODUCT = YES;
363 | };
364 | name = Release;
365 | };
366 | D021C73621C3FDB2004CFA5A /* Debug */ = {
367 | isa = XCBuildConfiguration;
368 | buildSettings = {
369 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
370 | CODE_SIGN_STYLE = Automatic;
371 | INFOPLIST_FILE = EmbeddedNCExample/Info.plist;
372 | LD_RUNPATH_SEARCH_PATHS = (
373 | "$(inherited)",
374 | "@executable_path/Frameworks",
375 | );
376 | PRODUCT_BUNDLE_IDENTIFIER = com.radianttap.EmbeddedNCExample;
377 | PRODUCT_NAME = "$(TARGET_NAME)";
378 | TARGETED_DEVICE_FAMILY = "1,2";
379 | };
380 | name = Debug;
381 | };
382 | D021C73721C3FDB2004CFA5A /* Release */ = {
383 | isa = XCBuildConfiguration;
384 | buildSettings = {
385 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
386 | CODE_SIGN_STYLE = Automatic;
387 | INFOPLIST_FILE = EmbeddedNCExample/Info.plist;
388 | LD_RUNPATH_SEARCH_PATHS = (
389 | "$(inherited)",
390 | "@executable_path/Frameworks",
391 | );
392 | PRODUCT_BUNDLE_IDENTIFIER = com.radianttap.EmbeddedNCExample;
393 | PRODUCT_NAME = "$(TARGET_NAME)";
394 | TARGETED_DEVICE_FAMILY = "1,2";
395 | };
396 | name = Release;
397 | };
398 | /* End XCBuildConfiguration section */
399 |
400 | /* Begin XCConfigurationList section */
401 | D021C71E21C3FDB0004CFA5A /* Build configuration list for PBXProject "EmbeddedNCExample" */ = {
402 | isa = XCConfigurationList;
403 | buildConfigurations = (
404 | D021C73321C3FDB2004CFA5A /* Debug */,
405 | D021C73421C3FDB2004CFA5A /* Release */,
406 | );
407 | defaultConfigurationIsVisible = 0;
408 | defaultConfigurationName = Release;
409 | };
410 | D021C73521C3FDB2004CFA5A /* Build configuration list for PBXNativeTarget "EmbeddedNCExample" */ = {
411 | isa = XCConfigurationList;
412 | buildConfigurations = (
413 | D021C73621C3FDB2004CFA5A /* Debug */,
414 | D021C73721C3FDB2004CFA5A /* Release */,
415 | );
416 | defaultConfigurationIsVisible = 0;
417 | defaultConfigurationName = Release;
418 | };
419 | /* End XCConfigurationList section */
420 | };
421 | rootObject = D021C71B21C3FDB0004CFA5A /* Project object */;
422 | }
423 |
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // EmbeddedNCExample
4 | //
5 | // Created by Aleksandar Vacić on 12/14/18.
6 | // Copyright © 2018 Radiant Tap. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | return true
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "ItunesArtwork@2x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/WonderWomanPoster.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "WonderWomanPoster.jpg",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/WonderWomanPoster.imageset/WonderWomanPoster.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/EmbeddedNCExample/EmbeddedNCExample/Assets.xcassets/WonderWomanPoster.imageset/WonderWomanPoster.jpg
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/ContentController.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
55 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/ContentController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PlainPopupController.swift
3 | // EmbeddedNCExample
4 | //
5 | // Created by Aleksandar Vacić on 10/28/18.
6 | // Copyright © 2018 Aleksandar Vacić. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class ContentController: UIViewController, StoryboardLoadable {
12 | // Configuration
13 |
14 | enum Context {
15 | case popup
16 | case embed
17 | }
18 | var context: Context = .popup {
19 | didSet {
20 | if !isViewLoaded { return }
21 | processContext()
22 | }
23 | }
24 |
25 |
26 | // UI
27 |
28 | @IBOutlet private weak var messageLabel: UILabel!
29 | @IBOutlet private weak var popupButton: UIButton!
30 |
31 | override var preferredStatusBarStyle: UIStatusBarStyle {
32 | return .lightContent
33 | }
34 |
35 |
36 | // Data source
37 |
38 | private var message: String?
39 |
40 |
41 | // View lifecycle
42 |
43 | override func viewDidLoad() {
44 | super.viewDidLoad()
45 |
46 | messageLabel.text = nil
47 | processContext()
48 | populateMessage()
49 | }
50 |
51 | func message(_ s: String) {
52 | self.message = s
53 | if !isViewLoaded { return }
54 | populateMessage()
55 | }
56 | }
57 |
58 | private extension ContentController {
59 | func processContext() {
60 | switch context {
61 | case .embed:
62 | popupButton.isHidden = true
63 | case .popup:
64 | popupButton.isHidden = false
65 | }
66 | }
67 |
68 | func populateMessage() {
69 | messageLabel.text = message
70 | }
71 |
72 | @IBAction func popup(_ sender: UIButton) {
73 | let vc = ContentController.instantiate()
74 |
75 | presentCard(vc,
76 | animated: true)
77 | }
78 | }
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | extension ContentController {
139 | override func viewWillAppear(_ animated: Bool) {
140 | super.viewWillAppear(animated)
141 |
142 | if countOfPresentingParents == 4 {
143 | popupButton.setTitle("Ehm, it's enough, don't you think?", for: .normal)
144 | popupButton.isUserInteractionEnabled = false
145 | popupButton.alpha = 0.6
146 | }
147 | }
148 | }
149 |
150 | fileprivate extension UIViewController {
151 | var countOfPresentingParents: Int {
152 | var c = 0
153 | if let vc = self.presentingViewController {
154 | c = 1 + vc.countOfPresentingParents
155 | }
156 | return c
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/EmbeddedNCExample/EmbeddedNCExample/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // EmbeddedNCExample
4 | //
5 | // Created by Aleksandar Vacić on 12/14/18.
6 | // Copyright © 2018 Radiant Tap. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class ViewController: UIViewController {
12 |
13 | private var controller: UIViewController?
14 |
15 | override func viewDidLoad() {
16 | super.viewDidLoad()
17 |
18 | let vc = ContentController.instantiate()
19 |
20 | let nc = UINavigationController(rootViewController: vc)
21 | nc.navigationBar.barTintColor = .red
22 | // nc.setNavigationBarHidden(true, animated: false)
23 | embed(controller: nc, into: view)
24 | controller = nc
25 |
26 | vc.message("The issue with this kind of setup is the magic that UIKit applies to safeAreaInsets and extending the navigationBar under status bar. So when you 'extract' views to make them part of transition, all that magic is gone and (sub)views reflow.\n\nNo idea is this fixable on general terms.")
27 | }
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/Example/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // CardPresentationExample
4 | //
5 | // Created by Aleksandar Vacić on 9/15/18.
6 | // Copyright © 2018 Aleksandar Vacić. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CardPresentationController
11 |
12 | @UIApplicationMain
13 | final class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 | var window: UIWindow?
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | applyTheme()
19 | return true
20 | }
21 |
22 | }
23 |
24 | private extension AppDelegate {
25 | func applyTheme() {
26 | // Example of globally changing every card in the app
27 | //
28 | // (uncomment to see it in action)
29 | // CardConfiguration.shared = CardConfiguration(verticalSpacing: 8,
30 | // horizontalInset: 8,
31 | // cornerRadius: 0,
32 | // backFadeAlpha: 0.5)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "ItunesArtwork@2x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/Example/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/Assets.xcassets/WonderWomanPoster.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "WonderWomanPoster.jpg",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/Assets.xcassets/WonderWomanPoster.imageset/WonderWomanPoster.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/Example/Assets.xcassets/WonderWomanPoster.imageset/WonderWomanPoster.jpg
--------------------------------------------------------------------------------
/Example/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Example/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
81 |
98 |
115 |
132 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
--------------------------------------------------------------------------------
/Example/ContentController.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
55 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/Example/ContentController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PlainPopupController.swift
3 | // CardPresentationExample
4 | //
5 | // Created by Aleksandar Vacić on 10/28/18.
6 | // Copyright © 2018 Aleksandar Vacić. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CardPresentationController
11 |
12 | final class ContentController: UIViewController, StoryboardLoadable {
13 | // Configuration
14 |
15 | enum Context {
16 | case popup
17 | case embed
18 | }
19 | var context: Context = .popup {
20 | didSet {
21 | if !isViewLoaded { return }
22 | processContext()
23 | }
24 | }
25 |
26 |
27 | // UI
28 |
29 | @IBOutlet private weak var messageLabel: UILabel!
30 | @IBOutlet private weak var popupButton: UIButton!
31 |
32 | override var preferredStatusBarStyle: UIStatusBarStyle {
33 | return .lightContent
34 | }
35 |
36 |
37 | // Data source
38 |
39 | private var message: String?
40 |
41 |
42 | // View lifecycle
43 |
44 | override func viewDidLoad() {
45 | super.viewDidLoad()
46 |
47 | messageLabel.text = nil
48 | processContext()
49 | populateMessage()
50 | }
51 |
52 | func message(_ s: String) {
53 | self.message = s
54 | if !isViewLoaded { return }
55 | populateMessage()
56 | }
57 | }
58 |
59 | private extension ContentController {
60 | func processContext() {
61 | switch context {
62 | case .embed:
63 | popupButton.isHidden = true
64 | case .popup:
65 | popupButton.isHidden = false
66 | }
67 | }
68 |
69 | func populateMessage() {
70 | messageLabel.text = message
71 | }
72 |
73 | @IBAction func popup(_ sender: UIButton) {
74 | let vc = ContentController.instantiate()
75 |
76 | presentCard(vc,
77 | animated: true)
78 | }
79 | }
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 | extension ContentController {
140 | override func viewWillAppear(_ animated: Bool) {
141 | super.viewWillAppear(animated)
142 |
143 | if countOfPresentingParents == 4 {
144 | popupButton.setTitle("Ehm, it's enough, don't you think?", for: .normal)
145 | popupButton.isUserInteractionEnabled = false
146 | popupButton.alpha = 0.6
147 | }
148 | }
149 | }
150 |
151 | fileprivate extension UIViewController {
152 | var countOfPresentingParents: Int {
153 | var c = 0
154 | if let vc = self.presentingViewController {
155 | c = 1 + vc.countOfPresentingParents
156 | }
157 | return c
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/Example/CustomContainerController.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/Example/CustomContainerController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomContainerController.swift
3 | // CardModal
4 | //
5 | // Created by Aleksandar Vacić on 3/3/19.
6 | // Copyright © 2019 Aleksandar Vacić. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class CustomContainerController: UIViewController, StoryboardLoadable {
12 | // UI
13 |
14 | @IBOutlet private var containingView: UIView!
15 |
16 |
17 | // Public
18 |
19 | private(set) var embeddedController: UIViewController?
20 |
21 | private(set) var edgeInsets: UIEdgeInsets = UIEdgeInsets(top: 44, left: 16, bottom: 16, right: 16)
22 |
23 | func display(vc: UIViewController) {
24 | embeddedController = vc
25 | if !isViewLoaded { return }
26 | embedIfNeeded()
27 | }
28 |
29 | func clear(vc: UIViewController?) {
30 | unembed(controller: vc)
31 | embeddedController = nil
32 | }
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | // UIKit
41 |
42 | override func viewDidLoad() {
43 | super.viewDidLoad()
44 |
45 | embedIfNeeded()
46 | }
47 |
48 | private func embedIfNeeded() {
49 | guard let vc = embeddedController else { return }
50 | embed(controller: vc, into: containingView)
51 | }
52 |
53 | override var preferredStatusBarStyle: UIStatusBarStyle {
54 | return .lightContent
55 | }
56 |
57 | override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
58 | super.dismiss(animated: flag) {
59 | [weak self] in
60 |
61 | self?.clear(vc: self?.embeddedController)
62 |
63 | completion?()
64 | }
65 | }
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/Example/GridCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GridCell.swift
3 | // CardPresentationExample
4 | //
5 | // Created by Aleksandar Vacić on 12/16/18.
6 | // Copyright © 2018 Aleksandar Vacić. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class GridCell: UICollectionViewCell, NibReusableView {
12 | @IBOutlet private weak var gradient: GradientView!
13 |
14 | private var startColor: UIColor = .darkGray
15 | private var endColor: UIColor = .orange
16 | }
17 |
18 | extension GridCell {
19 | override func awakeFromNib() {
20 | super.awakeFromNib()
21 | cleanup()
22 |
23 | applyTheme()
24 | }
25 |
26 | override func prepareForReuse() {
27 | super.prepareForReuse()
28 | cleanup()
29 | }
30 |
31 | func populate(with color: ColorPair) {
32 | startColor = color.start
33 | endColor = color.end
34 |
35 | applyTheme()
36 | }
37 | }
38 |
39 | private extension GridCell {
40 | func cleanup() {
41 | }
42 |
43 | func applyTheme() {
44 | gradient.direction = .vertical
45 | gradient.clipsToBounds = true
46 | gradient.layer.cornerRadius = 6
47 |
48 | gradient.colors = [
49 | startColor,
50 | endColor
51 | ]
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Example/GridCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/Example/GridController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GridController.swift
3 | // CardPresentationExample
4 | //
5 | // Created by Aleksandar Vacić on 12/16/18.
6 | // Copyright © 2018 Aleksandar Vacić. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | typealias ColorPair = (start: UIColor, end: UIColor)
12 |
13 | final class GridController: UICollectionViewController {
14 | init() {
15 | let layout = GridLayout()
16 | layout.minimumLineSpacing = 16
17 | layout.minimumInteritemSpacing = 16
18 | layout.sectionInset = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
19 |
20 | super.init(collectionViewLayout: layout)
21 | }
22 |
23 | required init?(coder aDecoder: NSCoder) {
24 | fatalError("init(coder:) has not been implemented")
25 | }
26 |
27 | private var gradients: [ColorPair] = []
28 | }
29 |
30 | extension GridController {
31 | override func viewDidLoad() {
32 | super.viewDidLoad()
33 | view.backgroundColor = .darkGray
34 | collectionView.backgroundColor = view.backgroundColor
35 |
36 | collectionView.register(GridCell.self)
37 | generateGradients()
38 | }
39 | }
40 |
41 | private extension GridController {
42 | func generateGradients() {
43 | for _ in 0 ..< 50 {
44 | let cp = ColorPair(.random, .random)
45 | gradients.append(cp)
46 | }
47 | }
48 | }
49 |
50 | extension GridController {
51 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
52 | return gradients.count
53 | }
54 |
55 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
56 | let cell: GridCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
57 | let colorPair = gradients[indexPath.item]
58 | cell.populate(with: colorPair)
59 | return cell
60 | }
61 | }
62 |
63 | private extension UIColor {
64 | static var random: UIColor {
65 | let random = { CGFloat(arc4random_uniform(255)) / 255.0 }
66 | return UIColor(red: random(), green: random(), blue: random(), alpha: 1)
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Example/GridLayout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GridLayout.swift
3 | // CardPresentationExample
4 | //
5 | // Created by Aleksandar Vacić on 12/16/18.
6 | // Copyright © 2018 Aleksandar Vacić. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class GridLayout: BaseGridLayout {
12 | var numberOfColumns: Int?
13 |
14 | override func prepare() {
15 | defer {
16 | super.prepare()
17 | }
18 | guard var availableWidth = collectionView?.bounds.width else { return }
19 |
20 | let aspectRatio = itemSize.width / itemSize.height
21 | let columns: CGFloat
22 | if let numberOfColumns = numberOfColumns {
23 | columns = CGFloat(numberOfColumns)
24 | } else {
25 | // customize for CV bounds.width
26 | columns = floor(availableWidth / itemSize.width)
27 | }
28 |
29 | availableWidth -= (columns - 1) * minimumInteritemSpacing
30 | availableWidth -= (sectionInset.left + sectionInset.right)
31 |
32 | itemSize.width = availableWidth / columns
33 | itemSize.height = itemSize.width * 1/aspectRatio
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Card Modal
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.8
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Example/PopupNavigationController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PopupNavigationController.swift
3 | // CardPresentationExample
4 | //
5 | // Created by Aleksandar Vacić on 12/9/18.
6 | // Copyright © 2018 Aleksandar Vacić. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class PopupNavigationController: UINavigationController {
12 | override var preferredStatusBarStyle: UIStatusBarStyle {
13 | return .lightContent
14 | }
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/Example/SecondController.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
29 |
37 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/Example/SecondController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SecondController.swift
3 | // CardPresentationExample
4 | //
5 | // Created by Aleksandar Vacić on 12/14/18.
6 | // Copyright © 2018 Aleksandar Vacić. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CardPresentationController
11 |
12 | final class SecondController: UIViewController, StoryboardLoadable {
13 |
14 | override func viewWillAppear(_ animated: Bool) {
15 | super.viewWillAppear(animated)
16 |
17 | navigationController?.setNavigationBarHidden(true, animated: animated)
18 | }
19 | }
20 |
21 | private extension SecondController {
22 | /// Return to previous VC in the NC stack
23 | ///
24 | /// - Parameter sender: button which initiated this action
25 | @IBAction func goBack(_ sender: UIButton) {
26 | navigationController?.setNavigationBarHidden(false, animated: true)
27 | navigationController?.popViewController(animated: true)
28 | }
29 |
30 | /// Display popup as inset card
31 | ///
32 | /// - Parameter sender: button which initiated this action
33 | @IBAction func popupCard(_ sender: UIButton) {
34 | let vc = ContentController.instantiate()
35 |
36 | presentCard(vc,
37 | animated: true)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Example/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // CardPresentationExample
4 | //
5 | // Created by Aleksandar Vacić on 9/15/18.
6 | // Copyright © 2018 Aleksandar Vacić. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CardPresentationController
11 |
12 | final class ViewController: UIViewController {
13 | @IBOutlet private var container: UIView!
14 |
15 | @IBOutlet private var toggle: UISwitch!
16 |
17 | // Embedded
18 |
19 | private var controller: ContentController?
20 |
21 | // View lifecycle
22 |
23 | override func viewDidLoad() {
24 | super.viewDidLoad()
25 |
26 | if #available(iOS 13, *) {
27 | toggle.isOn = CardPresentationController.useSystemPresentationOniOS13
28 | } else {
29 | toggle.superview?.isHidden = true
30 | }
31 |
32 | if let gradient = view as? GradientView {
33 | gradient.direction = .vertical
34 | gradient.colors = [.gray, .darkGray]
35 | }
36 |
37 | let vc = ContentController.instantiate()
38 | vc.context = .embed
39 | embed(controller: vc, into: container)
40 | controller = vc
41 | }
42 | }
43 |
44 | private extension ViewController {
45 | /// Display regular, familiar full screen popup
46 | ///
47 | /// - Parameter sender: button which initiated this action
48 | @IBAction func popupDefault(_ sender: UIButton) {
49 | let vc = ContentController.instantiate()
50 | addDismissBarButton(to: vc)
51 |
52 | // wrap inside NC
53 | let nc = UINavigationController(rootViewController: vc)
54 |
55 | present(nc,
56 | animated: true)
57 | }
58 |
59 | /// Display popup as inset card
60 | ///
61 | /// - Parameter sender: button which initiated this action
62 | @IBAction func popupNavBarCard(_ sender: UIButton) {
63 | let vc = ContentController.instantiate()
64 | addDismissBarButton(to: vc)
65 |
66 | // wrap inside custom NC, so we can enforce statusBarStyle
67 | let nc = PopupNavigationController(rootViewController: vc)
68 |
69 | presentCard(nc,
70 | animated: true)
71 | }
72 |
73 | /// Display custom container with embedded content, as popup card.
74 | ///
75 | /// - Parameter sender: button which initiated this action
76 | @IBAction func popupCustomContainerCard(_ sender: UIButton) {
77 | let vc = ContentController.instantiate()
78 | // vc.context = .embed
79 |
80 | let cc = CustomContainerController.initial()
81 | cc.display(vc: vc)
82 |
83 | // center the dismiss handle inside the reserved area at the top of the container
84 | let config = CardConfiguration(dismissAreaHeight: cc.edgeInsets.top)
85 |
86 | presentCard(cc,
87 | configuration: config,
88 | animated: true)
89 | }
90 |
91 | /// Expand popup from the arbitrary rect on the screen
92 | /// (and also collapse down to the same area)
93 | ///
94 | /// - Parameter sender: button which initiated this action
95 | @IBAction func expandCard(_ sender: UIButton) {
96 | let vc = ContentController.instantiate()
97 | vc.context = .embed
98 |
99 | let f = container.convert(sender.bounds, to: view.window!)
100 | let config = CardConfiguration(initialTransitionFrame: f)
101 |
102 | presentCard(vc,
103 | configuration: config,
104 | animated: true)
105 | }
106 |
107 | @IBAction func pushNext(_ sender: UIBarButtonItem) {
108 | let vc = SecondController.instantiate()
109 | show(vc, sender: self)
110 | }
111 |
112 | /// Display popup as inset card
113 | ///
114 | /// - Parameter sender: button which initiated this action
115 | @IBAction func popupGrid(_ sender: UIButton) {
116 | let vc = GridController()
117 |
118 | presentCard(vc,
119 | animated: true)
120 | }
121 |
122 | /// Display popup as inset card, with initial vertical inset of 132pt
123 | ///
124 | /// - Parameter sender: button which initiated this action
125 | @IBAction func popupLargeInsetCard(_ sender: UIButton) {
126 | let vc = ContentController.instantiate()
127 |
128 | let config = CardConfiguration(verticalInset: 132)
129 |
130 | presentCard(vc,
131 | configuration: config,
132 | animated: true)
133 | }
134 | }
135 |
136 |
137 | fileprivate extension ViewController {
138 | /// Dismisses whatever popup is currently shown
139 | ///
140 | /// - Parameter sender: An UI object that initiated dismissal
141 | @IBAction func dismissPopup(_ sender: Any) {
142 | dismiss(animated: true)
143 | }
144 |
145 | func addDismissBarButton(to vc: UIViewController) {
146 | // and add Done button to dismiss the popup
147 | let bbi = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(ViewController.dismissPopup))
148 | var buttonItems = vc.navigationItem.leftBarButtonItems ?? []
149 | buttonItems.append(bbi)
150 | vc.navigationItem.leftBarButtonItems = buttonItems
151 | }
152 |
153 | @IBAction func toggleSystemCardUsage(_ sender: UISwitch) {
154 | CardPresentationController.useSystemPresentationOniOS13 = sender.isOn
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/Framework/CardPresentationController.h:
--------------------------------------------------------------------------------
1 | //
2 | // CardPresentationController.h
3 | // CardPresentationController
4 | //
5 | // Created by Aleksandar Vacić on 1/28/19.
6 | // Copyright © 2019 Aleksandar Vacić. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for CardPresentationController.
12 | FOUNDATION_EXPORT double CardPresentationControllerVersionNumber;
13 |
14 | //! Project version string for CardPresentationController.
15 | FOUNDATION_EXPORT const unsigned char CardPresentationControllerVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Framework/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright © 2018 Aleksandar Vacić, Radiant Tap
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This component is deprecated.
2 |
3 | - We have native card support since iOS 13.
4 | - In iOS 15 Apple is adding custom presentation sheets for half-modals.
5 |
6 | This component has served its purpose, **please move on to native API**.
7 |
8 | ---
9 |
10 |
11 | [](https://github.com/radianttap/CardPresentationController/releases)
12 | 
13 | [](https://github.com/radianttap/CardPresentationController/blob/master/LICENSE)
14 | [](https://github.com/Carthage/Carthage)
15 | [](https://cocoapods.org)
16 | 
17 |
18 | # CardPresentationController
19 |
20 | Custom [UIPresentationController](https://developer.apple.com/documentation/uikit/uipresentationcontroller) which mimics the behavior of Apple Music UI. Should work just fine from iOS 10 and beyond.
21 |
22 | [DEMO video on iPhone Xs simulator](CardPresentationController.mp4)
23 |
24 | ### Modal presentation in iOS 13
25 |
26 | iOS 13 changed the behavior of the ordinary `present(vc, ...) calls` - all modals now look like cards. *Thus you don’t need this library on iOS 13.* I always recommend to use system stuff as much as possible thus this library will, by default, fallback to system look & behavior if you are on iOS 13.
27 |
28 | To toggle this off and still use this library to present modal cards, set this at some point before presenting your first `UIViewController`:
29 |
30 | ```
31 | CardPresentationController.useSystemPresentationOniOS13 = false
32 | ```
33 |
34 | Keep in mind that visual display of multiple cards in this library is different from what iOS 13 does. (I don’t intend to change this, it’s not really worth it.)
35 |
36 | ## Installation
37 |
38 | ### Manually
39 |
40 | Add the folder `CardPresentationController` into your project. It's only five files.
41 |
42 | If you prefer to use dependency managers, see below.
43 | Releases are tagged with [Semantic Versioning](https://semver.org) in mind.
44 |
45 | ### CocoaPods
46 |
47 | [CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate Coordinator into your Xcode project using CocoaPods, specify it in your `Podfile`:
48 |
49 | ```ruby
50 | pod 'CardPresentationController', :git => 'https://github.com/radianttap/CardPresentationController.git'
51 | ```
52 |
53 | ### Setting up with Carthage
54 |
55 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application.
56 |
57 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command:
58 |
59 | ```bash
60 | $ brew update
61 | $ brew install carthage
62 | ```
63 |
64 | To integrate CardPresentationController into your Xcode project using Carthage, specify it in your `Cartfile`:
65 |
66 | ```ogdl
67 | github "radianttap/CardPresentationController"
68 | ```
69 |
70 |
71 |
72 | ## Usage
73 |
74 | From anywhere you want to present some `UIViewController`, call
75 |
76 | ```swift
77 | let vc = ...
78 | presentCard(vc, animated: true)
79 | ```
80 |
81 | You dismiss it as any other modal:
82 |
83 | ```swift
84 | dismiss(animated: true)
85 | ```
86 |
87 | This will present `vc` modally, flying-in from the bottom edge. Existing view will be kept shown as dimmed background card, on black background.
88 |
89 | You can *present card from another card*; library will stack the cards nicely. Do use common sense as popups over popups don’t make pleasant user experience.
90 |
91 | ### Advanced behavior
92 |
93 | View of the `presenting` Controller will be (by default) 20% transparent to blend into the background a bit, thus looking dimmed.
94 |
95 | That back "card" is also inset a bit from the edges.
96 |
97 | 
98 |
99 | If the _presented_ VC is `UINavigationController` instance, nothing special happens. It’s assumed that you will add `UIBarButtonItem` which will facilitate dismissal.
100 |
101 | If it is not, then `CardPresentationController` will automatically add a button at the middle of the shown card. Tapping on that will dismiss the card.
102 |
103 | 
104 |
105 | As you present card over card, back cards will be ever more transparent and horizontally inset. In most cases, this should look rather nice.
106 |
107 | Library also supports interactive dismissal — simply pan from top to bottom and UI will obey you. You can pan up or down and the direction and position where you let go will determine will the card finish dismissing or return to presented state.
108 |
109 | ### Status bar style
110 |
111 | CardPresentationController tries its best to enforce `.lightContent` status bar style. You can help it, by adding this into your UIVC subclass:
112 |
113 | ```swift
114 | override var preferredStatusBarStyle: UIStatusBarStyle {
115 | return .lightContent
116 | }
117 | ```
118 |
119 | If you are presenting UINC, then my advice is to subclass it and override `preferredStatusBarStyle` property in the same way.
120 |
121 | ## Requirements
122 |
123 | *Requires iOS 10*, since it uses [UIViewPropertyAnimator](https://developer.apple.com/documentation/uikit/uiviewpropertyanimator), [UISpringTimingParameters](https://developer.apple.com/documentation/uikit/uispringtimingparameters) and a bunch of other modern UIKit animation APIs.
124 |
125 | On iOS 11 it uses [maskedCorners](https://developer.apple.com/documentation/quartzcore/calayer/2877488-maskedcorners) property to round just the top corners. On iOS 10.x it will fallback to rounding all corners.
126 |
127 | ## How it works
128 |
129 | The main object here is `CardTransitionManager`, which acts as `UIViewControllerTransitioningDelegate`. It is internally instantiated and assigned as property on UIVC which called `presentCard()` – that's _sourceController_ in the UIPresentationController parlance.
130 |
131 | This instance of CTM is automatically removed on dismissal.
132 |
133 | CTM creates and manages the other two required objects:
134 |
135 | * `CardPresentationController`: manages additional views (like dismiss handle at the top of the card) and other aspects of the custom presentation
136 | * `CardAnimator`: which performs the animated transition
137 |
138 | In case you missed it — *you don’t deal with any of that*. It’s all implementation detail, hidden inside these 3 classes. You never instantiate them directly.
139 |
140 | The only object you can put to use, if you want to, is…
141 |
142 | ### CardConfiguration
143 |
144 | When calling `presentCard`, you can supply optional `CardConfiguration` instance. This is simple struct containing the following parameters:
145 |
146 | ```swift
147 | /// Vertical inset from the top or already shown card
148 | var verticalSpacing: CGFloat = 16
149 |
150 | /// Leading and trailing inset for the existing (presenting) view
151 | /// when it's being pushed further back
152 | var horizontalInset: CGFloat = 16
153 |
154 | /// Height of the "empty" area at the top of the card
155 | /// where dismiss handle glyph will be centered.
156 | public var dismissAreaHeight: CGFloat = 16
157 |
158 | /// Cards have rounded corners, right?
159 | var cornerRadius: CGFloat = 12
160 |
161 | /// The starting frame for the presented card.
162 | var initialTransitionFrame: CGRect?
163 |
164 | /// How much to fade the back card.
165 | var backFadeAlpha: CGFloat = 0.8
166 |
167 | /// Set to false to disable interactive dismissal
168 | var allowInteractiveDismissal = true
169 | ```
170 |
171 | There’s a very handy `init` for it where you can supply any combination of these parameters.
172 |
173 | If you don't supply config, then `CardConfiguration.shared` will be used, consisting of the default values shown above.
174 | You can override this property early in app's lifecycle so adjust default look of the cards for the entire app (see AppDelegate.swift for an example).
175 |
176 | ### Advanced example
177 |
178 | Thus if you want to control where the card originates — say if you want to mimic Apple Music's now-playing card — you can:
179 |
180 | ```swift
181 | let vc = ContentController.instantiate()
182 |
183 | let f = container.convert(sender.bounds, to: view.window!)
184 | let config = CardConfiguration(initialTransitionFrame: f)
185 |
186 | presentCard(vc, configuration: config, animated: true)
187 | ```
188 |
189 | The important bit here is setting `initialTransitionFrame` property to the frame *in the UIWindow coordinating space*, since transition happens in it.
190 |
191 | ### Caveats
192 |
193 | `CardAnimator` animates layout of its own subviews – `from` and `to` views included in `transitionContext`. Behavior and layout of the internal subviews of both _presented_ and _presenting_/_source_ views is up to you *but* CardAnimator will try its best to animate them along.
194 |
195 | Depending on the complexity of your UI, in may be impossible to make the transition perfect. Usually in cases where UIKit applies its own private API magic related to status / navigation bars.
196 | See `EmbeddedNCExample` where I have `UINavigationController` embedded inside ordinary `UIViewController`. This is very unusual UIVC stack which I would love to solve since I have project using just that.
197 |
198 | ## LICENSE
199 |
200 | [MIT](LICENSE), as usual for all my stuff.
201 |
202 |
203 | ## Give back
204 |
205 | If you found this code useful, please consider [buying me a coffee](https://www.buymeacoffee.com/radianttap) or two. ☕️😋
206 |
--------------------------------------------------------------------------------
/Vendor/BaseGridLayout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GridLayout.swift
3 | // Radiant Tap Essentials
4 | //
5 | // Copyright © 2015 Radiant Tap
6 | // MIT License · http://choosealicense.com/licenses/mit/
7 | //
8 |
9 | import UIKit
10 |
11 | class BaseGridLayout: UICollectionViewFlowLayout {
12 |
13 | override func awakeFromNib() {
14 | super.awakeFromNib()
15 | commonInit()
16 | }
17 |
18 | override init() {
19 | super.init()
20 | commonInit()
21 | }
22 |
23 | required init?(coder aDecoder: NSCoder) {
24 | super.init(coder: aDecoder)
25 | commonInit()
26 | }
27 |
28 | func commonInit() {
29 | scrollDirection = .vertical
30 | itemSize = CGSize(width: 120, height: 120)
31 | headerReferenceSize = .zero
32 | footerReferenceSize = .zero
33 | minimumLineSpacing = 0
34 | minimumInteritemSpacing = 0
35 | sectionInset = .zero
36 | }
37 |
38 | override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
39 | switch scrollDirection {
40 | case .horizontal:
41 | return newBounds.height != collectionView?.bounds.height
42 | case .vertical:
43 | return newBounds.width != collectionView?.bounds.width
44 | @unknown default:
45 | return false
46 | }
47 | }
48 |
49 | override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
50 | let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext
51 | switch scrollDirection {
52 | case .horizontal:
53 | context.invalidateFlowLayoutDelegateMetrics = newBounds.height != collectionView?.bounds.height
54 | case .vertical:
55 | context.invalidateFlowLayoutDelegateMetrics = newBounds.width != collectionView?.bounds.width
56 | @unknown default:
57 | break
58 | }
59 | return context
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Vendor/Controllers/Embeddable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Embeddable.swift
3 | // Radiant Tap Essentials
4 | // https://github.com/radianttap/swift-essentials
5 | //
6 | // Copyright © 2016 Radiant Tap
7 | // MIT License · http://choosealicense.com/licenses/mit/
8 |
9 | import UIKit
10 |
11 | extension UIViewController {
12 | /// (view, parentView) -> Void
13 | public typealias LayoutBlock = (UIView, UIView) -> Void
14 |
15 | /// Embeds the `view` of the given UIViewController into supplied `parentView` (or into `self.view`, if `nil`).
16 | ///
17 | /// Default value of `LayoutBlock` aligns the embedded `view` with the edges of the `parentView`, using priority=999 for the bottom and trailing constraints).
18 | /// This helps to avoid auto-layout issues when embedding into zero-width or height container views.
19 | public func embed(controller vc: T, into parentView: UIView?, layout: LayoutBlock = {
20 | v, pv in
21 |
22 | let constraints: [NSLayoutConstraint] = [
23 | v.topAnchor.constraint(equalTo: pv.topAnchor),
24 | v.leadingAnchor.constraint(equalTo: pv.leadingAnchor),
25 | {
26 | let lc = v.bottomAnchor.constraint(equalTo: pv.bottomAnchor)
27 | lc.priority = UILayoutPriority(rawValue: 999)
28 | return lc
29 | }(),
30 | {
31 | let lc = v.trailingAnchor.constraint(equalTo: pv.trailingAnchor)
32 | lc.priority = UILayoutPriority(rawValue: 999)
33 | return lc
34 | }()
35 | ]
36 | constraints.forEach { $0.isActive = true }
37 | })
38 | where T: UIViewController
39 | {
40 | let container = parentView ?? self.view!
41 |
42 | addChild(vc)
43 | container.addSubview(vc.view)
44 | vc.view.translatesAutoresizingMaskIntoConstraints = false
45 | layout(vc.view, container)
46 | vc.didMove(toParent: self)
47 |
48 | // Note: after this, save the controller reference
49 | // somewhere in calling scope
50 | }
51 |
52 | public func unembed(controller: UIViewController?) {
53 | guard let controller = controller else { return }
54 |
55 | controller.willMove(toParent: nil)
56 | if controller.isViewLoaded {
57 | controller.view.removeFromSuperview()
58 | }
59 | controller.removeFromParent()
60 |
61 | // Note: don't forget to nullify your own controller instance
62 | // in order to clear it out from memory
63 | }
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/Vendor/Controllers/StoryboardLoadable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StoryboardLoadable.swift
3 | // Radiant Tap Essentials
4 | // https://github.com/radianttap/swift-essentials
5 | //
6 | // Copyright © 2016 Radiant Tap
7 | // MIT License · http://choosealicense.com/licenses/mit/
8 | //
9 |
10 | import UIKit
11 |
12 |
13 | public protocol StoryboardLoadable {
14 | static var storyboardName: String { get }
15 | static var storyboardIdentifier: String { get }
16 | }
17 |
18 |
19 | extension StoryboardLoadable where Self: UIViewController {
20 |
21 | public static var storyboardName: String {
22 | return String(describing: self)
23 | }
24 |
25 | public static var storyboardIdentifier: String {
26 | return String(describing: self)
27 | }
28 |
29 | public static func instantiate(fromStoryboardNamed name: String? = nil) -> Self {
30 | let sb = name ?? self.storyboardName
31 | let storyboard = UIStoryboard(name: sb, bundle: nil)
32 | return instantiate(fromStoryboard: storyboard)
33 | }
34 |
35 | public static func instantiate(fromStoryboard storyboard: UIStoryboard) -> Self {
36 | let identifier = self.storyboardIdentifier
37 | guard let vc = storyboard.instantiateViewController(withIdentifier: identifier) as? Self else {
38 | fatalError("Failed to instantiate view controller with identifier=\(identifier) from storyboard \( storyboard )")
39 | }
40 | return vc
41 |
42 | }
43 |
44 | public static func initial(fromStoryboardNamed name: String? = nil) -> Self {
45 | let sb = name ?? self.storyboardName
46 | let storyboard = UIStoryboard(name: sb, bundle: nil)
47 | return initial(fromStoryboard: storyboard)
48 | }
49 |
50 | public static func initial(fromStoryboard storyboard: UIStoryboard) -> Self {
51 | guard let vc = storyboard.instantiateInitialViewController() as? Self else {
52 | fatalError("Failed to instantiate initial view controller from storyboard named \( storyboard )")
53 | }
54 | return vc
55 | }
56 | }
57 |
58 |
59 | extension UINavigationController: StoryboardLoadable {}
60 | extension UITabBarController: StoryboardLoadable {}
61 | extension UISplitViewController: StoryboardLoadable {}
62 | extension UIPageViewController: StoryboardLoadable {}
63 |
--------------------------------------------------------------------------------
/Vendor/GradientView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GradientView.swift
3 | // Gradient View
4 | //
5 | // Created by Sam Soffes on 10/27/09.
6 | // Copyright (c) 2009-2014 Sam Soffes. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /// Simple view for drawing gradients and borders.
12 | @IBDesignable open class GradientView: UIView {
13 |
14 | // MARK: - Types
15 |
16 | /// The mode of the gradient.
17 | @objc public enum Mode: Int {
18 | /// A linear gradient.
19 | case linear
20 |
21 | /// A radial gradient.
22 | case radial
23 | }
24 |
25 |
26 | /// The direction of the gradient.
27 | @objc public enum Direction: Int {
28 | /// The gradient is vertical.
29 | case vertical
30 |
31 | /// The gradient is horizontal
32 | case horizontal
33 | }
34 |
35 |
36 | // MARK: - Properties
37 |
38 | /// An optional array of `UIColor` objects used to draw the gradient. If the value is `nil`, the `backgroundColor`
39 | /// will be drawn instead of a gradient. The default is `nil`.
40 | open var colors: [UIColor]? {
41 | didSet {
42 | updateGradient()
43 | }
44 | }
45 |
46 | /// An array of `UIColor` objects used to draw the dimmed gradient. If the value is `nil`, `colors` will be
47 | /// converted to grayscale. This will use the same `locations` as `colors`. If length of arrays don't match, bad
48 | /// things will happen. You must make sure the number of dimmed colors equals the number of regular colors.
49 | ///
50 | /// The default is `nil`.
51 | open var dimmedColors: [UIColor]? {
52 | didSet {
53 | updateGradient()
54 | }
55 | }
56 |
57 | /// Automatically dim gradient colors when prompted by the system (i.e. when an alert is shown).
58 | ///
59 | /// The default is `true`.
60 | open var automaticallyDims: Bool = true
61 |
62 | /// An optional array of `CGFloat`s defining the location of each gradient stop.
63 | ///
64 | /// The gradient stops are specified as values between `0` and `1`. The values must be monotonically increasing. If
65 | /// `nil`, the stops are spread uniformly across the range.
66 | ///
67 | /// Defaults to `nil`.
68 | open var locations: [CGFloat]? {
69 | didSet {
70 | updateGradient()
71 | }
72 | }
73 |
74 | /// The mode of the gradient. The default is `.Linear`.
75 | @IBInspectable open var mode: Mode = .linear {
76 | didSet {
77 | setNeedsDisplay()
78 | }
79 | }
80 |
81 | /// The direction of the gradient. Only valid for the `Mode.Linear` mode. The default is `.Vertical`.
82 | @IBInspectable open var direction: Direction = .horizontal {
83 | didSet {
84 | setNeedsDisplay()
85 | }
86 | }
87 |
88 | /// 1px borders will be drawn instead of 1pt borders. The default is `true`.
89 | @IBInspectable open var drawsThinBorders: Bool = true {
90 | didSet {
91 | setNeedsDisplay()
92 | }
93 | }
94 |
95 | /// The top border color. The default is `nil`.
96 | @IBInspectable open var topBorderColor: UIColor? {
97 | didSet {
98 | setNeedsDisplay()
99 | }
100 | }
101 |
102 | /// The right border color. The default is `nil`.
103 | @IBInspectable open var rightBorderColor: UIColor? {
104 | didSet {
105 | setNeedsDisplay()
106 | }
107 | }
108 |
109 | /// The bottom border color. The default is `nil`.
110 | @IBInspectable open var bottomBorderColor: UIColor? {
111 | didSet {
112 | setNeedsDisplay()
113 | }
114 | }
115 |
116 | /// The left border color. The default is `nil`.
117 | @IBInspectable open var leftBorderColor: UIColor? {
118 | didSet {
119 | setNeedsDisplay()
120 | }
121 | }
122 |
123 |
124 | // MARK: - UIView
125 |
126 | override open func draw(_ rect: CGRect) {
127 | let context = UIGraphicsGetCurrentContext()
128 | let size = bounds.size
129 |
130 | // Gradient
131 | if let gradient = gradient {
132 | let options: CGGradientDrawingOptions = [.drawsAfterEndLocation]
133 |
134 | if mode == .linear {
135 | let startPoint = CGPoint.zero
136 | let endPoint = direction == .vertical ? CGPoint(x: 0, y: size.height) : CGPoint(x: size.width, y: 0)
137 | context?.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: options)
138 | } else {
139 | let center = CGPoint(x: bounds.midX, y: bounds.midY)
140 | context?.drawRadialGradient(gradient, startCenter: center, startRadius: 0, endCenter: center, endRadius: min(size.width, size.height) / 2, options: options)
141 | }
142 | }
143 |
144 | let screen: UIScreen = window?.screen ?? UIScreen.main
145 | let borderWidth: CGFloat = drawsThinBorders ? 1.0 / screen.scale : 1.0
146 |
147 | // Top border
148 | if let color = topBorderColor {
149 | context?.setFillColor(color.cgColor)
150 | context?.fill(CGRect(x: 0, y: 0, width: size.width, height: borderWidth))
151 | }
152 |
153 | let sideY: CGFloat = topBorderColor != nil ? borderWidth : 0
154 | let sideHeight: CGFloat = size.height - sideY - (bottomBorderColor != nil ? borderWidth : 0)
155 |
156 | // Right border
157 | if let color = rightBorderColor {
158 | context?.setFillColor(color.cgColor)
159 | context?.fill(CGRect(x: size.width - borderWidth, y: sideY, width: borderWidth, height: sideHeight))
160 | }
161 |
162 | // Bottom border
163 | if let color = bottomBorderColor {
164 | context?.setFillColor(color.cgColor)
165 | context?.fill(CGRect(x: 0, y: size.height - borderWidth, width: size.width, height: borderWidth))
166 | }
167 |
168 | // Left border
169 | if let color = leftBorderColor {
170 | context?.setFillColor(color.cgColor)
171 | context?.fill(CGRect(x: 0, y: sideY, width: borderWidth, height: sideHeight))
172 | }
173 | }
174 |
175 | override open func tintColorDidChange() {
176 | super.tintColorDidChange()
177 |
178 | if automaticallyDims {
179 | updateGradient()
180 | }
181 | }
182 |
183 | override open func didMoveToWindow() {
184 | super.didMoveToWindow()
185 | contentMode = .redraw
186 | }
187 |
188 |
189 | // MARK: - Private
190 |
191 | fileprivate var gradient: CGGradient?
192 |
193 | fileprivate func updateGradient() {
194 | gradient = nil
195 | setNeedsDisplay()
196 |
197 | let colors = gradientColors()
198 | if let colors = colors {
199 | let colorSpace = CGColorSpaceCreateDeviceRGB()
200 | let colorSpaceModel = colorSpace.model
201 |
202 | let gradientColors: [CGColor] = colors.compactMap { (color: UIColor) -> CGColor? in
203 | let cgColor = color.cgColor
204 | let cgColorSpace = cgColor.colorSpace ?? colorSpace
205 |
206 | // The color's color space is RGB, simply add it.
207 | if cgColorSpace.model == colorSpaceModel {
208 | return cgColor
209 | }
210 |
211 | // Convert to RGB. There may be a more efficient way to do this.
212 | var red: CGFloat = 0
213 | var blue: CGFloat = 0
214 | var green: CGFloat = 0
215 | var alpha: CGFloat = 0
216 | color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
217 | return UIColor(red: red, green: green, blue: blue, alpha: alpha).cgColor
218 | }
219 |
220 | gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors as CFArray, locations: locations)
221 | }
222 | }
223 |
224 | fileprivate func gradientColors() -> [UIColor]? {
225 | if tintAdjustmentMode == .dimmed {
226 | if let dimmedColors = dimmedColors {
227 | return dimmedColors
228 | }
229 |
230 | if automaticallyDims {
231 | if let colors = colors {
232 | return colors.map {
233 | var hue: CGFloat = 0
234 | var brightness: CGFloat = 0
235 | var alpha: CGFloat = 0
236 |
237 | $0.getHue(&hue, saturation: nil, brightness: &brightness, alpha: &alpha)
238 |
239 | return UIColor(hue: hue, saturation: 0, brightness: brightness, alpha: alpha)
240 | }
241 | }
242 | }
243 | }
244 |
245 | return colors
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/Vendor/Views/DequeableView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DequeableView.swift
3 | // Radiant Tap Essentials
4 | // https://github.com/radianttap/swift-essentials
5 | //
6 | // Copyright © 2016 Radiant Tap
7 | // MIT License · http://choosealicense.com/licenses/mit/
8 | //
9 |
10 | import UIKit
11 |
12 |
13 | extension UICollectionView {
14 |
15 | // register for the Class-based cell
16 | public func register(_: T.Type)
17 | where T: ReusableView
18 | {
19 | register(T.self, forCellWithReuseIdentifier: T.reuseIdentifier)
20 | }
21 |
22 | // register for the Nib-based cell
23 | public func register(_: T.Type)
24 | where T:NibReusableView
25 | {
26 | register(T.nib, forCellWithReuseIdentifier: T.reuseIdentifier)
27 | }
28 |
29 | public func dequeueReusableCell(forIndexPath indexPath: IndexPath) -> T
30 | where T:ReusableView
31 | {
32 | // this deque and cast can fail if you forget to register the proper cell
33 | guard let cell = dequeueReusableCell(withReuseIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
34 | // thus crash instantly and nudge the developer
35 | fatalError("Dequeing a cell with identifier: \(T.reuseIdentifier) failed.\nDid you maybe forget to register it in viewDidLoad?")
36 | }
37 | return cell
38 | }
39 |
40 | // register for the Class-based supplementary view
41 | public func register(_: T.Type, kind: String)
42 | where T:ReusableView
43 | {
44 | register(T.self, forSupplementaryViewOfKind: kind, withReuseIdentifier: T.reuseIdentifier)
45 | }
46 |
47 | // register for the Nib-based supplementary view
48 | public func register(_: T.Type, kind: String)
49 | where T:NibReusableView
50 | {
51 | register(T.nib, forSupplementaryViewOfKind: kind, withReuseIdentifier: T.reuseIdentifier)
52 | }
53 |
54 | public func dequeueReusableView(kind: String, atIndexPath indexPath: IndexPath) -> T
55 | where T:ReusableView
56 | {
57 | // this deque and cast can fail if you forget to register the proper cell
58 | guard let view = dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
59 | // thus crash instantly and nudge the developer
60 | fatalError("Dequeing supplementary view of kind: \( kind ) with identifier: \( T.reuseIdentifier ) failed.\nDid you maybe forget to register it in viewDidLoad?")
61 | }
62 | return view
63 | }
64 | }
65 |
66 |
67 | extension UITableView {
68 |
69 | // register for the Class-based cell
70 | public func register(_: T.Type)
71 | where T: ReusableView
72 | {
73 | register(T.self, forCellReuseIdentifier: T.reuseIdentifier)
74 | }
75 |
76 | // register for the Nib-based cell
77 | public func register(_: T.Type)
78 | where T:NibReusableView
79 | {
80 | register(T.nib, forCellReuseIdentifier: T.reuseIdentifier)
81 | }
82 |
83 | public func dequeueReusableCell(forIndexPath indexPath: IndexPath) -> T
84 | where T:ReusableView
85 | {
86 | guard let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
87 | fatalError("Dequeing a cell with identifier: \(T.reuseIdentifier) failed.\nDid you maybe forget to register it in viewDidLoad?")
88 | }
89 | return cell
90 | }
91 |
92 | // register for the Class-based header/footer view
93 | public func register(_: T.Type)
94 | where T:ReusableView
95 | {
96 | register(T.self, forHeaderFooterViewReuseIdentifier: T.reuseIdentifier)
97 | }
98 |
99 | // register for the Nib-based header/footer view
100 | public func register(_: T.Type)
101 | where T:NibReusableView
102 | {
103 | register(T.nib, forHeaderFooterViewReuseIdentifier: T.reuseIdentifier)
104 | }
105 |
106 | public func dequeueReusableView() -> T?
107 | where T:ReusableView
108 | {
109 | let v = dequeueReusableHeaderFooterView(withIdentifier: T.reuseIdentifier) as? T
110 | return v
111 | }
112 | }
113 |
114 |
--------------------------------------------------------------------------------
/Vendor/Views/NibLoadable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NibLoadable.swift
3 | // Radiant Tap Essentials
4 | // https://github.com/radianttap/swift-essentials
5 | //
6 | // Copyright © 2016 Radiant Tap
7 | // MIT License · http://choosealicense.com/licenses/mit/
8 | //
9 |
10 | import UIKit
11 |
12 | /// Adopt this protocol on all subclasses of UITableViewCell and UICollectionViewCell
13 | /// that use their own .xib file
14 | public protocol NibLoadableView {
15 | /// By default, it returns the subclass name
16 | static var nibName: String { get }
17 |
18 | /// Instantiates UINib using `nibName` as the name, from the main bundle
19 | static var nib: UINib { get }
20 | }
21 |
22 | extension NibLoadableView where Self: UIView {
23 | public static var nibName: String {
24 | return String(describing: self)
25 | }
26 |
27 | public static var nib: UINib {
28 | return UINib(nibName: self.nibName, bundle: nil)
29 | }
30 | }
31 |
32 | public protocol NibReusableView : ReusableView, NibLoadableView {}
33 |
34 |
35 |
36 | /// Adopt this in cases where you need to create an ad-hoc instance of the given view
37 | /// Can be adopted only by classes marked as `final`, due to `Self` constraint
38 | public protocol NibLoadableFinalView: NibLoadableView {
39 | /// Creates an instance of the cell from the `nibName`.xib file
40 | static var nibInstance : Self { get }
41 | }
42 |
43 | extension NibLoadableFinalView {
44 | public static var nibInstance : Self {
45 | guard let nibObject = self.nib.instantiate(withOwner: nil, options: nil).last as? Self else {
46 | fatalError("Failed to create an instance of \(self) from \(self.nibName) nib.")
47 | }
48 | return nibObject
49 | }
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/Vendor/Views/ReusableView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReusableView.swift
3 | // Radiant Tap Essentials
4 | // https://github.com/radianttap/swift-essentials
5 | //
6 | // Copyright © 2016 Radiant Tap
7 | // MIT License · http://choosealicense.com/licenses/mit/
8 | //
9 |
10 | import UIKit
11 |
12 | /// Protocol to allow any UIView to become reusable view
13 | public protocol ReusableView {
14 | /// By default, it returns the subclass name
15 | static var reuseIdentifier: String { get }
16 | }
17 |
18 | extension ReusableView where Self: UIView {
19 | public static var reuseIdentifier: String {
20 | return String(describing: self)
21 | }
22 | }
23 |
24 |
25 |
--------------------------------------------------------------------------------
/resources/apple-music-iphone-xs.acorn:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/resources/apple-music-iphone-xs.acorn
--------------------------------------------------------------------------------
/resources/apple-music-iphone-xs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/resources/apple-music-iphone-xs.png
--------------------------------------------------------------------------------
/resources/cardpc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/resources/cardpc.png
--------------------------------------------------------------------------------
/resources/presentedNC-top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/resources/presentedNC-top.png
--------------------------------------------------------------------------------
/resources/presentedNC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/resources/presentedNC.png
--------------------------------------------------------------------------------
/resources/presentedVC-top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/resources/presentedVC-top.png
--------------------------------------------------------------------------------
/resources/presentedVC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radianttap/CardPresentationController/91aae49731cfa0b7107a03420f27eb0307f8a0ae/resources/presentedVC.png
--------------------------------------------------------------------------------