├── .gitignore
├── Example
├── AppStoreTransitionAnimation.xcodeproj
│ └── project.pbxproj
├── AppStoreTransitionAnimation.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── AppStoreTransitionAnimation
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── chat-icon.imageset
│ │ │ ├── Contents.json
│ │ │ └── icons8-chat-bubble-filled-100.png
│ │ ├── gray.colorset
│ │ │ └── Contents.json
│ │ ├── text-color.colorset
│ │ │ └── Contents.json
│ │ ├── type1-background.imageset
│ │ │ ├── Contents.json
│ │ │ └── beautiful-black-and-white-casual-2256268.jpg
│ │ ├── type1-bg-bottom.imageset
│ │ │ ├── Contents.json
│ │ │ └── kelsey-curtis-1596289-unsplash.jpg
│ │ ├── type1color.colorset
│ │ │ └── Contents.json
│ │ ├── type2-background.imageset
│ │ │ ├── Contents.json
│ │ │ └── landscape-mountain-mountain-peak-2262620.jpg
│ │ ├── type2-bg-bottom.imageset
│ │ │ ├── Contents.json
│ │ │ └── analise-benevides-1596040-unsplash.jpg
│ │ └── type2color.colorset
│ │ │ └── Contents.json
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Cells
│ │ ├── ItemTableViewCell.swift
│ │ ├── ItemTableViewCell.xib
│ │ ├── Type1CollectionViewCell.swift
│ │ ├── Type1CollectionViewCell.xib
│ │ ├── Type2CollectionViewCell.swift
│ │ └── Type2CollectionViewCell.xib
│ ├── Info.plist
│ ├── Type1ViewController.swift
│ ├── Type2ViewController.swift
│ ├── ViewController.swift
│ └── Views
│ │ ├── Type1HeaderView.swift
│ │ └── Type1HeaderView.xib
├── Podfile
├── Podfile.lock
└── Pods
│ ├── Local Podspecs
│ └── appstore-card-transition.podspec.json
│ ├── Manifest.lock
│ ├── Pods.xcodeproj
│ └── project.pbxproj
│ └── Target Support Files
│ ├── Pods-AppStoreTransitionAnimation
│ ├── Pods-AppStoreTransitionAnimation-Info.plist
│ ├── Pods-AppStoreTransitionAnimation-acknowledgements.markdown
│ ├── Pods-AppStoreTransitionAnimation-acknowledgements.plist
│ ├── Pods-AppStoreTransitionAnimation-dummy.m
│ ├── Pods-AppStoreTransitionAnimation-frameworks-Debug-input-files.xcfilelist
│ ├── Pods-AppStoreTransitionAnimation-frameworks-Debug-output-files.xcfilelist
│ ├── Pods-AppStoreTransitionAnimation-frameworks-Release-input-files.xcfilelist
│ ├── Pods-AppStoreTransitionAnimation-frameworks-Release-output-files.xcfilelist
│ ├── Pods-AppStoreTransitionAnimation-frameworks.sh
│ ├── Pods-AppStoreTransitionAnimation-umbrella.h
│ ├── Pods-AppStoreTransitionAnimation.debug.xcconfig
│ ├── Pods-AppStoreTransitionAnimation.modulemap
│ └── Pods-AppStoreTransitionAnimation.release.xcconfig
│ └── appstore-card-transition
│ ├── appstore-card-transition-Info.plist
│ ├── appstore-card-transition-dummy.m
│ ├── appstore-card-transition-prefix.pch
│ ├── appstore-card-transition-umbrella.h
│ ├── appstore-card-transition.modulemap
│ └── appstore-card-transition.xcconfig
├── LICENSE
├── Package.swift
├── README.md
├── Sources
├── AppstoreTransition.xcodeproj
│ └── project.pbxproj
└── AppstoreTransition
│ ├── AppstoreTransition.h
│ ├── CardCollectionViewCell.swift
│ ├── CardDetailViewController.swift
│ ├── CardPresentationController.swift
│ ├── CardTransition.swift
│ ├── DismissCardAnimator.swift
│ ├── Info.plist
│ ├── PresentCardAnimator.swift
│ ├── UIView+Extension.swift
│ └── UIViewController+Extension.swift
├── appstore-card-transition.podspec
└── gif
├── example1.gif
├── example2.gif
├── example3.gif
├── example4.gif
└── logo.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 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 | .swiftpm/
43 |
44 | # CocoaPods
45 | #
46 | # We recommend against adding the Pods directory to your .gitignore. However
47 | # you should judge for yourself, the pros and cons are mentioned at:
48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
49 | #
50 | # Pods/
51 |
52 | # Carthage
53 | #
54 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
55 | # Carthage/Checkouts
56 |
57 | Carthage/Build
58 |
59 | # fastlane
60 | #
61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
62 | # screenshots whenever they are needed.
63 | # For more information about the recommended setup visit:
64 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
65 |
66 | fastlane/report.xml
67 | fastlane/Preview.html
68 | fastlane/screenshots/**/*.png
69 | fastlane/test_output
70 |
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // AppStoreTransitionAnimation
4 | //
5 | // Created by Razvan Chelemen on 15/05/2019.
6 | // Copyright © 2019 appssemble. 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 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Assets.xcassets/chat-icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "filename" : "icons8-chat-bubble-filled-100.png",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "template"
23 | }
24 | }
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Assets.xcassets/chat-icon.imageset/icons8-chat-bubble-filled-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appssemble/appstore-card-transition/a220fc468f1a9a2b7ff7dba5029b737af4c69422/Example/AppStoreTransitionAnimation/Assets.xcassets/chat-icon.imageset/icons8-chat-bubble-filled-100.png
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Assets.xcassets/gray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "srgb",
11 | "components" : {
12 | "red" : "0xF2",
13 | "alpha" : "1.000",
14 | "blue" : "0xF2",
15 | "green" : "0xF2"
16 | }
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Assets.xcassets/text-color.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "srgb",
11 | "components" : {
12 | "red" : "0x5B",
13 | "alpha" : "1.000",
14 | "blue" : "0x5B",
15 | "green" : "0x5B"
16 | }
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Assets.xcassets/type1-background.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "filename" : "beautiful-black-and-white-casual-2256268.jpg",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Assets.xcassets/type1-background.imageset/beautiful-black-and-white-casual-2256268.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appssemble/appstore-card-transition/a220fc468f1a9a2b7ff7dba5029b737af4c69422/Example/AppStoreTransitionAnimation/Assets.xcassets/type1-background.imageset/beautiful-black-and-white-casual-2256268.jpg
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Assets.xcassets/type1-bg-bottom.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "filename" : "kelsey-curtis-1596289-unsplash.jpg",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Assets.xcassets/type1-bg-bottom.imageset/kelsey-curtis-1596289-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appssemble/appstore-card-transition/a220fc468f1a9a2b7ff7dba5029b737af4c69422/Example/AppStoreTransitionAnimation/Assets.xcassets/type1-bg-bottom.imageset/kelsey-curtis-1596289-unsplash.jpg
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Assets.xcassets/type1color.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "srgb",
11 | "components" : {
12 | "red" : "0x5E",
13 | "alpha" : "1.000",
14 | "blue" : "0xBE",
15 | "green" : "0x98"
16 | }
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Assets.xcassets/type2-background.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "filename" : "landscape-mountain-mountain-peak-2262620.jpg",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Assets.xcassets/type2-background.imageset/landscape-mountain-mountain-peak-2262620.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appssemble/appstore-card-transition/a220fc468f1a9a2b7ff7dba5029b737af4c69422/Example/AppStoreTransitionAnimation/Assets.xcassets/type2-background.imageset/landscape-mountain-mountain-peak-2262620.jpg
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Assets.xcassets/type2-bg-bottom.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "filename" : "analise-benevides-1596040-unsplash.jpg",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Assets.xcassets/type2-bg-bottom.imageset/analise-benevides-1596040-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appssemble/appstore-card-transition/a220fc468f1a9a2b7ff7dba5029b737af4c69422/Example/AppStoreTransitionAnimation/Assets.xcassets/type2-bg-bottom.imageset/analise-benevides-1596040-unsplash.jpg
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Assets.xcassets/type2color.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "srgb",
11 | "components" : {
12 | "red" : "0xFF",
13 | "alpha" : "1.000",
14 | "blue" : "0x03",
15 | "green" : "0xDE"
16 | }
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/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/AppStoreTransitionAnimation/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 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
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 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
130 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
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 |
225 |
226 |
227 |
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Cells/ItemTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ItemTableViewCell.swift
3 | // AppStoreTransitionAnimation
4 | //
5 | // Created by Razvan Chelemen on 16/05/2019.
6 | // Copyright © 2019 appssemble. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ItemTableViewCell: UITableViewCell {
12 |
13 | override func awakeFromNib() {
14 | super.awakeFromNib()
15 | // Initialization code
16 | }
17 |
18 | override func setSelected(_ selected: Bool, animated: Bool) {
19 | super.setSelected(selected, animated: animated)
20 |
21 | // Configure the view for the selected state
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Cells/ItemTableViewCell.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 |
32 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Cells/Type1CollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Type1CollectionViewCell.swift
3 | // AppStoreTransitionAnimation
4 | //
5 | // Created by Razvan Chelemen on 15/05/2019.
6 | // Copyright © 2019 appssemble. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import AppstoreTransition
11 |
12 | class Type1CollectionViewCell: UICollectionViewCell {
13 | @IBOutlet weak var containerView: UIView!
14 | @IBOutlet weak var bottomContainer: UIView!
15 | @IBOutlet weak var subtitleLabel: UILabel!
16 | @IBOutlet weak var backgroundImage: UIImageView!
17 |
18 | @IBOutlet weak var reviewsLabel: UILabel!
19 | @IBOutlet weak var commentImageView: UIButton!
20 |
21 | override func awakeFromNib() {
22 | super.awakeFromNib()
23 |
24 | containerView.layer.cornerRadius = 8
25 | containerView.clipsToBounds = true
26 | }
27 |
28 | // Make it appears very responsive to touch
29 | override func touchesBegan(_ touches: Set, with event: UIEvent?) {
30 | super.touchesBegan(touches, with: event)
31 | animate(isHighlighted: true)
32 | }
33 |
34 | override func touchesEnded(_ touches: Set, with event: UIEvent?) {
35 | super.touchesEnded(touches, with: event)
36 | animate(isHighlighted: false)
37 | }
38 |
39 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) {
40 | super.touchesCancelled(touches, with: event)
41 | animate(isHighlighted: false)
42 | }
43 |
44 | }
45 |
46 | extension Type1CollectionViewCell: CardCollectionViewCell {
47 |
48 | var cardContentView: UIView {
49 | get {
50 | return containerView
51 | }
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Cells/Type1CollectionViewCell.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 |
41 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
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 |
103 |
104 |
105 |
106 |
112 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
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 |
183 |
184 |
185 |
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Cells/Type2CollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Type2CollectionViewCell.swift
3 | // AppStoreTransitionAnimation
4 | //
5 | // Created by Razvan Chelemen on 15/05/2019.
6 | // Copyright © 2019 appssemble. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import AppstoreTransition
11 |
12 | class Type2CollectionViewCell: UICollectionViewCell {
13 | @IBOutlet weak var containerView: UIView!
14 | @IBOutlet weak var subtitleLabel: UILabel!
15 | @IBOutlet weak var backgroundImage: UIImageView!
16 |
17 | override func awakeFromNib() {
18 | super.awakeFromNib()
19 |
20 | containerView.layer.cornerRadius = 8
21 | containerView.clipsToBounds = true
22 |
23 | layer.shadowColor = UIColor.black.cgColor
24 | layer.shadowOpacity = 0.1
25 | layer.shadowOffset = .init(width: 0, height: 0)
26 | layer.shadowRadius = 8
27 | }
28 |
29 | // Make it appears very responsive to touch
30 | override func touchesBegan(_ touches: Set, with event: UIEvent?) {
31 | super.touchesBegan(touches, with: event)
32 | animate(isHighlighted: true)
33 | }
34 |
35 | override func touchesEnded(_ touches: Set, with event: UIEvent?) {
36 | super.touchesEnded(touches, with: event)
37 | animate(isHighlighted: false)
38 | }
39 |
40 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) {
41 | super.touchesCancelled(touches, with: event)
42 | animate(isHighlighted: false)
43 | }
44 | }
45 |
46 | extension Type2CollectionViewCell: CardCollectionViewCell {
47 |
48 | var cardContentView: UIView {
49 | get {
50 | return containerView
51 | }
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Cells/Type2CollectionViewCell.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 |
49 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
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 |
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 |
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/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 |
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Type1ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Type1ViewController.swift
3 | // AppStoreTransitionAnimation
4 | //
5 | // Created by Razvan Chelemen on 15/05/2019.
6 | // Copyright © 2019 appssemble. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import AppstoreTransition
11 |
12 | class Type1ViewController: UIViewController {
13 | @IBOutlet weak var tableView: UITableView!
14 |
15 | @IBOutlet weak var coverView: UIView!
16 | @IBOutlet weak var commentCoverViewTop: NSLayoutConstraint!
17 |
18 | var dismissAnimationFinishedAction: (()->())?
19 | var subtitle: String? = nil
20 | var backgroundImage: UIImage? = nil
21 | var backgroundColor: UIColor? = nil
22 |
23 | lazy var headerView: Type1HeaderView = {
24 | let view = UINib(nibName: "Type1HeaderView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! Type1HeaderView
25 |
26 | return view
27 | }()
28 |
29 | override func viewDidLoad() {
30 | super.viewDidLoad()
31 |
32 | view.clipsToBounds = true
33 |
34 | tableView.delegate = self
35 | tableView.dataSource = self
36 | tableView.register(UINib(nibName: "ItemTableViewCell", bundle: Bundle.main), forCellReuseIdentifier: "ItemTableViewCell")
37 |
38 | positionHeaderView()
39 |
40 | headerView.topContainerView.backgroundColor = UIColor(named: "type1color")
41 | if let subtitle = subtitle {
42 | headerView.subtitleLabel.text = subtitle
43 | }
44 | if let backgroundImage = backgroundImage {
45 | headerView.backgroundImage.image = backgroundImage
46 | }
47 | if let backgroundColor = backgroundColor {
48 | coverView.backgroundColor = backgroundColor
49 | }
50 | }
51 |
52 | override func viewDidLayoutSubviews() {
53 | super.viewDidLayoutSubviews()
54 |
55 | headerView.topContainerHeight?.constant = UIScreen.main.bounds.width * 1.272 - 66.0
56 | positionHeaderView()
57 | }
58 |
59 | private func positionHeaderView() {
60 | let size = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
61 | headerView.frame.size = size
62 | tableView.tableHeaderView = headerView
63 | }
64 |
65 | }
66 |
67 | extension Type1ViewController: UITableViewDataSource {
68 |
69 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
70 | return 20
71 | }
72 |
73 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
74 | let cell = tableView.dequeueReusableCell(withIdentifier: "ItemTableViewCell", for: indexPath)
75 |
76 | return cell
77 | }
78 |
79 | }
80 |
81 | extension Type1ViewController: UITableViewDelegate {
82 |
83 | func scrollViewDidScroll(_ scrollView: UIScrollView) {
84 | // prevent bouncing when swiping down to close
85 | scrollView.bounces = scrollView.contentOffset.y > 100
86 |
87 | dismissHandler.scrollViewDidScroll(scrollView)
88 |
89 | if scrollView.contentOffset.y < 0 {
90 | scrollView.contentOffset = .zero
91 | }
92 | }
93 |
94 | }
95 |
96 | extension Type1ViewController: CardDetailViewController {
97 |
98 | var cardContentView: UIView {
99 | return headerView
100 | }
101 |
102 | var scrollView: UIScrollView? {
103 | return tableView
104 | }
105 |
106 | func didStartPresentAnimationProgress() {
107 | tableView.contentOffset = CGPoint(x: 0, y: 0)
108 | }
109 |
110 | func didBeginDismissAnimation() {
111 | tableView.setContentOffset(.zero, animated: true)
112 | }
113 |
114 | func didFinishDismissAnimation() {
115 | dismissAnimationFinishedAction?()
116 | }
117 |
118 | func didChangeDismissAnimationProgress(progress: CGFloat) {
119 | var progress = max(0, progress)
120 | progress = min(1, progress)
121 | commentCoverViewTop.constant = -58 * progress
122 | }
123 |
124 | func didCancelDismissAnimation(progress: CGFloat) {
125 | if progress < 1.0 {
126 | commentCoverViewTop.constant = 0
127 | tableView.setContentOffset(.zero, animated: true)
128 |
129 | UIView.animate(withDuration: 0.2) {
130 | self.view.setNeedsLayout()
131 | self.view.layoutIfNeeded()
132 | }
133 | }
134 | }
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Type2ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Type2ViewController.swift
3 | // AppStoreTransitionAnimation
4 | //
5 | // Created by Razvan Chelemen on 15/05/2019.
6 | // Copyright © 2019 appssemble. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import AppstoreTransition
11 |
12 | class Type2ViewController: UIViewController {
13 | @IBOutlet weak var contentScrollView: UIScrollView!
14 | @IBOutlet weak var headerView: UIView!
15 | @IBOutlet weak var subtitleLabel: UILabel!
16 | @IBOutlet weak var backgroundImage: UIImageView!
17 | @IBOutlet weak var heightConstraint: NSLayoutConstraint!
18 |
19 | var subtitle: String? = nil
20 | var background: UIImage? = nil
21 |
22 | override func viewDidLoad() {
23 | super.viewDidLoad()
24 |
25 | view.clipsToBounds = true
26 | contentScrollView.delegate = self
27 |
28 | scrollView?.contentInsetAdjustmentBehavior = .never
29 |
30 | let _ = dismissHandler
31 | if let subtitle = subtitle {
32 | subtitleLabel.text = subtitle
33 | }
34 | if let background = background {
35 | backgroundImage.image = background
36 | }
37 |
38 | heightConstraint.constant = UIScreen.main.bounds.width * 1.272 - 16.0
39 | }
40 |
41 | }
42 |
43 | extension Type2ViewController: UIScrollViewDelegate {
44 |
45 | func scrollViewDidScroll(_ scrollView: UIScrollView) {
46 | // prevent bouncing when swiping down to close
47 | scrollView.bounces = scrollView.contentOffset.y > 100
48 |
49 | dismissHandler.scrollViewDidScroll(scrollView)
50 | }
51 |
52 | }
53 |
54 | extension Type2ViewController: CardDetailViewController {
55 |
56 | var scrollView: UIScrollView? {
57 | return contentScrollView
58 | }
59 |
60 |
61 | var cardContentView: UIView {
62 | return headerView
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // AppStoreTransitionAnimation
4 | //
5 | // Created by Razvan Chelemen on 15/05/2019.
6 | // Copyright © 2019 appssemble. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import AppstoreTransition
11 |
12 | class ViewController: UIViewController {
13 | @IBOutlet weak var collectionView: UICollectionView!
14 |
15 | private var transition: CardTransition?
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 |
20 | collectionView.dataSource = self
21 | collectionView.delegate = self
22 | collectionView.register(UINib(nibName: "Type1CollectionViewCell", bundle: Bundle.main), forCellWithReuseIdentifier: "Type1CollectionViewCell")
23 | collectionView.register(UINib(nibName: "Type2CollectionViewCell", bundle: Bundle.main), forCellWithReuseIdentifier: "Type2CollectionViewCell")
24 |
25 | let layout = (collectionView.collectionViewLayout as! UICollectionViewFlowLayout)
26 | let aspect: CGFloat = 1.272
27 | let width = UIScreen.main.bounds.width
28 | layout.itemSize = CGSize(width:width, height: width * aspect)
29 | layout.minimumLineSpacing = 0
30 | layout.minimumInteritemSpacing = 0
31 | }
32 |
33 | }
34 |
35 | extension ViewController: UICollectionViewDataSource {
36 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
37 | return 4
38 | }
39 |
40 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
41 | var cell: UICollectionViewCell
42 |
43 | switch indexPath.row {
44 | case 0:
45 | cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Type1CollectionViewCell", for: indexPath)
46 | case 1:
47 | cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Type2CollectionViewCell", for: indexPath)
48 | case 2:
49 | let customCell = collectionView.dequeueReusableCell(withReuseIdentifier: "Type1CollectionViewCell", for: indexPath) as! Type1CollectionViewCell
50 | customCell.subtitleLabel.text = "You can dismiss from bottom this one"
51 | customCell.backgroundImage.image = UIImage(named: "type1-bg-bottom")
52 | customCell.containerView.backgroundColor = .white
53 | customCell.reviewsLabel.textColor = UIColor(named: "text-color")
54 | customCell.commentImageView.tintColor = UIColor(named: "text-color")
55 | cell = customCell
56 | case 3:
57 | let customCell = collectionView.dequeueReusableCell(withReuseIdentifier: "Type2CollectionViewCell", for: indexPath) as! Type2CollectionViewCell
58 | customCell.subtitleLabel.text = "Bottom dismissible"
59 | customCell.backgroundImage.image = UIImage(named: "type2-bg-bottom")
60 | cell = customCell
61 | default:
62 | fatalError("Invalid cell")
63 | }
64 |
65 | return cell
66 | }
67 |
68 | }
69 |
70 | extension ViewController: UICollectionViewDelegate {
71 |
72 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
73 | switch indexPath.row {
74 | case 0:
75 | showType1(indexPath: indexPath)
76 | case 1:
77 | showType2(indexPath: indexPath)
78 | case 2:
79 | showType3(indexPath: indexPath, bottomDismiss: true)
80 | case 3:
81 | showType4(indexPath: indexPath, bottomDismiss: true)
82 | default:
83 | fatalError("Invalid cell")
84 | }
85 | }
86 |
87 | private func showType1(indexPath: IndexPath, bottomDismiss: Bool = false) {
88 | let storyboard = UIStoryboard(name: "Main", bundle: nil)
89 | let viewController = storyboard.instantiateViewController(withIdentifier: "type1") as! Type1ViewController
90 |
91 | if let cell = collectionView.cellForItem(at: indexPath) as? Type1CollectionViewCell {
92 | cell.bottomContainer.alpha = 0
93 |
94 | viewController.dismissAnimationFinishedAction = {
95 | UIView.animate(withDuration: 0.3, animations: {
96 | cell.bottomContainer.alpha = 1.0
97 | })
98 | }
99 | }
100 |
101 | // Get tapped cell location
102 | let cell = collectionView.cellForItem(at: indexPath) as! CardCollectionViewCell
103 |
104 | cell.settings.isEnabledBottomClose = bottomDismiss
105 | cell.settings.cardContainerInsets = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 0, right: 8.0)
106 |
107 | transition = CardTransition(cell: cell, settings: cell.settings)
108 | viewController.settings = cell.settings
109 | viewController.transitioningDelegate = transition
110 |
111 | // If `modalPresentationStyle` is not `.fullScreen`, this should be set to true to make status bar depends on presented vc.
112 | //viewController.modalPresentationCapturesStatusBarAppearance = true
113 | viewController.modalPresentationStyle = .custom
114 |
115 | presentExpansion(viewController, cell: cell, animated: true)
116 | }
117 |
118 | private func showType2(indexPath: IndexPath, bottomDismiss: Bool = false) {
119 | let storyboard = UIStoryboard(name: "Main", bundle: nil)
120 | let viewController = storyboard.instantiateViewController(withIdentifier: "type2") as! Type2ViewController
121 |
122 | // Get tapped cell location
123 | let cell = collectionView.cellForItem(at: indexPath) as! CardCollectionViewCell
124 | cell.settings.cardContainerInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 8.0, right: 16.0)
125 | cell.settings.isEnabledBottomClose = bottomDismiss
126 |
127 | transition = CardTransition(cell: cell, settings: cell.settings)
128 | viewController.settings = cell.settings
129 | viewController.transitioningDelegate = transition
130 |
131 | // If `modalPresentationStyle` is not `.fullScreen`, this should be set to true to make status bar depends on presented vc.
132 | //viewController.modalPresentationCapturesStatusBarAppearance = true
133 | viewController.modalPresentationStyle = .custom
134 |
135 | presentExpansion(viewController, cell: cell, animated: true)
136 | }
137 |
138 | private func showType3(indexPath: IndexPath, bottomDismiss: Bool = false) {
139 | let storyboard = UIStoryboard(name: "Main", bundle: nil)
140 | let viewController = storyboard.instantiateViewController(withIdentifier: "type1") as! Type1ViewController
141 |
142 | if let cell = collectionView.cellForItem(at: indexPath) as? Type1CollectionViewCell {
143 | cell.bottomContainer.alpha = 0
144 |
145 | viewController.dismissAnimationFinishedAction = {
146 | UIView.animate(withDuration: 0.3, animations: {
147 | cell.bottomContainer.alpha = 1.0
148 | })
149 | }
150 | }
151 |
152 | // Get tapped cell location
153 | let cell = collectionView.cellForItem(at: indexPath) as! CardCollectionViewCell
154 |
155 | cell.settings.isEnabledBottomClose = bottomDismiss
156 | cell.settings.cardContainerInsets = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 0, right: 8.0)
157 |
158 | transition = CardTransition(cell: cell, settings: cell.settings)
159 | viewController.settings = cell.settings
160 | viewController.transitioningDelegate = transition
161 | viewController.subtitle = "You can dismiss from bottom this one"
162 | viewController.backgroundImage = UIImage(named: "type1-bg-bottom")
163 | viewController.backgroundColor = .white
164 |
165 | // If `modalPresentationStyle` is not `.fullScreen`, this should be set to true to make status bar depends on presented vc.
166 | //viewController.modalPresentationCapturesStatusBarAppearance = true
167 | viewController.modalPresentationStyle = .custom
168 |
169 | presentExpansion(viewController, cell: cell, animated: true)
170 | }
171 |
172 | private func showType4(indexPath: IndexPath, bottomDismiss: Bool = false) {
173 | let storyboard = UIStoryboard(name: "Main", bundle: nil)
174 | let viewController = storyboard.instantiateViewController(withIdentifier: "type2") as! Type2ViewController
175 |
176 | // Get tapped cell location
177 | let cell = collectionView.cellForItem(at: indexPath) as! CardCollectionViewCell
178 | cell.settings.cardContainerInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 8.0, right: 16.0)
179 | cell.settings.isEnabledBottomClose = bottomDismiss
180 |
181 | transition = CardTransition(cell: cell, settings: cell.settings)
182 | viewController.settings = cell.settings
183 | viewController.transitioningDelegate = transition
184 | viewController.subtitle = "Bottom dismissible"
185 | viewController.background = UIImage(named: "type2-bg-bottom")
186 |
187 | // If `modalPresentationStyle` is not `.fullScreen`, this should be set to true to make status bar depends on presented vc.
188 | //viewController.modalPresentationCapturesStatusBarAppearance = true
189 | viewController.modalPresentationStyle = .custom
190 |
191 | presentExpansion(viewController, cell: cell, animated: true)
192 | }
193 |
194 | }
195 |
196 | extension ViewController: CardsViewController {
197 |
198 | }
199 |
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Views/Type1HeaderView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Type1HeaderView.swift
3 | // AppStoreTransitionAnimation
4 | //
5 | // Created by Razvan Chelemen on 15/05/2019.
6 | // Copyright © 2019 appssemble. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class Type1HeaderView: UIView {
12 | @IBOutlet weak var topContainerView: UIView!
13 | @IBOutlet weak var playerImageView: UIImageView!
14 | @IBOutlet weak var subtitleLabel: UILabel!
15 | @IBOutlet weak var backgroundImage: UIImageView!
16 |
17 | @IBOutlet weak var topContainerHeight: NSLayoutConstraint!
18 | @IBOutlet weak var textWidth: NSLayoutConstraint!
19 |
20 | override func awakeFromNib() {
21 | super.awakeFromNib()
22 |
23 | textWidth.constant = UIScreen.main.bounds.width - 32.0
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/Example/AppStoreTransitionAnimation/Views/Type1HeaderView.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 |
40 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
88 |
94 |
100 |
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 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | platform :ios, '11.0'
3 |
4 | target 'AppStoreTransitionAnimation' do
5 | # Comment the next line if you don't want to use dynamic frameworks
6 | use_frameworks!
7 |
8 | # Pods for AppStoreTransitionAnimation
9 | pod 'appstore-card-transition', :path => '../'
10 |
11 | end
12 |
--------------------------------------------------------------------------------
/Example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - appstore-card-transition (1.0.3)
3 |
4 | DEPENDENCIES:
5 | - appstore-card-transition (from `../`)
6 |
7 | EXTERNAL SOURCES:
8 | appstore-card-transition:
9 | :path: "../"
10 |
11 | SPEC CHECKSUMS:
12 | appstore-card-transition: 909d2198d6c22676eea76bac2721e9026265aa90
13 |
14 | PODFILE CHECKSUM: 1ed7eee8a7159fd92c1c137ef930ca0697e46588
15 |
16 | COCOAPODS: 1.7.5
17 |
--------------------------------------------------------------------------------
/Example/Pods/Local Podspecs/appstore-card-transition.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "appstore-card-transition",
3 | "version": "1.0.3",
4 | "summary": "Appstore card animation transition. UICollectionView and UITableView card expand animated transition for iOS.",
5 | "module_name": "AppstoreTransition",
6 | "swift_versions": "5.0",
7 | "description": "Appstore card animation transition. UICollectionView and UITableView card expand animated transition for iOS. Expandable collectionview and tableview cells. Master detail transition animation.",
8 | "homepage": "https://github.com/appssemble/appstore-card-transition",
9 | "screenshots": [
10 | "https://github.com/appssemble/appstore-card-transition/blob/master/gif/example1.gif?raw=true",
11 | "https://github.com/appssemble/appstore-card-transition/blob/master/gif/example2.gif?raw=true"
12 | ],
13 | "license": {
14 | "type": "MIT",
15 | "file": "LICENSE"
16 | },
17 | "authors": {
18 | "Razvan Chelemen": "chelemen.razvan@gmail.com"
19 | },
20 | "social_media_url": "https://twitter.com/chelemen_razvan",
21 | "platforms": {
22 | "ios": "11.0"
23 | },
24 | "source": {
25 | "git": "https://github.com/appssemble/appstore-card-transition.git",
26 | "tag": "1.0.3"
27 | },
28 | "source_files": "Sources/AppstoreTransition/*.{h,m,swift,c}",
29 | "swift_version": "5.0"
30 | }
31 |
--------------------------------------------------------------------------------
/Example/Pods/Manifest.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - appstore-card-transition (1.0.3)
3 |
4 | DEPENDENCIES:
5 | - appstore-card-transition (from `../`)
6 |
7 | EXTERNAL SOURCES:
8 | appstore-card-transition:
9 | :path: "../"
10 |
11 | SPEC CHECKSUMS:
12 | appstore-card-transition: 909d2198d6c22676eea76bac2721e9026265aa90
13 |
14 | PODFILE CHECKSUM: 1ed7eee8a7159fd92c1c137ef930ca0697e46588
15 |
16 | COCOAPODS: 1.7.5
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-AppStoreTransitionAnimation/Pods-AppStoreTransitionAnimation-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-AppStoreTransitionAnimation/Pods-AppStoreTransitionAnimation-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## appstore-card-transition
5 |
6 | MIT License
7 |
8 | Copyright (c) 2019 appssemble
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy
11 | of this software and associated documentation files (the "Software"), to deal
12 | in the Software without restriction, including without limitation the rights
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | copies of the Software, and to permit persons to whom the Software is
15 | furnished to do so, subject to the following conditions:
16 |
17 | The above copyright notice and this permission notice shall be included in all
18 | copies or substantial portions of the Software.
19 |
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 | SOFTWARE.
27 |
28 | Generated by CocoaPods - https://cocoapods.org
29 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-AppStoreTransitionAnimation/Pods-AppStoreTransitionAnimation-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | MIT License
18 |
19 | Copyright (c) 2019 appssemble
20 |
21 | Permission is hereby granted, free of charge, to any person obtaining a copy
22 | of this software and associated documentation files (the "Software"), to deal
23 | in the Software without restriction, including without limitation the rights
24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25 | copies of the Software, and to permit persons to whom the Software is
26 | furnished to do so, subject to the following conditions:
27 |
28 | The above copyright notice and this permission notice shall be included in all
29 | copies or substantial portions of the Software.
30 |
31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37 | SOFTWARE.
38 |
39 | License
40 | MIT
41 | Title
42 | appstore-card-transition
43 | Type
44 | PSGroupSpecifier
45 |
46 |
47 | FooterText
48 | Generated by CocoaPods - https://cocoapods.org
49 | Title
50 |
51 | Type
52 | PSGroupSpecifier
53 |
54 |
55 | StringsTable
56 | Acknowledgements
57 | Title
58 | Acknowledgements
59 |
60 |
61 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-AppStoreTransitionAnimation/Pods-AppStoreTransitionAnimation-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_AppStoreTransitionAnimation : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_AppStoreTransitionAnimation
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-AppStoreTransitionAnimation/Pods-AppStoreTransitionAnimation-frameworks-Debug-input-files.xcfilelist:
--------------------------------------------------------------------------------
1 | ${PODS_ROOT}/Target Support Files/Pods-AppStoreTransitionAnimation/Pods-AppStoreTransitionAnimation-frameworks.sh
2 | ${BUILT_PRODUCTS_DIR}/appstore-card-transition/AppstoreTransition.framework
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-AppStoreTransitionAnimation/Pods-AppStoreTransitionAnimation-frameworks-Debug-output-files.xcfilelist:
--------------------------------------------------------------------------------
1 | ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AppstoreTransition.framework
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-AppStoreTransitionAnimation/Pods-AppStoreTransitionAnimation-frameworks-Release-input-files.xcfilelist:
--------------------------------------------------------------------------------
1 | ${PODS_ROOT}/Target Support Files/Pods-AppStoreTransitionAnimation/Pods-AppStoreTransitionAnimation-frameworks.sh
2 | ${BUILT_PRODUCTS_DIR}/appstore-card-transition/AppstoreTransition.framework
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-AppStoreTransitionAnimation/Pods-AppStoreTransitionAnimation-frameworks-Release-output-files.xcfilelist:
--------------------------------------------------------------------------------
1 | ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AppstoreTransition.framework
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-AppStoreTransitionAnimation/Pods-AppStoreTransitionAnimation-frameworks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 | set -u
4 | set -o pipefail
5 |
6 | function on_error {
7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure"
8 | }
9 | trap 'on_error $LINENO' ERR
10 |
11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then
12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy
13 | # frameworks to, so exit 0 (signalling the script phase was successful).
14 | exit 0
15 | fi
16 |
17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
19 |
20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}"
21 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
22 |
23 | # Used as a return value for each invocation of `strip_invalid_archs` function.
24 | STRIP_BINARY_RETVAL=0
25 |
26 | # This protects against multiple targets copying the same framework dependency at the same time. The solution
27 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
28 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
29 |
30 | # Copies and strips a vendored framework
31 | install_framework()
32 | {
33 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
34 | local source="${BUILT_PRODUCTS_DIR}/$1"
35 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
36 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
37 | elif [ -r "$1" ]; then
38 | local source="$1"
39 | fi
40 |
41 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
42 |
43 | if [ -L "${source}" ]; then
44 | echo "Symlinked..."
45 | source="$(readlink "${source}")"
46 | fi
47 |
48 | # Use filter instead of exclude so missing patterns don't throw errors.
49 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
50 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
51 |
52 | local basename
53 | basename="$(basename -s .framework "$1")"
54 | binary="${destination}/${basename}.framework/${basename}"
55 |
56 | if ! [ -r "$binary" ]; then
57 | binary="${destination}/${basename}"
58 | elif [ -L "${binary}" ]; then
59 | echo "Destination binary is symlinked..."
60 | dirname="$(dirname "${binary}")"
61 | binary="${dirname}/$(readlink "${binary}")"
62 | fi
63 |
64 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
65 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
66 | strip_invalid_archs "$binary"
67 | fi
68 |
69 | # Resign the code if required by the build settings to avoid unstable apps
70 | code_sign_if_enabled "${destination}/$(basename "$1")"
71 |
72 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
73 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
74 | local swift_runtime_libs
75 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u)
76 | for lib in $swift_runtime_libs; do
77 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
78 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
79 | code_sign_if_enabled "${destination}/${lib}"
80 | done
81 | fi
82 | }
83 |
84 | # Copies and strips a vendored dSYM
85 | install_dsym() {
86 | local source="$1"
87 | if [ -r "$source" ]; then
88 | # Copy the dSYM into a the targets temp dir.
89 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\""
90 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}"
91 |
92 | local basename
93 | basename="$(basename -s .framework.dSYM "$source")"
94 | binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}"
95 |
96 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
97 | if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then
98 | strip_invalid_archs "$binary"
99 | fi
100 |
101 | if [[ $STRIP_BINARY_RETVAL == 1 ]]; then
102 | # Move the stripped file into its final destination.
103 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\""
104 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}"
105 | else
106 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing.
107 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM"
108 | fi
109 | fi
110 | }
111 |
112 | # Copies the bcsymbolmap files of a vendored framework
113 | install_bcsymbolmap() {
114 | local bcsymbolmap_path="$1"
115 | local destination="${BUILT_PRODUCTS_DIR}"
116 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}""
117 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"
118 | }
119 |
120 | # Signs a framework with the provided identity
121 | code_sign_if_enabled() {
122 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
123 | # Use the current code_sign_identity
124 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
125 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'"
126 |
127 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
128 | code_sign_cmd="$code_sign_cmd &"
129 | fi
130 | echo "$code_sign_cmd"
131 | eval "$code_sign_cmd"
132 | fi
133 | }
134 |
135 | # Strip invalid architectures
136 | strip_invalid_archs() {
137 | binary="$1"
138 | # Get architectures for current target binary
139 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)"
140 | # Intersect them with the architectures we are building for
141 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)"
142 | # If there are no archs supported by this binary then warn the user
143 | if [[ -z "$intersected_archs" ]]; then
144 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)."
145 | STRIP_BINARY_RETVAL=0
146 | return
147 | fi
148 | stripped=""
149 | for arch in $binary_archs; do
150 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then
151 | # Strip non-valid architectures in-place
152 | lipo -remove "$arch" -output "$binary" "$binary"
153 | stripped="$stripped $arch"
154 | fi
155 | done
156 | if [[ "$stripped" ]]; then
157 | echo "Stripped $binary of architectures:$stripped"
158 | fi
159 | STRIP_BINARY_RETVAL=1
160 | }
161 |
162 |
163 | if [[ "$CONFIGURATION" == "Debug" ]]; then
164 | install_framework "${BUILT_PRODUCTS_DIR}/appstore-card-transition/AppstoreTransition.framework"
165 | fi
166 | if [[ "$CONFIGURATION" == "Release" ]]; then
167 | install_framework "${BUILT_PRODUCTS_DIR}/appstore-card-transition/AppstoreTransition.framework"
168 | fi
169 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
170 | wait
171 | fi
172 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-AppStoreTransitionAnimation/Pods-AppStoreTransitionAnimation-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_AppStoreTransitionAnimationVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_AppStoreTransitionAnimationVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-AppStoreTransitionAnimation/Pods-AppStoreTransitionAnimation.debug.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/appstore-card-transition"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/appstore-card-transition/AppstoreTransition.framework/Headers"
5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
6 | OTHER_LDFLAGS = $(inherited) -framework "AppstoreTransition"
7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
11 | PODS_ROOT = ${SRCROOT}/Pods
12 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-AppStoreTransitionAnimation/Pods-AppStoreTransitionAnimation.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_AppStoreTransitionAnimation {
2 | umbrella header "Pods-AppStoreTransitionAnimation-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-AppStoreTransitionAnimation/Pods-AppStoreTransitionAnimation.release.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/appstore-card-transition"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/appstore-card-transition/AppstoreTransition.framework/Headers"
5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
6 | OTHER_LDFLAGS = $(inherited) -framework "AppstoreTransition"
7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
11 | PODS_ROOT = ${SRCROOT}/Pods
12 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/appstore-card-transition/appstore-card-transition-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 | 1.0.3
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/appstore-card-transition/appstore-card-transition-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_appstore_card_transition : NSObject
3 | @end
4 | @implementation PodsDummy_appstore_card_transition
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/appstore-card-transition/appstore-card-transition-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/appstore-card-transition/appstore-card-transition-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 | #import "AppstoreTransition.h"
14 |
15 | FOUNDATION_EXPORT double AppstoreTransitionVersionNumber;
16 | FOUNDATION_EXPORT const unsigned char AppstoreTransitionVersionString[];
17 |
18 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/appstore-card-transition/appstore-card-transition.modulemap:
--------------------------------------------------------------------------------
1 | framework module AppstoreTransition {
2 | umbrella header "appstore-card-transition-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/appstore-card-transition/appstore-card-transition.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/appstore-card-transition
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
4 | PODS_BUILD_DIR = ${BUILD_DIR}
5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
6 | PODS_ROOT = ${SRCROOT}
7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
9 | SKIP_INSTALL = YES
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 appssemble
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "AppstoreTransition",
8 | platforms: [.iOS(.v11)],
9 | products: [
10 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
11 | .library(
12 | name: "AppstoreTransition",
13 | targets: ["AppstoreTransition"]),
14 | ],
15 | dependencies: [
16 | // Dependencies declare other packages that this package depends on.
17 | // .package(url: /* package url */, from: "1.0.0"),
18 | ],
19 | targets: [
20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
21 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
22 | .target(
23 | name: "AppstoreTransition",
24 | dependencies: [])
25 | ]
26 | )
27 |
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # appstore-card-transition
4 |
5 | [](http://cocoapods.org/pods/appstore-card-transition)
6 | [](https://github.com/appssemble/appstore-card-transition/blob/master/LICENSE?raw=true)
7 | 
8 | 
9 | 
10 |
11 | Appstore card animation transition. UICollectionView and UITableView card expand animated transition. This library tries to add the appstore transition to your own app. The goal is to be as simple as possible to integrate in an app while keeping the flexibility and customization alive.
12 |
13 | ### Top dismissal
14 |
15 |
16 |
17 | ### Bottom dismissal
18 |
19 |
20 |
21 | ## How it works
22 |
23 | appstore-card-transition uses the `UIViewControllerTransitioningDelegate` to implement the a custom transition animation. The initial frame of the selected cell is taken and the details view controller is animated from that position to the final expanded mode. Making sure that the expansion of the details view controller looks good falls in your responsability.
24 |
25 | For closing the details view controller, gesture recognizers are used and the details view controller is animated back to the size of the cell, note that you can change the position of the cell while the details is shown to provide a more dynamic behaviour.
26 |
27 | Most of the parameteres are customizable and callbacks for each meaningful action is provided.
28 |
29 | ## Installation
30 |
31 | ### CocoaPods
32 |
33 | [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command:
34 |
35 | ```bash
36 | $ gem install cocoapods
37 | ```
38 |
39 | To integrate appstore-card-transition into your Xcode project using CocoaPods, specify it in your `Podfile`:
40 |
41 | ```ruby
42 | use_frameworks!
43 |
44 | target '' do
45 | pod 'appstore-card-transition'
46 | end
47 | ```
48 |
49 | Then, run the following command:
50 |
51 | ```bash
52 | $ pod repo update
53 | $ pod install
54 | ```
55 |
56 | ### Swift Package Manager
57 |
58 | The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. It is in early development, but appstore-card-transition does support its use on supported platforms.
59 |
60 | Once you have your Swift package set up, adding appstore-card-transition as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.
61 |
62 | ```swift
63 | dependencies: [
64 | .package(url: "https://github.com/appssemble/appstore-card-transition.git", from: "1.0.3")
65 | ]
66 | ```
67 |
68 | ### Manual
69 |
70 | Add the library project as a subproject and set the library as a target dependency. Here is a step by step that we recommend:
71 |
72 | 1. Clone this repo (as a submodule or in a different directory, it's up to you);
73 | 2. Drag `AppstoreTransition.xcodeproj` as a subproject;
74 | 3. In your main `.xcodeproj` file, select the desired target(s);
75 | 4. Go to **Build Phases**, expand Target Dependencies, and add `AppstoreTransition`;
76 | 5. In Swift, `import AppstoreTransition` and you are good to go!
77 |
78 | ## Basic usage guide
79 |
80 | First make sure your cells implement the `CardCollectionViewCell` protocol.
81 |
82 | ```swift
83 | extension YourCollectionViewCell: CardCollectionViewCell {
84 | var cardContentView: UIView {
85 | get {
86 | return containerView
87 | }
88 | }
89 | }
90 | ```
91 |
92 | If you want your cells to also be responsive to touches, override the following methods:
93 |
94 | ```swift
95 | override func touchesBegan(_ touches: Set, with event: UIEvent?) {
96 | super.touchesBegan(touches, with: event)
97 | animate(isHighlighted: true)
98 | }
99 |
100 | override func touchesEnded(_ touches: Set, with event: UIEvent?) {
101 | super.touchesEnded(touches, with: event)
102 | animate(isHighlighted: false)
103 | }
104 |
105 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) {
106 | super.touchesCancelled(touches, with: event)
107 | animate(isHighlighted: false)
108 | }
109 | ```
110 |
111 | Your cell is now all setup, now go to your details view controller and make it conform to the `CardDetailViewController` protocol.
112 |
113 | ```swift
114 | extension YourDetailsViewController: CardDetailViewController {
115 |
116 | var scrollView: UIScrollView {
117 | return contentScrollView // the scrollview (or tableview) you use in your details view controller
118 | }
119 |
120 |
121 | var cardContentView: UIView {
122 | return headerView // can be just a view at the top of the scrollview or the tableHeaderView
123 | }
124 |
125 | }
126 | ```
127 |
128 | One more thing you need to hook in your details view controller. Make sure you call the `dismissHandler` in your `scrollViewDidScroll`:
129 |
130 | ```swift
131 | func scrollViewDidScroll(_ scrollView: UIScrollView) {
132 | dismissHandler.scrollViewDidScroll(scrollView)
133 | }
134 | ```
135 |
136 | Now you are ready to add the actual transition. In your `cellForItemAt` method, after you configured your cell as desired, make sure you set the following:
137 |
138 | ```swift
139 | cell.settings.cardContainerInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 8.0, right: 16.0) //set this only if your cardContentView has some margins relative to the actual cell content view.
140 |
141 | transition = CardTransition(cell: cell, settings: cell.settings) //create the transition
142 | viewController.settings = cell.settings //make sure same settings are used by both the details view controller and the cell
143 | //set the transition
144 | viewController.transitioningDelegate = transition
145 | viewController.modalPresentationStyle = .custom
146 |
147 | //actually present the details view controller
148 | presentExpansion(viewController, cell: cell, animated: true)
149 | ```
150 |
151 | If you got here you should now have a basic appstore transition. It might not be perfect yet but its definitely a strong start. If something doesn't look well check if the constraints from your details view controller play well with resizing.
152 |
153 | ## Tweaking and troubleshooting
154 |
155 | Playing with the parameters: check the `TransitionSettings` class.
156 | Most common issues are animation glitches. To prevent those, make sure your constraints are properly set (especailly the top ones) and safe areas work as expected.
157 |
158 | Next, make sure your `cardContainerInsets` are properly set and they reflect the actual ones from your cell.
159 |
160 | Lastly, your scrollview might need some scrolling to match the actual cell look (it might need some more top inset than the cell for instance). For this case you can scroll the content as needed in your `viewDidLoad` method and for the dismiss animation you can use the `dismissalScrollViewContentOffset` property from `TransitionSettings`.
161 |
162 | ## Customization
163 |
164 | Most often than not, you'll want to animate some other content alongside the appstore animation. For this purpose action blocks are available. You can implement the following callbacks to receive changes in the transition progress.
165 |
166 | ```swift
167 | extension YourDetailsViewController: CardDetailViewController {
168 |
169 | func didStartPresentAnimationProgress() { ... }
170 | func didFinishPresentAnimationProgress() { ... }
171 |
172 | func didBeginDismissAnimation() { ... }
173 | func didChangeDismissAnimationProgress(progress:CGFloat) { ... }
174 | func didFinishDismissAnimation() { ... }
175 | func didCancelDismissAnimation(progress:CGFloat) { ... }
176 |
177 | }
178 | ```
179 |
180 | ## Example
181 |
182 | Checkout the demo project to see some examples of what the library can do and how its done.
183 |
184 | ## Contribute
185 |
186 | We're aware that this is far from perfect so any improvement or feature is welcomed. If you made an awesome change don't hesitate to create a pull request.
187 |
188 | This project is inspired from [this project](https://github.com/aunnnn/AppStoreiOS11InteractiveTransition)
189 |
190 | ## Let us help you
191 |
192 | Do you need help with your app development? [Drop us a line](https://appssemble.com)
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
--------------------------------------------------------------------------------
/Sources/AppstoreTransition.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 74C1F3D8228BECEB000B6AF2 /* AppstoreTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = 74C1F3D6228BECEB000B6AF2 /* AppstoreTransition.h */; settings = {ATTRIBUTES = (Public, ); }; };
11 | 74C1F3E6228BEDE8000B6AF2 /* CardCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C1F3E5228BEDE8000B6AF2 /* CardCollectionViewCell.swift */; };
12 | 74C1F3EC228BEDF9000B6AF2 /* CardTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C1F3E7228BEDF7000B6AF2 /* CardTransition.swift */; };
13 | 74C1F3ED228BEDF9000B6AF2 /* CardPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C1F3E8228BEDF9000B6AF2 /* CardPresentationController.swift */; };
14 | 74C1F3EE228BEDF9000B6AF2 /* DismissCardAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C1F3E9228BEDF9000B6AF2 /* DismissCardAnimator.swift */; };
15 | 74C1F3EF228BEDF9000B6AF2 /* CardDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C1F3EA228BEDF9000B6AF2 /* CardDetailViewController.swift */; };
16 | 74C1F3F0228BEDF9000B6AF2 /* PresentCardAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C1F3EB228BEDF9000B6AF2 /* PresentCardAnimator.swift */; };
17 | 74C1F3F9228BEF2C000B6AF2 /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C1F3F8228BEF2C000B6AF2 /* UIView+Extension.swift */; };
18 | 74C1F405228C03FB000B6AF2 /* UIViewController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C1F404228C03FB000B6AF2 /* UIViewController+Extension.swift */; };
19 | /* End PBXBuildFile section */
20 |
21 | /* Begin PBXFileReference section */
22 | 74C1F3D3228BECEB000B6AF2 /* AppstoreTransition.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppstoreTransition.framework; sourceTree = BUILT_PRODUCTS_DIR; };
23 | 74C1F3D6228BECEB000B6AF2 /* AppstoreTransition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppstoreTransition.h; sourceTree = ""; };
24 | 74C1F3D7228BECEB000B6AF2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
25 | 74C1F3E5228BEDE8000B6AF2 /* CardCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardCollectionViewCell.swift; sourceTree = ""; };
26 | 74C1F3E7228BEDF7000B6AF2 /* CardTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardTransition.swift; sourceTree = ""; };
27 | 74C1F3E8228BEDF9000B6AF2 /* CardPresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardPresentationController.swift; sourceTree = ""; };
28 | 74C1F3E9228BEDF9000B6AF2 /* DismissCardAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DismissCardAnimator.swift; sourceTree = ""; };
29 | 74C1F3EA228BEDF9000B6AF2 /* CardDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardDetailViewController.swift; sourceTree = ""; };
30 | 74C1F3EB228BEDF9000B6AF2 /* PresentCardAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentCardAnimator.swift; sourceTree = ""; };
31 | 74C1F3F8228BEF2C000B6AF2 /* UIView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = ""; };
32 | 74C1F404228C03FB000B6AF2 /* UIViewController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extension.swift"; sourceTree = ""; };
33 | /* End PBXFileReference section */
34 |
35 | /* Begin PBXFrameworksBuildPhase section */
36 | 74C1F3D0228BECEB000B6AF2 /* Frameworks */ = {
37 | isa = PBXFrameworksBuildPhase;
38 | buildActionMask = 2147483647;
39 | files = (
40 | );
41 | runOnlyForDeploymentPostprocessing = 0;
42 | };
43 | /* End PBXFrameworksBuildPhase section */
44 |
45 | /* Begin PBXGroup section */
46 | 74C1F3C9228BECEB000B6AF2 = {
47 | isa = PBXGroup;
48 | children = (
49 | 74C1F3D5228BECEB000B6AF2 /* AppstoreTransition */,
50 | 74C1F3D4228BECEB000B6AF2 /* Products */,
51 | );
52 | sourceTree = "";
53 | };
54 | 74C1F3D4228BECEB000B6AF2 /* Products */ = {
55 | isa = PBXGroup;
56 | children = (
57 | 74C1F3D3228BECEB000B6AF2 /* AppstoreTransition.framework */,
58 | );
59 | name = Products;
60 | sourceTree = "";
61 | };
62 | 74C1F3D5228BECEB000B6AF2 /* AppstoreTransition */ = {
63 | isa = PBXGroup;
64 | children = (
65 | 74C1F3EA228BEDF9000B6AF2 /* CardDetailViewController.swift */,
66 | 74C1F3E8228BEDF9000B6AF2 /* CardPresentationController.swift */,
67 | 74C1F3E7228BEDF7000B6AF2 /* CardTransition.swift */,
68 | 74C1F3E9228BEDF9000B6AF2 /* DismissCardAnimator.swift */,
69 | 74C1F3EB228BEDF9000B6AF2 /* PresentCardAnimator.swift */,
70 | 74C1F3E5228BEDE8000B6AF2 /* CardCollectionViewCell.swift */,
71 | 74C1F3D6228BECEB000B6AF2 /* AppstoreTransition.h */,
72 | 74C1F3D7228BECEB000B6AF2 /* Info.plist */,
73 | 74C1F3F8228BEF2C000B6AF2 /* UIView+Extension.swift */,
74 | 74C1F404228C03FB000B6AF2 /* UIViewController+Extension.swift */,
75 | );
76 | path = AppstoreTransition;
77 | sourceTree = "";
78 | };
79 | /* End PBXGroup section */
80 |
81 | /* Begin PBXHeadersBuildPhase section */
82 | 74C1F3CE228BECEB000B6AF2 /* Headers */ = {
83 | isa = PBXHeadersBuildPhase;
84 | buildActionMask = 2147483647;
85 | files = (
86 | 74C1F3D8228BECEB000B6AF2 /* AppstoreTransition.h in Headers */,
87 | );
88 | runOnlyForDeploymentPostprocessing = 0;
89 | };
90 | /* End PBXHeadersBuildPhase section */
91 |
92 | /* Begin PBXNativeTarget section */
93 | 74C1F3D2228BECEB000B6AF2 /* AppstoreTransition */ = {
94 | isa = PBXNativeTarget;
95 | buildConfigurationList = 74C1F3DB228BECEB000B6AF2 /* Build configuration list for PBXNativeTarget "AppstoreTransition" */;
96 | buildPhases = (
97 | 74C1F3CE228BECEB000B6AF2 /* Headers */,
98 | 74C1F3CF228BECEB000B6AF2 /* Sources */,
99 | 74C1F3D0228BECEB000B6AF2 /* Frameworks */,
100 | 74C1F3D1228BECEB000B6AF2 /* Resources */,
101 | );
102 | buildRules = (
103 | );
104 | dependencies = (
105 | );
106 | name = AppstoreTransition;
107 | productName = AppstoreTransition;
108 | productReference = 74C1F3D3228BECEB000B6AF2 /* AppstoreTransition.framework */;
109 | productType = "com.apple.product-type.framework";
110 | };
111 | /* End PBXNativeTarget section */
112 |
113 | /* Begin PBXProject section */
114 | 74C1F3CA228BECEB000B6AF2 /* Project object */ = {
115 | isa = PBXProject;
116 | attributes = {
117 | LastUpgradeCheck = 1020;
118 | ORGANIZATIONNAME = appssemble;
119 | TargetAttributes = {
120 | 74C1F3D2228BECEB000B6AF2 = {
121 | CreatedOnToolsVersion = 10.2;
122 | LastSwiftMigration = 1020;
123 | };
124 | };
125 | };
126 | buildConfigurationList = 74C1F3CD228BECEB000B6AF2 /* Build configuration list for PBXProject "AppstoreTransition" */;
127 | compatibilityVersion = "Xcode 9.3";
128 | developmentRegion = en;
129 | hasScannedForEncodings = 0;
130 | knownRegions = (
131 | en,
132 | );
133 | mainGroup = 74C1F3C9228BECEB000B6AF2;
134 | productRefGroup = 74C1F3D4228BECEB000B6AF2 /* Products */;
135 | projectDirPath = "";
136 | projectRoot = "";
137 | targets = (
138 | 74C1F3D2228BECEB000B6AF2 /* AppstoreTransition */,
139 | );
140 | };
141 | /* End PBXProject section */
142 |
143 | /* Begin PBXResourcesBuildPhase section */
144 | 74C1F3D1228BECEB000B6AF2 /* Resources */ = {
145 | isa = PBXResourcesBuildPhase;
146 | buildActionMask = 2147483647;
147 | files = (
148 | );
149 | runOnlyForDeploymentPostprocessing = 0;
150 | };
151 | /* End PBXResourcesBuildPhase section */
152 |
153 | /* Begin PBXSourcesBuildPhase section */
154 | 74C1F3CF228BECEB000B6AF2 /* Sources */ = {
155 | isa = PBXSourcesBuildPhase;
156 | buildActionMask = 2147483647;
157 | files = (
158 | 74C1F405228C03FB000B6AF2 /* UIViewController+Extension.swift in Sources */,
159 | 74C1F3EF228BEDF9000B6AF2 /* CardDetailViewController.swift in Sources */,
160 | 74C1F3F9228BEF2C000B6AF2 /* UIView+Extension.swift in Sources */,
161 | 74C1F3ED228BEDF9000B6AF2 /* CardPresentationController.swift in Sources */,
162 | 74C1F3EE228BEDF9000B6AF2 /* DismissCardAnimator.swift in Sources */,
163 | 74C1F3EC228BEDF9000B6AF2 /* CardTransition.swift in Sources */,
164 | 74C1F3F0228BEDF9000B6AF2 /* PresentCardAnimator.swift in Sources */,
165 | 74C1F3E6228BEDE8000B6AF2 /* CardCollectionViewCell.swift in Sources */,
166 | );
167 | runOnlyForDeploymentPostprocessing = 0;
168 | };
169 | /* End PBXSourcesBuildPhase section */
170 |
171 | /* Begin XCBuildConfiguration section */
172 | 74C1F3D9228BECEB000B6AF2 /* Debug */ = {
173 | isa = XCBuildConfiguration;
174 | buildSettings = {
175 | ALWAYS_SEARCH_USER_PATHS = NO;
176 | CLANG_ANALYZER_NONNULL = YES;
177 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
178 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
179 | CLANG_CXX_LIBRARY = "libc++";
180 | CLANG_ENABLE_MODULES = YES;
181 | CLANG_ENABLE_OBJC_ARC = YES;
182 | CLANG_ENABLE_OBJC_WEAK = YES;
183 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
184 | CLANG_WARN_BOOL_CONVERSION = YES;
185 | CLANG_WARN_COMMA = YES;
186 | CLANG_WARN_CONSTANT_CONVERSION = YES;
187 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
188 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
189 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
190 | CLANG_WARN_EMPTY_BODY = YES;
191 | CLANG_WARN_ENUM_CONVERSION = YES;
192 | CLANG_WARN_INFINITE_RECURSION = YES;
193 | CLANG_WARN_INT_CONVERSION = YES;
194 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
195 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
196 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
197 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
198 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
199 | CLANG_WARN_STRICT_PROTOTYPES = YES;
200 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
201 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
202 | CLANG_WARN_UNREACHABLE_CODE = YES;
203 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
204 | CODE_SIGN_IDENTITY = "iPhone Developer";
205 | COPY_PHASE_STRIP = NO;
206 | CURRENT_PROJECT_VERSION = 1;
207 | DEBUG_INFORMATION_FORMAT = dwarf;
208 | ENABLE_STRICT_OBJC_MSGSEND = YES;
209 | ENABLE_TESTABILITY = YES;
210 | GCC_C_LANGUAGE_STANDARD = gnu11;
211 | GCC_DYNAMIC_NO_PIC = NO;
212 | GCC_NO_COMMON_BLOCKS = YES;
213 | GCC_OPTIMIZATION_LEVEL = 0;
214 | GCC_PREPROCESSOR_DEFINITIONS = (
215 | "DEBUG=1",
216 | "$(inherited)",
217 | );
218 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
219 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
220 | GCC_WARN_UNDECLARED_SELECTOR = YES;
221 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
222 | GCC_WARN_UNUSED_FUNCTION = YES;
223 | GCC_WARN_UNUSED_VARIABLE = YES;
224 | IPHONEOS_DEPLOYMENT_TARGET = 12.2;
225 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
226 | MTL_FAST_MATH = YES;
227 | ONLY_ACTIVE_ARCH = YES;
228 | SDKROOT = iphoneos;
229 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
230 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
231 | VERSIONING_SYSTEM = "apple-generic";
232 | VERSION_INFO_PREFIX = "";
233 | };
234 | name = Debug;
235 | };
236 | 74C1F3DA228BECEB000B6AF2 /* Release */ = {
237 | isa = XCBuildConfiguration;
238 | buildSettings = {
239 | ALWAYS_SEARCH_USER_PATHS = NO;
240 | CLANG_ANALYZER_NONNULL = YES;
241 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
242 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
243 | CLANG_CXX_LIBRARY = "libc++";
244 | CLANG_ENABLE_MODULES = YES;
245 | CLANG_ENABLE_OBJC_ARC = YES;
246 | CLANG_ENABLE_OBJC_WEAK = YES;
247 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
248 | CLANG_WARN_BOOL_CONVERSION = YES;
249 | CLANG_WARN_COMMA = YES;
250 | CLANG_WARN_CONSTANT_CONVERSION = YES;
251 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
252 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
253 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
254 | CLANG_WARN_EMPTY_BODY = YES;
255 | CLANG_WARN_ENUM_CONVERSION = YES;
256 | CLANG_WARN_INFINITE_RECURSION = YES;
257 | CLANG_WARN_INT_CONVERSION = YES;
258 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
259 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
260 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
261 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
262 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
263 | CLANG_WARN_STRICT_PROTOTYPES = YES;
264 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
265 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
266 | CLANG_WARN_UNREACHABLE_CODE = YES;
267 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
268 | CODE_SIGN_IDENTITY = "iPhone Developer";
269 | COPY_PHASE_STRIP = NO;
270 | CURRENT_PROJECT_VERSION = 1;
271 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
272 | ENABLE_NS_ASSERTIONS = NO;
273 | ENABLE_STRICT_OBJC_MSGSEND = YES;
274 | GCC_C_LANGUAGE_STANDARD = gnu11;
275 | GCC_NO_COMMON_BLOCKS = YES;
276 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
277 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
278 | GCC_WARN_UNDECLARED_SELECTOR = YES;
279 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
280 | GCC_WARN_UNUSED_FUNCTION = YES;
281 | GCC_WARN_UNUSED_VARIABLE = YES;
282 | IPHONEOS_DEPLOYMENT_TARGET = 12.2;
283 | MTL_ENABLE_DEBUG_INFO = NO;
284 | MTL_FAST_MATH = YES;
285 | SDKROOT = iphoneos;
286 | SWIFT_COMPILATION_MODE = wholemodule;
287 | SWIFT_OPTIMIZATION_LEVEL = "-O";
288 | VALIDATE_PRODUCT = YES;
289 | VERSIONING_SYSTEM = "apple-generic";
290 | VERSION_INFO_PREFIX = "";
291 | };
292 | name = Release;
293 | };
294 | 74C1F3DC228BECEB000B6AF2 /* Debug */ = {
295 | isa = XCBuildConfiguration;
296 | buildSettings = {
297 | CLANG_ENABLE_MODULES = YES;
298 | CODE_SIGN_IDENTITY = "";
299 | CODE_SIGN_STYLE = Automatic;
300 | DEFINES_MODULE = YES;
301 | DEVELOPMENT_TEAM = WZYAY65GLB;
302 | DYLIB_COMPATIBILITY_VERSION = 1;
303 | DYLIB_CURRENT_VERSION = 1;
304 | DYLIB_INSTALL_NAME_BASE = "@rpath";
305 | INFOPLIST_FILE = AppstoreTransition/Info.plist;
306 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
307 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
308 | LD_RUNPATH_SEARCH_PATHS = (
309 | "$(inherited)",
310 | "@executable_path/Frameworks",
311 | "@loader_path/Frameworks",
312 | );
313 | PRODUCT_BUNDLE_IDENTIFIER = com.appssemble.AppstoreTransition;
314 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
315 | SKIP_INSTALL = YES;
316 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
317 | SWIFT_VERSION = 5.0;
318 | TARGETED_DEVICE_FAMILY = "1,2";
319 | };
320 | name = Debug;
321 | };
322 | 74C1F3DD228BECEB000B6AF2 /* Release */ = {
323 | isa = XCBuildConfiguration;
324 | buildSettings = {
325 | CLANG_ENABLE_MODULES = YES;
326 | CODE_SIGN_IDENTITY = "";
327 | CODE_SIGN_STYLE = Automatic;
328 | DEFINES_MODULE = YES;
329 | DEVELOPMENT_TEAM = WZYAY65GLB;
330 | DYLIB_COMPATIBILITY_VERSION = 1;
331 | DYLIB_CURRENT_VERSION = 1;
332 | DYLIB_INSTALL_NAME_BASE = "@rpath";
333 | INFOPLIST_FILE = AppstoreTransition/Info.plist;
334 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
335 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
336 | LD_RUNPATH_SEARCH_PATHS = (
337 | "$(inherited)",
338 | "@executable_path/Frameworks",
339 | "@loader_path/Frameworks",
340 | );
341 | PRODUCT_BUNDLE_IDENTIFIER = com.appssemble.AppstoreTransition;
342 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
343 | SKIP_INSTALL = YES;
344 | SWIFT_VERSION = 5.0;
345 | TARGETED_DEVICE_FAMILY = "1,2";
346 | };
347 | name = Release;
348 | };
349 | /* End XCBuildConfiguration section */
350 |
351 | /* Begin XCConfigurationList section */
352 | 74C1F3CD228BECEB000B6AF2 /* Build configuration list for PBXProject "AppstoreTransition" */ = {
353 | isa = XCConfigurationList;
354 | buildConfigurations = (
355 | 74C1F3D9228BECEB000B6AF2 /* Debug */,
356 | 74C1F3DA228BECEB000B6AF2 /* Release */,
357 | );
358 | defaultConfigurationIsVisible = 0;
359 | defaultConfigurationName = Release;
360 | };
361 | 74C1F3DB228BECEB000B6AF2 /* Build configuration list for PBXNativeTarget "AppstoreTransition" */ = {
362 | isa = XCConfigurationList;
363 | buildConfigurations = (
364 | 74C1F3DC228BECEB000B6AF2 /* Debug */,
365 | 74C1F3DD228BECEB000B6AF2 /* Release */,
366 | );
367 | defaultConfigurationIsVisible = 0;
368 | defaultConfigurationName = Release;
369 | };
370 | /* End XCConfigurationList section */
371 | };
372 | rootObject = 74C1F3CA228BECEB000B6AF2 /* Project object */;
373 | }
374 |
--------------------------------------------------------------------------------
/Sources/AppstoreTransition/AppstoreTransition.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppstoreTransition.h
3 | // AppstoreTransition
4 | //
5 | // Created by Razvan Chelemen on 15/05/2019.
6 | // Copyright © 2019 appssemble. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for AppstoreTransition.
12 | FOUNDATION_EXPORT double AppstoreTransitionVersionNumber;
13 |
14 | //! Project version string for AppstoreTransition.
15 | FOUNDATION_EXPORT const unsigned char AppstoreTransitionVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Sources/AppstoreTransition/CardCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CardCollectionViewCell.swift
3 | // Kickster
4 | //
5 | // Created by Razvan Chelemen on 08/05/2019.
6 | // Copyright © 2019 appssemble. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | private struct AssociatedKeys {
12 | static var settingsKey: UInt8 = 0
13 | static var disabledHighlightedAnimationKey: UInt8 = 0
14 | }
15 |
16 | public protocol CardCollectionViewCell: UIView {
17 | var cardContentView: UIView { get }
18 | var disabledHighlightedAnimation: Bool { get set }
19 | var settings: TransitionSettings { get }
20 |
21 | func resetTransform()
22 | func freezeAnimations()
23 | func unfreezeAnimations()
24 |
25 | }
26 |
27 | public extension CardCollectionViewCell {
28 |
29 | var disabledHighlightedAnimation:Bool {
30 | get {
31 | if let disabledHighlight = objc_getAssociatedObject(self, &AssociatedKeys.disabledHighlightedAnimationKey) as? Bool {
32 | return disabledHighlight
33 | } else {
34 | self.disabledHighlightedAnimation = false
35 | return disabledHighlightedAnimation
36 | }
37 | }
38 | set {
39 | objc_setAssociatedObject(self, &AssociatedKeys.disabledHighlightedAnimationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
40 | }
41 | }
42 |
43 | var settings:TransitionSettings {
44 | get {
45 | if let settings = objc_getAssociatedObject(self, &AssociatedKeys.settingsKey) as? TransitionSettings {
46 | return settings
47 | } else {
48 | self.settings = TransitionSettings()
49 | return settings
50 | }
51 | }
52 | set {
53 | objc_setAssociatedObject(self, &AssociatedKeys.settingsKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
54 | }
55 | }
56 |
57 | func resetTransform() {
58 | transform = .identity
59 | }
60 |
61 | func freezeAnimations() {
62 | disabledHighlightedAnimation = true
63 | layer.removeAllAnimations()
64 | }
65 |
66 | func unfreezeAnimations() {
67 | disabledHighlightedAnimation = false
68 | }
69 |
70 | func animate(isHighlighted: Bool, completion: ((Bool) -> Void)?=nil) {
71 | if disabledHighlightedAnimation {
72 | return
73 | }
74 | let animationOptions: UIView.AnimationOptions = settings.isEnabledAllowsUserInteractionWhileHighlightingCard
75 | ? [.allowUserInteraction] : []
76 | if isHighlighted {
77 | UIView.animate(withDuration: 0.5,
78 | delay: 0,
79 | usingSpringWithDamping: 1,
80 | initialSpringVelocity: 0,
81 | options: animationOptions, animations: {
82 | self.transform = .init(scaleX: self.settings.cardHighlightedFactor, y: self.settings.cardHighlightedFactor)
83 | }, completion: completion)
84 | } else {
85 | UIView.animate(withDuration: 0.5,
86 | delay: 0,
87 | usingSpringWithDamping: 1,
88 | initialSpringVelocity: 0,
89 | options: animationOptions, animations: {
90 | self.transform = .identity
91 | }, completion: completion)
92 | }
93 | }
94 |
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/Sources/AppstoreTransition/CardDetailViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CardDetailViewController.swift
3 | // Kickster
4 | //
5 | // Created by Razvan Chelemen on 08/05/2019.
6 | // Copyright © 2019 appssemble. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | private struct AssociatedKeys {
12 | static var settingsKey: UInt8 = 0
13 | static var dismissHandlerKey: UInt8 = 0
14 | }
15 |
16 | public protocol CardsViewController {
17 |
18 | }
19 |
20 | public protocol CardDetailViewController: UIViewController {
21 | var cardContentView: UIView { get }
22 | var scrollView: UIScrollView? { get }
23 | var settings: TransitionSettings { get set }
24 | var dismissHandler: CardDismissHandler { get }
25 |
26 | func didStartPresentAnimationProgress()
27 | func didFinishPresentAnimationProgress()
28 |
29 | func didBeginDismissAnimation()
30 | func didChangeDismissAnimationProgress(progress:CGFloat)
31 | func didStartDismissAnimation()
32 | func didFinishDismissAnimation()
33 | func didCancelDismissAnimation(progress:CGFloat)
34 | }
35 |
36 | public extension CardDetailViewController {
37 |
38 | var dismissHandler:CardDismissHandler {
39 | get {
40 | if let settings = objc_getAssociatedObject(self, &AssociatedKeys.dismissHandlerKey) as? CardDismissHandler {
41 | return settings
42 | } else {
43 | self.dismissHandler = CardDismissHandler(source: self)
44 | return dismissHandler
45 | }
46 | }
47 | set {
48 | objc_setAssociatedObject(self, &AssociatedKeys.dismissHandlerKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
49 | }
50 | }
51 |
52 | var settings:TransitionSettings {
53 | get {
54 | if let settings = objc_getAssociatedObject(self, &AssociatedKeys.settingsKey) as? TransitionSettings {
55 | return settings
56 | } else {
57 | self.settings = TransitionSettings()
58 | return settings
59 | }
60 | }
61 | set {
62 | objc_setAssociatedObject(self, &AssociatedKeys.settingsKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
63 | }
64 | }
65 |
66 | var dismissEnabled: Bool {
67 | get {
68 | return dismissHandler.dismissalPanGesture.isEnabled
69 | }
70 |
71 | set {
72 | dismissHandler.dismissalPanGesture.isEnabled = newValue
73 | dismissHandler.dismissalScreenEdgePanGesture.isEnabled = newValue
74 | }
75 | }
76 |
77 | func didStartPresentAnimationProgress() {}
78 | func didFinishPresentAnimationProgress() {}
79 |
80 | func didBeginDismissAnimation() {}
81 | func didChangeDismissAnimationProgress(progress:CGFloat) {}
82 | func didStartDismissAnimation() {}
83 | func didFinishDismissAnimation() {}
84 | func didCancelDismissAnimation(progress:CGFloat) {}
85 |
86 | }
87 |
88 | public final class CardDismissHandler: NSObject {
89 |
90 | public final class DismissalPanGesture: UIPanGestureRecognizer {}
91 | public final class DismissalScreenEdgePanGesture: UIScreenEdgePanGestureRecognizer {}
92 |
93 | public lazy var dismissalPanGesture: DismissalPanGesture = {
94 | let pan = DismissalPanGesture()
95 | pan.maximumNumberOfTouches = 1
96 | return pan
97 | }()
98 |
99 | public lazy var dismissalScreenEdgePanGesture: DismissalScreenEdgePanGesture = {
100 | let pan = DismissalScreenEdgePanGesture()
101 | pan.edges = .left
102 | return pan
103 | }()
104 |
105 | var interactiveStartingPoint: CGPoint?
106 | var dismissalAnimator: UIViewPropertyAnimator?
107 | var draggingDownToDismiss = false
108 |
109 | private let source: CardDetailViewController
110 |
111 | init(source: CardDetailViewController) {
112 | // We require source object in case we need access some properties etc.
113 | self.source = source
114 |
115 | super.init()
116 |
117 | dismissalPanGesture.addTarget(self, action: #selector(handleDismissalPan(gesture:)))
118 | dismissalPanGesture.delegate = self
119 |
120 | dismissalScreenEdgePanGesture.addTarget(self, action: #selector(handleDismissalPan(gesture:)))
121 | dismissalScreenEdgePanGesture.delegate = self
122 |
123 | // Make drag down/scroll pan gesture waits til screen edge pan to fail first to begin
124 | dismissalPanGesture.require(toFail: dismissalScreenEdgePanGesture)
125 | source.scrollView?.panGestureRecognizer.require(toFail: dismissalScreenEdgePanGesture)
126 |
127 | source.loadViewIfNeeded()
128 | source.view.addGestureRecognizer(dismissalPanGesture)
129 | source.view.addGestureRecognizer(dismissalScreenEdgePanGesture)
130 |
131 | checkScrolling(scrollView: source.scrollView)
132 | }
133 |
134 | public func scrollViewDidScroll(_ scrollView: UIScrollView) {
135 | checkScrolling(scrollView: scrollView)
136 | }
137 |
138 | private var dismissTop = true
139 | private var lastContentOffset: CGFloat = 0
140 |
141 | // This handles both screen edge and dragdown pan. As screen edge pan is a subclass of pan gesture, this input param works.
142 | @objc func handleDismissalPan(gesture: UIPanGestureRecognizer) {
143 |
144 | // let velocity = gesture.velocity(in: source.view)
145 | //if velocity.y > 0 { return }
146 |
147 | let isScreenEdgePan = gesture.isKind(of: DismissalScreenEdgePanGesture.self)
148 | let canStartDragDownToDismissPan = !isScreenEdgePan && !draggingDownToDismiss
149 |
150 | // Don't do anything when it's not in the drag down mode
151 | if canStartDragDownToDismissPan { return }
152 |
153 | let targetAnimatedView = gesture.view!
154 | let startingPoint: CGPoint
155 |
156 | if let p = interactiveStartingPoint {
157 | startingPoint = p
158 | } else {
159 | // Initial location
160 | startingPoint = gesture.location(in: nil)
161 | interactiveStartingPoint = startingPoint
162 | }
163 |
164 | let currentLocation = gesture.location(in: nil)
165 |
166 | let progress: CGFloat
167 | if (dismissTop) {
168 | progress = isScreenEdgePan ? (gesture.translation(in: targetAnimatedView).x / 100) : (currentLocation.y - startingPoint.y) / 100
169 | } else {
170 | progress = isScreenEdgePan ? (gesture.translation(in: targetAnimatedView).x / 100) : (startingPoint.y - currentLocation.y) / 100
171 | }
172 |
173 | let targetShrinkScale: CGFloat = 0.86
174 | let targetCornerRadius: CGFloat = source.settings.cardCornerRadius
175 |
176 | func createInteractiveDismissalAnimatorIfNeeded() -> UIViewPropertyAnimator {
177 | if let animator = dismissalAnimator {
178 | return animator
179 | } else {
180 | let animator = UIViewPropertyAnimator(duration: 0, curve: .linear, animations: {
181 | targetAnimatedView.transform = .init(scaleX: targetShrinkScale, y: targetShrinkScale)
182 | targetAnimatedView.layer.cornerRadius = targetCornerRadius
183 | })
184 | animator.isReversed = false
185 | animator.pauseAnimation()
186 | animator.fractionComplete = progress
187 | return animator
188 | }
189 | }
190 |
191 | switch gesture.state {
192 | case .began:
193 |
194 | if let scrollView = source.scrollView {
195 | if (scrollView.contentOffset.y <= 0) {
196 | dismissTop = true
197 | } else if (scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.frame.height && source.settings.isEnabledBottomClose) {
198 | dismissTop = false
199 | }
200 | } else {
201 | dismissTop = true
202 | }
203 |
204 | dismissalAnimator = createInteractiveDismissalAnimatorIfNeeded()
205 | source.didBeginDismissAnimation()
206 | case .changed:
207 | dismissalAnimator = createInteractiveDismissalAnimatorIfNeeded()
208 |
209 | let actualProgress = progress
210 | let isDismissalSuccess = actualProgress >= 1.0
211 |
212 | dismissalAnimator!.fractionComplete = actualProgress
213 | if progress >= 0 && progress <= 1 && dismissTop {
214 | source.scrollView?.contentOffset = CGPoint(x: 0, y: 100 * max(progress, 0))
215 | }
216 | source.didChangeDismissAnimationProgress(progress: progress)
217 |
218 | if isDismissalSuccess {
219 | dismissalAnimator!.stopAnimation(false)
220 | dismissalAnimator!.addCompletion { [unowned self] (pos) in
221 | switch pos {
222 | case .end:
223 | self.didSuccessfullyDragDownToDismiss()
224 | default:
225 | fatalError("Must finish dismissal at end!")
226 | }
227 | }
228 | dismissalAnimator!.finishAnimation(at: .end)
229 | }
230 |
231 | case .ended, .cancelled:
232 | if dismissalAnimator == nil {
233 | // Gesture's too quick that it doesn't have dismissalAnimator!
234 | print("Too quick there's no animator!")
235 | didCancelDismissalTransition()
236 | return
237 | }
238 | // NOTE:
239 | // If user lift fingers -> ended
240 | // If gesture.isEnabled -> cancelled
241 |
242 | // Ended, Animate back to start
243 | dismissalAnimator!.pauseAnimation()
244 | dismissalAnimator!.isReversed = true
245 |
246 | source.didCancelDismissAnimation(progress: progress)
247 | // Disable gesture until reverse closing animation finishes.
248 | gesture.isEnabled = false
249 | dismissalAnimator!.addCompletion { [unowned self] (pos) in
250 | self.didCancelDismissalTransition()
251 | gesture.isEnabled = true
252 |
253 | //if (!self.dismissTop && self.lastContentOffset < self.source.scrollView.contentOffset.y) {
254 | //self.source.scrollView.setContentOffset(CGPoint(x: 0, y: self.source.scrollView.contentSize.height - self.source.scrollView.bounds.size.height + self.source.scrollView.contentInset.bottom), animated: true)
255 | //}
256 | }
257 | dismissalAnimator!.startAnimation()
258 | default:
259 | fatalError("Impossible gesture state? \(gesture.state.rawValue)")
260 | }
261 |
262 | do {
263 | self.lastContentOffset = source.scrollView?.contentOffset.y ?? 0
264 | }
265 |
266 | source.scrollView?.bounces = (source.scrollView?.contentOffset.y ?? 0) > 100
267 | }
268 |
269 | func didSuccessfullyDragDownToDismiss() {
270 | //cardViewModel = unhighlightedCardViewModel
271 | //source.dismiss(animated: true)
272 | self.source.didStartDismissAnimation()
273 | source.dismiss(animated: true) {
274 | self.source.didFinishDismissAnimation()
275 | }
276 | }
277 |
278 | func didCancelDismissalTransition() {
279 | // Clean up
280 | interactiveStartingPoint = nil
281 | dismissalAnimator = nil
282 | draggingDownToDismiss = false
283 | }
284 |
285 | private func checkScrolling(scrollView: UIScrollView?) {
286 | if (shouldDismiss()) {
287 | draggingDownToDismiss = true
288 | }
289 |
290 | scrollView?.showsVerticalScrollIndicator = !draggingDownToDismiss
291 | }
292 |
293 | func shouldDismiss() -> Bool {
294 | guard let scrollView = source.scrollView else { return true }
295 |
296 | if (source.settings.isEnabledBottomClose) {
297 | return scrollView.contentOffset.y <= 0 || scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.frame.height
298 | } else {
299 | return scrollView.contentOffset.y <= 0
300 | }
301 | }
302 |
303 | }
304 |
305 | extension CardDismissHandler: UIGestureRecognizerDelegate {
306 | public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
307 | checkScrolling(scrollView: source.scrollView)
308 | return shouldDismiss()
309 | }
310 | }
311 |
--------------------------------------------------------------------------------
/Sources/AppstoreTransition/CardPresentationController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CardPresentationController.swift
3 | // Kickster
4 | //
5 | // Created by Razvan Chelemen on 06/05/2019.
6 | // Copyright © 2019 appssemble. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class CardPresentationController: UIPresentationController {
12 |
13 | private lazy var blurView = UIVisualEffectView(effect: nil)
14 |
15 | var settings: TransitionSettings?
16 |
17 | // Default is false.
18 | // And also means you can access only `.to` when present, and `.from` when dismiss (e.g., can touch only 'presented view').
19 | //
20 | // If true, the presenting view is removed and you have to add it during animation accessing `.from` key.
21 | // And you will have access to both `.to` and `.from` view. (In the typical .fullScreen mode)
22 | override var shouldRemovePresentersView: Bool {
23 | return false
24 | }
25 |
26 | override func presentationTransitionWillBegin() {
27 | let container = containerView!
28 | blurView.translatesAutoresizingMaskIntoConstraints = false
29 | container.addSubview(blurView)
30 | blurView.edges(to: container)
31 | blurView.alpha = 0.0
32 | if let settings = settings {
33 | blurView.backgroundColor = settings.blurColor
34 | if settings.blurEnabled {
35 | blurView.effect = UIBlurEffect(style: .light)
36 | }
37 | }
38 |
39 | presentingViewController.beginAppearanceTransition(false, animated: false)
40 | presentedViewController.transitionCoordinator!.animate(alongsideTransition: { (ctx) in
41 | UIView.animate(withDuration: 0.5, animations: {
42 | self.blurView.alpha = self.settings?.blurAlpha ?? 1.0
43 | })
44 | }) { (ctx) in }
45 | }
46 |
47 | override func presentationTransitionDidEnd(_ completed: Bool) {
48 | presentingViewController.endAppearanceTransition()
49 | }
50 |
51 | override func dismissalTransitionWillBegin() {
52 | presentingViewController.beginAppearanceTransition(true, animated: true)
53 | presentedViewController.transitionCoordinator!.animate(alongsideTransition: { (ctx) in
54 | self.blurView.alpha = 0.0
55 | }, completion: nil)
56 | }
57 |
58 | override func dismissalTransitionDidEnd(_ completed: Bool) {
59 | presentingViewController.endAppearanceTransition()
60 | if completed {
61 | blurView.removeFromSuperview()
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Sources/AppstoreTransition/CardTransition.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CardTransition.swift
3 | // Kickster
4 | //
5 | // Created by Razvan Chelemen on 06/05/2019.
6 | // Copyright © 2019 appssemble. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public class TransitionSettings {
12 | public var cardHighlightedFactor: CGFloat = 0.96
13 | public var cardCornerRadius: CGFloat = 8
14 | public var dismissalAnimationDuration = 0.6
15 | public var dismissalScrollViewContentOffset = CGPoint.zero
16 | public var blurEnabled = true
17 | public var blurColor = UIColor.clear
18 | public var blurAlpha: CGFloat = 1.0
19 |
20 | public var cardVerticalExpandingStyle: CardVerticalExpandingStyle = .fromTop
21 |
22 | /// Without this, there'll be weird offset (probably from scrollView) that obscures the card content view of the cardDetailView.
23 | public var isEnabledWeirdTopInsetsFix = true
24 |
25 | /// Swipe from bottom should also closes the detail screen.
26 | public var isEnabledBottomClose = false
27 |
28 | /// If true, will draw borders on animating views.
29 | public var isEnabledDebugAnimatingViews = false
30 |
31 | /// If true, this will add a 'reverse' additional top safe area insets to make the final top safe area insets zero.
32 | public var isEnabledTopSafeAreaInsetsFixOnCardDetailViewController = true
33 |
34 | /// If true, will always allow user to scroll while it's animated.
35 | public var isEnabledAllowsUserInteractionWhileHighlightingCard = true
36 |
37 | public var cardContainerInsets: UIEdgeInsets = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 8.0, right: 8.0)
38 |
39 | public enum CardVerticalExpandingStyle {
40 | /// Expanding card pinning at the top of animatingContainerView
41 | case fromTop
42 |
43 | /// Expanding card pinning at the center of animatingContainerView
44 | case fromCenter
45 | }
46 |
47 | public init() {
48 | }
49 |
50 | }
51 |
52 | public final class CardTransition: NSObject, UIViewControllerTransitioningDelegate {
53 |
54 | struct Params {
55 | let fromCardFrame: CGRect
56 | let fromCardFrameWithoutTransform: CGRect
57 | let fromCell: CardCollectionViewCell
58 | let settings: TransitionSettings
59 | }
60 |
61 | let cell: CardCollectionViewCell
62 | let settings: TransitionSettings
63 |
64 | public var updatedCardFrame: (()->(CGRect))?
65 |
66 | public init(cell: CardCollectionViewCell, settings: TransitionSettings = TransitionSettings()) {
67 | // Freeze highlighted state (or else it will bounce back)
68 | cell.freezeAnimations()
69 |
70 | self.cell = cell
71 | self.settings = settings
72 |
73 | super.init()
74 | }
75 |
76 | private func params() -> Params {
77 |
78 | // Get current frame on screen
79 | let currentCellFrame = cell.layer.presentation()!.frame
80 |
81 | // Convert current frame to screen's coordinates
82 | let cardPresentationFrameOnScreen = cell.superview!.convert(currentCellFrame, to: nil)
83 |
84 | // Get card frame without transform in screen's coordinates (for the dismissing back later to original location)
85 | let cardFrameWithoutTransform = { () -> CGRect in
86 | let center = cell.center
87 | let size = cell.bounds.size
88 | let r = CGRect(
89 | x: center.x - size.width / 2,
90 | y: center.y - size.height / 2,
91 | width: size.width,
92 | height: size.height
93 | )
94 | return cell.superview!.convert(r, to: nil)
95 | }()
96 |
97 | let params = CardTransition.Params(fromCardFrame: cardPresentationFrameOnScreen,
98 | fromCardFrameWithoutTransform: cardFrameWithoutTransform,
99 | fromCell: cell,
100 | settings: settings)
101 |
102 | return params
103 | }
104 |
105 | public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
106 | let baseParams = params()
107 |
108 | let params = PresentCardAnimator.Params.init(
109 | fromCardFrame: baseParams.fromCardFrame,
110 | fromCell: baseParams.fromCell,
111 | settings: baseParams.settings
112 | )
113 | return PresentCardAnimator(params: params)
114 | }
115 |
116 | public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
117 | let baseParams = params()
118 |
119 | let params = DismissCardAnimator.Params.init(
120 | fromCardFrame: baseParams.fromCardFrame,
121 | fromCardFrameWithoutTransform: baseParams.fromCardFrameWithoutTransform,
122 | fromCell: baseParams.fromCell,
123 | settings: baseParams.settings
124 | )
125 | return DismissCardAnimator(params: params)
126 | }
127 |
128 | public func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
129 | return nil
130 | }
131 |
132 | public func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
133 | return nil
134 | }
135 |
136 | // IMPORTANT: Must set modalPresentationStyle to `.custom` for this to be used.
137 | public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
138 | let cardPresentationController = CardPresentationController(presentedViewController: presented, presenting: presenting)
139 | cardPresentationController.settings = settings
140 |
141 | return cardPresentationController
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/Sources/AppstoreTransition/DismissCardAnimator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DismissCardAnimator.swift
3 | // Kickster
4 | //
5 | // Created by Razvan Chelemen on 06/05/2019.
6 | // Copyright © 2019 appssemble. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class DismissCardAnimator: NSObject, UIViewControllerAnimatedTransitioning {
12 |
13 | struct Params {
14 | let fromCardFrame: CGRect
15 | let fromCardFrameWithoutTransform: CGRect
16 | let fromCell: CardCollectionViewCell
17 | let settings: TransitionSettings
18 | }
19 |
20 | struct Constants {
21 | static let relativeDurationBeforeNonInteractive: TimeInterval = 0.5
22 | static let minimumScaleBeforeNonInteractive: CGFloat = 0.8
23 | }
24 |
25 | private let params: Params
26 |
27 | init(params: Params) {
28 | self.params = params
29 | super.init()
30 | }
31 |
32 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
33 | return params.settings.dismissalAnimationDuration
34 | }
35 |
36 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
37 | let ctx = transitionContext
38 | let container = ctx.containerView
39 |
40 | let toViewController: CardsViewController! = ctx.viewController(forKey: .to)?.cardsViewController()
41 |
42 | let screens: (cardDetail: CardDetailViewController, home: CardsViewController) = (
43 | ctx.viewController(forKey: .from)! as! CardDetailViewController,
44 | toViewController
45 | )
46 |
47 | let cardDetailView = ctx.view(forKey: .from)!
48 |
49 | let animatedContainerView = UIView()
50 | if params.settings.isEnabledDebugAnimatingViews {
51 | animatedContainerView.layer.borderColor = UIColor.yellow.cgColor
52 | animatedContainerView.layer.borderWidth = 4
53 | cardDetailView.layer.borderColor = UIColor.red.cgColor
54 | cardDetailView.layer.borderWidth = 2
55 | }
56 | animatedContainerView.translatesAutoresizingMaskIntoConstraints = false
57 | cardDetailView.translatesAutoresizingMaskIntoConstraints = false
58 |
59 | container.removeConstraints(container.constraints)
60 |
61 | container.addSubview(animatedContainerView)
62 | animatedContainerView.addSubview(cardDetailView)
63 |
64 | // Card fills inside animated container view
65 | cardDetailView.edges(to: animatedContainerView)
66 |
67 | animatedContainerView.centerXAnchor.constraint(equalTo: container.centerXAnchor).isActive = true
68 | let animatedContainerTopConstraint = animatedContainerView.topAnchor.constraint(equalTo: container.topAnchor, constant: params.settings.cardContainerInsets.top)
69 | let animatedContainerWidthConstraint = animatedContainerView.widthAnchor.constraint(equalToConstant: cardDetailView.frame.width - (params.settings.cardContainerInsets.left + params.settings.cardContainerInsets.right))
70 | let animatedContainerHeightConstraint = animatedContainerView.heightAnchor.constraint(equalToConstant: cardDetailView.frame.height - (params.settings.cardContainerInsets.top + params.settings.cardContainerInsets.bottom))
71 |
72 | NSLayoutConstraint.activate([animatedContainerTopConstraint, animatedContainerWidthConstraint, animatedContainerHeightConstraint])
73 |
74 | // Fix weird top inset
75 | let topTemporaryFix = screens.cardDetail.cardContentView.topAnchor.constraint(equalTo: cardDetailView.topAnchor)
76 | topTemporaryFix.isActive = params.settings.isEnabledWeirdTopInsetsFix
77 |
78 | container.layoutIfNeeded()
79 |
80 | // Force card filling bottom
81 | let stretchCardToFillBottom = screens.cardDetail.cardContentView.bottomAnchor.constraint(equalTo: cardDetailView.bottomAnchor)
82 | // for tableview header required confilcts with autoresizing mask constraints
83 | stretchCardToFillBottom.priority = .defaultHigh
84 |
85 | func animateCardViewBackToPlace() {
86 | stretchCardToFillBottom.isActive = true
87 | //screens.cardDetail.isFontStateHighlighted = false
88 | // Back to identity
89 | // NOTE: Animated container view in a way, helps us to not messing up `transform` with `AutoLayout` animation.
90 | cardDetailView.transform = CGAffineTransform.identity
91 | animatedContainerTopConstraint.constant = self.params.fromCardFrameWithoutTransform.minY + params.settings.cardContainerInsets.top
92 | animatedContainerWidthConstraint.constant = self.params.fromCardFrameWithoutTransform.width - (params.settings.cardContainerInsets.left + params.settings.cardContainerInsets.right)
93 | animatedContainerHeightConstraint.constant = self.params.fromCardFrameWithoutTransform.height - (params.settings.cardContainerInsets.top + params.settings.cardContainerInsets.bottom)
94 | container.layoutIfNeeded()
95 | }
96 |
97 | func completeEverything() {
98 | let success = !ctx.transitionWasCancelled
99 | animatedContainerView.removeConstraints(animatedContainerView.constraints)
100 | animatedContainerView.removeFromSuperview()
101 | if success {
102 | cardDetailView.removeFromSuperview()
103 | self.params.fromCell.isHidden = false
104 | } else {
105 | //screens.cardDetail.isFontStateHighlighted = true
106 |
107 | // Remove temporary fixes if not success!
108 | topTemporaryFix.isActive = false
109 | stretchCardToFillBottom.isActive = false
110 |
111 | cardDetailView.removeConstraint(topTemporaryFix)
112 | cardDetailView.removeConstraint(stretchCardToFillBottom)
113 |
114 | container.removeConstraints(container.constraints)
115 |
116 | container.addSubview(cardDetailView)
117 | cardDetailView.edges(to: container)
118 | }
119 | ctx.completeTransition(success)
120 | }
121 |
122 | UIView.animate(withDuration: transitionDuration(using: ctx), delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.0, options: [], animations: {
123 | animateCardViewBackToPlace()
124 | }) { (finished) in
125 | completeEverything()
126 | }
127 |
128 | UIView.animate(withDuration: transitionDuration(using: ctx) * 0.4) {
129 | //print("godam")
130 | //screens.cardDetail.scrollView.setContentOffset(self.params.settings.dismissalScrollViewContentOffset, animated: true)
131 | screens.cardDetail.scrollView?.contentOffset = self.params.settings.dismissalScrollViewContentOffset
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/Sources/AppstoreTransition/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 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Sources/AppstoreTransition/PresentCardAnimator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PresentCardAnimator.swift
3 | // Kickster
4 | //
5 | // Created by Razvan Chelemen on 06/05/2019.
6 | // Copyright © 2019 appssemble. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIViewController {
12 |
13 | func cardsViewController() -> CardsViewController? {
14 | if let viewController = self as? CardsViewController {
15 | return viewController
16 | } else if let viewController = self as? UITabBarController {
17 | return viewController.selectedViewController?.cardsViewController()
18 | } else if let viewController = self as? UINavigationController {
19 | return viewController.topViewController?.cardsViewController()
20 | }
21 |
22 | return nil
23 | }
24 |
25 | }
26 |
27 | final class PresentCardAnimator: NSObject, UIViewControllerAnimatedTransitioning {
28 |
29 | private let params: Params
30 |
31 | struct Params {
32 | let fromCardFrame: CGRect
33 | let fromCell: CardCollectionViewCell
34 | let settings: TransitionSettings
35 | }
36 |
37 | private let presentAnimationDuration: TimeInterval
38 | private let springAnimator: UIViewPropertyAnimator
39 | private var transitionDriver: PresentCardTransitionDriver?
40 |
41 | init(params: Params) {
42 | self.params = params
43 | self.springAnimator = PresentCardAnimator.createBaseSpringAnimator(params: params)
44 | self.presentAnimationDuration = springAnimator.duration
45 | super.init()
46 | }
47 |
48 | private static func createBaseSpringAnimator(params: PresentCardAnimator.Params) -> UIViewPropertyAnimator {
49 | // Damping between 0.7 (far away) and 1.0 (nearer)
50 | let cardPositionY = params.fromCardFrame.minY
51 | let distanceToBounce = abs(params.fromCardFrame.minY)
52 | let extentToBounce = cardPositionY < 0 ? params.fromCardFrame.height : UIScreen.main.bounds.height
53 | let dampFactorInterval: CGFloat = 0.3
54 | let damping: CGFloat = 1.0 - dampFactorInterval * (distanceToBounce / extentToBounce)
55 |
56 | // Duration between 0.5 (nearer) and 0.9 (nearer)
57 | let baselineDuration: TimeInterval = 0.5
58 | let maxDuration: TimeInterval = 0.9
59 | let duration: TimeInterval = baselineDuration + (maxDuration - baselineDuration) * TimeInterval(max(0, distanceToBounce)/UIScreen.main.bounds.height)
60 |
61 | let springTiming = UISpringTimingParameters(dampingRatio: damping, initialVelocity: .init(dx: 0, dy: 0))
62 | return UIViewPropertyAnimator(duration: duration, timingParameters: springTiming)
63 | }
64 |
65 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
66 | // 1.
67 | return presentAnimationDuration
68 | }
69 |
70 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
71 | // 2.
72 | transitionDriver = PresentCardTransitionDriver(params: params,
73 | transitionContext: transitionContext,
74 | baseAnimator: springAnimator)
75 | interruptibleAnimator(using: transitionContext).startAnimation()
76 | }
77 |
78 | func animationEnded(_ transitionCompleted: Bool) {
79 | // 4.
80 | transitionDriver = nil
81 | }
82 |
83 | func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
84 | // 3.
85 | return transitionDriver!.animator
86 | }
87 | }
88 |
89 | final class PresentCardTransitionDriver {
90 | let animator: UIViewPropertyAnimator
91 | init(params: PresentCardAnimator.Params, transitionContext: UIViewControllerContextTransitioning, baseAnimator: UIViewPropertyAnimator) {
92 | let ctx = transitionContext
93 | let container = ctx.containerView
94 | let fromViewController: CardsViewController! = ctx.viewController(forKey: .from)?.cardsViewController()
95 |
96 | let screens: (home: CardsViewController, cardDetail: CardDetailViewController) = (
97 | fromViewController,
98 | ctx.viewController(forKey: .to)! as! CardDetailViewController
99 | )
100 |
101 | let cardDetailView = ctx.view(forKey: .to)!
102 | cardDetailView.backgroundColor = .clear
103 | let fromCardFrame = params.fromCardFrame
104 |
105 | // Temporary container view for animation
106 | let animatedContainerView = UIView()
107 | animatedContainerView.translatesAutoresizingMaskIntoConstraints = false
108 | if params.settings.isEnabledDebugAnimatingViews {
109 | animatedContainerView.layer.borderColor = UIColor.yellow.cgColor
110 | animatedContainerView.layer.borderWidth = 4
111 | cardDetailView.layer.borderColor = UIColor.red.cgColor
112 | cardDetailView.layer.borderWidth = 2
113 | }
114 | container.addSubview(animatedContainerView)
115 |
116 | do /* Fix centerX/width/height of animated container to container */ {
117 | let animatedContainerConstraints = [
118 | animatedContainerView.widthAnchor.constraint(equalToConstant: container.bounds.width - (params.settings.cardContainerInsets.left + params.settings.cardContainerInsets.right)),
119 | animatedContainerView.heightAnchor.constraint(equalToConstant: container.bounds.height - (params.settings.cardContainerInsets.top + params.settings.cardContainerInsets.bottom)),
120 | animatedContainerView.centerXAnchor.constraint(equalTo: container.centerXAnchor)
121 | ]
122 | NSLayoutConstraint.activate(animatedContainerConstraints)
123 | }
124 |
125 | let animatedContainerVerticalConstraint: NSLayoutConstraint = {
126 | switch params.settings.cardVerticalExpandingStyle {
127 | case .fromCenter:
128 | return animatedContainerView.centerYAnchor.constraint(
129 | equalTo: container.centerYAnchor,
130 | constant: (fromCardFrame.height/2 + fromCardFrame.minY) - container.bounds.height/2
131 | )
132 | case .fromTop:
133 | return animatedContainerView.topAnchor.constraint(equalTo: container.topAnchor, constant: fromCardFrame.minY + params.settings.cardContainerInsets.top)
134 | }
135 |
136 | }()
137 | animatedContainerVerticalConstraint.isActive = true
138 |
139 | animatedContainerView.addSubview(cardDetailView)
140 | cardDetailView.translatesAutoresizingMaskIntoConstraints = false
141 |
142 | do /* Pin top (or center Y) and center X of the card, in animated container view */ {
143 | let verticalAnchor: NSLayoutConstraint = {
144 | switch params.settings.cardVerticalExpandingStyle {
145 | case .fromCenter:
146 | return cardDetailView.centerYAnchor.constraint(equalTo: animatedContainerView.centerYAnchor)
147 | case .fromTop:
148 | return cardDetailView.topAnchor.constraint(equalTo: animatedContainerView.topAnchor)
149 | }
150 | }()
151 | let cardConstraints = [
152 | verticalAnchor,
153 | cardDetailView.centerXAnchor.constraint(equalTo: animatedContainerView.centerXAnchor),
154 | ]
155 | NSLayoutConstraint.activate(cardConstraints)
156 | }
157 | let cardWidthConstraint = cardDetailView.widthAnchor.constraint(equalToConstant: fromCardFrame.width - (params.settings.cardContainerInsets.left + params.settings.cardContainerInsets.right))
158 | let cardHeightConstraint = cardDetailView.heightAnchor.constraint(equalToConstant: fromCardFrame.height - (params.settings.cardContainerInsets.top + params.settings.cardContainerInsets.bottom))
159 | NSLayoutConstraint.activate([cardWidthConstraint, cardHeightConstraint])
160 |
161 | cardDetailView.layer.cornerRadius = params.settings.cardCornerRadius
162 |
163 | // -------------------------------
164 | // Final preparation
165 | // -------------------------------
166 | params.fromCell.isHidden = true
167 | params.fromCell.resetTransform()
168 |
169 | let topTemporaryFix = screens.cardDetail.cardContentView.topAnchor.constraint(equalTo: cardDetailView.topAnchor, constant: 0)
170 | topTemporaryFix.isActive = params.settings.isEnabledWeirdTopInsetsFix
171 |
172 | container.layoutIfNeeded()
173 |
174 | // ------------------------------
175 | // 1. Animate container bouncing up
176 | // ------------------------------
177 | func animateContainerBouncingUp() {
178 | animatedContainerVerticalConstraint.constant = 0
179 | container.layoutIfNeeded()
180 | }
181 |
182 | // ------------------------------
183 | // 2. Animate cardDetail filling up the container
184 | // ------------------------------
185 | func animateCardDetailViewSizing() {
186 | screens.cardDetail.didStartPresentAnimationProgress()
187 |
188 | cardWidthConstraint.constant = animatedContainerView.bounds.width + (params.settings.cardContainerInsets.left + params.settings.cardContainerInsets.right)
189 | cardHeightConstraint.constant = animatedContainerView.bounds.height + (params.settings.cardContainerInsets.top + params.settings.cardContainerInsets.bottom)
190 | cardDetailView.layer.cornerRadius = 0
191 | container.layoutIfNeeded()
192 | }
193 |
194 | func completeEverything() {
195 | // Remove temporary `animatedContainerView`
196 | animatedContainerView.removeConstraints(animatedContainerView.constraints)
197 | animatedContainerView.removeFromSuperview()
198 |
199 | // Re-add to the top
200 | container.addSubview(cardDetailView)
201 |
202 | cardDetailView.removeConstraints([topTemporaryFix, cardWidthConstraint, cardHeightConstraint])
203 |
204 | cardDetailView.edges(to: container)
205 |
206 | // No longer need the bottom constraint that pins bottom of card content to its root.
207 | //screens.cardDetail.cardBottomToRootBottomConstraint.isActive = false
208 | screens.cardDetail.scrollView?.isScrollEnabled = true
209 |
210 | let success = !ctx.transitionWasCancelled
211 | ctx.completeTransition(success)
212 |
213 | screens.cardDetail.didFinishPresentAnimationProgress()
214 | }
215 |
216 | baseAnimator.addAnimations {
217 |
218 | // Spring animation for bouncing up
219 | animateContainerBouncingUp()
220 |
221 | // Linear animation for expansion
222 | let cardExpanding = UIViewPropertyAnimator(duration: baseAnimator.duration * 0.6, curve: .linear) {
223 | animateCardDetailViewSizing()
224 | }
225 | cardExpanding.startAnimation()
226 | }
227 |
228 | baseAnimator.addCompletion { (_) in
229 | completeEverything()
230 | }
231 |
232 | self.animator = baseAnimator
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/Sources/AppstoreTransition/UIView+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Extension.swift
3 | // AppstoreTransition
4 | //
5 | // Created by Razvan Chelemen on 15/05/2019.
6 | // Copyright © 2019 appssemble. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIView {
12 |
13 | /// Constrain 4 edges of `self` to specified `view`.
14 | func edges(to view: UIView, top: CGFloat=0, left: CGFloat=0, bottom: CGFloat=0, right: CGFloat=0) {
15 | NSLayoutConstraint.activate([
16 | self.leftAnchor.constraint(equalTo: view.leftAnchor, constant: left),
17 | self.rightAnchor.constraint(equalTo: view.rightAnchor, constant: right),
18 | self.topAnchor.constraint(equalTo: view.topAnchor, constant: top),
19 | self.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: bottom)
20 | ])
21 | }
22 |
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/Sources/AppstoreTransition/UIViewController+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewController+Extension.swift
3 | // AppstoreTransition
4 | //
5 | // Created by Razvan Chelemen on 15/05/2019.
6 | // Copyright © 2019 appssemble. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public extension UIViewController {
12 |
13 | func presentExpansion(_ viewControllerToPresent: UIViewController, cell:CardCollectionViewCell, animated flag: Bool, completion: (() -> Void)? = nil) {
14 |
15 | present(viewControllerToPresent, animated: true, completion: { [unowned cell] in
16 | // Unfreeze
17 | cell.unfreezeAnimations()
18 | })
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/appstore-card-transition.podspec:
--------------------------------------------------------------------------------
1 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html
2 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/
3 | #
4 |
5 | Pod::Spec.new do |s|
6 |
7 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
8 | #
9 | # These will help people to find your library, and whilst it
10 | # can feel like a chore to fill in it's definitely to your advantage. The
11 | # summary should be tweet-length, and the description more in depth.
12 | #
13 |
14 | s.name = "appstore-card-transition"
15 | s.version = "1.0.4"
16 | s.summary = "Appstore card animation transition. UICollectionView and UITableView card expand animated transition for iOS."
17 | s.module_name = 'AppstoreTransition'
18 | s.swift_version = '5.0'
19 |
20 | # This description is used to generate tags and improve search results.
21 | # * Think: What does it do? Why did you write it? What is the focus?
22 | # * Try to keep it short, snappy and to the point.
23 | # * Write the description between the DESC delimiters below.
24 | # * Finally, don't worry about the indent, CocoaPods strips it!
25 | s.description = <<-DESC
26 | Appstore card animation transition. UICollectionView and UITableView card expand animated transition for iOS. Expandable collectionview and tableview cells. Master detail transition animation.
27 | DESC
28 |
29 | s.homepage = "https://github.com/appssemble/appstore-card-transition"
30 | s.screenshots = "https://github.com/appssemble/appstore-card-transition/blob/master/gif/example1.gif?raw=true", "https://github.com/appssemble/appstore-card-transition/blob/master/gif/example2.gif?raw=true"
31 |
32 |
33 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
34 | #
35 | # Licensing your code is important. See http://choosealicense.com for more info.
36 | # CocoaPods will detect a license file if there is a named LICENSE*
37 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'.
38 | #
39 |
40 | s.license = { :type => "MIT", :file => "LICENSE" }
41 |
42 |
43 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
44 | #
45 | # Specify the authors of the library, with email addresses. Email addresses
46 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also
47 | # accepts just a name if you'd rather not provide an email address.
48 | #
49 | # Specify a social_media_url where others can refer to, for example a twitter
50 | # profile URL.
51 | #
52 |
53 | s.author = { "Razvan Chelemen" => "chelemen.razvan@gmail.com" }
54 | # Or just: s.author = "Razvan Chelemen"
55 | # s.authors = { "Razvan Chelemen" => "chelemen.razvan@gmail.com" }
56 | s.social_media_url = "https://twitter.com/chelemen_razvan"
57 |
58 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
59 | #
60 | # If this Pod runs only on iOS or OS X, then specify the platform and
61 | # the deployment target. You can optionally include the target after the platform.
62 | #
63 |
64 | # When using multiple platforms
65 | s.ios.deployment_target = "11.0"
66 | # s.watchos.deployment_target = "2.0"
67 | # s.tvos.deployment_target = "9.0"
68 |
69 |
70 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
71 | #
72 | # Specify the location from where the source should be retrieved.
73 | # Supports git, hg, bzr, svn and HTTP.
74 | #
75 |
76 | s.source = { :git => "https://github.com/appssemble/appstore-card-transition.git", :tag => "#{s.version}" }
77 |
78 |
79 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
80 | #
81 | # CocoaPods is smart about how it includes source code. For source files
82 | # giving a folder will include any swift, h, m, mm, c & cpp files.
83 | # For header files it will include any header in the folder.
84 | # Not including the public_header_files will make all headers public.
85 | #
86 |
87 | s.source_files = "Sources/AppstoreTransition/*.{h,m,swift,c}"
88 | #s.exclude_files = "Classes/Exclude"
89 |
90 | # s.public_header_files = "Classes/**/*.h"
91 |
92 |
93 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
94 | #
95 | # A list of resources included with the Pod. These are copied into the
96 | # target bundle with a build phase script. Anything else will be cleaned.
97 | # You can preserve files from being cleaned, please don't preserve
98 | # non-essential files like tests, examples and documentation.
99 | #
100 |
101 | # s.resource = "icon.png"
102 | # s.resources = "Resources/*.png"
103 |
104 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave"
105 |
106 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
107 | #
108 | # Link your library with frameworks, or libraries. Libraries do not include
109 | # the lib prefix of their name.
110 | #
111 |
112 | # s.framework = "SomeFramework"
113 | # s.frameworks = "SomeFramework", "AnotherFramework"
114 |
115 | # s.library = 'CommonCrypto'
116 | # s.libraries = "iconv", "xml2"
117 |
118 | # s.ios.vendored_libraries = 'stellarsdk/stellarsdk/libs/**/*'
119 |
120 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
121 | #
122 | # If your library depends on compiler flags you can set them in the xcconfig hash
123 | # where they will only apply to your library. If you depend on other Podspecs
124 | # you can include multiple dependencies to ensure it works.
125 |
126 | # s.requires_arc = true
127 |
128 | # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }
129 | # s.dependency "JSONKit", "~> 1.4"
130 |
131 | end
132 |
--------------------------------------------------------------------------------
/gif/example1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appssemble/appstore-card-transition/a220fc468f1a9a2b7ff7dba5029b737af4c69422/gif/example1.gif
--------------------------------------------------------------------------------
/gif/example2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appssemble/appstore-card-transition/a220fc468f1a9a2b7ff7dba5029b737af4c69422/gif/example2.gif
--------------------------------------------------------------------------------
/gif/example3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appssemble/appstore-card-transition/a220fc468f1a9a2b7ff7dba5029b737af4c69422/gif/example3.gif
--------------------------------------------------------------------------------
/gif/example4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appssemble/appstore-card-transition/a220fc468f1a9a2b7ff7dba5029b737af4c69422/gif/example4.gif
--------------------------------------------------------------------------------
/gif/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appssemble/appstore-card-transition/a220fc468f1a9a2b7ff7dba5029b737af4c69422/gif/logo.png
--------------------------------------------------------------------------------