├── LICENSE.md ├── Lazy Pop SwiftUI.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── josephhinkle.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── Lazy Pop SwiftUI ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ ├── Icon-App-83.5x83.5@2x.png │ │ └── ItunesArtwork@2x.png │ ├── Contents.json │ ├── example1.imageset │ │ ├── Contents.json │ │ └── example1.png │ ├── example2.imageset │ │ ├── Contents.json │ │ └── example2.png │ ├── iTunesArtwork@1x.png │ ├── iTunesArtwork@2x.png │ └── iTunesArtwork@3x.png ├── Base.lproj │ └── LaunchScreen.storyboard ├── ContentView.swift ├── Info.plist ├── Lazy Pop │ ├── SlideAnimatedTransitioning.swift │ └── SwipeRightToPopViewController.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json └── SceneDelegate.swift ├── README.md └── demo.gif /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Joe Hinkle https://www.joehinkle.io/ 2 | 3 | This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. 4 | 5 | Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 6 | 7 | The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. This notice may not be removed or altered from any source distribution. 8 | -------------------------------------------------------------------------------- /Lazy Pop SwiftUI.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9FF6AD7C23941F400026063D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF6AD7B23941F400026063D /* AppDelegate.swift */; }; 11 | 9FF6AD7E23941F400026063D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF6AD7D23941F400026063D /* SceneDelegate.swift */; }; 12 | 9FF6AD8023941F400026063D /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF6AD7F23941F400026063D /* ContentView.swift */; }; 13 | 9FF6AD8223941F410026063D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9FF6AD8123941F410026063D /* Assets.xcassets */; }; 14 | 9FF6AD8523941F410026063D /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9FF6AD8423941F410026063D /* Preview Assets.xcassets */; }; 15 | 9FF6AD8823941F410026063D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9FF6AD8623941F410026063D /* LaunchScreen.storyboard */; }; 16 | 9FF6AD91239424580026063D /* SlideAnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF6AD90239424580026063D /* SlideAnimatedTransitioning.swift */; }; 17 | 9FF6AD93239424BE0026063D /* SwipeRightToPopViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF6AD92239424BE0026063D /* SwipeRightToPopViewController.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 9FF6AD7823941F400026063D /* Lazy Pop SwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Lazy Pop SwiftUI.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | 9FF6AD7B23941F400026063D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 23 | 9FF6AD7D23941F400026063D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 24 | 9FF6AD7F23941F400026063D /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 25 | 9FF6AD8123941F410026063D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | 9FF6AD8423941F410026063D /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 27 | 9FF6AD8723941F410026063D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | 9FF6AD8923941F410026063D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | 9FF6AD90239424580026063D /* SlideAnimatedTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideAnimatedTransitioning.swift; sourceTree = ""; }; 30 | 9FF6AD92239424BE0026063D /* SwipeRightToPopViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeRightToPopViewController.swift; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | 9FF6AD7523941F400026063D /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | 9FF6AD6F23941F400026063D = { 45 | isa = PBXGroup; 46 | children = ( 47 | 9FF6AD7A23941F400026063D /* Lazy Pop SwiftUI */, 48 | 9FF6AD7923941F400026063D /* Products */, 49 | ); 50 | sourceTree = ""; 51 | }; 52 | 9FF6AD7923941F400026063D /* Products */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | 9FF6AD7823941F400026063D /* Lazy Pop SwiftUI.app */, 56 | ); 57 | name = Products; 58 | sourceTree = ""; 59 | }; 60 | 9FF6AD7A23941F400026063D /* Lazy Pop SwiftUI */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 9FF6AD7B23941F400026063D /* AppDelegate.swift */, 64 | 9FF6AD7D23941F400026063D /* SceneDelegate.swift */, 65 | 9FF6AD7F23941F400026063D /* ContentView.swift */, 66 | 9FF6AD8F239424470026063D /* Lazy Pop */, 67 | 9FF6AD8123941F410026063D /* Assets.xcassets */, 68 | 9FF6AD8623941F410026063D /* LaunchScreen.storyboard */, 69 | 9FF6AD8923941F410026063D /* Info.plist */, 70 | 9FF6AD8323941F410026063D /* Preview Content */, 71 | ); 72 | path = "Lazy Pop SwiftUI"; 73 | sourceTree = ""; 74 | }; 75 | 9FF6AD8323941F410026063D /* Preview Content */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 9FF6AD8423941F410026063D /* Preview Assets.xcassets */, 79 | ); 80 | path = "Preview Content"; 81 | sourceTree = ""; 82 | }; 83 | 9FF6AD8F239424470026063D /* Lazy Pop */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 9FF6AD90239424580026063D /* SlideAnimatedTransitioning.swift */, 87 | 9FF6AD92239424BE0026063D /* SwipeRightToPopViewController.swift */, 88 | ); 89 | path = "Lazy Pop"; 90 | sourceTree = ""; 91 | }; 92 | /* End PBXGroup section */ 93 | 94 | /* Begin PBXNativeTarget section */ 95 | 9FF6AD7723941F400026063D /* Lazy Pop SwiftUI */ = { 96 | isa = PBXNativeTarget; 97 | buildConfigurationList = 9FF6AD8C23941F410026063D /* Build configuration list for PBXNativeTarget "Lazy Pop SwiftUI" */; 98 | buildPhases = ( 99 | 9FF6AD7423941F400026063D /* Sources */, 100 | 9FF6AD7523941F400026063D /* Frameworks */, 101 | 9FF6AD7623941F400026063D /* Resources */, 102 | ); 103 | buildRules = ( 104 | ); 105 | dependencies = ( 106 | ); 107 | name = "Lazy Pop SwiftUI"; 108 | productName = "Lazy Pop SwiftUI"; 109 | productReference = 9FF6AD7823941F400026063D /* Lazy Pop SwiftUI.app */; 110 | productType = "com.apple.product-type.application"; 111 | }; 112 | /* End PBXNativeTarget section */ 113 | 114 | /* Begin PBXProject section */ 115 | 9FF6AD7023941F400026063D /* Project object */ = { 116 | isa = PBXProject; 117 | attributes = { 118 | LastSwiftUpdateCheck = 1120; 119 | LastUpgradeCheck = 1120; 120 | ORGANIZATIONNAME = "Joseph Hinkle"; 121 | TargetAttributes = { 122 | 9FF6AD7723941F400026063D = { 123 | CreatedOnToolsVersion = 11.2.1; 124 | }; 125 | }; 126 | }; 127 | buildConfigurationList = 9FF6AD7323941F400026063D /* Build configuration list for PBXProject "Lazy Pop SwiftUI" */; 128 | compatibilityVersion = "Xcode 9.3"; 129 | developmentRegion = en; 130 | hasScannedForEncodings = 0; 131 | knownRegions = ( 132 | en, 133 | Base, 134 | ); 135 | mainGroup = 9FF6AD6F23941F400026063D; 136 | productRefGroup = 9FF6AD7923941F400026063D /* Products */; 137 | projectDirPath = ""; 138 | projectRoot = ""; 139 | targets = ( 140 | 9FF6AD7723941F400026063D /* Lazy Pop SwiftUI */, 141 | ); 142 | }; 143 | /* End PBXProject section */ 144 | 145 | /* Begin PBXResourcesBuildPhase section */ 146 | 9FF6AD7623941F400026063D /* Resources */ = { 147 | isa = PBXResourcesBuildPhase; 148 | buildActionMask = 2147483647; 149 | files = ( 150 | 9FF6AD8823941F410026063D /* LaunchScreen.storyboard in Resources */, 151 | 9FF6AD8523941F410026063D /* Preview Assets.xcassets in Resources */, 152 | 9FF6AD8223941F410026063D /* Assets.xcassets in Resources */, 153 | ); 154 | runOnlyForDeploymentPostprocessing = 0; 155 | }; 156 | /* End PBXResourcesBuildPhase section */ 157 | 158 | /* Begin PBXSourcesBuildPhase section */ 159 | 9FF6AD7423941F400026063D /* Sources */ = { 160 | isa = PBXSourcesBuildPhase; 161 | buildActionMask = 2147483647; 162 | files = ( 163 | 9FF6AD91239424580026063D /* SlideAnimatedTransitioning.swift in Sources */, 164 | 9FF6AD93239424BE0026063D /* SwipeRightToPopViewController.swift in Sources */, 165 | 9FF6AD7C23941F400026063D /* AppDelegate.swift in Sources */, 166 | 9FF6AD7E23941F400026063D /* SceneDelegate.swift in Sources */, 167 | 9FF6AD8023941F400026063D /* ContentView.swift in Sources */, 168 | ); 169 | runOnlyForDeploymentPostprocessing = 0; 170 | }; 171 | /* End PBXSourcesBuildPhase section */ 172 | 173 | /* Begin PBXVariantGroup section */ 174 | 9FF6AD8623941F410026063D /* LaunchScreen.storyboard */ = { 175 | isa = PBXVariantGroup; 176 | children = ( 177 | 9FF6AD8723941F410026063D /* Base */, 178 | ); 179 | name = LaunchScreen.storyboard; 180 | sourceTree = ""; 181 | }; 182 | /* End PBXVariantGroup section */ 183 | 184 | /* Begin XCBuildConfiguration section */ 185 | 9FF6AD8A23941F410026063D /* Debug */ = { 186 | isa = XCBuildConfiguration; 187 | buildSettings = { 188 | ALWAYS_SEARCH_USER_PATHS = NO; 189 | CLANG_ANALYZER_NONNULL = YES; 190 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 191 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 192 | CLANG_CXX_LIBRARY = "libc++"; 193 | CLANG_ENABLE_MODULES = YES; 194 | CLANG_ENABLE_OBJC_ARC = YES; 195 | CLANG_ENABLE_OBJC_WEAK = YES; 196 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 197 | CLANG_WARN_BOOL_CONVERSION = YES; 198 | CLANG_WARN_COMMA = YES; 199 | CLANG_WARN_CONSTANT_CONVERSION = YES; 200 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 201 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 202 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 203 | CLANG_WARN_EMPTY_BODY = YES; 204 | CLANG_WARN_ENUM_CONVERSION = YES; 205 | CLANG_WARN_INFINITE_RECURSION = YES; 206 | CLANG_WARN_INT_CONVERSION = YES; 207 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 208 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 209 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 210 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 211 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 212 | CLANG_WARN_STRICT_PROTOTYPES = YES; 213 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 214 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 215 | CLANG_WARN_UNREACHABLE_CODE = YES; 216 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 217 | COPY_PHASE_STRIP = NO; 218 | DEBUG_INFORMATION_FORMAT = dwarf; 219 | ENABLE_STRICT_OBJC_MSGSEND = YES; 220 | ENABLE_TESTABILITY = YES; 221 | GCC_C_LANGUAGE_STANDARD = gnu11; 222 | GCC_DYNAMIC_NO_PIC = NO; 223 | GCC_NO_COMMON_BLOCKS = YES; 224 | GCC_OPTIMIZATION_LEVEL = 0; 225 | GCC_PREPROCESSOR_DEFINITIONS = ( 226 | "DEBUG=1", 227 | "$(inherited)", 228 | ); 229 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 230 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 231 | GCC_WARN_UNDECLARED_SELECTOR = YES; 232 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 233 | GCC_WARN_UNUSED_FUNCTION = YES; 234 | GCC_WARN_UNUSED_VARIABLE = YES; 235 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 236 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 237 | MTL_FAST_MATH = YES; 238 | ONLY_ACTIVE_ARCH = YES; 239 | SDKROOT = iphoneos; 240 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 241 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 242 | }; 243 | name = Debug; 244 | }; 245 | 9FF6AD8B23941F410026063D /* Release */ = { 246 | isa = XCBuildConfiguration; 247 | buildSettings = { 248 | ALWAYS_SEARCH_USER_PATHS = NO; 249 | CLANG_ANALYZER_NONNULL = YES; 250 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 251 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 252 | CLANG_CXX_LIBRARY = "libc++"; 253 | CLANG_ENABLE_MODULES = YES; 254 | CLANG_ENABLE_OBJC_ARC = YES; 255 | CLANG_ENABLE_OBJC_WEAK = YES; 256 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 257 | CLANG_WARN_BOOL_CONVERSION = YES; 258 | CLANG_WARN_COMMA = YES; 259 | CLANG_WARN_CONSTANT_CONVERSION = YES; 260 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 261 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 262 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 263 | CLANG_WARN_EMPTY_BODY = YES; 264 | CLANG_WARN_ENUM_CONVERSION = YES; 265 | CLANG_WARN_INFINITE_RECURSION = YES; 266 | CLANG_WARN_INT_CONVERSION = YES; 267 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 268 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 269 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 270 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 271 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 272 | CLANG_WARN_STRICT_PROTOTYPES = YES; 273 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 274 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 275 | CLANG_WARN_UNREACHABLE_CODE = YES; 276 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 277 | COPY_PHASE_STRIP = NO; 278 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 279 | ENABLE_NS_ASSERTIONS = NO; 280 | ENABLE_STRICT_OBJC_MSGSEND = YES; 281 | GCC_C_LANGUAGE_STANDARD = gnu11; 282 | GCC_NO_COMMON_BLOCKS = YES; 283 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 284 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 285 | GCC_WARN_UNDECLARED_SELECTOR = YES; 286 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 287 | GCC_WARN_UNUSED_FUNCTION = YES; 288 | GCC_WARN_UNUSED_VARIABLE = YES; 289 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 290 | MTL_ENABLE_DEBUG_INFO = NO; 291 | MTL_FAST_MATH = YES; 292 | SDKROOT = iphoneos; 293 | SWIFT_COMPILATION_MODE = wholemodule; 294 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 295 | VALIDATE_PRODUCT = YES; 296 | }; 297 | name = Release; 298 | }; 299 | 9FF6AD8D23941F410026063D /* Debug */ = { 300 | isa = XCBuildConfiguration; 301 | buildSettings = { 302 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 303 | CODE_SIGN_STYLE = Automatic; 304 | DEVELOPMENT_ASSET_PATHS = "\"Lazy Pop SwiftUI/Preview Content\""; 305 | DEVELOPMENT_TEAM = B3GJXRTH64; 306 | ENABLE_PREVIEWS = YES; 307 | INFOPLIST_FILE = "Lazy Pop SwiftUI/Info.plist"; 308 | LD_RUNPATH_SEARCH_PATHS = ( 309 | "$(inherited)", 310 | "@executable_path/Frameworks", 311 | ); 312 | PRODUCT_BUNDLE_IDENTIFIER = io.joehinkle.lazypopswiftui; 313 | PRODUCT_NAME = "$(TARGET_NAME)"; 314 | SWIFT_VERSION = 5.0; 315 | TARGETED_DEVICE_FAMILY = "1,2"; 316 | }; 317 | name = Debug; 318 | }; 319 | 9FF6AD8E23941F410026063D /* Release */ = { 320 | isa = XCBuildConfiguration; 321 | buildSettings = { 322 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 323 | CODE_SIGN_STYLE = Automatic; 324 | DEVELOPMENT_ASSET_PATHS = "\"Lazy Pop SwiftUI/Preview Content\""; 325 | DEVELOPMENT_TEAM = B3GJXRTH64; 326 | ENABLE_PREVIEWS = YES; 327 | INFOPLIST_FILE = "Lazy Pop SwiftUI/Info.plist"; 328 | LD_RUNPATH_SEARCH_PATHS = ( 329 | "$(inherited)", 330 | "@executable_path/Frameworks", 331 | ); 332 | PRODUCT_BUNDLE_IDENTIFIER = io.joehinkle.lazypopswiftui; 333 | PRODUCT_NAME = "$(TARGET_NAME)"; 334 | SWIFT_VERSION = 5.0; 335 | TARGETED_DEVICE_FAMILY = "1,2"; 336 | }; 337 | name = Release; 338 | }; 339 | /* End XCBuildConfiguration section */ 340 | 341 | /* Begin XCConfigurationList section */ 342 | 9FF6AD7323941F400026063D /* Build configuration list for PBXProject "Lazy Pop SwiftUI" */ = { 343 | isa = XCConfigurationList; 344 | buildConfigurations = ( 345 | 9FF6AD8A23941F410026063D /* Debug */, 346 | 9FF6AD8B23941F410026063D /* Release */, 347 | ); 348 | defaultConfigurationIsVisible = 0; 349 | defaultConfigurationName = Release; 350 | }; 351 | 9FF6AD8C23941F410026063D /* Build configuration list for PBXNativeTarget "Lazy Pop SwiftUI" */ = { 352 | isa = XCConfigurationList; 353 | buildConfigurations = ( 354 | 9FF6AD8D23941F410026063D /* Debug */, 355 | 9FF6AD8E23941F410026063D /* Release */, 356 | ); 357 | defaultConfigurationIsVisible = 0; 358 | defaultConfigurationName = Release; 359 | }; 360 | /* End XCConfigurationList section */ 361 | }; 362 | rootObject = 9FF6AD7023941F400026063D /* Project object */; 363 | } 364 | -------------------------------------------------------------------------------- /Lazy Pop SwiftUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Lazy Pop SwiftUI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Lazy Pop SwiftUI.xcodeproj/xcuserdata/josephhinkle.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Lazy Pop SwiftUI.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Lazy Pop SwiftUI 4 | // 5 | // Created by Joseph Hinkle on 12/1/19. 6 | // Copyright © 2019 Joseph Hinkle. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images":[ 3 | { 4 | "idiom":"iphone", 5 | "size":"20x20", 6 | "scale":"2x", 7 | "filename":"Icon-App-20x20@2x.png" 8 | }, 9 | { 10 | "idiom":"iphone", 11 | "size":"20x20", 12 | "scale":"3x", 13 | "filename":"Icon-App-20x20@3x.png" 14 | }, 15 | { 16 | "idiom":"iphone", 17 | "size":"29x29", 18 | "scale":"1x", 19 | "filename":"Icon-App-29x29@1x.png" 20 | }, 21 | { 22 | "idiom":"iphone", 23 | "size":"29x29", 24 | "scale":"2x", 25 | "filename":"Icon-App-29x29@2x.png" 26 | }, 27 | { 28 | "idiom":"iphone", 29 | "size":"29x29", 30 | "scale":"3x", 31 | "filename":"Icon-App-29x29@3x.png" 32 | }, 33 | { 34 | "idiom":"iphone", 35 | "size":"40x40", 36 | "scale":"2x", 37 | "filename":"Icon-App-40x40@2x.png" 38 | }, 39 | { 40 | "idiom":"iphone", 41 | "size":"40x40", 42 | "scale":"3x", 43 | "filename":"Icon-App-40x40@3x.png" 44 | }, 45 | { 46 | "idiom":"iphone", 47 | "size":"60x60", 48 | "scale":"2x", 49 | "filename":"Icon-App-60x60@2x.png" 50 | }, 51 | { 52 | "idiom":"iphone", 53 | "size":"60x60", 54 | "scale":"3x", 55 | "filename":"Icon-App-60x60@3x.png" 56 | }, 57 | { 58 | "idiom":"iphone", 59 | "size":"76x76", 60 | "scale":"2x", 61 | "filename":"Icon-App-76x76@2x.png" 62 | }, 63 | { 64 | "idiom":"ipad", 65 | "size":"20x20", 66 | "scale":"1x", 67 | "filename":"Icon-App-20x20@1x.png" 68 | }, 69 | { 70 | "idiom":"ipad", 71 | "size":"20x20", 72 | "scale":"2x", 73 | "filename":"Icon-App-20x20@2x.png" 74 | }, 75 | { 76 | "idiom":"ipad", 77 | "size":"29x29", 78 | "scale":"1x", 79 | "filename":"Icon-App-29x29@1x.png" 80 | }, 81 | { 82 | "idiom":"ipad", 83 | "size":"29x29", 84 | "scale":"2x", 85 | "filename":"Icon-App-29x29@2x.png" 86 | }, 87 | { 88 | "idiom":"ipad", 89 | "size":"40x40", 90 | "scale":"1x", 91 | "filename":"Icon-App-40x40@1x.png" 92 | }, 93 | { 94 | "idiom":"ipad", 95 | "size":"40x40", 96 | "scale":"2x", 97 | "filename":"Icon-App-40x40@2x.png" 98 | }, 99 | { 100 | "idiom":"ipad", 101 | "size":"76x76", 102 | "scale":"1x", 103 | "filename":"Icon-App-76x76@1x.png" 104 | }, 105 | { 106 | "idiom":"ipad", 107 | "size":"76x76", 108 | "scale":"2x", 109 | "filename":"Icon-App-76x76@2x.png" 110 | }, 111 | { 112 | "idiom":"ipad", 113 | "size":"83.5x83.5", 114 | "scale":"2x", 115 | "filename":"Icon-App-83.5x83.5@2x.png" 116 | }, 117 | { 118 | "size" : "1024x1024", 119 | "idiom" : "ios-marketing", 120 | "scale" : "1x", 121 | "filename" : "ItunesArtwork@2x.png" 122 | } 123 | ], 124 | "info":{ 125 | "version":1, 126 | "author":"makeappicon" 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/example1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "example1.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/example1.imageset/example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/example1.imageset/example1.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/example2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "example2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/example2.imageset/example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/example2.imageset/example2.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/iTunesArtwork@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/iTunesArtwork@1x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/iTunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/iTunesArtwork@2x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Assets.xcassets/iTunesArtwork@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/Lazy Pop SwiftUI/Assets.xcassets/iTunesArtwork@3x.png -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/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 | -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Lazy Pop SwiftUI 4 | // 5 | // Created by Joseph Hinkle on 12/1/19. 6 | // Copyright © 2019 Joseph Hinkle. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ContentView: View { 12 | var body: some View { 13 | NavigationView { 14 | List { 15 | Section(header: Text("Code Examples")) { 16 | NavigationLink( 17 | destination: Example1() 18 | ) { 19 | Text("Example 1") 20 | } 21 | NavigationLink( 22 | destination: Example2() 23 | ) { 24 | Text("Example 2") 25 | } 26 | } 27 | Spacer() 28 | Section(header: Text("Demos")) { 29 | NavigationLink( 30 | destination: DetailsView() 31 | ) { 32 | Text("Default behavior 😑") 33 | } 34 | NavigationLink( 35 | destination: DetailsViewWithLazyPop() 36 | ) { 37 | Text("Lazy pop 🗯️") 38 | } 39 | NavigationLink( 40 | destination: DetailsViewWithToggleableLazyPop() 41 | ) { 42 | Text("Toggleable lazy pop 🔦") 43 | } 44 | } 45 | Spacer() 46 | Section(header: Text("Links")) { 47 | Button(action: { 48 | let webURL = URL(string: "https://github.com/joehinkle11/Lazy-Pop-SwiftUI")! 49 | if UIApplication.shared.canOpenURL(webURL as URL) { 50 | UIApplication.shared.open(webURL) 51 | } 52 | }) { 53 | Text("Source Code on GitHub 📃") 54 | } 55 | Button(action: { 56 | let screenName = "joehink95" 57 | let appURL = URL(string: "twitter://user?screen_name=\(screenName)")! 58 | let webURL = URL(string: "https://twitter.com/\(screenName)")! 59 | if UIApplication.shared.canOpenURL(appURL as URL) { 60 | UIApplication.shared.open(appURL) 61 | } else { 62 | UIApplication.shared.open(webURL) 63 | } 64 | }) { 65 | Text("Contact me ♥️") 66 | } 67 | Button(action: { 68 | let tweetText = "I found a SwiftUI component called Lazy Pop which lets you dismiss a view by dragging anywhere! #SwiftUI" 69 | let tweetUrl = "https://github.com/joehinkle11/Lazy-Pop-SwiftUI" 70 | let shareString = "https://twitter.com/intent/tweet?text=\(tweetText)&url=\(tweetUrl)" 71 | let escapedShareString = shareString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)! 72 | let url = URL(string: escapedShareString)! 73 | UIApplication.shared.open(url) 74 | }) { 75 | Text("Share 🐦") 76 | } 77 | } 78 | } 79 | .navigationBarTitle("Lazy Pop SwiftUI") 80 | } 81 | } 82 | } 83 | 84 | struct Example1: View { 85 | var body: some View { 86 | VStack { 87 | Text("To enabled Lazy Pop on your SwiftUI view, just add the \".lazyPop()\" modifier to your existing view.") 88 | .padding(50) 89 | Image("example1") 90 | .resizable() 91 | .aspectRatio(contentMode: .fit) 92 | Spacer() 93 | } 94 | .lazyPop() 95 | .navigationBarTitle("Example 1") 96 | } 97 | } 98 | 99 | struct Example2: View { 100 | var body: some View { 101 | VStack { 102 | Text("If you want to toggle the effect, you can pass a boolean @State to the modifier.") 103 | .padding(50) 104 | Image("example2") 105 | .resizable() 106 | .aspectRatio(contentMode: .fit) 107 | Spacer() 108 | } 109 | .lazyPop() 110 | .navigationBarTitle("Example 2") 111 | } 112 | } 113 | 114 | struct DetailsView: View { 115 | var body: some View { 116 | Text("Default behavior enabled 🥱 Swipe from the leftmost part of the screen to dismiss...").padding(50) 117 | .navigationBarTitle("Default behavior 😑") 118 | } 119 | } 120 | 121 | struct DetailsViewWithLazyPop: View { 122 | var body: some View { 123 | Text("Lazy pop enabled 😄 Swipe anywhere to dismiss ↔️").padding(50) 124 | .lazyPop() 125 | .navigationBarTitle("Lazy pop 🗯️") 126 | } 127 | } 128 | 129 | struct DetailsViewWithToggleableLazyPop: View { 130 | @State var isEnabled: Bool = true 131 | var body: some View { 132 | VStack { 133 | Toggle(isOn: $isEnabled) { 134 | if isEnabled { 135 | Text("Lazy pop on") 136 | } else { 137 | Text("Lazy pop off") 138 | } 139 | }.padding(100) 140 | if isEnabled { 141 | Text("Lazy pop enabled 😄 Swipe anywhere to dismiss ↔️").padding(50) 142 | } else { 143 | Text("Lazy pop disabled 😭").padding(50) 144 | } 145 | } 146 | .lazyPop(isEnabled: $isEnabled) 147 | .navigationBarTitle("Toggleable lazy pop 🔦") 148 | } 149 | } 150 | 151 | struct ContentView_Previews: PreviewProvider { 152 | static var previews: some View { 153 | ContentView() 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/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 | -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Lazy Pop/SlideAnimatedTransitioning.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SlideAnimatedTransitioning.swift 3 | // SwipeRightToPopController 4 | // 5 | // Created by Warif Akhand Rishi on 2/19/16. 6 | // Copyright © 2016 Warif Akhand Rishi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SlideAnimatedTransitioning: NSObject { 12 | 13 | } 14 | 15 | extension SlideAnimatedTransitioning: UIViewControllerAnimatedTransitioning { 16 | 17 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 18 | 19 | let containerView = transitionContext.containerView 20 | let fromView = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!.view 21 | let toView = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!.view 22 | 23 | let width = containerView.frame.width 24 | 25 | var offsetLeft = fromView?.frame 26 | offsetLeft?.origin.x = width 27 | 28 | var offscreenRight = toView?.frame 29 | offscreenRight?.origin.x = -width / 3.33; 30 | 31 | toView?.frame = offscreenRight!; 32 | 33 | fromView?.layer.shadowRadius = 5.0 34 | fromView?.layer.shadowOpacity = 1.0 35 | toView?.layer.opacity = 0.9 36 | 37 | containerView.insertSubview(toView!, belowSubview: fromView!) 38 | 39 | UIView.animate(withDuration: transitionDuration(using: transitionContext), delay:0, options:.curveLinear, animations:{ 40 | 41 | toView?.frame = (fromView?.frame)! 42 | fromView?.frame = offsetLeft! 43 | 44 | toView?.layer.opacity = 1.0 45 | fromView?.layer.shadowOpacity = 0.1 46 | 47 | }, completion: { finished in 48 | toView?.layer.opacity = 1.0 49 | toView?.layer.shadowOpacity = 0 50 | fromView?.layer.opacity = 1.0 51 | fromView?.layer.shadowOpacity = 0 52 | 53 | // when cancelling or completing the animation, ios simulator seems to sometimes flash black backgrounds during the animation. on devices, this doesn't seem to happen though. 54 | // containerView.backgroundColor = [UIColor whiteColor]; 55 | transitionContext.completeTransition(!transitionContext.transitionWasCancelled) 56 | }) 57 | } 58 | 59 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 60 | 61 | return 0.3 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Lazy Pop/SwipeRightToPopViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipeRightToPopViewController.swift 3 | // SwipeRightToPopController 4 | // 5 | // Created by Warif Akhand Rishi on 2/19/16. 6 | // Copyright © 2016 Warif Akhand Rishi. All rights reserved. 7 | // 8 | // Modified by Joseph Hinkle on 12/1/19. 9 | // Modified version allows use in SwiftUI by subclassing UIHostingController. 10 | // Copyright © 2019 Joseph Hinkle. All rights reserved. 11 | // 12 | 13 | import SwiftUI 14 | fileprivate func < (lhs: T?, rhs: T?) -> Bool { 15 | switch (lhs, rhs) { 16 | case let (l?, r?): 17 | return l < r 18 | case (nil, _?): 19 | return true 20 | default: 21 | return false 22 | } 23 | } 24 | 25 | fileprivate func > (lhs: T?, rhs: T?) -> Bool { 26 | switch (lhs, rhs) { 27 | case let (l?, r?): 28 | return l > r 29 | default: 30 | return rhs < lhs 31 | } 32 | } 33 | 34 | class SwipeRightToPopViewController: UIHostingController, UINavigationControllerDelegate where Content : View { 35 | 36 | fileprivate var lazyPopContent: LazyPop? 37 | private var percentDrivenInteractiveTransition: UIPercentDrivenInteractiveTransition? 38 | private var panGestureRecognizer: UIPanGestureRecognizer! 39 | private var parentNavigationControllerToUse: UINavigationController? 40 | private var gestureAdded = false 41 | 42 | override func viewDidLayoutSubviews() { 43 | // You need to add gesture events after every subview layout to protect against weird edge cases 44 | // One notable edgecase is if you are in a splitview in landscape. In this case, there will be 45 | // no nav controller with 2 vcs, so our addGesture will fail. After rotating back to portrait, 46 | // the splitview will combine into one view with the details pushed on top. So only then would 47 | // would the addGesture find a parent nav controller with 2 view controllers. I don't know if 48 | // there are other edge cases, but running addGesture on every viewDidLayoutSubviews seems safe. 49 | addGesture() 50 | } 51 | 52 | public func addGesture() { 53 | if !gestureAdded { 54 | // attempt to find a parent navigationController 55 | var currentVc: UIViewController = self 56 | while true { 57 | if (currentVc.navigationController != nil) && 58 | currentVc.navigationController?.viewControllers.count > 1 59 | { 60 | parentNavigationControllerToUse = currentVc.navigationController 61 | break 62 | } 63 | guard let parent = currentVc.parent else { 64 | return 65 | } 66 | currentVc = parent 67 | } 68 | guard parentNavigationControllerToUse?.viewControllers.count > 1 else { 69 | return 70 | } 71 | 72 | panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(SwipeRightToPopViewController.handlePanGesture(_:))) 73 | self.view.addGestureRecognizer(panGestureRecognizer) 74 | gestureAdded = true 75 | } 76 | } 77 | 78 | @objc func handlePanGesture(_ panGesture: UIPanGestureRecognizer) { 79 | 80 | // if the parentNavigationControllerToUse has a width value, use that because it's more accurate. Otherwise use this view's width as a backup 81 | let total = parentNavigationControllerToUse?.view.frame.width ?? view.frame.width 82 | let percent = max(panGesture.translation(in: view).x, 0) / total 83 | 84 | switch panGesture.state { 85 | 86 | case .began: 87 | if lazyPopContent?.isEnabled == true { 88 | parentNavigationControllerToUse?.delegate = self 89 | _ = parentNavigationControllerToUse?.popViewController(animated: true) 90 | } 91 | 92 | case .changed: 93 | if let percentDrivenInteractiveTransition = percentDrivenInteractiveTransition { 94 | percentDrivenInteractiveTransition.update(percent) 95 | } 96 | 97 | case .ended: 98 | let velocity = panGesture.velocity(in: view).x 99 | 100 | // Continue if drag more than 50% of screen width or velocity is higher than 100 101 | if percent > 0.5 || velocity > 100 { 102 | percentDrivenInteractiveTransition?.finish() 103 | } else { 104 | percentDrivenInteractiveTransition?.cancel() 105 | } 106 | 107 | case .cancelled, .failed: 108 | percentDrivenInteractiveTransition?.cancel() 109 | 110 | default: 111 | break 112 | } 113 | } 114 | 115 | override func didReceiveMemoryWarning() { 116 | super.didReceiveMemoryWarning() 117 | } 118 | 119 | func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { 120 | 121 | return SlideAnimatedTransitioning() 122 | } 123 | 124 | func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { 125 | 126 | parentNavigationControllerToUse?.delegate = nil 127 | navigationController.delegate = nil 128 | 129 | if panGestureRecognizer.state == .began { 130 | percentDrivenInteractiveTransition = UIPercentDrivenInteractiveTransition() 131 | percentDrivenInteractiveTransition?.completionCurve = .easeOut 132 | } else { 133 | percentDrivenInteractiveTransition = nil 134 | } 135 | 136 | return percentDrivenInteractiveTransition 137 | } 138 | } 139 | 140 | 141 | // 142 | // Lazy Pop SwiftUI Component 143 | // 144 | // Created by Joseph Hinkle on 12/1/19. 145 | // Copyright © 2019 Joseph Hinkle. All rights reserved. 146 | // 147 | 148 | fileprivate struct LazyPop: UIViewControllerRepresentable { 149 | let rootView: Content 150 | @Binding var isEnabled: Bool 151 | 152 | init(_ rootView: Content, isEnabled: (Binding)? = nil) { 153 | self.rootView = rootView 154 | self._isEnabled = isEnabled ?? Binding(get: { return true }, set: { _ in }) 155 | } 156 | 157 | func makeUIViewController(context: Context) -> UIViewController { 158 | let vc = SwipeRightToPopViewController(rootView: rootView) 159 | vc.lazyPopContent = self 160 | return vc 161 | } 162 | 163 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) { 164 | if let host = uiViewController as? UIHostingController { 165 | host.rootView = rootView 166 | } 167 | } 168 | } 169 | extension View { 170 | public func lazyPop(isEnabled: (Binding)? = nil) -> some View { 171 | return LazyPop(self, isEnabled: isEnabled) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Lazy Pop SwiftUI/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Lazy Pop SwiftUI 4 | // 5 | // Created by Joseph Hinkle on 12/1/19. 6 | // Copyright © 2019 Joseph Hinkle. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 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 | // Create the SwiftUI view that provides the window contents. 23 | let contentView = ContentView() 24 | 25 | // Use a UIHostingController as window root view controller. 26 | if let windowScene = scene as? UIWindowScene { 27 | let window = UIWindow(windowScene: windowScene) 28 | window.rootViewController = UIHostingController(rootView: contentView) 29 | self.window = window 30 | window.makeKeyAndVisible() 31 | } 32 | } 33 | 34 | func sceneDidDisconnect(_ scene: UIScene) { 35 | // Called as the scene is being released by the system. 36 | // This occurs shortly after the scene enters the background, or when its session is discarded. 37 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 38 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 39 | } 40 | 41 | func sceneDidBecomeActive(_ scene: UIScene) { 42 | // Called when the scene has moved from an inactive state to an active state. 43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 44 | } 45 | 46 | func sceneWillResignActive(_ scene: UIScene) { 47 | // Called when the scene will move from an active state to an inactive state. 48 | // This may occur due to temporary interruptions (ex. an incoming phone call). 49 | } 50 | 51 | func sceneWillEnterForeground(_ scene: UIScene) { 52 | // Called as the scene transitions from the background to the foreground. 53 | // Use this method to undo the changes made on entering the background. 54 | } 55 | 56 | func sceneDidEnterBackground(_ scene: UIScene) { 57 | // Called as the scene transitions from the foreground to the background. 58 | // Use this method to save data, release shared resources, and store enough scene-specific state information 59 | // to restore the scene back to its current state. 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lazy Pop SwiftUI 2 | 3 | Swiping on any part of the screen starts an interruptible pop animation to the previous view. 4 | 5 |

