├── AnimationDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── truongnguyen.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── truongnguyen.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── AnimationDemo ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── cancel.imageset │ │ ├── Contents.json │ │ ├── output-onlinepngtools (1)-1.png │ │ ├── output-onlinepngtools (1).png │ │ └── output-onlinepngtools.png │ ├── double_down_arrow.imageset │ │ ├── Contents.json │ │ ├── output-onlinepngtools (3).png │ │ ├── output-onlinepngtools (4).png │ │ └── output-onlinepngtools (5).png │ ├── icn_tab_contact_normal.imageset │ │ ├── Contents.json │ │ ├── icn_tabbar_contact_normal.png │ │ ├── icn_tabbar_contact_normal@2x.png │ │ └── icn_tabbar_contact_normal@3x.png │ ├── icn_tab_contact_selected.imageset │ │ ├── Contents.json │ │ ├── icn_tabbar_contact_selected.png │ │ ├── icn_tabbar_contact_selected@2x.png │ │ └── icn_tabbar_contact_selected@3x.png │ ├── icn_tab_more_normal.imageset │ │ ├── Contents.json │ │ ├── icn_tabbar_more_normal.png │ │ ├── icn_tabbar_more_normal@2x.png │ │ └── icn_tabbar_more_normal@3x.png │ ├── icn_tab_more_selected.imageset │ │ ├── Contents.json │ │ ├── icn_tabbar_more_selected.png │ │ ├── icn_tabbar_more_selected@2x.png │ │ └── icn_tabbar_more_selected@3x.png │ ├── img1.imageset │ │ ├── Contents.json │ │ └── Image.png │ ├── img2.imageset │ │ ├── Contents.json │ │ └── img2.jpg │ ├── img3.imageset │ │ ├── Contents.json │ │ └── drowning.png │ ├── img4.imageset │ │ ├── Contents.json │ │ └── img1.png │ └── img5.imageset │ │ ├── Contents.json │ │ └── Image.png ├── Base.lproj │ └── LaunchScreen.storyboard ├── CardDetailViewController.swift ├── HomeViewController.swift ├── Info.plist ├── SceneDelegate.swift ├── Transition │ ├── BlurPresentationController.swift │ ├── CardTransition.swift │ ├── DismissCardAnimator.swift │ └── PresentCardAnimator.swift └── View │ ├── CardCell.swift │ ├── CardContentView.swift │ ├── CardModel.swift │ └── UIView.swift ├── AnimationDemoTests ├── AnimationDemoTests.swift └── Info.plist ├── AnimationDemoUITests ├── AnimationDemoUITests.swift └── Info.plist ├── Demo.gif └── README.md /AnimationDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4861121B243A4607009752D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4861121A243A4607009752D9 /* AppDelegate.swift */; }; 11 | 4861121D243A4607009752D9 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4861121C243A4607009752D9 /* SceneDelegate.swift */; }; 12 | 48611224243A4609009752D9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 48611223243A4609009752D9 /* Assets.xcassets */; }; 13 | 48611227243A4609009752D9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 48611225243A4609009752D9 /* LaunchScreen.storyboard */; }; 14 | 48611232243A4609009752D9 /* AnimationDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48611231243A4609009752D9 /* AnimationDemoTests.swift */; }; 15 | 4861123D243A4609009752D9 /* AnimationDemoUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4861123C243A4609009752D9 /* AnimationDemoUITests.swift */; }; 16 | 48611256243A465E009752D9 /* CardTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4861124B243A465E009752D9 /* CardTransition.swift */; }; 17 | 48611257243A465E009752D9 /* BlurPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4861124C243A465E009752D9 /* BlurPresentationController.swift */; }; 18 | 48611258243A465E009752D9 /* PresentCardAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4861124D243A465E009752D9 /* PresentCardAnimator.swift */; }; 19 | 48611259243A465E009752D9 /* DismissCardAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4861124E243A465E009752D9 /* DismissCardAnimator.swift */; }; 20 | 4861125A243A465E009752D9 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48611250243A465E009752D9 /* UIView.swift */; }; 21 | 4861125B243A465E009752D9 /* CardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48611251243A465E009752D9 /* CardCell.swift */; }; 22 | 4861125C243A465E009752D9 /* CardModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48611252243A465E009752D9 /* CardModel.swift */; }; 23 | 4861125D243A465E009752D9 /* CardContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48611253243A465E009752D9 /* CardContentView.swift */; }; 24 | 4861125E243A465E009752D9 /* CardDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48611254243A465E009752D9 /* CardDetailViewController.swift */; }; 25 | 4861125F243A465E009752D9 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48611255243A465E009752D9 /* HomeViewController.swift */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXContainerItemProxy section */ 29 | 4861122E243A4609009752D9 /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = 4861120F243A4607009752D9 /* Project object */; 32 | proxyType = 1; 33 | remoteGlobalIDString = 48611216243A4607009752D9; 34 | remoteInfo = AnimationDemo; 35 | }; 36 | 48611239243A4609009752D9 /* PBXContainerItemProxy */ = { 37 | isa = PBXContainerItemProxy; 38 | containerPortal = 4861120F243A4607009752D9 /* Project object */; 39 | proxyType = 1; 40 | remoteGlobalIDString = 48611216243A4607009752D9; 41 | remoteInfo = AnimationDemo; 42 | }; 43 | /* End PBXContainerItemProxy section */ 44 | 45 | /* Begin PBXFileReference section */ 46 | 48611217243A4607009752D9 /* AnimationDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AnimationDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 4861121A243A4607009752D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 48 | 4861121C243A4607009752D9 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 49 | 48611223243A4609009752D9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 50 | 48611226243A4609009752D9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 51 | 48611228243A4609009752D9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | 4861122D243A4609009752D9 /* AnimationDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AnimationDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | 48611231243A4609009752D9 /* AnimationDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationDemoTests.swift; sourceTree = ""; }; 54 | 48611233243A4609009752D9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 55 | 48611238243A4609009752D9 /* AnimationDemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AnimationDemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 4861123C243A4609009752D9 /* AnimationDemoUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationDemoUITests.swift; sourceTree = ""; }; 57 | 4861123E243A4609009752D9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | 4861124B243A465E009752D9 /* CardTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardTransition.swift; sourceTree = ""; }; 59 | 4861124C243A465E009752D9 /* BlurPresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurPresentationController.swift; sourceTree = ""; }; 60 | 4861124D243A465E009752D9 /* PresentCardAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentCardAnimator.swift; sourceTree = ""; }; 61 | 4861124E243A465E009752D9 /* DismissCardAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DismissCardAnimator.swift; sourceTree = ""; }; 62 | 48611250243A465E009752D9 /* UIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = ""; }; 63 | 48611251243A465E009752D9 /* CardCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardCell.swift; sourceTree = ""; }; 64 | 48611252243A465E009752D9 /* CardModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardModel.swift; sourceTree = ""; }; 65 | 48611253243A465E009752D9 /* CardContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardContentView.swift; sourceTree = ""; }; 66 | 48611254243A465E009752D9 /* CardDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardDetailViewController.swift; sourceTree = ""; }; 67 | 48611255243A465E009752D9 /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; 68 | /* End PBXFileReference section */ 69 | 70 | /* Begin PBXFrameworksBuildPhase section */ 71 | 48611214243A4607009752D9 /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | 4861122A243A4609009752D9 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | 48611235243A4609009752D9 /* Frameworks */ = { 86 | isa = PBXFrameworksBuildPhase; 87 | buildActionMask = 2147483647; 88 | files = ( 89 | ); 90 | runOnlyForDeploymentPostprocessing = 0; 91 | }; 92 | /* End PBXFrameworksBuildPhase section */ 93 | 94 | /* Begin PBXGroup section */ 95 | 4861120E243A4607009752D9 = { 96 | isa = PBXGroup; 97 | children = ( 98 | 48611219243A4607009752D9 /* AnimationDemo */, 99 | 48611230243A4609009752D9 /* AnimationDemoTests */, 100 | 4861123B243A4609009752D9 /* AnimationDemoUITests */, 101 | 48611218243A4607009752D9 /* Products */, 102 | ); 103 | sourceTree = ""; 104 | }; 105 | 48611218243A4607009752D9 /* Products */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 48611217243A4607009752D9 /* AnimationDemo.app */, 109 | 4861122D243A4609009752D9 /* AnimationDemoTests.xctest */, 110 | 48611238243A4609009752D9 /* AnimationDemoUITests.xctest */, 111 | ); 112 | name = Products; 113 | sourceTree = ""; 114 | }; 115 | 48611219243A4607009752D9 /* AnimationDemo */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 48611254243A465E009752D9 /* CardDetailViewController.swift */, 119 | 48611255243A465E009752D9 /* HomeViewController.swift */, 120 | 4861124A243A465E009752D9 /* Transition */, 121 | 4861124F243A465E009752D9 /* View */, 122 | 4861121A243A4607009752D9 /* AppDelegate.swift */, 123 | 4861121C243A4607009752D9 /* SceneDelegate.swift */, 124 | 48611223243A4609009752D9 /* Assets.xcassets */, 125 | 48611225243A4609009752D9 /* LaunchScreen.storyboard */, 126 | 48611228243A4609009752D9 /* Info.plist */, 127 | ); 128 | path = AnimationDemo; 129 | sourceTree = ""; 130 | }; 131 | 48611230243A4609009752D9 /* AnimationDemoTests */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 48611231243A4609009752D9 /* AnimationDemoTests.swift */, 135 | 48611233243A4609009752D9 /* Info.plist */, 136 | ); 137 | path = AnimationDemoTests; 138 | sourceTree = ""; 139 | }; 140 | 4861123B243A4609009752D9 /* AnimationDemoUITests */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 4861123C243A4609009752D9 /* AnimationDemoUITests.swift */, 144 | 4861123E243A4609009752D9 /* Info.plist */, 145 | ); 146 | path = AnimationDemoUITests; 147 | sourceTree = ""; 148 | }; 149 | 4861124A243A465E009752D9 /* Transition */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 4861124B243A465E009752D9 /* CardTransition.swift */, 153 | 4861124C243A465E009752D9 /* BlurPresentationController.swift */, 154 | 4861124D243A465E009752D9 /* PresentCardAnimator.swift */, 155 | 4861124E243A465E009752D9 /* DismissCardAnimator.swift */, 156 | ); 157 | path = Transition; 158 | sourceTree = ""; 159 | }; 160 | 4861124F243A465E009752D9 /* View */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | 48611250243A465E009752D9 /* UIView.swift */, 164 | 48611251243A465E009752D9 /* CardCell.swift */, 165 | 48611252243A465E009752D9 /* CardModel.swift */, 166 | 48611253243A465E009752D9 /* CardContentView.swift */, 167 | ); 168 | path = View; 169 | sourceTree = ""; 170 | }; 171 | /* End PBXGroup section */ 172 | 173 | /* Begin PBXNativeTarget section */ 174 | 48611216243A4607009752D9 /* AnimationDemo */ = { 175 | isa = PBXNativeTarget; 176 | buildConfigurationList = 48611241243A4609009752D9 /* Build configuration list for PBXNativeTarget "AnimationDemo" */; 177 | buildPhases = ( 178 | 48611213243A4607009752D9 /* Sources */, 179 | 48611214243A4607009752D9 /* Frameworks */, 180 | 48611215243A4607009752D9 /* Resources */, 181 | ); 182 | buildRules = ( 183 | ); 184 | dependencies = ( 185 | ); 186 | name = AnimationDemo; 187 | productName = AnimationDemo; 188 | productReference = 48611217243A4607009752D9 /* AnimationDemo.app */; 189 | productType = "com.apple.product-type.application"; 190 | }; 191 | 4861122C243A4609009752D9 /* AnimationDemoTests */ = { 192 | isa = PBXNativeTarget; 193 | buildConfigurationList = 48611244243A4609009752D9 /* Build configuration list for PBXNativeTarget "AnimationDemoTests" */; 194 | buildPhases = ( 195 | 48611229243A4609009752D9 /* Sources */, 196 | 4861122A243A4609009752D9 /* Frameworks */, 197 | 4861122B243A4609009752D9 /* Resources */, 198 | ); 199 | buildRules = ( 200 | ); 201 | dependencies = ( 202 | 4861122F243A4609009752D9 /* PBXTargetDependency */, 203 | ); 204 | name = AnimationDemoTests; 205 | productName = AnimationDemoTests; 206 | productReference = 4861122D243A4609009752D9 /* AnimationDemoTests.xctest */; 207 | productType = "com.apple.product-type.bundle.unit-test"; 208 | }; 209 | 48611237243A4609009752D9 /* AnimationDemoUITests */ = { 210 | isa = PBXNativeTarget; 211 | buildConfigurationList = 48611247243A4609009752D9 /* Build configuration list for PBXNativeTarget "AnimationDemoUITests" */; 212 | buildPhases = ( 213 | 48611234243A4609009752D9 /* Sources */, 214 | 48611235243A4609009752D9 /* Frameworks */, 215 | 48611236243A4609009752D9 /* Resources */, 216 | ); 217 | buildRules = ( 218 | ); 219 | dependencies = ( 220 | 4861123A243A4609009752D9 /* PBXTargetDependency */, 221 | ); 222 | name = AnimationDemoUITests; 223 | productName = AnimationDemoUITests; 224 | productReference = 48611238243A4609009752D9 /* AnimationDemoUITests.xctest */; 225 | productType = "com.apple.product-type.bundle.ui-testing"; 226 | }; 227 | /* End PBXNativeTarget section */ 228 | 229 | /* Begin PBXProject section */ 230 | 4861120F243A4607009752D9 /* Project object */ = { 231 | isa = PBXProject; 232 | attributes = { 233 | LastSwiftUpdateCheck = 1140; 234 | LastUpgradeCheck = 1140; 235 | ORGANIZATIONNAME = "Truong Nguyen"; 236 | TargetAttributes = { 237 | 48611216243A4607009752D9 = { 238 | CreatedOnToolsVersion = 11.4; 239 | }; 240 | 4861122C243A4609009752D9 = { 241 | CreatedOnToolsVersion = 11.4; 242 | TestTargetID = 48611216243A4607009752D9; 243 | }; 244 | 48611237243A4609009752D9 = { 245 | CreatedOnToolsVersion = 11.4; 246 | TestTargetID = 48611216243A4607009752D9; 247 | }; 248 | }; 249 | }; 250 | buildConfigurationList = 48611212243A4607009752D9 /* Build configuration list for PBXProject "AnimationDemo" */; 251 | compatibilityVersion = "Xcode 9.3"; 252 | developmentRegion = en; 253 | hasScannedForEncodings = 0; 254 | knownRegions = ( 255 | en, 256 | Base, 257 | ); 258 | mainGroup = 4861120E243A4607009752D9; 259 | productRefGroup = 48611218243A4607009752D9 /* Products */; 260 | projectDirPath = ""; 261 | projectRoot = ""; 262 | targets = ( 263 | 48611216243A4607009752D9 /* AnimationDemo */, 264 | 4861122C243A4609009752D9 /* AnimationDemoTests */, 265 | 48611237243A4609009752D9 /* AnimationDemoUITests */, 266 | ); 267 | }; 268 | /* End PBXProject section */ 269 | 270 | /* Begin PBXResourcesBuildPhase section */ 271 | 48611215243A4607009752D9 /* Resources */ = { 272 | isa = PBXResourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | 48611227243A4609009752D9 /* LaunchScreen.storyboard in Resources */, 276 | 48611224243A4609009752D9 /* Assets.xcassets in Resources */, 277 | ); 278 | runOnlyForDeploymentPostprocessing = 0; 279 | }; 280 | 4861122B243A4609009752D9 /* Resources */ = { 281 | isa = PBXResourcesBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | }; 287 | 48611236243A4609009752D9 /* Resources */ = { 288 | isa = PBXResourcesBuildPhase; 289 | buildActionMask = 2147483647; 290 | files = ( 291 | ); 292 | runOnlyForDeploymentPostprocessing = 0; 293 | }; 294 | /* End PBXResourcesBuildPhase section */ 295 | 296 | /* Begin PBXSourcesBuildPhase section */ 297 | 48611213243A4607009752D9 /* Sources */ = { 298 | isa = PBXSourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | 48611257243A465E009752D9 /* BlurPresentationController.swift in Sources */, 302 | 48611259243A465E009752D9 /* DismissCardAnimator.swift in Sources */, 303 | 4861125E243A465E009752D9 /* CardDetailViewController.swift in Sources */, 304 | 4861125D243A465E009752D9 /* CardContentView.swift in Sources */, 305 | 48611258243A465E009752D9 /* PresentCardAnimator.swift in Sources */, 306 | 4861125B243A465E009752D9 /* CardCell.swift in Sources */, 307 | 4861125A243A465E009752D9 /* UIView.swift in Sources */, 308 | 4861125C243A465E009752D9 /* CardModel.swift in Sources */, 309 | 4861121B243A4607009752D9 /* AppDelegate.swift in Sources */, 310 | 48611256243A465E009752D9 /* CardTransition.swift in Sources */, 311 | 4861121D243A4607009752D9 /* SceneDelegate.swift in Sources */, 312 | 4861125F243A465E009752D9 /* HomeViewController.swift in Sources */, 313 | ); 314 | runOnlyForDeploymentPostprocessing = 0; 315 | }; 316 | 48611229243A4609009752D9 /* Sources */ = { 317 | isa = PBXSourcesBuildPhase; 318 | buildActionMask = 2147483647; 319 | files = ( 320 | 48611232243A4609009752D9 /* AnimationDemoTests.swift in Sources */, 321 | ); 322 | runOnlyForDeploymentPostprocessing = 0; 323 | }; 324 | 48611234243A4609009752D9 /* Sources */ = { 325 | isa = PBXSourcesBuildPhase; 326 | buildActionMask = 2147483647; 327 | files = ( 328 | 4861123D243A4609009752D9 /* AnimationDemoUITests.swift in Sources */, 329 | ); 330 | runOnlyForDeploymentPostprocessing = 0; 331 | }; 332 | /* End PBXSourcesBuildPhase section */ 333 | 334 | /* Begin PBXTargetDependency section */ 335 | 4861122F243A4609009752D9 /* PBXTargetDependency */ = { 336 | isa = PBXTargetDependency; 337 | target = 48611216243A4607009752D9 /* AnimationDemo */; 338 | targetProxy = 4861122E243A4609009752D9 /* PBXContainerItemProxy */; 339 | }; 340 | 4861123A243A4609009752D9 /* PBXTargetDependency */ = { 341 | isa = PBXTargetDependency; 342 | target = 48611216243A4607009752D9 /* AnimationDemo */; 343 | targetProxy = 48611239243A4609009752D9 /* PBXContainerItemProxy */; 344 | }; 345 | /* End PBXTargetDependency section */ 346 | 347 | /* Begin PBXVariantGroup section */ 348 | 48611225243A4609009752D9 /* LaunchScreen.storyboard */ = { 349 | isa = PBXVariantGroup; 350 | children = ( 351 | 48611226243A4609009752D9 /* Base */, 352 | ); 353 | name = LaunchScreen.storyboard; 354 | sourceTree = ""; 355 | }; 356 | /* End PBXVariantGroup section */ 357 | 358 | /* Begin XCBuildConfiguration section */ 359 | 4861123F243A4609009752D9 /* Debug */ = { 360 | isa = XCBuildConfiguration; 361 | buildSettings = { 362 | ALWAYS_SEARCH_USER_PATHS = NO; 363 | CLANG_ANALYZER_NONNULL = YES; 364 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 365 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 366 | CLANG_CXX_LIBRARY = "libc++"; 367 | CLANG_ENABLE_MODULES = YES; 368 | CLANG_ENABLE_OBJC_ARC = YES; 369 | CLANG_ENABLE_OBJC_WEAK = YES; 370 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 371 | CLANG_WARN_BOOL_CONVERSION = YES; 372 | CLANG_WARN_COMMA = YES; 373 | CLANG_WARN_CONSTANT_CONVERSION = YES; 374 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 375 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 376 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 377 | CLANG_WARN_EMPTY_BODY = YES; 378 | CLANG_WARN_ENUM_CONVERSION = YES; 379 | CLANG_WARN_INFINITE_RECURSION = YES; 380 | CLANG_WARN_INT_CONVERSION = YES; 381 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 382 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 383 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 384 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 385 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 386 | CLANG_WARN_STRICT_PROTOTYPES = YES; 387 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 388 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 389 | CLANG_WARN_UNREACHABLE_CODE = YES; 390 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 391 | COPY_PHASE_STRIP = NO; 392 | DEBUG_INFORMATION_FORMAT = dwarf; 393 | ENABLE_STRICT_OBJC_MSGSEND = YES; 394 | ENABLE_TESTABILITY = YES; 395 | GCC_C_LANGUAGE_STANDARD = gnu11; 396 | GCC_DYNAMIC_NO_PIC = NO; 397 | GCC_NO_COMMON_BLOCKS = YES; 398 | GCC_OPTIMIZATION_LEVEL = 0; 399 | GCC_PREPROCESSOR_DEFINITIONS = ( 400 | "DEBUG=1", 401 | "$(inherited)", 402 | ); 403 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 404 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 405 | GCC_WARN_UNDECLARED_SELECTOR = YES; 406 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 407 | GCC_WARN_UNUSED_FUNCTION = YES; 408 | GCC_WARN_UNUSED_VARIABLE = YES; 409 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 410 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 411 | MTL_FAST_MATH = YES; 412 | ONLY_ACTIVE_ARCH = YES; 413 | SDKROOT = iphoneos; 414 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 415 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 416 | }; 417 | name = Debug; 418 | }; 419 | 48611240243A4609009752D9 /* Release */ = { 420 | isa = XCBuildConfiguration; 421 | buildSettings = { 422 | ALWAYS_SEARCH_USER_PATHS = NO; 423 | CLANG_ANALYZER_NONNULL = YES; 424 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 425 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 426 | CLANG_CXX_LIBRARY = "libc++"; 427 | CLANG_ENABLE_MODULES = YES; 428 | CLANG_ENABLE_OBJC_ARC = YES; 429 | CLANG_ENABLE_OBJC_WEAK = YES; 430 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 431 | CLANG_WARN_BOOL_CONVERSION = YES; 432 | CLANG_WARN_COMMA = YES; 433 | CLANG_WARN_CONSTANT_CONVERSION = YES; 434 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 435 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 436 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 437 | CLANG_WARN_EMPTY_BODY = YES; 438 | CLANG_WARN_ENUM_CONVERSION = YES; 439 | CLANG_WARN_INFINITE_RECURSION = YES; 440 | CLANG_WARN_INT_CONVERSION = YES; 441 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 442 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 443 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 444 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 445 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 446 | CLANG_WARN_STRICT_PROTOTYPES = YES; 447 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 448 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 449 | CLANG_WARN_UNREACHABLE_CODE = YES; 450 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 451 | COPY_PHASE_STRIP = NO; 452 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 453 | ENABLE_NS_ASSERTIONS = NO; 454 | ENABLE_STRICT_OBJC_MSGSEND = YES; 455 | GCC_C_LANGUAGE_STANDARD = gnu11; 456 | GCC_NO_COMMON_BLOCKS = YES; 457 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 458 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 459 | GCC_WARN_UNDECLARED_SELECTOR = YES; 460 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 461 | GCC_WARN_UNUSED_FUNCTION = YES; 462 | GCC_WARN_UNUSED_VARIABLE = YES; 463 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 464 | MTL_ENABLE_DEBUG_INFO = NO; 465 | MTL_FAST_MATH = YES; 466 | SDKROOT = iphoneos; 467 | SWIFT_COMPILATION_MODE = wholemodule; 468 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 469 | VALIDATE_PRODUCT = YES; 470 | }; 471 | name = Release; 472 | }; 473 | 48611242243A4609009752D9 /* Debug */ = { 474 | isa = XCBuildConfiguration; 475 | buildSettings = { 476 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 477 | CODE_SIGN_STYLE = Automatic; 478 | DEVELOPMENT_TEAM = V4K6YSYUT4; 479 | INFOPLIST_FILE = AnimationDemo/Info.plist; 480 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 481 | LD_RUNPATH_SEARCH_PATHS = ( 482 | "$(inherited)", 483 | "@executable_path/Frameworks", 484 | ); 485 | PRODUCT_BUNDLE_IDENTIFIER = truongnguyen.AnimationDemo234234; 486 | PRODUCT_NAME = "$(TARGET_NAME)"; 487 | SWIFT_VERSION = 5.0; 488 | TARGETED_DEVICE_FAMILY = "1,2"; 489 | }; 490 | name = Debug; 491 | }; 492 | 48611243243A4609009752D9 /* Release */ = { 493 | isa = XCBuildConfiguration; 494 | buildSettings = { 495 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 496 | CODE_SIGN_STYLE = Automatic; 497 | DEVELOPMENT_TEAM = V4K6YSYUT4; 498 | INFOPLIST_FILE = AnimationDemo/Info.plist; 499 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 500 | LD_RUNPATH_SEARCH_PATHS = ( 501 | "$(inherited)", 502 | "@executable_path/Frameworks", 503 | ); 504 | PRODUCT_BUNDLE_IDENTIFIER = truongnguyen.AnimationDemo234234; 505 | PRODUCT_NAME = "$(TARGET_NAME)"; 506 | SWIFT_VERSION = 5.0; 507 | TARGETED_DEVICE_FAMILY = "1,2"; 508 | }; 509 | name = Release; 510 | }; 511 | 48611245243A4609009752D9 /* Debug */ = { 512 | isa = XCBuildConfiguration; 513 | buildSettings = { 514 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 515 | BUNDLE_LOADER = "$(TEST_HOST)"; 516 | CODE_SIGN_STYLE = Automatic; 517 | DEVELOPMENT_TEAM = V4K6YSYUT4; 518 | INFOPLIST_FILE = AnimationDemoTests/Info.plist; 519 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 520 | LD_RUNPATH_SEARCH_PATHS = ( 521 | "$(inherited)", 522 | "@executable_path/Frameworks", 523 | "@loader_path/Frameworks", 524 | ); 525 | PRODUCT_BUNDLE_IDENTIFIER = truongnguyen.AnimationDemoTests; 526 | PRODUCT_NAME = "$(TARGET_NAME)"; 527 | SWIFT_VERSION = 5.0; 528 | TARGETED_DEVICE_FAMILY = "1,2"; 529 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AnimationDemo.app/AnimationDemo"; 530 | }; 531 | name = Debug; 532 | }; 533 | 48611246243A4609009752D9 /* Release */ = { 534 | isa = XCBuildConfiguration; 535 | buildSettings = { 536 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 537 | BUNDLE_LOADER = "$(TEST_HOST)"; 538 | CODE_SIGN_STYLE = Automatic; 539 | DEVELOPMENT_TEAM = V4K6YSYUT4; 540 | INFOPLIST_FILE = AnimationDemoTests/Info.plist; 541 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 542 | LD_RUNPATH_SEARCH_PATHS = ( 543 | "$(inherited)", 544 | "@executable_path/Frameworks", 545 | "@loader_path/Frameworks", 546 | ); 547 | PRODUCT_BUNDLE_IDENTIFIER = truongnguyen.AnimationDemoTests; 548 | PRODUCT_NAME = "$(TARGET_NAME)"; 549 | SWIFT_VERSION = 5.0; 550 | TARGETED_DEVICE_FAMILY = "1,2"; 551 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AnimationDemo.app/AnimationDemo"; 552 | }; 553 | name = Release; 554 | }; 555 | 48611248243A4609009752D9 /* Debug */ = { 556 | isa = XCBuildConfiguration; 557 | buildSettings = { 558 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 559 | CODE_SIGN_STYLE = Automatic; 560 | DEVELOPMENT_TEAM = V4K6YSYUT4; 561 | INFOPLIST_FILE = AnimationDemoUITests/Info.plist; 562 | LD_RUNPATH_SEARCH_PATHS = ( 563 | "$(inherited)", 564 | "@executable_path/Frameworks", 565 | "@loader_path/Frameworks", 566 | ); 567 | PRODUCT_BUNDLE_IDENTIFIER = truongnguyen.AnimationDemoUITests; 568 | PRODUCT_NAME = "$(TARGET_NAME)"; 569 | SWIFT_VERSION = 5.0; 570 | TARGETED_DEVICE_FAMILY = "1,2"; 571 | TEST_TARGET_NAME = AnimationDemo; 572 | }; 573 | name = Debug; 574 | }; 575 | 48611249243A4609009752D9 /* Release */ = { 576 | isa = XCBuildConfiguration; 577 | buildSettings = { 578 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 579 | CODE_SIGN_STYLE = Automatic; 580 | DEVELOPMENT_TEAM = V4K6YSYUT4; 581 | INFOPLIST_FILE = AnimationDemoUITests/Info.plist; 582 | LD_RUNPATH_SEARCH_PATHS = ( 583 | "$(inherited)", 584 | "@executable_path/Frameworks", 585 | "@loader_path/Frameworks", 586 | ); 587 | PRODUCT_BUNDLE_IDENTIFIER = truongnguyen.AnimationDemoUITests; 588 | PRODUCT_NAME = "$(TARGET_NAME)"; 589 | SWIFT_VERSION = 5.0; 590 | TARGETED_DEVICE_FAMILY = "1,2"; 591 | TEST_TARGET_NAME = AnimationDemo; 592 | }; 593 | name = Release; 594 | }; 595 | /* End XCBuildConfiguration section */ 596 | 597 | /* Begin XCConfigurationList section */ 598 | 48611212243A4607009752D9 /* Build configuration list for PBXProject "AnimationDemo" */ = { 599 | isa = XCConfigurationList; 600 | buildConfigurations = ( 601 | 4861123F243A4609009752D9 /* Debug */, 602 | 48611240243A4609009752D9 /* Release */, 603 | ); 604 | defaultConfigurationIsVisible = 0; 605 | defaultConfigurationName = Release; 606 | }; 607 | 48611241243A4609009752D9 /* Build configuration list for PBXNativeTarget "AnimationDemo" */ = { 608 | isa = XCConfigurationList; 609 | buildConfigurations = ( 610 | 48611242243A4609009752D9 /* Debug */, 611 | 48611243243A4609009752D9 /* Release */, 612 | ); 613 | defaultConfigurationIsVisible = 0; 614 | defaultConfigurationName = Release; 615 | }; 616 | 48611244243A4609009752D9 /* Build configuration list for PBXNativeTarget "AnimationDemoTests" */ = { 617 | isa = XCConfigurationList; 618 | buildConfigurations = ( 619 | 48611245243A4609009752D9 /* Debug */, 620 | 48611246243A4609009752D9 /* Release */, 621 | ); 622 | defaultConfigurationIsVisible = 0; 623 | defaultConfigurationName = Release; 624 | }; 625 | 48611247243A4609009752D9 /* Build configuration list for PBXNativeTarget "AnimationDemoUITests" */ = { 626 | isa = XCConfigurationList; 627 | buildConfigurations = ( 628 | 48611248243A4609009752D9 /* Debug */, 629 | 48611249243A4609009752D9 /* Release */, 630 | ); 631 | defaultConfigurationIsVisible = 0; 632 | defaultConfigurationName = Release; 633 | }; 634 | /* End XCConfigurationList section */ 635 | }; 636 | rootObject = 4861120F243A4607009752D9 /* Project object */; 637 | } 638 | -------------------------------------------------------------------------------- /AnimationDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AnimationDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AnimationDemo.xcodeproj/project.xcworkspace/xcuserdata/truongnguyen.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo.xcodeproj/project.xcworkspace/xcuserdata/truongnguyen.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /AnimationDemo.xcodeproj/xcuserdata/truongnguyen.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /AnimationDemo.xcodeproj/xcuserdata/truongnguyen.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | AnimationDemo.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /AnimationDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AnimationDemo 4 | // 5 | // Created by Truong Nguyen on 4/5/20. 6 | // Copyright © 2020 Truong Nguyen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | 18 | window = UIWindow(frame: UIScreen.main.bounds) 19 | 20 | let tabbarVC = UITabBarController() 21 | 22 | let vc1 = HomeViewController() 23 | vc1.title = "Show Tabbar" 24 | vc1.isTabbarHiddenWhenDismissingDestVC = false 25 | let nVC1 = UINavigationController(rootViewController: vc1) 26 | nVC1.tabBarItem = UITabBarItem(title: "Show Tabbar", image: UIImage(named: "icn_tab_contact_normal")?.withRenderingMode(.alwaysOriginal), selectedImage: UIImage(named: "icn_tab_contact_selected")?.withRenderingMode(.alwaysOriginal)) 27 | 28 | let vc2 = HomeViewController() 29 | vc2.title = "Show Tabbar" 30 | vc2.isTabbarHiddenWhenDismissingDestVC = true 31 | let nVC2 = UINavigationController(rootViewController: vc2) 32 | nVC2.tabBarItem = UITabBarItem(title: "Hide Tabbar", image: UIImage(named: "icn_tab_more_normal")?.withRenderingMode(.alwaysOriginal), selectedImage: UIImage(named: "icn_tab_more_selected")?.withRenderingMode(.alwaysOriginal)) 33 | 34 | tabbarVC.viewControllers = [nVC1, nVC2] 35 | 36 | window?.rootViewController = tabbarVC 37 | window?.makeKeyAndVisible() 38 | 39 | return true 40 | } 41 | 42 | // MARK: UISceneSession Lifecycle 43 | 44 | @available(iOS 13.0, *) 45 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 46 | // Called when a new scene session is being created. 47 | // Use this method to select a configuration to create the new scene with. 48 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 49 | } 50 | 51 | @available(iOS 13.0, *) 52 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 53 | // Called when the user discards a scene session. 54 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 55 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 56 | } 57 | 58 | 59 | } 60 | 61 | -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/cancel.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "output-onlinepngtools (1)-1.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "output-onlinepngtools (1).png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "output-onlinepngtools.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/cancel.imageset/output-onlinepngtools (1)-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/cancel.imageset/output-onlinepngtools (1)-1.png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/cancel.imageset/output-onlinepngtools (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/cancel.imageset/output-onlinepngtools (1).png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/cancel.imageset/output-onlinepngtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/cancel.imageset/output-onlinepngtools.png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/double_down_arrow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "output-onlinepngtools (3).png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "output-onlinepngtools (4).png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "output-onlinepngtools (5).png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/double_down_arrow.imageset/output-onlinepngtools (3).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/double_down_arrow.imageset/output-onlinepngtools (3).png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/double_down_arrow.imageset/output-onlinepngtools (4).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/double_down_arrow.imageset/output-onlinepngtools (4).png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/double_down_arrow.imageset/output-onlinepngtools (5).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/double_down_arrow.imageset/output-onlinepngtools (5).png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/icn_tab_contact_normal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icn_tabbar_contact_normal.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "icn_tabbar_contact_normal@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "icn_tabbar_contact_normal@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/icn_tab_contact_normal.imageset/icn_tabbar_contact_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/icn_tab_contact_normal.imageset/icn_tabbar_contact_normal.png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/icn_tab_contact_normal.imageset/icn_tabbar_contact_normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/icn_tab_contact_normal.imageset/icn_tabbar_contact_normal@2x.png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/icn_tab_contact_normal.imageset/icn_tabbar_contact_normal@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/icn_tab_contact_normal.imageset/icn_tabbar_contact_normal@3x.png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/icn_tab_contact_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icn_tabbar_contact_selected.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "icn_tabbar_contact_selected@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "icn_tabbar_contact_selected@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/icn_tab_contact_selected.imageset/icn_tabbar_contact_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/icn_tab_contact_selected.imageset/icn_tabbar_contact_selected.png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/icn_tab_contact_selected.imageset/icn_tabbar_contact_selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/icn_tab_contact_selected.imageset/icn_tabbar_contact_selected@2x.png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/icn_tab_contact_selected.imageset/icn_tabbar_contact_selected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/icn_tab_contact_selected.imageset/icn_tabbar_contact_selected@3x.png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/icn_tab_more_normal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icn_tabbar_more_normal.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "icn_tabbar_more_normal@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "icn_tabbar_more_normal@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/icn_tab_more_normal.imageset/icn_tabbar_more_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/icn_tab_more_normal.imageset/icn_tabbar_more_normal.png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/icn_tab_more_normal.imageset/icn_tabbar_more_normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/icn_tab_more_normal.imageset/icn_tabbar_more_normal@2x.png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/icn_tab_more_normal.imageset/icn_tabbar_more_normal@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/icn_tab_more_normal.imageset/icn_tabbar_more_normal@3x.png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/icn_tab_more_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icn_tabbar_more_selected.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "icn_tabbar_more_selected@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "icn_tabbar_more_selected@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/icn_tab_more_selected.imageset/icn_tabbar_more_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/icn_tab_more_selected.imageset/icn_tabbar_more_selected.png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/icn_tab_more_selected.imageset/icn_tabbar_more_selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/icn_tab_more_selected.imageset/icn_tabbar_more_selected@2x.png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/icn_tab_more_selected.imageset/icn_tabbar_more_selected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/icn_tab_more_selected.imageset/icn_tabbar_more_selected@3x.png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/img1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Image.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/img1.imageset/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/img1.imageset/Image.png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/img2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "img2.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/img2.imageset/img2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/img2.imageset/img2.jpg -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/img3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "drowning.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/img3.imageset/drowning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/img3.imageset/drowning.png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/img4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "img1.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/img4.imageset/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/img4.imageset/img1.png -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/img5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Image.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AnimationDemo/Assets.xcassets/img5.imageset/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/AnimationDemo/Assets.xcassets/img5.imageset/Image.png -------------------------------------------------------------------------------- /AnimationDemo/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 | -------------------------------------------------------------------------------- /AnimationDemo/CardDetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CardDetailViewController.swift 3 | // AnimationDemo 4 | // 5 | // Created by Truong Nguyen on 4/3/20. 6 | // Copyright © 2020 Truong Nguyen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let text = " Swift 5.1 now makes it easier to create and share binary frameworks with others. It also includes features that make it easier to design better APIs and reduce the amount of common boilerplate code.\n Swift is developed in the open at Swift.org, with source code, a bug tracker, forums, and regular development builds available for everyone. This broad community of developers, both inside Apple as well as hundreds of outside contributors, work together to make Swift even more amazing. There is an even broader range of blogs, podcasts, conferences and meetups where developers in the community share their experiences of how to realize Swift’s great potential.\n Swift already supports all Apple platforms and Linux, with community members actively working to port to even more platforms. With SourceKit-LSP, the community is also working to integrate Swift support into a wide-variety of developer tools. We’re excited to see more ways in which Swift makes software safer and faster, while also making programming more fun.\n While Swift powers many new apps on Apple platforms, it’s also being used for a new class of modern server applications. Swift is perfect for use in server apps that need runtime safety, compiled performance and a small memory footprint. To steer the direction of Swift for developing and deploying server applications, the community formed the Swift Server work group. The first product of this effort was SwiftNIO, a cross-platform asynchronous event-driven network application framework for high performance protocol servers and clients. It serves as the foundation for building additional server-oriented tools and technologies, including logging, metrics and database drivers which are all in active development." 12 | 13 | class CardDetailViewController: UIViewController { 14 | public var cardModel: CardModel{ 15 | didSet{ 16 | cardContentView.model = cardModel 17 | } 18 | } 19 | 20 | public var isStatusBarHiddenWhenPresentingBySourceVC = false{ 21 | didSet{ 22 | if isStatusBarHiddenWhenPresentingBySourceVC != oldValue{ 23 | setNeedsStatusBarAppearanceUpdate() 24 | } 25 | } 26 | } 27 | 28 | private var cardContentView = CardContentView() 29 | private var scrollView = UIScrollView() 30 | private var labelView = UILabel() 31 | private var dismissButton = UIButton() 32 | 33 | private var enabledDraggingDownToDismiss = false 34 | 35 | private lazy var dismissalPanGesture: UIPanGestureRecognizer = { 36 | let pan = UIPanGestureRecognizer() 37 | pan.maximumNumberOfTouches = 1 38 | return pan 39 | }() 40 | 41 | private lazy var dismissalScreenEdgePanGesture: UIScreenEdgePanGestureRecognizer = { 42 | let pan = UIScreenEdgePanGestureRecognizer() 43 | pan.edges = .left 44 | return pan 45 | }() 46 | 47 | private var interactiveStartingPoint: CGPoint? 48 | 49 | private let dismissalScreenEdgePanGestureDistance: CGFloat = 100 50 | private let dismissalPanGestureDistance: CGFloat = 100 51 | 52 | private var dismissalAnimator: UIViewPropertyAnimator? 53 | 54 | init(model: CardModel) { 55 | cardModel = model 56 | 57 | super.init(nibName: nil, bundle: nil) 58 | } 59 | 60 | required init?(coder: NSCoder) { 61 | fatalError("init(coder:) has not been implemented") 62 | } 63 | 64 | override func viewDidLoad() { 65 | super.viewDidLoad() 66 | 67 | view.addSubview(scrollView) 68 | view.layer.masksToBounds = true 69 | view.clipsToBounds = true 70 | 71 | scrollView.delegate = self 72 | scrollView.isDirectionalLockEnabled = true 73 | scrollView.backgroundColor = .white 74 | scrollView.edges(top: 0, leading: 0, bottom: 0, trailling: 0) 75 | if #available(iOS 11.0, *) { 76 | scrollView.contentInsetAdjustmentBehavior = .never 77 | } else { 78 | // Fallback on earlier versions 79 | } 80 | 81 | scrollView.addSubview(cardContentView) 82 | scrollView.addSubview(labelView) 83 | scrollView.addSubview(dismissButton) 84 | 85 | cardContentView.edges(top: 0, leading: 0, bottom: nil, trailling: nil) 86 | cardContentView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true 87 | cardContentView.heightAnchor.constraint(equalTo: cardContentView.widthAnchor, multiplier: 1.2).isActive = true 88 | cardContentView.model = cardModel 89 | 90 | var padding: CGFloat = 16 91 | labelView.text = text 92 | labelView.textColor = .black 93 | labelView.font = UIFont.systemFont(ofSize: 20) 94 | labelView.numberOfLines = 1000 95 | labelView.topAnchor.constraint(equalTo: cardContentView.bottomAnchor, constant: padding).isActive = true 96 | labelView.edges(top: nil, leading: padding, bottom: -padding, trailling: nil) 97 | labelView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -padding * 2).isActive = true 98 | 99 | let buttonWidth: CGFloat = 30 100 | dismissButton.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.7) 101 | dismissButton.setImage(UIImage(named: "cancel"), for: .normal) 102 | padding = 7 103 | dismissButton.imageEdgeInsets = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding) 104 | dismissButton.edges(to: view, top: 35, leading: nil, bottom: nil, trailling: -25) 105 | dismissButton.widthAnchor.constraint(equalToConstant: buttonWidth).isActive = true 106 | dismissButton.heightAnchor.constraint(equalTo: dismissButton.widthAnchor).isActive = true 107 | dismissButton.layer.cornerRadius = buttonWidth / 2 108 | dismissButton.addTarget(self, action: #selector(dismissButtonPressed), for: .touchUpInside) 109 | 110 | dismissalScreenEdgePanGesture.addTarget(self, action: #selector(handleGesturePan(gesture:))) 111 | dismissalScreenEdgePanGesture.delegate = self 112 | 113 | dismissalPanGesture.addTarget(self, action: #selector(handleGesturePan(gesture:))) 114 | dismissalPanGesture.delegate = self 115 | dismissalPanGesture.require(toFail: dismissalScreenEdgePanGesture) 116 | 117 | view.addGestureRecognizer(dismissalPanGesture) 118 | view.addGestureRecognizer(dismissalScreenEdgePanGesture) 119 | } 120 | 121 | override func viewSafeAreaInsetsDidChange() { 122 | if #available(iOS 11.0, *) { 123 | super.viewSafeAreaInsetsDidChange() 124 | 125 | let topSafeInset = view.safeAreaInsets.top 126 | cardContentView.topSafeInset = topSafeInset 127 | 128 | } else { 129 | // Fallback on earlier versions 130 | } 131 | } 132 | 133 | override func viewWillLayoutSubviews() { 134 | super.viewWillLayoutSubviews() 135 | scrollView.scrollIndicatorInsets = .init(top: cardContentView.bounds.height, left: 0, bottom: 0, right: 0) 136 | } 137 | } 138 | 139 | extension CardDetailViewController: UIScrollViewDelegate{ 140 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 141 | if enabledDraggingDownToDismiss || (scrollView.isTracking && scrollView.contentOffset.y < 0) { 142 | enabledDraggingDownToDismiss = true 143 | scrollView.contentOffset = .zero 144 | } 145 | 146 | scrollView.showsVerticalScrollIndicator = !enabledDraggingDownToDismiss 147 | } 148 | 149 | func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { 150 | if velocity.y > 0 && scrollView.contentOffset.y <= 0 { 151 | scrollView.contentOffset = .zero 152 | } 153 | } 154 | 155 | } 156 | 157 | extension CardDetailViewController{ 158 | 159 | private func dismissalGestureDidBegin(progress: CGFloat){ 160 | dismissalAnimator = dismissalAnimator(progress: progress) 161 | } 162 | 163 | private func dismissalGestureDidChange(progress: CGFloat){ 164 | dismissalAnimator = dismissalAnimator(progress: progress) 165 | 166 | let isDismissalSuccess = progress >= 1.0 167 | 168 | if let dismissalAnimator = dismissalAnimator{ 169 | dismissalAnimator.fractionComplete = progress 170 | 171 | if isDismissalSuccess { 172 | dismissalAnimator.stopAnimation(false) 173 | 174 | dismissalAnimator.addCompletion { [unowned self] (pos) in 175 | switch pos { 176 | case .current, .end: 177 | self.didSuccessfullyDragDownToDismiss() 178 | default: 179 | fatalError("Must finish dismissal at end!") 180 | } 181 | } 182 | 183 | dismissalAnimator.finishAnimation(at: .current) 184 | } 185 | } 186 | } 187 | 188 | private func didSuccessfullyDragDownToDismiss() { 189 | dismiss(animated: true, completion: nil) 190 | } 191 | 192 | private func cancelDismissalTransition(gesture: UIGestureRecognizer) { 193 | if dismissalAnimator == nil { 194 | // Gesture's too quick that it doesn't have dismissalAnimator! 195 | print("Too quick there's no animator!") 196 | didCancelDismissalTransition() 197 | return 198 | } 199 | 200 | if let dismissalAnimator = dismissalAnimator{ 201 | dismissalAnimator.pauseAnimation() 202 | dismissalAnimator.isReversed = true 203 | 204 | // Disable gesture until reverse closing animation finishes. 205 | gesture.isEnabled = false 206 | 207 | dismissalAnimator.addCompletion { [unowned self] (pos) in 208 | self.didCancelDismissalTransition() 209 | 210 | gesture.isEnabled = true 211 | } 212 | 213 | dismissalAnimator.startAnimation() 214 | } 215 | } 216 | 217 | private func didCancelDismissalTransition() { 218 | interactiveStartingPoint = nil 219 | dismissalAnimator = nil 220 | enabledDraggingDownToDismiss = false 221 | } 222 | 223 | private func dismissalProgress(gesture: UIPanGestureRecognizer) -> CGFloat{ 224 | let isScreenEdgePan = gesture.isKind(of: UIScreenEdgePanGestureRecognizer.self) 225 | 226 | guard isScreenEdgePan || enabledDraggingDownToDismiss else { 227 | return 0 228 | } 229 | 230 | var progress: CGFloat 231 | 232 | if isScreenEdgePan{ 233 | progress = gesture.translation(in: gesture.view).x / dismissalScreenEdgePanGestureDistance 234 | }else{ 235 | 236 | if let startingPoint = interactiveStartingPoint{ 237 | let currentPoint = gesture.location(in: gesture.view) 238 | progress = (currentPoint.y - startingPoint.y) / dismissalPanGestureDistance 239 | 240 | }else{ 241 | progress = 0 242 | } 243 | 244 | } 245 | 246 | return progress 247 | } 248 | 249 | @objc private func dismissButtonPressed(){ 250 | dismiss(animated: true, completion: nil) 251 | } 252 | 253 | private func dismissalAnimator(progress: CGFloat) -> UIViewPropertyAnimator{ 254 | if let animator = dismissalAnimator { 255 | return animator 256 | } else { 257 | 258 | let shrinkScaleTarget: CGFloat = 0.86 259 | let cornerRadius: CGFloat = 16 260 | 261 | let animator = UIViewPropertyAnimator(duration: 0.1, curve: .linear, animations: {[weak self] in 262 | self?.view.transform = .init(scaleX: shrinkScaleTarget, y: shrinkScaleTarget) 263 | self?.view.layer.cornerRadius = cornerRadius 264 | 265 | self?.dismissButton.alpha = 0 266 | }) 267 | 268 | animator.isReversed = false 269 | animator.pauseAnimation() 270 | animator.fractionComplete = progress 271 | 272 | return animator 273 | } 274 | } 275 | 276 | private func canHandleDismissalGesture(gesture: UIPanGestureRecognizer) -> Bool{ 277 | let isScreenEdgePan = gesture.isKind(of: UIScreenEdgePanGestureRecognizer.self) 278 | 279 | return (isScreenEdgePan || enabledDraggingDownToDismiss) && gesture.view != nil && scrollView.contentOffset == .zero 280 | } 281 | 282 | private func handleDismissalGesture(gesture: UIPanGestureRecognizer){ 283 | // Update startingPoint 284 | if interactiveStartingPoint == nil { 285 | interactiveStartingPoint = gesture.location(in: gesture.view) 286 | } 287 | 288 | let progress = dismissalProgress(gesture: gesture) 289 | 290 | switch gesture.state { 291 | case .began: 292 | dismissalGestureDidBegin(progress: progress) 293 | 294 | case .changed: 295 | dismissalGestureDidChange(progress: progress) 296 | 297 | case .ended, .cancelled: 298 | cancelDismissalTransition(gesture: gesture) 299 | 300 | default: 301 | fatalError("Impossible gesture state? \(gesture.state.rawValue)") 302 | } 303 | } 304 | 305 | @objc func handleGesturePan(gesture: UIPanGestureRecognizer) { 306 | if canHandleDismissalGesture(gesture: gesture){ 307 | handleDismissalGesture(gesture: gesture) 308 | } 309 | } 310 | } 311 | 312 | extension CardDetailViewController: UIGestureRecognizerDelegate{ 313 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { 314 | return true 315 | } 316 | } 317 | 318 | 319 | extension CardDetailViewController: DestinationTransitionDelegate{ 320 | 321 | func presentingDestTransitionWillBegin(){ 322 | dismissButton.alpha = 0 323 | 324 | cardContentView.layer.cornerRadius = 16 325 | cardContentView.topSafeInset = 0 326 | } 327 | 328 | func presentingDestAnimation(containnerView containner: UIView) { 329 | dismissButton.alpha = 1 330 | 331 | cardContentView.layer.cornerRadius = 0 332 | if #available(iOS 11.0, *) { 333 | cardContentView.topSafeInset = view.safeAreaInsets.top 334 | } 335 | 336 | isStatusBarHiddenWhenPresentingBySourceVC = true 337 | } 338 | 339 | func presentingDestTransitionDidEnd(){ 340 | 341 | } 342 | 343 | func dismissalDestTransitionWillBegin(){ 344 | 345 | } 346 | 347 | func dismissingDestAnimation(containnerView containner: UIView) { 348 | dismissButton.alpha = 0 349 | 350 | cardContentView.layer.cornerRadius = 16 351 | cardContentView.topSafeInset = 0 352 | 353 | scrollView.contentOffset = .zero 354 | 355 | view.transform = .identity 356 | } 357 | 358 | func dismissalDestTransitionDidEnd(_ completed: Bool) { 359 | 360 | } 361 | } 362 | 363 | extension CardDetailViewController{ 364 | override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation{ 365 | return .slide 366 | } 367 | 368 | override var prefersStatusBarHidden: Bool{ 369 | return isStatusBarHiddenWhenPresentingBySourceVC 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /AnimationDemo/HomeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AnimationDemo 4 | // 5 | // Created by Truong Nguyen on 3/24/20. 6 | // Copyright © 2020 Truong Nguyen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HomeViewController: UIViewController { 12 | private weak var selectedCell: UICollectionViewCell? 13 | 14 | private var collectionView: UICollectionView! 15 | 16 | private var transition: CardTransition? 17 | 18 | private var button: UIButton! 19 | 20 | private var tabbarFrame: CGRect? 21 | 22 | private var cardModels: [CardModel] = [ 23 | CardModel(primary: "GAME OF THE DAY", 24 | secondary: "Minecraft makes a splash", 25 | description: "The ocean is a big place. Tap to read how Minecraft's just got even bigger.", 26 | image: UIImage(named: "img1")!), 27 | CardModel(primary: "You won't believe this guy", 28 | secondary: "Something we want", 29 | description: "They have something we want which is not something we need.", 30 | image: UIImage(named: "img2")!), 31 | CardModel(primary: "You are beautiful", 32 | secondary: "Kiss me if you want", 33 | description: "Vertical Game Background (With images) | Game background ...", 34 | image: UIImage(named: "img5")!), 35 | CardModel(primary: "LET'S PLAY", 36 | secondary: "Cats, cats, cats!", 37 | description: "Play these games right meo. We gooooo...", 38 | image: UIImage(named: "img3")!), 39 | CardModel(primary: "What's your name", 40 | secondary: "Kill Me. GO", 41 | description: "Toxic Ocean Game Background with Obstacles by bevouliin on Dribbble", 42 | image: UIImage(named: "img4")!), 43 | ] 44 | 45 | public var isTabbarHiddenWhenDismissingDestVC = false 46 | 47 | override func viewDidLoad() { 48 | super.viewDidLoad() 49 | view.backgroundColor = .white 50 | 51 | let viewFlowLayout = UICollectionViewFlowLayout() 52 | let cardHorizontalOffset: CGFloat = 25 53 | let cardHeightByWidthRatio: CGFloat = 1.2 54 | let width = UIScreen.main.bounds.width - 2 * cardHorizontalOffset 55 | let height: CGFloat = width * cardHeightByWidthRatio 56 | viewFlowLayout.itemSize = CGSize(width: width, height: height) 57 | viewFlowLayout.minimumLineSpacing = 20 58 | 59 | collectionView = UICollectionView(frame: view.frame, collectionViewLayout: viewFlowLayout) 60 | collectionView.delegate = self 61 | collectionView.dataSource = self 62 | collectionView.register(CardCell.self, forCellWithReuseIdentifier: "Cell") 63 | collectionView.backgroundColor = .white 64 | collectionView.delaysContentTouches = false 65 | 66 | view.addSubview(collectionView) 67 | 68 | let buttonHeight: CGFloat = 30 69 | button = UIButton() 70 | button.frame = CGRect(x: view.frame.maxX - buttonHeight * 2, y: view.frame.maxY - 200, width: buttonHeight, height: buttonHeight) 71 | button.backgroundColor = .yellow 72 | button.layer.cornerRadius = button.frame.width / 2 73 | button.layer.borderColor = UIColor.black.cgColor 74 | button.layer.borderWidth = 2 75 | button.setImage(UIImage(named: "double_down_arrow"), for: .normal) 76 | button.isHidden = true 77 | view.addSubview(button) 78 | } 79 | 80 | override func viewWillAppear(_ animated: Bool) { 81 | super.viewWillAppear(animated) 82 | 83 | navigationController?.setNavigationBarHidden(true, animated: animated) 84 | 85 | if tabbarFrame == nil{ 86 | tabbarFrame = tabBarController?.tabBar.frame 87 | } 88 | } 89 | } 90 | 91 | extension HomeViewController: UICollectionViewDataSource{ 92 | 93 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 94 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as? CardCell else { 95 | fatalError("You don't register cell") 96 | } 97 | 98 | cell.model = cardModels[indexPath.row] 99 | 100 | return cell 101 | } 102 | 103 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 104 | return cardModels.count 105 | } 106 | } 107 | 108 | extension HomeViewController: UICollectionViewDelegate{ 109 | 110 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 111 | 112 | let cell = collectionView.cellForItem(at: indexPath) as! CardCell 113 | selectedCell = cell 114 | 115 | cell.freezeAnimations() 116 | 117 | // Get current frame on collectionView 118 | let currentCellFrame = cell.layer.presentation()!.frame 119 | 120 | // Convert current frame to screen's coordinates 121 | // current frame on Screen 122 | let cardPresentationFrameOnScreen = cell.superview!.convert(currentCellFrame, to: nil) 123 | 124 | // Get card frame without transform in screen's coordinates (for the dismissing back later to original location) 125 | let cardFrameWithoutTransform = { () -> CGRect in 126 | let center = cell.center 127 | let size = cell.bounds.size 128 | let r = CGRect( 129 | x: center.x - size.width / 2, 130 | y: center.y - size.height / 2, 131 | width: size.width, 132 | height: size.height 133 | ) 134 | return cell.superview!.convert(r, to: nil) 135 | }() 136 | 137 | let vc = CardDetailViewController(model: cardModels[indexPath.row]) 138 | vc.isStatusBarHiddenWhenPresentingBySourceVC = true 139 | 140 | let params = CardTransition.Params(verticalExpandingStyle: .fromTop, 141 | originalCardFrame: cardPresentationFrameOnScreen, 142 | originalCardFrameWithoutTransform: cardFrameWithoutTransform, 143 | sourceTransition: self, 144 | destTransition: vc) 145 | transition = CardTransition(params: params) 146 | vc.transitioningDelegate = transition 147 | vc.modalPresentationCapturesStatusBarAppearance = true 148 | vc.modalPresentationStyle = .custom 149 | 150 | present(vc, animated: true, completion: { [unowned cell] in 151 | // Unfreeze 152 | cell.unfreezeAnimations() 153 | }) 154 | } 155 | } 156 | 157 | extension HomeViewController: SourceTransitionDelegate{ 158 | func sourceTransitionPresentWillBegin() { 159 | selectedCell?.isHidden = true 160 | 161 | (selectedCell as? CardCell)?.resetTransform() 162 | } 163 | 164 | func sourceTransitionPresentingAnimation(containnerView containner: UIView) { 165 | if isTabbarHiddenWhenDismissingDestVC{ 166 | tabBarController?.tabBar.frame.origin.y += tabBarController?.tabBar.frame.height ?? 0 167 | } 168 | } 169 | 170 | func sourceTransitionDismissingAnimation(containnerView containner: UIView) { 171 | if isTabbarHiddenWhenDismissingDestVC, let tabbarFrame = tabbarFrame{ 172 | tabBarController?.tabBar.frame = tabbarFrame 173 | } 174 | } 175 | 176 | func sourceTransitionDismissDidEnd(_ completed: Bool) { 177 | if completed{ 178 | selectedCell?.isHidden = false 179 | } 180 | } 181 | 182 | func safeInsetWhenSourceTransitionDismissing() -> UIEdgeInsets { 183 | var safeInset: UIEdgeInsets 184 | if #available(iOS 11.0, *) { 185 | safeInset = view.safeAreaInsets 186 | safeInset.top = 0 187 | } else { 188 | safeInset = .zero 189 | } 190 | 191 | return safeInset 192 | } 193 | } 194 | 195 | 196 | -------------------------------------------------------------------------------- /AnimationDemo/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /AnimationDemo/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // AnimationDemo 4 | // 5 | // Created by Truong Nguyen on 4/5/20. 6 | // Copyright © 2020 Truong Nguyen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @available(iOS 13.0, *) 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | guard let windowScene = (scene as? UIWindowScene) else { return } 23 | window = UIWindow(frame: windowScene.coordinateSpace.bounds) 24 | window?.windowScene = windowScene 25 | 26 | let tabbarVC = UITabBarController() 27 | 28 | let vc1 = HomeViewController() 29 | vc1.title = "Show Tabbar" 30 | vc1.isTabbarHiddenWhenDismissingDestVC = false 31 | let nVC1 = UINavigationController(rootViewController: vc1) 32 | nVC1.tabBarItem = UITabBarItem(title: "Show Tabbar", image: UIImage(named: "icn_tab_contact_normal")?.withRenderingMode(.alwaysOriginal), selectedImage: UIImage(named: "icn_tab_contact_selected")?.withRenderingMode(.alwaysOriginal)) 33 | 34 | let vc2 = HomeViewController() 35 | vc2.title = "Show Tabbar" 36 | vc2.isTabbarHiddenWhenDismissingDestVC = true 37 | let nVC2 = UINavigationController(rootViewController: vc2) 38 | nVC2.tabBarItem = UITabBarItem(title: "Hide Tabbar", image: UIImage(named: "icn_tab_more_normal")?.withRenderingMode(.alwaysOriginal), selectedImage: UIImage(named: "icn_tab_more_selected")?.withRenderingMode(.alwaysOriginal)) 39 | 40 | tabbarVC.viewControllers = [nVC1, nVC2] 41 | 42 | window?.rootViewController = tabbarVC 43 | window?.makeKeyAndVisible() 44 | 45 | guard let _ = (scene as? UIWindowScene) else { return } 46 | } 47 | 48 | func sceneDidDisconnect(_ scene: UIScene) { 49 | // Called as the scene is being released by the system. 50 | // This occurs shortly after the scene enters the background, or when its session is discarded. 51 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 52 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 53 | } 54 | 55 | func sceneDidBecomeActive(_ scene: UIScene) { 56 | // Called when the scene has moved from an inactive state to an active state. 57 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 58 | } 59 | 60 | func sceneWillResignActive(_ scene: UIScene) { 61 | // Called when the scene will move from an active state to an inactive state. 62 | // This may occur due to temporary interruptions (ex. an incoming phone call). 63 | } 64 | 65 | func sceneWillEnterForeground(_ scene: UIScene) { 66 | // Called as the scene transitions from the background to the foreground. 67 | // Use this method to undo the changes made on entering the background. 68 | } 69 | 70 | func sceneDidEnterBackground(_ scene: UIScene) { 71 | // Called as the scene transitions from the foreground to the background. 72 | // Use this method to save data, release shared resources, and store enough scene-specific state information 73 | // to restore the scene back to its current state. 74 | } 75 | 76 | 77 | } 78 | 79 | -------------------------------------------------------------------------------- /AnimationDemo/Transition/BlurPresentationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CardPresentationController.swift 3 | // AnimationDemo 4 | // 5 | // Created by Truong Nguyen on 3/24/20. 6 | // Copyright © 2020 Truong Nguyen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class BlurPresentationController: UIPresentationController { 12 | 13 | private lazy var blurView = UIVisualEffectView(effect: nil) 14 | 15 | override func presentationTransitionWillBegin() { 16 | super.presentationTransitionWillBegin() 17 | 18 | if let container = containerView{ 19 | blurView.translatesAutoresizingMaskIntoConstraints = false 20 | container.addSubview(blurView) 21 | blurView.edges(to: container, top: 0, left: 0, bottom: 0, right: 0) 22 | blurView.alpha = 0.0 23 | 24 | presentingViewController.beginAppearanceTransition(false, animated: false) 25 | presentedViewController.transitionCoordinator!.animate(alongsideTransition: { (ctx) in 26 | UIView.animate(withDuration: 0.5, animations: { 27 | self.blurView.effect = UIBlurEffect(style: .light) 28 | self.blurView.alpha = 1 29 | }) 30 | }) { (ctx) in } 31 | } 32 | } 33 | 34 | override func presentationTransitionDidEnd(_ completed: Bool) { 35 | presentingViewController.endAppearanceTransition() 36 | } 37 | 38 | override func dismissalTransitionWillBegin() { 39 | super.dismissalTransitionWillBegin() 40 | 41 | presentingViewController.beginAppearanceTransition(true, animated: true) 42 | presentedViewController.transitionCoordinator!.animate(alongsideTransition: { (ctx) in 43 | self.blurView.alpha = 0.0 44 | }, completion: nil) 45 | } 46 | 47 | override func dismissalTransitionDidEnd(_ completed: Bool) { 48 | presentingViewController.endAppearanceTransition() 49 | if completed { 50 | blurView.removeFromSuperview() 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /AnimationDemo/Transition/CardTransition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CardTransition.swift 3 | // AnimationDemo 4 | // 5 | // Created by Truong Nguyen on 3/24/20. 6 | // Copyright © 2020 Truong Nguyen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | 13 | @objc public protocol DestinationTransitionDelegate{ 14 | // Is called before block animation 15 | @objc optional func presentingDestTransitionWillBegin() 16 | 17 | // Is called in block animation 18 | @objc optional func presentingDestAnimation(containnerView containner: UIView) 19 | 20 | // Is called after block animation 21 | @objc optional func presentingDestTransitionDidEnd(_ completed: Bool) 22 | 23 | // Is called before block animation 24 | @objc optional func dismissalDestTransitionWillBegin() 25 | 26 | // Is called in block animation 27 | @objc optional func dismissingDestAnimation(containnerView containner: UIView) 28 | 29 | // Is called after block animation 30 | @objc optional func dismissalDestTransitionDidEnd(_ completed: Bool) 31 | } 32 | 33 | @objc public protocol SourceTransitionDelegate{ 34 | 35 | @objc optional func sourceTransitionPresentWillBegin() 36 | 37 | @objc optional func sourceTransitionPresentingAnimation(containnerView containner: UIView) 38 | 39 | @objc optional func sourceTransitionPresentDidEnd(_ completed: Bool) 40 | 41 | @objc optional func sourceTransitionDismissWillBegin(containnerView containner: UIView) 42 | 43 | @objc optional func sourceTransitionDismissingAnimation(containnerView containner: UIView) 44 | 45 | @objc optional func sourceTransitionDismissDidEnd(_ completed: Bool) 46 | 47 | // Note: transition smoother depend on safeInset.origin 48 | @objc optional func safeInsetWhenSourceTransitionDismissing() -> UIEdgeInsets 49 | } 50 | 51 | enum CardVerticalExpandingStyle { 52 | /// Expanding card pinning at the top of animatingContainerView 53 | case fromTop 54 | 55 | /// Expanding card pinning at the center of animatingContainerView 56 | case fromCenter 57 | } 58 | 59 | final class CardTransition: NSObject, UIViewControllerTransitioningDelegate { 60 | 61 | struct Params { 62 | let verticalExpandingStyle: CardVerticalExpandingStyle 63 | let originalCardFrame: CGRect 64 | let originalCardFrameWithoutTransform: CGRect 65 | var sourceTransition: SourceTransitionDelegate? 66 | var destTransition: DestinationTransitionDelegate? 67 | } 68 | 69 | let params: Params 70 | 71 | init(params: Params) { 72 | self.params = params 73 | super.init() 74 | } 75 | 76 | func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 77 | let p = PresentCardAnimator.Params.init(verticalExpandingStyle: params.verticalExpandingStyle, 78 | originalCardFrame: params.originalCardFrame, 79 | sourceTransition: params.sourceTransition, 80 | destTransition: params.destTransition) 81 | return PresentCardAnimator(params: p) 82 | } 83 | 84 | func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 85 | let p = DismissCardAnimator.Params.init(originalCardFrame: params.originalCardFrame, 86 | originalCardFrameWithoutTransform: params.originalCardFrameWithoutTransform, 87 | sourceTransition: params.sourceTransition, 88 | destTransition: params.destTransition) 89 | return DismissCardAnimator(params: p) 90 | } 91 | 92 | func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { 93 | return nil 94 | } 95 | 96 | func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { 97 | return nil 98 | } 99 | 100 | // IMPORTANT: Must set modalPresentationStyle to `.custom` for this to be used. 101 | func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { 102 | return BlurPresentationController(presentedViewController: presented, presenting: presenting) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /AnimationDemo/Transition/DismissCardAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DismissCardAnimator.swift 3 | // AnimationDemo 4 | // 5 | // Created by Truong Nguyen on 3/24/20. 6 | // Copyright © 2020 Truong Nguyen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class DismissCardAnimator: NSObject, UIViewControllerAnimatedTransitioning { 12 | 13 | struct Params { 14 | let originalCardFrame: CGRect 15 | let originalCardFrameWithoutTransform: CGRect 16 | var sourceTransition: SourceTransitionDelegate? 17 | var destTransition: DestinationTransitionDelegate? 18 | } 19 | 20 | private let params: Params 21 | 22 | init(params: Params) { 23 | self.params = params 24 | super.init() 25 | } 26 | 27 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 28 | return 0.9 29 | } 30 | 31 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 32 | let safeInset = params.sourceTransition?.safeInsetWhenSourceTransitionDismissing?() ?? UIEdgeInsets.zero 33 | 34 | let cardDetailView = transitionContext.view(forKey: .from)! 35 | cardDetailView.translatesAutoresizingMaskIntoConstraints = true 36 | 37 | let container = transitionContext.containerView 38 | container.removeConstraints(container.constraints) 39 | container.frame.add(safeInset: safeInset) 40 | container.clipsToBounds = true 41 | container.addSubview(cardDetailView) 42 | 43 | var newFromCardFrameWithoutTransform = params.originalCardFrameWithoutTransform 44 | newFromCardFrameWithoutTransform.origin.x -= safeInset.left 45 | newFromCardFrameWithoutTransform.origin.y -= safeInset.top 46 | 47 | params.destTransition?.dismissalDestTransitionWillBegin?() 48 | params.sourceTransition?.sourceTransitionDismissWillBegin?(containnerView: container) 49 | 50 | if safeInset != .zero{ 51 | container.layoutIfNeeded() 52 | } 53 | 54 | let duration = transitionDuration(using: transitionContext) 55 | UIView.animate(withDuration: max(duration * 0.4, 0.3)) { 56 | self.params.destTransition?.dismissingDestAnimation?(containnerView: container) 57 | self.params.sourceTransition?.sourceTransitionDismissingAnimation?(containnerView: container) 58 | 59 | cardDetailView.frame.size = newFromCardFrameWithoutTransform.size 60 | 61 | container.layoutIfNeeded() 62 | } 63 | 64 | UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.0, options: [], animations: { 65 | 66 | cardDetailView.frame.origin = newFromCardFrameWithoutTransform.origin 67 | 68 | container.layoutIfNeeded() 69 | 70 | }) { (finished) in 71 | 72 | let success = !transitionContext.transitionWasCancelled 73 | 74 | if success { 75 | cardDetailView.removeFromSuperview() 76 | 77 | } else { 78 | 79 | container.removeConstraints(container.constraints) 80 | 81 | container.addSubview(cardDetailView) 82 | cardDetailView.edges(top: 0, left: 0, bottom: 0, right: 0) 83 | } 84 | 85 | self.params.destTransition?.dismissalDestTransitionDidEnd?(success) 86 | self.params.sourceTransition?.sourceTransitionDismissDidEnd?(success) 87 | 88 | transitionContext.completeTransition(success) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /AnimationDemo/Transition/PresentCardAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PresentCardAnimatorUsingUIView.swift 3 | // AnimationDemo 4 | // 5 | // Created by Truong Nguyen on 4/4/20. 6 | // Copyright © 2020 Truong Nguyen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PresentCardAnimator: NSObject, UIViewControllerAnimatedTransitioning { 12 | private let params: Params 13 | 14 | struct Params { 15 | let verticalExpandingStyle: CardVerticalExpandingStyle 16 | let originalCardFrame: CGRect 17 | var sourceTransition: SourceTransitionDelegate? 18 | var destTransition: DestinationTransitionDelegate? 19 | } 20 | 21 | private let animationDuration: TimeInterval 22 | 23 | private let animationSpringDamping: CGFloat 24 | 25 | init(params: Params) { 26 | self.params = params 27 | 28 | let cardPositionY = params.originalCardFrame.minY 29 | let distanceToBounce = abs(cardPositionY) 30 | let extentToBounce = cardPositionY < 0 ? params.originalCardFrame.height : UIScreen.main.bounds.height 31 | let dampFactorInterval: CGFloat = 0.35 32 | animationSpringDamping = min(1.0 - dampFactorInterval * (distanceToBounce / extentToBounce), 0.65) 33 | 34 | let baselineDuration: TimeInterval = 0.7 35 | let maxDuration: TimeInterval = baselineDuration + 0.4 36 | animationDuration = baselineDuration + (maxDuration - baselineDuration) * TimeInterval(distanceToBounce / extentToBounce) 37 | 38 | super.init() 39 | } 40 | 41 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 42 | return animationDuration 43 | } 44 | 45 | func animateTransitionƯithAutoLayout(using transitionContext: UIViewControllerContextTransitioning) { 46 | let container = transitionContext.containerView 47 | let presentedView = transitionContext.view(forKey: .to)! 48 | let fromOwnerFrame = params.originalCardFrame 49 | 50 | container.addSubview(presentedView) 51 | presentedView.translatesAutoresizingMaskIntoConstraints = false 52 | 53 | /* Pin top (or center Y) and center X of the card, in animated container view */ 54 | let verticalCardDetailAnchor: NSLayoutConstraint = { 55 | switch params.verticalExpandingStyle { 56 | case .fromCenter: 57 | return presentedView.centerYAnchor.constraint(equalTo: container.centerYAnchor, constant: fromOwnerFrame.height/2 + fromOwnerFrame.minY - container.bounds.height/2) 58 | case .fromTop: 59 | return presentedView.topAnchor.constraint(equalTo: container.topAnchor, constant: fromOwnerFrame.minY) 60 | } 61 | }() 62 | let widthCardDetailConstraint = presentedView.widthAnchor.constraint(equalToConstant: fromOwnerFrame.width) 63 | let heightCardDetailConstraint = presentedView.heightAnchor.constraint(equalToConstant: fromOwnerFrame.height) 64 | NSLayoutConstraint.activate([widthCardDetailConstraint, 65 | heightCardDetailConstraint, 66 | verticalCardDetailAnchor, 67 | presentedView.centerXAnchor.constraint(equalTo: container.centerXAnchor),]) 68 | 69 | params.sourceTransition?.sourceTransitionPresentWillBegin?() 70 | 71 | params.destTransition?.presentingDestTransitionWillBegin?() 72 | 73 | container.layoutIfNeeded() 74 | 75 | let duration = transitionDuration(using: transitionContext) 76 | UIView.animate(withDuration: duration * 0.6) { 77 | self.params.destTransition?.presentingDestAnimation?(containnerView: container) 78 | self.params.sourceTransition?.sourceTransitionPresentingAnimation?(containnerView: container) 79 | 80 | widthCardDetailConstraint.constant = container.bounds.width 81 | heightCardDetailConstraint.constant = container.bounds.height 82 | 83 | container.layoutIfNeeded() 84 | } 85 | 86 | UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: animationSpringDamping, initialSpringVelocity: 0, options: [], animations: { 87 | 88 | verticalCardDetailAnchor.constant = 0 89 | 90 | container.layoutIfNeeded() 91 | 92 | }) { (finised) in 93 | 94 | let success = !transitionContext.transitionWasCancelled 95 | if success{ 96 | presentedView.removeConstraints([widthCardDetailConstraint, heightCardDetailConstraint]) 97 | presentedView.edges(top: 0, leading: 0, bottom: 0, trailling: 0) 98 | }else{ 99 | 100 | } 101 | 102 | self.params.destTransition?.presentingDestTransitionDidEnd?(success) 103 | self.params.sourceTransition?.sourceTransitionPresentDidEnd?(success) 104 | 105 | transitionContext.completeTransition(success) 106 | } 107 | } 108 | 109 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 110 | //return animateTransitionƯithAutoLayout(using: transitionContext) 111 | 112 | let container = transitionContext.containerView 113 | container.removeConstraints(container.constraints) 114 | container.clipsToBounds = true 115 | 116 | let presentedView = transitionContext.view(forKey: .to)! 117 | presentedView.translatesAutoresizingMaskIntoConstraints = true 118 | presentedView.frame = params.originalCardFrame 119 | container.addSubview(presentedView) 120 | 121 | params.sourceTransition?.sourceTransitionPresentWillBegin?() 122 | params.destTransition?.presentingDestTransitionWillBegin?() 123 | 124 | container.layoutIfNeeded() 125 | 126 | let duration = transitionDuration(using: transitionContext) 127 | UIView.animate(withDuration: duration * 0.6) { 128 | self.params.destTransition?.presentingDestAnimation?(containnerView: container) 129 | self.params.sourceTransition?.sourceTransitionPresentingAnimation?(containnerView: container) 130 | 131 | presentedView.frame.size = container.bounds.size 132 | 133 | container.layoutIfNeeded() 134 | } 135 | 136 | UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: animationSpringDamping, initialSpringVelocity: 0, options: [], animations: { 137 | 138 | presentedView.frame.origin = container.bounds.origin 139 | 140 | container.layoutIfNeeded() 141 | 142 | }) { (finised) in 143 | 144 | let success = !transitionContext.transitionWasCancelled 145 | if success{ 146 | //presentedView.removeConstraints([widthCardDetailConstraint, heightCardDetailConstraint]) 147 | presentedView.edges(top: 0, leading: 0, bottom: 0, trailling: 0) 148 | }else{ 149 | 150 | } 151 | 152 | self.params.destTransition?.presentingDestTransitionDidEnd?(success) 153 | self.params.sourceTransition?.sourceTransitionPresentDidEnd?(success) 154 | 155 | transitionContext.completeTransition(success) 156 | } 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /AnimationDemo/View/CardCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageCell.swift 3 | // AnimationDemo 4 | // 5 | // Created by Truong Nguyen on 3/27/20. 6 | // Copyright © 2020 Truong Nguyen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CardCell: UICollectionViewCell { 12 | private var cardContentView: CardContentView 13 | 14 | private var disabledHighlightedAnimation = false 15 | 16 | public var model: CardModel!{ 17 | didSet{ 18 | cardContentView.model = model 19 | } 20 | } 21 | 22 | public var animatedDuration: TimeInterval = 0.4 23 | 24 | public var highlightScale: CGFloat = 0.96 25 | 26 | override init(frame: CGRect) { 27 | cardContentView = CardContentView() 28 | 29 | super.init(frame: frame) 30 | 31 | addSubview(cardContentView) 32 | cardContentView.edges(top: 0, leading: 0, bottom: 0, trailling: 0) 33 | 34 | layer.cornerRadius = 16 35 | layer.masksToBounds = true 36 | } 37 | 38 | required init?(coder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | func resetTransform() { 43 | transform = .identity 44 | } 45 | 46 | func freezeAnimations() { 47 | disabledHighlightedAnimation = true 48 | layer.removeAllAnimations() 49 | } 50 | 51 | func unfreezeAnimations() { 52 | disabledHighlightedAnimation = false 53 | } 54 | 55 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 56 | super.touchesBegan(touches, with: event) 57 | animate(isHighlighted: true) 58 | } 59 | 60 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 61 | super.touchesEnded(touches, with: event) 62 | animate(isHighlighted: false) 63 | } 64 | 65 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) { 66 | super.touchesCancelled(touches, with: event) 67 | animate(isHighlighted: false) 68 | } 69 | 70 | private func animate(isHighlighted: Bool, completion: ((Bool) -> Void)?=nil) { 71 | if disabledHighlightedAnimation { 72 | return 73 | } 74 | let animationOptions: UIView.AnimationOptions = [.allowUserInteraction] 75 | let springWithDamping: CGFloat = 0.8 76 | 77 | if isHighlighted { 78 | UIView.animate(withDuration: animatedDuration, delay: 0, usingSpringWithDamping: springWithDamping, initialSpringVelocity: 0, options: animationOptions, animations: { 79 | self.transform = .init(scaleX: self.highlightScale, y: self.highlightScale) 80 | 81 | }, completion: completion) 82 | } else { 83 | UIView.animate(withDuration: animatedDuration, delay: 0, usingSpringWithDamping: springWithDamping, initialSpringVelocity: 0, options: animationOptions, animations: { 84 | self.transform = .identity 85 | 86 | }, completion: completion) 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /AnimationDemo/View/CardContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CardContentView.swift 3 | // AnimationDemo 4 | // 5 | // Created by Truong Nguyen on 4/3/20. 6 | // Copyright © 2020 Truong Nguyen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CardContentView: UIView { 12 | var model: CardModel?{ 13 | didSet{ 14 | if let model = model{ 15 | imageView.image = model.image 16 | 17 | secondaryLabel.text = model.secondary 18 | 19 | primaryLabel.text = model.primary 20 | 21 | descriptionLabel.text = model.description 22 | } 23 | } 24 | } 25 | 26 | var topSafeInset: CGFloat = 0{ 27 | didSet{ 28 | secondaryLabelTopAnchorConstraint?.isActive = false 29 | 30 | secondaryLabelTopAnchorConstraint = secondaryLabel.topAnchor.constraint(equalTo: topAnchor, constant: padding + topSafeInset) 31 | secondaryLabelTopAnchorConstraint?.isActive = true 32 | 33 | layoutIfNeeded() 34 | } 35 | } 36 | 37 | private let padding: CGFloat = 20 38 | private var imageView = UIImageView() 39 | private var primaryLabel = UILabel() 40 | private var secondaryLabel = UILabel() 41 | private var descriptionLabel = UILabel() 42 | 43 | private var secondaryLabelTopAnchorConstraint: NSLayoutConstraint? 44 | 45 | init(){ 46 | super.init(frame: .zero) 47 | 48 | layer.masksToBounds = true 49 | addSubview(imageView) 50 | addSubview(primaryLabel) 51 | addSubview(secondaryLabel) 52 | addSubview(descriptionLabel) 53 | 54 | imageView.clipsToBounds = true 55 | imageView.contentMode = .scaleToFill 56 | imageView.edges(top: 0, leading: 0, bottom: 0, trailling: 0) 57 | 58 | secondaryLabel.font = UIFont.systemFont(ofSize: 20, weight: .semibold) 59 | secondaryLabel.textColor = .lightGray 60 | secondaryLabelTopAnchorConstraint = secondaryLabel.topAnchor.constraint(equalTo: topAnchor, constant: padding) 61 | secondaryLabelTopAnchorConstraint?.isActive = true 62 | secondaryLabel.edges(top: nil, leading: padding, bottom: nil, trailling: nil) 63 | secondaryLabel.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.7).isActive = true 64 | 65 | primaryLabel.font = UIFont.systemFont(ofSize: 36, weight: .bold) 66 | primaryLabel.textColor = .white 67 | primaryLabel.numberOfLines = 3 68 | primaryLabel.edges(to: secondaryLabel, top: 50, leading: 0, bottom: nil, trailling: nil) 69 | primaryLabel.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.7).isActive = true 70 | 71 | descriptionLabel.font = UIFont.systemFont(ofSize: padding, weight: .medium) 72 | descriptionLabel.textColor = .lightGray 73 | descriptionLabel.numberOfLines = 4 74 | descriptionLabel.edges(top: nil, leading: padding, bottom: -padding, trailling: -padding) 75 | } 76 | 77 | required init?(coder: NSCoder) { 78 | fatalError("init(coder:) has not been implemented") 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /AnimationDemo/View/CardModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CardModel.swift 3 | // AnimationDemo 4 | // 5 | // Created by Truong Nguyen on 4/3/20. 6 | // Copyright © 2020 Truong Nguyen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CardModel { 12 | let primary: String 13 | let secondary: String 14 | let description: String 15 | let image: UIImage 16 | 17 | init(primary: String, secondary: String, description: String, image: UIImage) { 18 | self.primary = primary 19 | self.secondary = secondary 20 | self.description = description 21 | self.image = image 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AnimationDemo/View/UIView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView.swift 3 | // AnimationDemo 4 | // 5 | // Created by Truong Nguyen on 4/3/20. 6 | // Copyright © 2020 Truong Nguyen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIView { 12 | /// Constrain 4 edges of `self` to specified `view`. 13 | func edges(to view: UIView? = nil, top: CGFloat?, left: CGFloat?, bottom: CGFloat?, right: CGFloat?) { 14 | 15 | translatesAutoresizingMaskIntoConstraints = false 16 | guard let v: UIView = view == nil ? superview : view else { 17 | return 18 | } 19 | 20 | if let top = top{ 21 | self.topAnchor.constraint(equalTo: v.topAnchor, constant: top).isActive = true 22 | } 23 | 24 | if let left = left{ 25 | self.leftAnchor.constraint(equalTo: v.leftAnchor, constant: left).isActive = true 26 | } 27 | 28 | if let bottom = bottom{ 29 | self.bottomAnchor.constraint(equalTo: v.bottomAnchor, constant: bottom).isActive = true 30 | } 31 | 32 | if let right = right{ 33 | self.rightAnchor.constraint(equalTo: v.rightAnchor, constant: right).isActive = true 34 | } 35 | } 36 | 37 | func edges(to view: UIView? = nil, top: CGFloat?, leading: CGFloat?, bottom: CGFloat?, trailling: CGFloat?) { 38 | 39 | translatesAutoresizingMaskIntoConstraints = false 40 | guard let v: UIView = view == nil ? superview : view else { 41 | return 42 | } 43 | 44 | if let top = top{ 45 | self.topAnchor.constraint(equalTo: v.topAnchor, constant: top).isActive = true 46 | } 47 | 48 | if let leading = leading{ 49 | self.leadingAnchor.constraint(equalTo: v.leadingAnchor, constant: leading).isActive = true 50 | } 51 | 52 | if let bottom = bottom{ 53 | self.bottomAnchor.constraint(equalTo: v.bottomAnchor, constant: bottom).isActive = true 54 | } 55 | 56 | if let trailling = trailling{ 57 | self.trailingAnchor.constraint(equalTo: v.trailingAnchor, constant: trailling).isActive = true 58 | } 59 | } 60 | 61 | func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailling: NSLayoutXAxisAnchor?, padding: UIEdgeInsets, size: CGSize = .zero){ 62 | 63 | translatesAutoresizingMaskIntoConstraints = false 64 | 65 | if let top = top{ 66 | topAnchor.constraint(equalTo: top, constant: padding.top).isActive = true 67 | } 68 | if let leading = leading{ 69 | leadingAnchor.constraint(equalTo: leading, constant: padding.left).isActive = true 70 | } 71 | if let bottom = bottom{ 72 | bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom).isActive = true 73 | } 74 | if let trailling = trailling{ 75 | trailingAnchor.constraint(equalTo: trailling, constant: padding.right).isActive = true 76 | } 77 | 78 | if size.width != 0{ 79 | widthAnchor.constraint(equalToConstant: size.width).isActive = true 80 | } 81 | if size.height != 0{ 82 | heightAnchor.constraint(equalToConstant: size.height).isActive = true 83 | } 84 | } 85 | } 86 | 87 | extension CGRect{ 88 | public mutating func add(safeInset: UIEdgeInsets){ 89 | let newRect = CGRect(x: minX + safeInset.left, 90 | y: minY + safeInset.top, 91 | width: width - safeInset.left - safeInset.right, 92 | height: height - safeInset.top - safeInset.bottom) 93 | origin = newRect.origin 94 | size = newRect.size 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /AnimationDemoTests/AnimationDemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationDemoTests.swift 3 | // AnimationDemoTests 4 | // 5 | // Created by Truong Nguyen on 4/5/20. 6 | // Copyright © 2020 Truong Nguyen. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import AnimationDemo 11 | 12 | class AnimationDemoTests: XCTestCase { 13 | 14 | override func setUpWithError() throws { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDownWithError() throws { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() throws { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() throws { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /AnimationDemoTests/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /AnimationDemoUITests/AnimationDemoUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationDemoUITests.swift 3 | // AnimationDemoUITests 4 | // 5 | // Created by Truong Nguyen on 4/5/20. 6 | // Copyright © 2020 Truong Nguyen. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class AnimationDemoUITests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 20 | } 21 | 22 | override func tearDownWithError() throws { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | } 25 | 26 | func testExample() throws { 27 | // UI tests must launch the application that they test. 28 | let app = XCUIApplication() 29 | app.launch() 30 | 31 | // Use recording to get started writing UI tests. 32 | // Use XCTAssert and related functions to verify your tests produce the correct results. 33 | } 34 | 35 | func testLaunchPerformance() throws { 36 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { 37 | // This measures how long it takes to launch your application. 38 | measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) { 39 | XCUIApplication().launch() 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /AnimationDemoUITests/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Truong98/AppStoreDemoAnimation/54d6bae243fda127b42bc85002a7a40cff2e02e8/Demo.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # App Store Demo Animation 2 | Demo UIVIewControllerTransition animation like AppStore-Today 3 | 4 | ![demo](https://github.com/Truong98/AppStoreDemoAnimation/blob/master/Demo.gif) 5 | 6 | ## Introduction 7 | Using native APIs to show how such App Store presentation might work. 8 | 9 | ## Features 10 | - [x] Perfecting card bouncing up animation (Spring animation) - as smooth as AppStore-Today tab 11 | - [x] Work perfectly with tab bar, navigation bar and so on 12 | - [x] Flexible code and reuse, easy custom 13 | - [x] Very responsive card cell highlighting animation 14 | - [x] Drag down or left screen edge pan to dimiss 15 | 16 | --------------------------------------------------------------------------------