6 | 7 | Forked from https://github.com/rishi420/SwipeRightToPopController and adapted for SwiftUI. 8 | 9 | Also thanks to [lyinsteve on this Reddit comment](https://www.reddit.com/r/iOSProgramming/comments/e4zeoi/i_made_a_swiftui_component_so_you_can_drag/f9gkllt/) for suggesting I turn this into modifier. 10 | 11 | # App Store Demo 12 | 13 | [![](https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/master/Lazy%20Pop%20SwiftUI/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60%402x.png)](https://apps.apple.com/us/app/lazy-pop-swiftui-demo/id1490371801) 14 | 15 | [Download the Lazy Pop SwiftUI demo here!](https://apps.apple.com/us/app/lazy-pop-swiftui-demo/id1490371801) 16 | 17 | # Use 18 | 19 | To make your view lazily poppable, just add the `lazyPop()` modifer to it. 20 | 21 | ```swift 22 | struct DetailsViewWithLazyPop: View { 23 | var body: some View { 24 | Text("Lazy pop enabled. Swipe anywhere to dismiss.") 25 | .lazyPop() 26 | } 27 | } 28 | ``` 29 | If you would like to toggle when the lazy pop is enabled, just pass it a boolean state. 30 | 31 | ```swift 32 | struct DetailsViewWithToggleableLazyPop: View { 33 | @State var isEnabled: Bool = true 34 | var body: some View { 35 | Toggle(isOn: $isEnabled) { 36 | Text("Toggle lazy pop") 37 | } 38 | .lazyPop(isEnabled: $isEnabled) 39 | } 40 | } 41 | ``` 42 | 43 | # Gotchas 44 | 45 | The current implementation does not play well with some SwiftUI modifiers like `.ignoresSafeArea()`. There is currently no known workaround. If you find anything related to this problem, you can write about it in [this issue](https://github.com/joehinkle11/Lazy-Pop-SwiftUI/issues/3#issuecomment-1079688013). 46 | 47 | # Install 48 | 49 | Just inlude the two files under `LazyPop` in this repo. Here's a link to them https://github.com/joehinkle11/Lazy-Pop-SwiftUI/tree/master/Lazy%20Pop%20SwiftUI/Lazy%20Pop 50 | 51 | If this gets enough use, I'll put this in a Swift Package or a Cocopod. 52 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joehinkle11/Lazy-Pop-SwiftUI/118d521f3aa37655453d612bb03959e7fd8ffb5d/demo.gif --------------------------------------------------------------------------------