├── .gitignore ├── HeroAnimation ├── .DS_Store ├── HeroAnimation.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── HeroAnimation │ ├── .DS_Store │ ├── Assets.xcassets │ │ ├── .DS_Store │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── header.imageset │ │ │ ├── Contents.json │ │ │ └── undraw_programmer.svg │ ├── Card.swift │ ├── CardDetail.swift │ ├── Constants │ │ ├── AnimationId.swift │ │ ├── Dimens.swift │ │ └── Texts.swift │ ├── ContentView.swift │ ├── Extensions │ │ └── Animation+Extension.swift │ ├── HeroAnimationApp.swift │ ├── Modifiers │ │ ├── AnimatableFontModifier.swift │ │ └── CornerRadiusModifier.swift │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ └── Views │ │ ├── AnimatableLabels.swift │ │ ├── AnimatableTitle.swift │ │ └── CloseButton.swift └── README.md ├── LICENSE ├── LottieAnimationExample ├── LottieAnimationExample.xcodeproj │ └── project.pbxproj ├── LottieAnimationExample │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── Ressources │ │ ├── Assets.xcassets │ │ │ ├── AccentColor.colorset │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── image.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── pexels-iván-cisneros-14086443@1x.png │ │ │ │ ├── pexels-iván-cisneros-14086443@2x.png │ │ │ │ └── pexels-iván-cisneros-14086443@3x.png │ │ └── heartAnimation.json │ └── Sources │ │ ├── Constants │ │ ├── Dimens.swift │ │ ├── ImageNames.swift │ │ └── Strings.swift │ │ ├── ContentView.swift │ │ └── LottieAnimationExampleApp.swift └── README.md ├── PdfImporter ├── PdfImporter │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── Illustrations │ │ │ ├── Contents.json │ │ │ ├── Empty.imageset │ │ │ ├── Contents.json │ │ │ └── undraw_no_data_re_kwbl-2.svg │ │ │ ├── Error.imageset │ │ │ ├── Contents.json │ │ │ └── undraw_fixing_bugs_w7gi.svg │ │ │ └── PageLimit.imageset │ │ │ ├── Contents.json │ │ │ └── undraw_file_manager_re_ms29.svg │ ├── PdfImporter.entitlements │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ └── Sources │ │ ├── App │ │ └── PdfImporterApp.swift │ │ ├── Constants │ │ ├── Dimens.swift │ │ ├── ImageNames.swift │ │ └── Texts.swift │ │ ├── Error │ │ └── PDFError.swift │ │ ├── ViewModel │ │ └── PdfImporterViewModel.swift │ │ └── Views │ │ ├── EmptyPdfImporterView.swift │ │ ├── PDFImporterBottomSheet.swift │ │ ├── PDFKitView.swift │ │ ├── PdfImporterErrorView.swift │ │ ├── PdfImporterLoadedView.swift │ │ ├── PdfImporterLoadingView.swift │ │ └── PdfImporterView.swift ├── README.md └── ReadMeResources │ └── GifWithDevices.gif └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # 44 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 45 | # hence it is not needed unless you have added a package configuration file to your project 46 | # .swiftpm 47 | 48 | .build/ 49 | 50 | # CocoaPods 51 | # 52 | # We recommend against adding the Pods directory to your .gitignore. However 53 | # you should judge for yourself, the pros and cons are mentioned at: 54 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 55 | # 56 | # Pods/ 57 | # 58 | # Add this line if you want to avoid checking in source code from the Xcode workspace 59 | # *.xcworkspace 60 | 61 | # Carthage 62 | # 63 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 64 | # Carthage/Checkouts 65 | 66 | Carthage/Build/ 67 | 68 | # Accio dependency management 69 | Dependencies/ 70 | .accio/ 71 | 72 | # fastlane 73 | # 74 | # It is recommended to not store the screenshots in the git repo. 75 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 76 | # For more information about the recommended setup visit: 77 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 78 | 79 | fastlane/report.xml 80 | fastlane/Preview.html 81 | fastlane/screenshots/**/*.png 82 | fastlane/test_output 83 | 84 | # Code Injection 85 | # 86 | # After new code Injection tools there's a generated folder /iOSInjectionProject 87 | # https://github.com/johnno1962/injectionforxcode 88 | 89 | iOSInjectionProject/ 90 | -------------------------------------------------------------------------------- /HeroAnimation/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EXXETA/swiftui-examples/6b6913a1f4c134b92299e1e5f34bd283d3ac63cb/HeroAnimation/.DS_Store -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 472ADE64299CD733001E80D8 /* Animation+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472ADE63299CD733001E80D8 /* Animation+Extension.swift */; }; 11 | 474E8E7C299B819A00574B30 /* HeroAnimationApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474E8E7B299B819A00574B30 /* HeroAnimationApp.swift */; }; 12 | 474E8E7E299B819A00574B30 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474E8E7D299B819A00574B30 /* ContentView.swift */; }; 13 | 474E8E80299B819C00574B30 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 474E8E7F299B819C00574B30 /* Assets.xcassets */; }; 14 | 474E8E83299B819C00574B30 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 474E8E82299B819C00574B30 /* Preview Assets.xcassets */; }; 15 | 474E8E8B299B81BF00574B30 /* Dimens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474E8E8A299B81BF00574B30 /* Dimens.swift */; }; 16 | 474E8E8D299B81D400574B30 /* AnimationId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474E8E8C299B81D400574B30 /* AnimationId.swift */; }; 17 | 474E8E90299B81F200574B30 /* CornerRadiusModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474E8E8F299B81F200574B30 /* CornerRadiusModifier.swift */; }; 18 | 474E8E92299B820800574B30 /* AnimatableFontModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474E8E91299B820800574B30 /* AnimatableFontModifier.swift */; }; 19 | 474E8E95299B822A00574B30 /* AnimatableTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474E8E94299B822A00574B30 /* AnimatableTitle.swift */; }; 20 | 474E8E97299B823B00574B30 /* AnimatableLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474E8E96299B823B00574B30 /* AnimatableLabels.swift */; }; 21 | 474E8E99299B824C00574B30 /* CloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474E8E98299B824C00574B30 /* CloseButton.swift */; }; 22 | 474E8E9B299B827400574B30 /* CardDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474E8E9A299B827400574B30 /* CardDetail.swift */; }; 23 | 474E8E9D299B828900574B30 /* Card.swift in Sources */ = {isa = PBXBuildFile; fileRef = 474E8E9C299B828900574B30 /* Card.swift */; }; 24 | 47A84EDB299E7F4A00C1B62E /* Texts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47A84EDA299E7F4A00C1B62E /* Texts.swift */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 472ADE63299CD733001E80D8 /* Animation+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Animation+Extension.swift"; sourceTree = ""; }; 29 | 472ADE65299CDEDF001E80D8 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 30 | 474E8E78299B819A00574B30 /* HeroAnimation.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HeroAnimation.app; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | 474E8E7B299B819A00574B30 /* HeroAnimationApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeroAnimationApp.swift; sourceTree = ""; }; 32 | 474E8E7D299B819A00574B30 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 33 | 474E8E7F299B819C00574B30 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 34 | 474E8E82299B819C00574B30 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 35 | 474E8E8A299B81BF00574B30 /* Dimens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dimens.swift; sourceTree = ""; }; 36 | 474E8E8C299B81D400574B30 /* AnimationId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationId.swift; sourceTree = ""; }; 37 | 474E8E8F299B81F200574B30 /* CornerRadiusModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerRadiusModifier.swift; sourceTree = ""; }; 38 | 474E8E91299B820800574B30 /* AnimatableFontModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatableFontModifier.swift; sourceTree = ""; }; 39 | 474E8E94299B822A00574B30 /* AnimatableTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatableTitle.swift; sourceTree = ""; }; 40 | 474E8E96299B823B00574B30 /* AnimatableLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatableLabels.swift; sourceTree = ""; }; 41 | 474E8E98299B824C00574B30 /* CloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseButton.swift; sourceTree = ""; }; 42 | 474E8E9A299B827400574B30 /* CardDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardDetail.swift; sourceTree = ""; }; 43 | 474E8E9C299B828900574B30 /* Card.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Card.swift; sourceTree = ""; }; 44 | 47A84EDA299E7F4A00C1B62E /* Texts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Texts.swift; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 474E8E75299B819A00574B30 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 472ADE62299CD722001E80D8 /* Extensions */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 472ADE63299CD733001E80D8 /* Animation+Extension.swift */, 62 | ); 63 | path = Extensions; 64 | sourceTree = ""; 65 | }; 66 | 474E8E6F299B819A00574B30 = { 67 | isa = PBXGroup; 68 | children = ( 69 | 472ADE65299CDEDF001E80D8 /* README.md */, 70 | 474E8E7A299B819A00574B30 /* HeroAnimation */, 71 | 474E8E79299B819A00574B30 /* Products */, 72 | ); 73 | sourceTree = ""; 74 | }; 75 | 474E8E79299B819A00574B30 /* Products */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 474E8E78299B819A00574B30 /* HeroAnimation.app */, 79 | ); 80 | name = Products; 81 | sourceTree = ""; 82 | }; 83 | 474E8E7A299B819A00574B30 /* HeroAnimation */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 472ADE62299CD722001E80D8 /* Extensions */, 87 | 474E8E93299B821A00574B30 /* Views */, 88 | 474E8E8E299B81DF00574B30 /* Modifiers */, 89 | 474E8E89299B81A500574B30 /* Constants */, 90 | 474E8E7B299B819A00574B30 /* HeroAnimationApp.swift */, 91 | 474E8E7D299B819A00574B30 /* ContentView.swift */, 92 | 474E8E9A299B827400574B30 /* CardDetail.swift */, 93 | 474E8E9C299B828900574B30 /* Card.swift */, 94 | 474E8E7F299B819C00574B30 /* Assets.xcassets */, 95 | 474E8E81299B819C00574B30 /* Preview Content */, 96 | ); 97 | path = HeroAnimation; 98 | sourceTree = ""; 99 | }; 100 | 474E8E81299B819C00574B30 /* Preview Content */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 474E8E82299B819C00574B30 /* Preview Assets.xcassets */, 104 | ); 105 | path = "Preview Content"; 106 | sourceTree = ""; 107 | }; 108 | 474E8E89299B81A500574B30 /* Constants */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 474E8E8A299B81BF00574B30 /* Dimens.swift */, 112 | 474E8E8C299B81D400574B30 /* AnimationId.swift */, 113 | 47A84EDA299E7F4A00C1B62E /* Texts.swift */, 114 | ); 115 | path = Constants; 116 | sourceTree = ""; 117 | }; 118 | 474E8E8E299B81DF00574B30 /* Modifiers */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 474E8E8F299B81F200574B30 /* CornerRadiusModifier.swift */, 122 | 474E8E91299B820800574B30 /* AnimatableFontModifier.swift */, 123 | ); 124 | path = Modifiers; 125 | sourceTree = ""; 126 | }; 127 | 474E8E93299B821A00574B30 /* Views */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 474E8E94299B822A00574B30 /* AnimatableTitle.swift */, 131 | 474E8E96299B823B00574B30 /* AnimatableLabels.swift */, 132 | 474E8E98299B824C00574B30 /* CloseButton.swift */, 133 | ); 134 | path = Views; 135 | sourceTree = ""; 136 | }; 137 | /* End PBXGroup section */ 138 | 139 | /* Begin PBXNativeTarget section */ 140 | 474E8E77299B819A00574B30 /* HeroAnimation */ = { 141 | isa = PBXNativeTarget; 142 | buildConfigurationList = 474E8E86299B819C00574B30 /* Build configuration list for PBXNativeTarget "HeroAnimation" */; 143 | buildPhases = ( 144 | 474E8E74299B819A00574B30 /* Sources */, 145 | 474E8E75299B819A00574B30 /* Frameworks */, 146 | 474E8E76299B819A00574B30 /* Resources */, 147 | ); 148 | buildRules = ( 149 | ); 150 | dependencies = ( 151 | ); 152 | name = HeroAnimation; 153 | productName = HeroAnimation; 154 | productReference = 474E8E78299B819A00574B30 /* HeroAnimation.app */; 155 | productType = "com.apple.product-type.application"; 156 | }; 157 | /* End PBXNativeTarget section */ 158 | 159 | /* Begin PBXProject section */ 160 | 474E8E70299B819A00574B30 /* Project object */ = { 161 | isa = PBXProject; 162 | attributes = { 163 | BuildIndependentTargetsInParallel = 1; 164 | LastSwiftUpdateCheck = 1410; 165 | LastUpgradeCheck = 1410; 166 | TargetAttributes = { 167 | 474E8E77299B819A00574B30 = { 168 | CreatedOnToolsVersion = 14.1; 169 | }; 170 | }; 171 | }; 172 | buildConfigurationList = 474E8E73299B819A00574B30 /* Build configuration list for PBXProject "HeroAnimation" */; 173 | compatibilityVersion = "Xcode 14.0"; 174 | developmentRegion = en; 175 | hasScannedForEncodings = 0; 176 | knownRegions = ( 177 | en, 178 | Base, 179 | ); 180 | mainGroup = 474E8E6F299B819A00574B30; 181 | productRefGroup = 474E8E79299B819A00574B30 /* Products */; 182 | projectDirPath = ""; 183 | projectRoot = ""; 184 | targets = ( 185 | 474E8E77299B819A00574B30 /* HeroAnimation */, 186 | ); 187 | }; 188 | /* End PBXProject section */ 189 | 190 | /* Begin PBXResourcesBuildPhase section */ 191 | 474E8E76299B819A00574B30 /* Resources */ = { 192 | isa = PBXResourcesBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | 474E8E83299B819C00574B30 /* Preview Assets.xcassets in Resources */, 196 | 474E8E80299B819C00574B30 /* Assets.xcassets in Resources */, 197 | ); 198 | runOnlyForDeploymentPostprocessing = 0; 199 | }; 200 | /* End PBXResourcesBuildPhase section */ 201 | 202 | /* Begin PBXSourcesBuildPhase section */ 203 | 474E8E74299B819A00574B30 /* Sources */ = { 204 | isa = PBXSourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | 472ADE64299CD733001E80D8 /* Animation+Extension.swift in Sources */, 208 | 474E8E9B299B827400574B30 /* CardDetail.swift in Sources */, 209 | 474E8E8B299B81BF00574B30 /* Dimens.swift in Sources */, 210 | 474E8E8D299B81D400574B30 /* AnimationId.swift in Sources */, 211 | 474E8E97299B823B00574B30 /* AnimatableLabels.swift in Sources */, 212 | 474E8E99299B824C00574B30 /* CloseButton.swift in Sources */, 213 | 474E8E7E299B819A00574B30 /* ContentView.swift in Sources */, 214 | 474E8E90299B81F200574B30 /* CornerRadiusModifier.swift in Sources */, 215 | 474E8E7C299B819A00574B30 /* HeroAnimationApp.swift in Sources */, 216 | 474E8E92299B820800574B30 /* AnimatableFontModifier.swift in Sources */, 217 | 474E8E95299B822A00574B30 /* AnimatableTitle.swift in Sources */, 218 | 47A84EDB299E7F4A00C1B62E /* Texts.swift in Sources */, 219 | 474E8E9D299B828900574B30 /* Card.swift in Sources */, 220 | ); 221 | runOnlyForDeploymentPostprocessing = 0; 222 | }; 223 | /* End PBXSourcesBuildPhase section */ 224 | 225 | /* Begin XCBuildConfiguration section */ 226 | 474E8E84299B819C00574B30 /* Debug */ = { 227 | isa = XCBuildConfiguration; 228 | buildSettings = { 229 | ALWAYS_SEARCH_USER_PATHS = NO; 230 | CLANG_ANALYZER_NONNULL = YES; 231 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 232 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 233 | CLANG_ENABLE_MODULES = YES; 234 | CLANG_ENABLE_OBJC_ARC = YES; 235 | CLANG_ENABLE_OBJC_WEAK = YES; 236 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 237 | CLANG_WARN_BOOL_CONVERSION = YES; 238 | CLANG_WARN_COMMA = YES; 239 | CLANG_WARN_CONSTANT_CONVERSION = YES; 240 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 241 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 242 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 243 | CLANG_WARN_EMPTY_BODY = YES; 244 | CLANG_WARN_ENUM_CONVERSION = YES; 245 | CLANG_WARN_INFINITE_RECURSION = YES; 246 | CLANG_WARN_INT_CONVERSION = YES; 247 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 248 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 249 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 250 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 251 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 252 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 253 | CLANG_WARN_STRICT_PROTOTYPES = YES; 254 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 255 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 256 | CLANG_WARN_UNREACHABLE_CODE = YES; 257 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 258 | COPY_PHASE_STRIP = NO; 259 | DEBUG_INFORMATION_FORMAT = dwarf; 260 | ENABLE_STRICT_OBJC_MSGSEND = YES; 261 | ENABLE_TESTABILITY = YES; 262 | GCC_C_LANGUAGE_STANDARD = gnu11; 263 | GCC_DYNAMIC_NO_PIC = NO; 264 | GCC_NO_COMMON_BLOCKS = YES; 265 | GCC_OPTIMIZATION_LEVEL = 0; 266 | GCC_PREPROCESSOR_DEFINITIONS = ( 267 | "DEBUG=1", 268 | "$(inherited)", 269 | ); 270 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 271 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 272 | GCC_WARN_UNDECLARED_SELECTOR = YES; 273 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 274 | GCC_WARN_UNUSED_FUNCTION = YES; 275 | GCC_WARN_UNUSED_VARIABLE = YES; 276 | IPHONEOS_DEPLOYMENT_TARGET = 16.1; 277 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 278 | MTL_FAST_MATH = YES; 279 | ONLY_ACTIVE_ARCH = YES; 280 | SDKROOT = iphoneos; 281 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 282 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 283 | }; 284 | name = Debug; 285 | }; 286 | 474E8E85299B819C00574B30 /* Release */ = { 287 | isa = XCBuildConfiguration; 288 | buildSettings = { 289 | ALWAYS_SEARCH_USER_PATHS = NO; 290 | CLANG_ANALYZER_NONNULL = YES; 291 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 292 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 293 | CLANG_ENABLE_MODULES = YES; 294 | CLANG_ENABLE_OBJC_ARC = YES; 295 | CLANG_ENABLE_OBJC_WEAK = YES; 296 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 297 | CLANG_WARN_BOOL_CONVERSION = YES; 298 | CLANG_WARN_COMMA = YES; 299 | CLANG_WARN_CONSTANT_CONVERSION = YES; 300 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 301 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 302 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 303 | CLANG_WARN_EMPTY_BODY = YES; 304 | CLANG_WARN_ENUM_CONVERSION = YES; 305 | CLANG_WARN_INFINITE_RECURSION = YES; 306 | CLANG_WARN_INT_CONVERSION = YES; 307 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 308 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 309 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 310 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 311 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 312 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 313 | CLANG_WARN_STRICT_PROTOTYPES = YES; 314 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 315 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 316 | CLANG_WARN_UNREACHABLE_CODE = YES; 317 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 318 | COPY_PHASE_STRIP = NO; 319 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 320 | ENABLE_NS_ASSERTIONS = NO; 321 | ENABLE_STRICT_OBJC_MSGSEND = YES; 322 | GCC_C_LANGUAGE_STANDARD = gnu11; 323 | GCC_NO_COMMON_BLOCKS = YES; 324 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 325 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 326 | GCC_WARN_UNDECLARED_SELECTOR = YES; 327 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 328 | GCC_WARN_UNUSED_FUNCTION = YES; 329 | GCC_WARN_UNUSED_VARIABLE = YES; 330 | IPHONEOS_DEPLOYMENT_TARGET = 16.1; 331 | MTL_ENABLE_DEBUG_INFO = NO; 332 | MTL_FAST_MATH = YES; 333 | SDKROOT = iphoneos; 334 | SWIFT_COMPILATION_MODE = wholemodule; 335 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 336 | VALIDATE_PRODUCT = YES; 337 | }; 338 | name = Release; 339 | }; 340 | 474E8E87299B819C00574B30 /* Debug */ = { 341 | isa = XCBuildConfiguration; 342 | buildSettings = { 343 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 344 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 345 | CODE_SIGN_STYLE = Automatic; 346 | CURRENT_PROJECT_VERSION = 1; 347 | DEVELOPMENT_ASSET_PATHS = "\"HeroAnimation/Preview Content\""; 348 | DEVELOPMENT_TEAM = 7K95FFBYB7; 349 | ENABLE_PREVIEWS = YES; 350 | GENERATE_INFOPLIST_FILE = YES; 351 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 352 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 353 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 354 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 355 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 356 | LD_RUNPATH_SEARCH_PATHS = ( 357 | "$(inherited)", 358 | "@executable_path/Frameworks", 359 | ); 360 | MARKETING_VERSION = 1.0; 361 | PRODUCT_BUNDLE_IDENTIFIER = Exxeta.HeroAnimation; 362 | PRODUCT_NAME = "$(TARGET_NAME)"; 363 | SWIFT_EMIT_LOC_STRINGS = YES; 364 | SWIFT_VERSION = 5.0; 365 | TARGETED_DEVICE_FAMILY = "1,2"; 366 | }; 367 | name = Debug; 368 | }; 369 | 474E8E88299B819C00574B30 /* Release */ = { 370 | isa = XCBuildConfiguration; 371 | buildSettings = { 372 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 373 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 374 | CODE_SIGN_STYLE = Automatic; 375 | CURRENT_PROJECT_VERSION = 1; 376 | DEVELOPMENT_ASSET_PATHS = "\"HeroAnimation/Preview Content\""; 377 | DEVELOPMENT_TEAM = 7K95FFBYB7; 378 | ENABLE_PREVIEWS = YES; 379 | GENERATE_INFOPLIST_FILE = YES; 380 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 381 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 382 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 383 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 384 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 385 | LD_RUNPATH_SEARCH_PATHS = ( 386 | "$(inherited)", 387 | "@executable_path/Frameworks", 388 | ); 389 | MARKETING_VERSION = 1.0; 390 | PRODUCT_BUNDLE_IDENTIFIER = Exxeta.HeroAnimation; 391 | PRODUCT_NAME = "$(TARGET_NAME)"; 392 | SWIFT_EMIT_LOC_STRINGS = YES; 393 | SWIFT_VERSION = 5.0; 394 | TARGETED_DEVICE_FAMILY = "1,2"; 395 | }; 396 | name = Release; 397 | }; 398 | /* End XCBuildConfiguration section */ 399 | 400 | /* Begin XCConfigurationList section */ 401 | 474E8E73299B819A00574B30 /* Build configuration list for PBXProject "HeroAnimation" */ = { 402 | isa = XCConfigurationList; 403 | buildConfigurations = ( 404 | 474E8E84299B819C00574B30 /* Debug */, 405 | 474E8E85299B819C00574B30 /* Release */, 406 | ); 407 | defaultConfigurationIsVisible = 0; 408 | defaultConfigurationName = Release; 409 | }; 410 | 474E8E86299B819C00574B30 /* Build configuration list for PBXNativeTarget "HeroAnimation" */ = { 411 | isa = XCConfigurationList; 412 | buildConfigurations = ( 413 | 474E8E87299B819C00574B30 /* Debug */, 414 | 474E8E88299B819C00574B30 /* Release */, 415 | ); 416 | defaultConfigurationIsVisible = 0; 417 | defaultConfigurationName = Release; 418 | }; 419 | /* End XCConfigurationList section */ 420 | }; 421 | rootObject = 474E8E70299B819A00574B30 /* Project object */; 422 | } 423 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EXXETA/swiftui-examples/6b6913a1f4c134b92299e1e5f34bd283d3ac63cb/HeroAnimation/HeroAnimation/.DS_Store -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/Assets.xcassets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EXXETA/swiftui-examples/6b6913a1f4c134b92299e1e5f34bd283d3ac63cb/HeroAnimation/HeroAnimation/Assets.xcassets/.DS_Store -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/Assets.xcassets/header.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "undraw_programmer.svg", 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 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/Assets.xcassets/header.imageset/undraw_programmer.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/Card.swift: -------------------------------------------------------------------------------- 1 | // 2 | // From Mobile@Exxeta by Laura Siewert 3 | // https://medium.com/@mobile_44538 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct Card: View { 9 | // MARK: - Properties 10 | @Binding var isShowingDetail: Bool 11 | @Binding var isAppeared: Bool 12 | let animation: Namespace.ID 13 | 14 | var body: some View { 15 | ZStack(alignment: .bottom) { 16 | // MARK: - Header 17 | VStack(spacing: 0) { 18 | Image("header") 19 | .resizable() 20 | .matchedGeometryEffect(id: AnimationId.imageId, in: animation, anchor: .top) 21 | .aspectRatio(contentMode: .fit) 22 | .frame(height: Dimens.cardImageHeight, alignment: .top) 23 | Spacer() 24 | .frame(height: Dimens.cardForegroundHeight) 25 | } 26 | .frame(width: Dimens.cardWidth, height: Dimens.cardHeight) 27 | .background( 28 | Color.black 29 | .cornerRadius(Dimens.unit24) 30 | .matchedGeometryEffect(id: AnimationId.imageBackgroundId, in: animation) 31 | ) 32 | // MARK: - Content 33 | VStack(alignment: .leading, spacing: 0) { 34 | AnimatableTitle(isAppeared: isAppeared) 35 | .matchedGeometryEffect(id: AnimationId.titleId, in: animation, anchor: .center) 36 | .padding(.bottom, Dimens.unit6) 37 | HStack(spacing: Dimens.unit12) { 38 | AnimatableLabels(isAppeared: isAppeared, text: Texts.points) 39 | .matchedGeometryEffect(id: AnimationId.label1Id, in: animation) 40 | AnimatableLabels(isAppeared: isAppeared, text: Texts.category) 41 | .matchedGeometryEffect(id: AnimationId.label2Id, in: animation) 42 | } 43 | } 44 | .padding(Dimens.unit16) 45 | .frame(width: Dimens.cardWidth, height: Dimens.cardForegroundHeight) 46 | .background( 47 | Color.white 48 | .cornerRadius(Dimens.unit24, corners: [.topLeft, .topRight]) 49 | .matchedGeometryEffect(id: AnimationId.textBackgroundId, in: animation) 50 | ) 51 | } 52 | .onAppear { 53 | withAnimation(.linear) { 54 | isAppeared = isShowingDetail 55 | } 56 | } 57 | .mask { 58 | RoundedRectangle(cornerRadius: Dimens.unit24) 59 | .matchedGeometryEffect(id: AnimationId.backgroundShapeId, in: animation) 60 | } 61 | .shadow(radius: Dimens.unit16) 62 | .onTapGesture { 63 | withAnimation(.hero) { 64 | isShowingDetail = true 65 | } 66 | } 67 | } 68 | } 69 | 70 | struct Card_Previews: PreviewProvider { 71 | struct TestCard: View { 72 | @Namespace var animation 73 | var body: some View { 74 | Card( 75 | isShowingDetail: .constant(false), 76 | isAppeared: .constant(false), 77 | animation: animation 78 | ) 79 | } 80 | } 81 | static var previews: some View { 82 | TestCard() 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/CardDetail.swift: -------------------------------------------------------------------------------- 1 | // 2 | // From Mobile@Exxeta by Laura Siewert 3 | // https://medium.com/@mobile_44538 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct CardDetail: View { 9 | // MARK: - Properties 10 | @Binding var isShowingDetail: Bool 11 | @Binding var isAppeared: Bool 12 | let animation: Namespace.ID 13 | 14 | // MARK: - Private State 15 | @State private var animateText: Bool = false 16 | 17 | var body: some View { 18 | ScrollView(showsIndicators: false) { 19 | ZStack(alignment: .bottom) { 20 | // MARK: - Header 21 | VStack(spacing: 0) { 22 | Image("header") 23 | .resizable() 24 | .matchedGeometryEffect(id: AnimationId.imageId, in: animation, anchor: .top) 25 | .aspectRatio(contentMode: .fit) 26 | .frame(height: Dimens.cardDetailImageHeight) 27 | .padding(.vertical, 2*Dimens.unit24) 28 | Spacer() 29 | .frame(height: UIScreen.main.bounds.height - Dimens.cardImageHeight + 2*Dimens.unit24) 30 | } 31 | .frame(width: UIScreen.main.bounds.width) 32 | .background( 33 | Color.black 34 | .cornerRadius(0) 35 | .matchedGeometryEffect(id: AnimationId.imageBackgroundId, in: animation) 36 | ) 37 | // MARK: - Content 38 | VStack(alignment: .leading, spacing: 0) { 39 | AnimatableTitle(isAppeared: isAppeared) 40 | .matchedGeometryEffect(id: AnimationId.titleId, in: animation) 41 | .padding(.bottom, Dimens.unit16) 42 | HStack(spacing: Dimens.unit12) { 43 | AnimatableLabels(isAppeared: isAppeared, text: Texts.points) 44 | .matchedGeometryEffect(id: AnimationId.label1Id, in: animation) 45 | AnimatableLabels(isAppeared: isAppeared, text: Texts.category) 46 | .matchedGeometryEffect(id: AnimationId.label2Id, in: animation) 47 | } 48 | .padding(.bottom, Dimens.unit24) 49 | Text(Texts.content) 50 | .opacity(animateText ? 1 : 0) 51 | } 52 | .padding(Dimens.unit24) 53 | .background( 54 | Color.white 55 | .cornerRadius(Dimens.unit24, corners: [.topLeft, .topRight]) 56 | .matchedGeometryEffect(id: AnimationId.textBackgroundId, in: animation) 57 | ) 58 | } 59 | } 60 | .mask { 61 | RoundedRectangle(cornerRadius: 0) 62 | .matchedGeometryEffect(id: AnimationId.backgroundShapeId, in: animation) 63 | } 64 | .onAppear { 65 | UIScrollView.appearance().bounces = false 66 | 67 | withAnimation(.linear) { 68 | isAppeared = isShowingDetail 69 | } 70 | withAnimation(.linear.delay(0.2)) { 71 | animateText = true 72 | } 73 | } 74 | .onDisappear { 75 | withAnimation(.linear) { 76 | animateText = false 77 | } 78 | } 79 | // MARK: - Close Button 80 | .overlay( 81 | CloseButton(isShowingDetail: $isShowingDetail) 82 | .opacity(isAppeared ? 1 : 0) 83 | .padding(.top, Dimens.unit24) 84 | .padding(.trailing, Dimens.unit24), 85 | alignment: .topTrailing 86 | ) 87 | .statusBarHidden(true) 88 | .ignoresSafeArea() 89 | } 90 | } 91 | 92 | 93 | struct CardDetail_Previews: PreviewProvider { 94 | struct TestCardDetail: View { 95 | @Namespace var animation 96 | var body: some View { 97 | CardDetail( 98 | isShowingDetail: .constant(true), 99 | isAppeared: .constant(true), 100 | animation: animation 101 | ) 102 | } 103 | } 104 | static var previews: some View { 105 | TestCardDetail() 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/Constants/AnimationId.swift: -------------------------------------------------------------------------------- 1 | // 2 | // From Mobile@Exxeta by Laura Siewert 3 | // https://medium.com/@mobile_44538 4 | // 5 | 6 | import Foundation 7 | 8 | class AnimationId { 9 | static let imageBackgroundId = "imageBackground" 10 | static let imageId = "image" 11 | static let titleId = "title" 12 | static let label1Id = "label1" 13 | static let label2Id = "label2" 14 | static let textBackgroundId = "textBackgrounud" 15 | static let textBackgroundShapeId = "textBackgroundShape" 16 | static let backgroundShapeId = "backgroundShape" 17 | } 18 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/Constants/Dimens.swift: -------------------------------------------------------------------------------- 1 | // 2 | // From Mobile@Exxeta by Laura Siewert 3 | // https://medium.com/@mobile_44538 4 | // 5 | 6 | import Foundation 7 | 8 | class Dimens { 9 | // MARK: - Paddings 10 | static let unit6: CGFloat = 6 11 | static let unit12: CGFloat = 12 12 | static let unit16: CGFloat = 16 13 | static let unit24: CGFloat = 24 14 | 15 | // MARK: - Card 16 | static let cardWidth: CGFloat = 210 17 | static let cardHeight: CGFloat = 205 18 | static let cardImageHeight: CGFloat = 92 19 | static let cardForegroundHeight: CGFloat = 98 20 | 21 | // MARK: - CardDetail 22 | static let cardDetailImageHeight: CGFloat = 240 23 | static let cardDetailHeaderHeight: CGFloat = 288 24 | 25 | // MARK: - Other 26 | static let closeButtonSize: CGFloat = 32 27 | 28 | } 29 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/Constants/Texts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // From Mobile@Exxeta by Laura Siewert 3 | // https://medium.com/@mobile_44538 4 | // 5 | 6 | import Foundation 7 | 8 | class Texts { 9 | static let content = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." 10 | static let points = "10 Points" 11 | static let category = "Clarification" 12 | static let title = "Learning: Do a\nSwiftUI tutorial" 13 | } 14 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // From Mobile@Exxeta by Laura Siewert 3 | // https://medium.com/@mobile_44538 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct ContentView: View { 9 | // show either the card or the carddetail 10 | @State private var isShowingDetail = false 11 | // Reflects isShowingDetail value but after appearance of view 12 | @State private var isAppeared = false 13 | // control if you can interact with the card 14 | @State private var isAnimating = false 15 | 16 | @Namespace var animation 17 | 18 | // Check if animation is on going to disable user interaction 19 | private func checkOnGoingAnimation(newValue: Bool) { 20 | if newValue { 21 | // Disable user interaction while animation 22 | isAnimating = true 23 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { 24 | isAnimating = false 25 | } 26 | } else { 27 | // Disable user interaction while animation 28 | isAnimating = true 29 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { 30 | isAnimating = false 31 | } 32 | } 33 | } 34 | 35 | var body: some View { 36 | ZStack { 37 | if isShowingDetail { 38 | CardDetail( 39 | isShowingDetail: $isShowingDetail, 40 | isAppeared: $isAppeared, 41 | animation: animation 42 | ) 43 | .transition(.scale(scale: 1)) 44 | .disabled(isAnimating) 45 | } else { 46 | Card( 47 | isShowingDetail: $isShowingDetail, 48 | isAppeared: $isAppeared, 49 | animation: animation 50 | ) 51 | .transition(.scale(scale: 1)) 52 | .disabled(isAnimating) 53 | } 54 | } 55 | .onChange(of: isShowingDetail) { checkOnGoingAnimation(newValue: $0)} 56 | } 57 | } 58 | 59 | struct ContentView_Previews: PreviewProvider { 60 | static var previews: some View { 61 | ContentView() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/Extensions/Animation+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // From Mobile@Exxeta by Laura Siewert 3 | // https://medium.com/@mobile_44538 4 | // 5 | 6 | import SwiftUI 7 | 8 | extension Animation { 9 | static var hero: Animation { 10 | .interactiveSpring(response: 0.6, dampingFraction: 0.85, blendDuration: 0.25) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/HeroAnimationApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // From Mobile@Exxeta by Laura Siewert 3 | // https://medium.com/@mobile_44538 4 | // 5 | 6 | import SwiftUI 7 | 8 | @main 9 | struct HeroAnimationApp: App { 10 | var body: some Scene { 11 | WindowGroup { 12 | ContentView() 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/Modifiers/AnimatableFontModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // From Mobile@Exxeta by Laura Siewert 3 | // https://medium.com/@mobile_44538 4 | // 5 | 6 | import SwiftUI 7 | // Source: https://www.hackingwithswift.com/quick-start/swiftui/how-to-animate-the-size-of-text 8 | // Animate the text size of a text 9 | struct AnimatableSystemFontModifier: ViewModifier, Animatable { 10 | var size: Double 11 | var weight: Font.Weight 12 | var design: Font.Design 13 | 14 | var animatableData: Double { 15 | get { size } 16 | set { size = newValue } 17 | } 18 | 19 | func body(content: Content) -> some View { 20 | content 21 | .font(.system(size: size, weight: weight, design: design)) 22 | } 23 | } 24 | 25 | extension View { 26 | func animatableSystemFont(size: Double, weight: Font.Weight = .regular, design: Font.Design = .rounded) -> some View { 27 | self.modifier(AnimatableSystemFontModifier(size: size, weight: weight, design: design)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/Modifiers/CornerRadiusModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // From Mobile@Exxeta by Laura Siewert 3 | // https://medium.com/@mobile_44538 4 | // 5 | 6 | import SwiftUI 7 | 8 | // Source: https://stackoverflow.com/a/58606176 9 | // Define corner radius for specific corners 10 | extension View { 11 | func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { 12 | clipShape(RoundedCorner(radius: radius, corners: corners)) 13 | } 14 | } 15 | 16 | struct RoundedCorner: Shape { 17 | 18 | var radius: CGFloat = .infinity 19 | var corners: UIRectCorner = .allCorners 20 | 21 | func path(in rect: CGRect) -> Path { 22 | let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) 23 | return Path(path.cgPath) 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/Views/AnimatableLabels.swift: -------------------------------------------------------------------------------- 1 | // 2 | // From Mobile@Exxeta by Laura Siewert 3 | // https://medium.com/@mobile_44538 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct AnimatableLabels: View { 9 | let isAppeared: Bool 10 | let text: String 11 | 12 | var body: some View { 13 | VStack { 14 | Text(text) 15 | .animatableSystemFont(size: isAppeared ? 16 : 12) 16 | .foregroundColor(.white) 17 | .padding(.horizontal, 8) 18 | .padding(.vertical, 2) 19 | .background(Color.black) 20 | .clipShape(RoundedRectangle(cornerRadius: isAppeared ? 12 : 8)) 21 | .fixedSize() 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/Views/AnimatableTitle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // From Mobile@Exxeta by Laura Siewert 3 | // https://medium.com/@mobile_44538 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct AnimatableTitle: View { 9 | let isAppeared: Bool 10 | 11 | var body: some View { 12 | Text(Texts.title) 13 | .animatableSystemFont(size: isAppeared ? 32 : 16, weight: .bold) 14 | .fixedSize(horizontal: false, vertical: true) 15 | .frame(maxWidth: .infinity, alignment: .leading) 16 | .lineLimit(2, reservesSpace: true) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /HeroAnimation/HeroAnimation/Views/CloseButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // From Mobile@Exxeta by Laura Siewert 3 | // https://medium.com/@mobile_44538 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct CloseButton: View { 9 | @Binding var isShowingDetail: Bool 10 | 11 | var body: some View { 12 | Image(systemName: "xmark") 13 | .font(.system(size: Dimens.unit16)) 14 | .frame(width: Dimens.closeButtonSize, height: Dimens.closeButtonSize) 15 | .foregroundColor(.black) 16 | .background(.white) 17 | .clipShape(Circle()) 18 | .onTapGesture { 19 | withAnimation(.hero) { 20 | isShowingDetail = false 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /HeroAnimation/README.md: -------------------------------------------------------------------------------- 1 | # Hero animations in SwiftUI with the matchedGeometryEffect modifier 2 | 3 | ## Description 4 | 5 | This project shows an example of how to use the matchedGeometryEffect in SwiftUI to create a hero animation. 6 | 7 | To use the matchedGeometryEffect we first need to define a namespace. This namespace is defined in the file `ContentView`. 8 | ```swift 9 | @Namespace var animation 10 | ```` 11 | Next we need two views which will be added or removed by a state variable with an animation. This can also be found in the `ContentView`. 12 | 13 | ```swift 14 | ZStack { 15 | if isShowingDetail { 16 | CardDetail() 17 | } else { 18 | Card() 19 | } 20 | } 21 | .onTapGesture { 22 | withAnimation(.hero) { 23 | isShowingDetail.toggle() 24 | } 25 | } 26 | ``` 27 | 28 | Last we need to define which elements should be matched by the matchedGeometryEffect. For example we need to attach the modifier for the image in the `Card` and in the `CardDetail` with the same id and SwiftUI will match those views during the transition. 29 | 30 | ```swift 31 | Image("header") 32 | .matchedGeometryEffect(id: AnimationId.imageId, in: animation) 33 | ``` 34 | 35 | ## How to use the project 36 | 37 | The project was tested for iPhone 14 for the iOS Version 16. You need at least the Xcode version 14 to use the project. 38 | 39 | ## Structure of the project 40 | 41 | ### `ContentView` 42 | 43 | Shows either the `Card` or the `CardDetail` controlled by the state variable `isShowingDetail`. 44 | 45 | ### `Card` 46 | 47 | This view presents the small card. 48 | 49 | ### `CardDetail` 50 | 51 | This view presents the detail of the card. It is a expanded view of the small card. 52 | 53 | ### `AnimatableTitle` 54 | 55 | This view holds a title which can be animated by the modfier `animatableSystemFont`. 56 | 57 | 58 | ### `AnimatableLabel` 59 | 60 | This view holds a label which can be animated by the modfier `animatableSystemFont`. 61 | 62 | ### `CloseButton` 63 | 64 | This view presents a circular button which closes the CardDetail. 65 | 66 | ### `Modifiers` 67 | 68 | The project uses two custom modifiers. The first one is `cornerRadius` with which you can define the corner radius for specific corners. The second one is the `animatableSystemFont` modifier with which a font size can be animated. 69 | 70 | ### `Animation+Extension` 71 | 72 | This file defines used the hero animation. 73 | 74 | ### `Constants` 75 | 76 | The project uses two constants files. The first one is the `Dimens` file, which specifies the used sizes in the project. The second is the `AnimationId`, which defines the used ids for the matchedGeometryEffect. 77 | 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Exxeta AG 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LottieAnimationExample/LottieAnimationExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D3416CE52B3575F600CDB126 /* LottieAnimationExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3416CE42B3575F600CDB126 /* LottieAnimationExampleApp.swift */; }; 11 | D3416CE72B3575F600CDB126 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3416CE62B3575F600CDB126 /* ContentView.swift */; }; 12 | D3416CE92B3575F800CDB126 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D3416CE82B3575F800CDB126 /* Assets.xcassets */; }; 13 | D3416CED2B3575F800CDB126 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D3416CEC2B3575F800CDB126 /* Preview Assets.xcassets */; }; 14 | D3416CF52B357C2700CDB126 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = D3416CF42B357C2700CDB126 /* Lottie */; }; 15 | D3416D052B35974500CDB126 /* heartAnimation.json in Resources */ = {isa = PBXBuildFile; fileRef = D3416D042B35974500CDB126 /* heartAnimation.json */; }; 16 | D3E0CD702B5A710700C116BD /* Dimens.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3E0CD6F2B5A710700C116BD /* Dimens.swift */; }; 17 | D3E0CD722B5A71CE00C116BD /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3E0CD712B5A71CE00C116BD /* Strings.swift */; }; 18 | D3E0CD742B5A720A00C116BD /* ImageNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3E0CD732B5A720A00C116BD /* ImageNames.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | D3416CE12B3575F600CDB126 /* LottieAnimationExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LottieAnimationExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | D3416CE42B3575F600CDB126 /* LottieAnimationExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieAnimationExampleApp.swift; sourceTree = ""; }; 24 | D3416CE62B3575F600CDB126 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 25 | D3416CE82B3575F800CDB126 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | D3416CEA2B3575F800CDB126 /* LottieAnimationExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LottieAnimationExample.entitlements; sourceTree = ""; }; 27 | D3416CEC2B3575F800CDB126 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 28 | D3416D042B35974500CDB126 /* heartAnimation.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = heartAnimation.json; sourceTree = ""; }; 29 | D3E0CD6F2B5A710700C116BD /* Dimens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dimens.swift; sourceTree = ""; }; 30 | D3E0CD712B5A71CE00C116BD /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; 31 | D3E0CD732B5A720A00C116BD /* ImageNames.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageNames.swift; sourceTree = ""; }; 32 | /* End PBXFileReference section */ 33 | 34 | /* Begin PBXFrameworksBuildPhase section */ 35 | D3416CDE2B3575F600CDB126 /* Frameworks */ = { 36 | isa = PBXFrameworksBuildPhase; 37 | buildActionMask = 2147483647; 38 | files = ( 39 | D3416CF52B357C2700CDB126 /* Lottie in Frameworks */, 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | D3416CD82B3575F600CDB126 = { 47 | isa = PBXGroup; 48 | children = ( 49 | D3416CE32B3575F600CDB126 /* LottieAnimationExample */, 50 | D3416CE22B3575F600CDB126 /* Products */, 51 | ); 52 | sourceTree = ""; 53 | }; 54 | D3416CE22B3575F600CDB126 /* Products */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | D3416CE12B3575F600CDB126 /* LottieAnimationExample.app */, 58 | ); 59 | name = Products; 60 | sourceTree = ""; 61 | }; 62 | D3416CE32B3575F600CDB126 /* LottieAnimationExample */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | D3416CF72B357CAB00CDB126 /* Ressources */, 66 | D3416CF62B357CA000CDB126 /* Sources */, 67 | D3416CEA2B3575F800CDB126 /* LottieAnimationExample.entitlements */, 68 | D3416CEB2B3575F800CDB126 /* Preview Content */, 69 | ); 70 | path = LottieAnimationExample; 71 | sourceTree = ""; 72 | }; 73 | D3416CEB2B3575F800CDB126 /* Preview Content */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | D3416CEC2B3575F800CDB126 /* Preview Assets.xcassets */, 77 | ); 78 | path = "Preview Content"; 79 | sourceTree = ""; 80 | }; 81 | D3416CF62B357CA000CDB126 /* Sources */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | D3E0CD6E2B5A70FF00C116BD /* Constants */, 85 | D3416CE62B3575F600CDB126 /* ContentView.swift */, 86 | D3416CE42B3575F600CDB126 /* LottieAnimationExampleApp.swift */, 87 | ); 88 | path = Sources; 89 | sourceTree = ""; 90 | }; 91 | D3416CF72B357CAB00CDB126 /* Ressources */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | D3416CE82B3575F800CDB126 /* Assets.xcassets */, 95 | D3416D042B35974500CDB126 /* heartAnimation.json */, 96 | ); 97 | path = Ressources; 98 | sourceTree = ""; 99 | }; 100 | D3E0CD6E2B5A70FF00C116BD /* Constants */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | D3E0CD6F2B5A710700C116BD /* Dimens.swift */, 104 | D3E0CD712B5A71CE00C116BD /* Strings.swift */, 105 | D3E0CD732B5A720A00C116BD /* ImageNames.swift */, 106 | ); 107 | path = Constants; 108 | sourceTree = ""; 109 | }; 110 | /* End PBXGroup section */ 111 | 112 | /* Begin PBXNativeTarget section */ 113 | D3416CE02B3575F600CDB126 /* LottieAnimationExample */ = { 114 | isa = PBXNativeTarget; 115 | buildConfigurationList = D3416CF02B3575F800CDB126 /* Build configuration list for PBXNativeTarget "LottieAnimationExample" */; 116 | buildPhases = ( 117 | D3416CDD2B3575F600CDB126 /* Sources */, 118 | D3416CDE2B3575F600CDB126 /* Frameworks */, 119 | D3416CDF2B3575F600CDB126 /* Resources */, 120 | ); 121 | buildRules = ( 122 | ); 123 | dependencies = ( 124 | ); 125 | name = LottieAnimationExample; 126 | packageProductDependencies = ( 127 | D3416CF42B357C2700CDB126 /* Lottie */, 128 | ); 129 | productName = LottieAnimationExample; 130 | productReference = D3416CE12B3575F600CDB126 /* LottieAnimationExample.app */; 131 | productType = "com.apple.product-type.application"; 132 | }; 133 | /* End PBXNativeTarget section */ 134 | 135 | /* Begin PBXProject section */ 136 | D3416CD92B3575F600CDB126 /* Project object */ = { 137 | isa = PBXProject; 138 | attributes = { 139 | BuildIndependentTargetsInParallel = 1; 140 | LastSwiftUpdateCheck = 1500; 141 | LastUpgradeCheck = 1500; 142 | TargetAttributes = { 143 | D3416CE02B3575F600CDB126 = { 144 | CreatedOnToolsVersion = 15.0.1; 145 | }; 146 | }; 147 | }; 148 | buildConfigurationList = D3416CDC2B3575F600CDB126 /* Build configuration list for PBXProject "LottieAnimationExample" */; 149 | compatibilityVersion = "Xcode 14.0"; 150 | developmentRegion = en; 151 | hasScannedForEncodings = 0; 152 | knownRegions = ( 153 | en, 154 | Base, 155 | ); 156 | mainGroup = D3416CD82B3575F600CDB126; 157 | packageReferences = ( 158 | D3416CF32B357C2700CDB126 /* XCRemoteSwiftPackageReference "lottie-ios" */, 159 | ); 160 | productRefGroup = D3416CE22B3575F600CDB126 /* Products */; 161 | projectDirPath = ""; 162 | projectRoot = ""; 163 | targets = ( 164 | D3416CE02B3575F600CDB126 /* LottieAnimationExample */, 165 | ); 166 | }; 167 | /* End PBXProject section */ 168 | 169 | /* Begin PBXResourcesBuildPhase section */ 170 | D3416CDF2B3575F600CDB126 /* Resources */ = { 171 | isa = PBXResourcesBuildPhase; 172 | buildActionMask = 2147483647; 173 | files = ( 174 | D3416D052B35974500CDB126 /* heartAnimation.json in Resources */, 175 | D3416CED2B3575F800CDB126 /* Preview Assets.xcassets in Resources */, 176 | D3416CE92B3575F800CDB126 /* Assets.xcassets in Resources */, 177 | ); 178 | runOnlyForDeploymentPostprocessing = 0; 179 | }; 180 | /* End PBXResourcesBuildPhase section */ 181 | 182 | /* Begin PBXSourcesBuildPhase section */ 183 | D3416CDD2B3575F600CDB126 /* Sources */ = { 184 | isa = PBXSourcesBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | D3E0CD702B5A710700C116BD /* Dimens.swift in Sources */, 188 | D3416CE72B3575F600CDB126 /* ContentView.swift in Sources */, 189 | D3E0CD722B5A71CE00C116BD /* Strings.swift in Sources */, 190 | D3416CE52B3575F600CDB126 /* LottieAnimationExampleApp.swift in Sources */, 191 | D3E0CD742B5A720A00C116BD /* ImageNames.swift in Sources */, 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | }; 195 | /* End PBXSourcesBuildPhase section */ 196 | 197 | /* Begin XCBuildConfiguration section */ 198 | D3416CEE2B3575F800CDB126 /* Debug */ = { 199 | isa = XCBuildConfiguration; 200 | buildSettings = { 201 | ALWAYS_SEARCH_USER_PATHS = NO; 202 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 203 | CLANG_ANALYZER_NONNULL = YES; 204 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 205 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 206 | CLANG_ENABLE_MODULES = YES; 207 | CLANG_ENABLE_OBJC_ARC = YES; 208 | CLANG_ENABLE_OBJC_WEAK = YES; 209 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 210 | CLANG_WARN_BOOL_CONVERSION = YES; 211 | CLANG_WARN_COMMA = YES; 212 | CLANG_WARN_CONSTANT_CONVERSION = YES; 213 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 214 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 215 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 216 | CLANG_WARN_EMPTY_BODY = YES; 217 | CLANG_WARN_ENUM_CONVERSION = YES; 218 | CLANG_WARN_INFINITE_RECURSION = YES; 219 | CLANG_WARN_INT_CONVERSION = YES; 220 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 221 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 222 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 223 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 224 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 225 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 226 | CLANG_WARN_STRICT_PROTOTYPES = YES; 227 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 228 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 229 | CLANG_WARN_UNREACHABLE_CODE = YES; 230 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 231 | COPY_PHASE_STRIP = NO; 232 | DEBUG_INFORMATION_FORMAT = dwarf; 233 | ENABLE_STRICT_OBJC_MSGSEND = YES; 234 | ENABLE_TESTABILITY = YES; 235 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 236 | GCC_C_LANGUAGE_STANDARD = gnu17; 237 | GCC_DYNAMIC_NO_PIC = NO; 238 | GCC_NO_COMMON_BLOCKS = YES; 239 | GCC_OPTIMIZATION_LEVEL = 0; 240 | GCC_PREPROCESSOR_DEFINITIONS = ( 241 | "DEBUG=1", 242 | "$(inherited)", 243 | ); 244 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 245 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 246 | GCC_WARN_UNDECLARED_SELECTOR = YES; 247 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 248 | GCC_WARN_UNUSED_FUNCTION = YES; 249 | GCC_WARN_UNUSED_VARIABLE = YES; 250 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 251 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 252 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 253 | MTL_FAST_MATH = YES; 254 | ONLY_ACTIVE_ARCH = YES; 255 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 256 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 257 | }; 258 | name = Debug; 259 | }; 260 | D3416CEF2B3575F800CDB126 /* Release */ = { 261 | isa = XCBuildConfiguration; 262 | buildSettings = { 263 | ALWAYS_SEARCH_USER_PATHS = NO; 264 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 265 | CLANG_ANALYZER_NONNULL = YES; 266 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 267 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 268 | CLANG_ENABLE_MODULES = YES; 269 | CLANG_ENABLE_OBJC_ARC = YES; 270 | CLANG_ENABLE_OBJC_WEAK = YES; 271 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 272 | CLANG_WARN_BOOL_CONVERSION = YES; 273 | CLANG_WARN_COMMA = YES; 274 | CLANG_WARN_CONSTANT_CONVERSION = YES; 275 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 276 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 277 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 278 | CLANG_WARN_EMPTY_BODY = YES; 279 | CLANG_WARN_ENUM_CONVERSION = YES; 280 | CLANG_WARN_INFINITE_RECURSION = YES; 281 | CLANG_WARN_INT_CONVERSION = YES; 282 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 283 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 284 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 285 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 286 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 287 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 288 | CLANG_WARN_STRICT_PROTOTYPES = YES; 289 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 290 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 291 | CLANG_WARN_UNREACHABLE_CODE = YES; 292 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 293 | COPY_PHASE_STRIP = NO; 294 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 295 | ENABLE_NS_ASSERTIONS = NO; 296 | ENABLE_STRICT_OBJC_MSGSEND = YES; 297 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 298 | GCC_C_LANGUAGE_STANDARD = gnu17; 299 | GCC_NO_COMMON_BLOCKS = YES; 300 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 301 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 302 | GCC_WARN_UNDECLARED_SELECTOR = YES; 303 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 304 | GCC_WARN_UNUSED_FUNCTION = YES; 305 | GCC_WARN_UNUSED_VARIABLE = YES; 306 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 307 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 308 | MTL_ENABLE_DEBUG_INFO = NO; 309 | MTL_FAST_MATH = YES; 310 | SWIFT_COMPILATION_MODE = wholemodule; 311 | }; 312 | name = Release; 313 | }; 314 | D3416CF12B3575F800CDB126 /* Debug */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 318 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 319 | CODE_SIGN_ENTITLEMENTS = LottieAnimationExample/LottieAnimationExample.entitlements; 320 | CODE_SIGN_STYLE = Automatic; 321 | CURRENT_PROJECT_VERSION = 1; 322 | DEVELOPMENT_ASSET_PATHS = "\"LottieAnimationExample/Preview Content\""; 323 | DEVELOPMENT_TEAM = 3U52S2C5H4; 324 | ENABLE_HARDENED_RUNTIME = YES; 325 | ENABLE_PREVIEWS = YES; 326 | GENERATE_INFOPLIST_FILE = YES; 327 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 328 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 329 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 330 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 331 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 332 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 333 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 334 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 335 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 336 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 337 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 338 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 339 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 340 | MACOSX_DEPLOYMENT_TARGET = 13.6; 341 | MARKETING_VERSION = 1.0; 342 | PRODUCT_BUNDLE_IDENTIFIER = exxeta.com.LottieAnimationExample; 343 | PRODUCT_NAME = "$(TARGET_NAME)"; 344 | SDKROOT = auto; 345 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 346 | SWIFT_EMIT_LOC_STRINGS = YES; 347 | SWIFT_VERSION = 5.0; 348 | TARGETED_DEVICE_FAMILY = "1,2"; 349 | }; 350 | name = Debug; 351 | }; 352 | D3416CF22B3575F800CDB126 /* Release */ = { 353 | isa = XCBuildConfiguration; 354 | buildSettings = { 355 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 356 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 357 | CODE_SIGN_ENTITLEMENTS = LottieAnimationExample/LottieAnimationExample.entitlements; 358 | CODE_SIGN_STYLE = Automatic; 359 | CURRENT_PROJECT_VERSION = 1; 360 | DEVELOPMENT_ASSET_PATHS = "\"LottieAnimationExample/Preview Content\""; 361 | DEVELOPMENT_TEAM = 3U52S2C5H4; 362 | ENABLE_HARDENED_RUNTIME = YES; 363 | ENABLE_PREVIEWS = YES; 364 | GENERATE_INFOPLIST_FILE = YES; 365 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 366 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 367 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 368 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 369 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 370 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 371 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 372 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 373 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 374 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 375 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 376 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 377 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 378 | MACOSX_DEPLOYMENT_TARGET = 13.6; 379 | MARKETING_VERSION = 1.0; 380 | PRODUCT_BUNDLE_IDENTIFIER = exxeta.com.LottieAnimationExample; 381 | PRODUCT_NAME = "$(TARGET_NAME)"; 382 | SDKROOT = auto; 383 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 384 | SWIFT_EMIT_LOC_STRINGS = YES; 385 | SWIFT_VERSION = 5.0; 386 | TARGETED_DEVICE_FAMILY = "1,2"; 387 | }; 388 | name = Release; 389 | }; 390 | /* End XCBuildConfiguration section */ 391 | 392 | /* Begin XCConfigurationList section */ 393 | D3416CDC2B3575F600CDB126 /* Build configuration list for PBXProject "LottieAnimationExample" */ = { 394 | isa = XCConfigurationList; 395 | buildConfigurations = ( 396 | D3416CEE2B3575F800CDB126 /* Debug */, 397 | D3416CEF2B3575F800CDB126 /* Release */, 398 | ); 399 | defaultConfigurationIsVisible = 0; 400 | defaultConfigurationName = Release; 401 | }; 402 | D3416CF02B3575F800CDB126 /* Build configuration list for PBXNativeTarget "LottieAnimationExample" */ = { 403 | isa = XCConfigurationList; 404 | buildConfigurations = ( 405 | D3416CF12B3575F800CDB126 /* Debug */, 406 | D3416CF22B3575F800CDB126 /* Release */, 407 | ); 408 | defaultConfigurationIsVisible = 0; 409 | defaultConfigurationName = Release; 410 | }; 411 | /* End XCConfigurationList section */ 412 | 413 | /* Begin XCRemoteSwiftPackageReference section */ 414 | D3416CF32B357C2700CDB126 /* XCRemoteSwiftPackageReference "lottie-ios" */ = { 415 | isa = XCRemoteSwiftPackageReference; 416 | repositoryURL = "https://github.com/airbnb/lottie-ios.git"; 417 | requirement = { 418 | kind = upToNextMajorVersion; 419 | minimumVersion = 4.3.4; 420 | }; 421 | }; 422 | /* End XCRemoteSwiftPackageReference section */ 423 | 424 | /* Begin XCSwiftPackageProductDependency section */ 425 | D3416CF42B357C2700CDB126 /* Lottie */ = { 426 | isa = XCSwiftPackageProductDependency; 427 | package = D3416CF32B357C2700CDB126 /* XCRemoteSwiftPackageReference "lottie-ios" */; 428 | productName = Lottie; 429 | }; 430 | /* End XCSwiftPackageProductDependency section */ 431 | }; 432 | rootObject = D3416CD92B3575F600CDB126 /* Project object */; 433 | } 434 | -------------------------------------------------------------------------------- /LottieAnimationExample/LottieAnimationExample/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /LottieAnimationExample/LottieAnimationExample/Ressources/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /LottieAnimationExample/LottieAnimationExample/Ressources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "1x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "2x", 16 | "size" : "16x16" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "1x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "2x", 26 | "size" : "32x32" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "2x", 36 | "size" : "128x128" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "1x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "2x", 46 | "size" : "256x256" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "1x", 51 | "size" : "512x512" 52 | }, 53 | { 54 | "idiom" : "mac", 55 | "scale" : "2x", 56 | "size" : "512x512" 57 | } 58 | ], 59 | "info" : { 60 | "author" : "xcode", 61 | "version" : 1 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /LottieAnimationExample/LottieAnimationExample/Ressources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /LottieAnimationExample/LottieAnimationExample/Ressources/Assets.xcassets/image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "pexels-iván-cisneros-14086443@1x.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "pexels-iván-cisneros-14086443@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "pexels-iván-cisneros-14086443@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LottieAnimationExample/LottieAnimationExample/Ressources/Assets.xcassets/image.imageset/pexels-iván-cisneros-14086443@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EXXETA/swiftui-examples/6b6913a1f4c134b92299e1e5f34bd283d3ac63cb/LottieAnimationExample/LottieAnimationExample/Ressources/Assets.xcassets/image.imageset/pexels-iván-cisneros-14086443@1x.png -------------------------------------------------------------------------------- /LottieAnimationExample/LottieAnimationExample/Ressources/Assets.xcassets/image.imageset/pexels-iván-cisneros-14086443@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EXXETA/swiftui-examples/6b6913a1f4c134b92299e1e5f34bd283d3ac63cb/LottieAnimationExample/LottieAnimationExample/Ressources/Assets.xcassets/image.imageset/pexels-iván-cisneros-14086443@2x.png -------------------------------------------------------------------------------- /LottieAnimationExample/LottieAnimationExample/Ressources/Assets.xcassets/image.imageset/pexels-iván-cisneros-14086443@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EXXETA/swiftui-examples/6b6913a1f4c134b92299e1e5f34bd283d3ac63cb/LottieAnimationExample/LottieAnimationExample/Ressources/Assets.xcassets/image.imageset/pexels-iván-cisneros-14086443@3x.png -------------------------------------------------------------------------------- /LottieAnimationExample/LottieAnimationExample/Ressources/heartAnimation.json: -------------------------------------------------------------------------------- 1 | {"nm":"heart","ddd":0,"h":100,"w":100,"meta":{"g":"@lottiefiles/toolkit-js 0.33.2"},"layers":[{"ty":4,"nm":"Layer 2 Outlines","sr":1,"st":0,"op":900.000036657751,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[20.392,17.022,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.486,"y":0},"i":{"x":0.667,"y":1},"s":[0,0,100],"t":0},{"o":{"x":0.439,"y":0},"i":{"x":0.576,"y":1},"s":[119,119,100],"t":5},{"o":{"x":0.152,"y":0},"i":{"x":0.288,"y":1},"s":[82,82,100],"t":10},{"s":[100,100,100],"t":15.0000006109625}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[50.085,49.972,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-2.008,2.091],[-2.887,0.06],[0,0],[-2.098,-2.123],[0,0],[0,0],[-3.003,0.063],[0,0],[-2.092,-2.008],[-0.059,-2.889],[2.542,-1.428],[0.178,8.815]],"o":[[2.007,-2.09],[0,0],[3.002,-0.062],[0,0],[0,0],[2.009,-2.205],[0,0],[2.89,-0.058],[2.09,2.008],[0.229,11.336],[-7.185,-3.885],[-0.059,-2.89]],"v":[[-17.06,-13.009],[-9.471,-16.342],[-9.465,-16.342],[-1.555,-13.145],[-0.209,-11.783],[1.081,-13.198],[8.851,-16.714],[8.855,-16.714],[16.579,-13.69],[19.913,-6.096],[0.38,16.772],[-20.083,-5.285]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.9647,0.3098,0.4],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[20.392,17.022],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1},{"ty":0,"nm":"Shape Layer 2 Comp 1","sr":1,"st":3.00000012219251,"op":903.000036779944,"ip":3.00000012219251,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[50,50,0],"ix":1},"s":{"a":0,"k":[86,86,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[50,50,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":100,"h":100,"refId":"comp_4","ind":2}],"v":"5.1.7","fr":12,"op":24.00000097754,"ip":0,"assets":[{"nm":"","id":"comp_4","layers":[{"ty":0,"nm":"Shape Layer 2 Comp 2","sr":1,"st":0,"op":900.000036657751,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[50,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[50,50,0],"ix":2},"r":{"a":0,"k":180,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":100,"h":100,"refId":"comp_5","ind":1},{"ty":0,"nm":"Shape Layer 2 Comp 2","sr":1,"st":0,"op":900.000036657751,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[50,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[50,50,0],"ix":2},"r":{"a":0,"k":130,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":100,"h":100,"refId":"comp_5","ind":2},{"ty":0,"nm":"Shape Layer 2 Comp 2","sr":1,"st":0,"op":900.000036657751,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[50,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[50,50,0],"ix":2},"r":{"a":0,"k":90,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":100,"h":100,"refId":"comp_5","ind":3},{"ty":0,"nm":"Shape Layer 2 Comp 2","sr":1,"st":0,"op":900.000036657751,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[50,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[50,50,0],"ix":2},"r":{"a":0,"k":45,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":100,"h":100,"refId":"comp_5","ind":4},{"ty":0,"nm":"Shape Layer 2 Comp 2","sr":1,"st":0,"op":900.000036657751,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[50,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[50,50,0],"ix":2},"r":{"a":0,"k":-135,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":100,"h":100,"refId":"comp_5","ind":5},{"ty":0,"nm":"Shape Layer 2 Comp 2","sr":1,"st":0,"op":900.000036657751,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[50,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[50,50,0],"ix":2},"r":{"a":0,"k":-90,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":100,"h":100,"refId":"comp_5","ind":6},{"ty":0,"nm":"Shape Layer 2 Comp 2","sr":1,"st":0,"op":900.000036657751,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[50,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[50,50,0],"ix":2},"r":{"a":0,"k":-45,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":100,"h":100,"refId":"comp_5","ind":7},{"ty":0,"nm":"Shape Layer 2 Comp 2","sr":1,"st":0,"op":900.000036657751,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[50,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[50,50,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":100,"h":100,"refId":"comp_5","ind":8}]},{"nm":"","id":"comp_5","layers":[{"ty":4,"nm":"Shape Layer 4","sr":1,"st":0,"op":900.000036657751,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-0.122,-27.75,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.333,"y":-44.172},"i":{"x":0.667,"y":15.897},"s":[78.68,78.68,100],"t":3},{"o":{"x":0.333,"y":-0.562},"i":{"x":0.667,"y":1},"s":[78,78,100],"t":9},{"s":[54,54,100],"t":17.0000006924242}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.333,"y":0.406},"i":{"x":0.667,"y":0.567},"s":[39.878,39.582,0],"t":3,"ti":[0,2.48720121383667,0],"to":[0,-0.83333331346512,0]},{"o":{"x":0.333,"y":0.5},"i":{"x":0.667,"y":1},"s":[39.878,22.253,0],"t":9,"ti":[0,3.3337619304657,0],"to":[0,-8.33440494537354,0]},{"s":[39.878,2.25,0],"t":17.0000006924242}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.333,"y":0.281},"i":{"x":0.667,"y":1},"s":[0],"t":3},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":9},{"s":[0],"t":17.0000006924242}],"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[6.66,6.66],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.9098,0.1843,0.5686],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,1.076],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-0.17,-26.594],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1},{"ty":4,"nm":"Shape Layer 3","sr":1,"st":0,"op":900.000036657751,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-0.122,-27.75,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.333,"y":-44.172},"i":{"x":0.667,"y":15.897},"s":[78.68,78.68,100],"t":0},{"o":{"x":0.333,"y":-0.773},"i":{"x":0.667,"y":1},"s":[78,78,100],"t":6},{"s":[54,54,100],"t":17.0000006924242}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.333,"y":0.406},"i":{"x":0.667,"y":0.567},"s":[49.878,39.582,0],"t":0,"ti":[0,2.48720121383667,0],"to":[0,-2.35414934158325,0]},{"o":{"x":0.333,"y":0.687},"i":{"x":0.667,"y":1},"s":[49.878,22.253,0],"t":6,"ti":[0,3.3337619304657,0],"to":[0,-8.33440494537354,0]},{"s":[49.878,2.25,0],"t":17.0000006924242}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.333,"y":0.281},"i":{"x":0.667,"y":1},"s":[0],"t":0},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":6},{"s":[0],"t":17.0000006924242}],"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[6.66,6.66],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.9647,0.3098,0.4],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,1.076],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-0.17,-26.594],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":2},{"ty":4,"nm":"Shape Layer 2","sr":1,"st":5.00000020365417,"op":905.000036861406,"ip":5.00000020365417,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-0.122,-27.75,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.333,"y":-44.172},"i":{"x":0.667,"y":15.897},"s":[78.68,78.68,100],"t":5},{"o":{"x":0.333,"y":-0.562},"i":{"x":0.667,"y":1},"s":[78,78,100],"t":11},{"s":[54,54,100],"t":19.0000007738859}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.333,"y":0.406},"i":{"x":0.667,"y":0.567},"s":[59.878,39.582,0],"t":5,"ti":[0,2.48720121383667,0],"to":[0,-2.35414934158325,0]},{"o":{"x":0.333,"y":0.5},"i":{"x":0.667,"y":1},"s":[59.878,22.253,0],"t":11,"ti":[0,3.3337619304657,0],"to":[0,-8.33440494537354,0]},{"s":[59.878,2.25,0],"t":19.0000007738859}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.333,"y":0.281},"i":{"x":0.667,"y":1},"s":[0],"t":5},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":11},{"s":[0],"t":19.0000007738859}],"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[6.66,6.66],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8784,0.6235,0.6824],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,1.076],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-0.17,-26.594],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":3}]}]} 2 | -------------------------------------------------------------------------------- /LottieAnimationExample/LottieAnimationExample/Sources/Constants/Dimens.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dimens.swift 3 | // LottieAnimationExample 4 | // 5 | // Created by Siewert, Laura on 19.01.24. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | enum Dimens { 12 | // MARK: - Opacity 13 | static let opacity1: Double = 1 14 | static let opacity05: Double = 0.5 15 | 16 | // MARK: - Padding 17 | static let padding16: CGFloat = 16 18 | static let padding100: CGFloat = 100 19 | 20 | // MARK: - Corner Radius 21 | static let cornerRadius: CGFloat = 16 22 | 23 | // MARK: - Sizes 24 | static let imageHeight: CGFloat = UIScreen.main.bounds.width 25 | static let animationHeight: CGFloat = UIScreen.main.bounds.width - Dimens.padding100 26 | 27 | // MARK: - Offsets 28 | static let offSet0: CGFloat = 0 29 | static let offSetFull: CGFloat = -UIScreen.main.bounds.height 30 | } 31 | -------------------------------------------------------------------------------- /LottieAnimationExample/LottieAnimationExample/Sources/Constants/ImageNames.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageNames.swift 3 | // LottieAnimationExample 4 | // 5 | // Created by Siewert, Laura on 19.01.24. 6 | // 7 | 8 | import Foundation 9 | 10 | enum ImageNames { 11 | static let displayedImage = "image" 12 | static let heartAnimation = "heartAnimation" 13 | static let likeImage = "heart" 14 | } 15 | -------------------------------------------------------------------------------- /LottieAnimationExample/LottieAnimationExample/Sources/Constants/Strings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Strings.swift 3 | // LottieAnimationExample 4 | // 5 | // Created by Siewert, Laura on 19.01.24. 6 | // 7 | 8 | import Foundation 9 | 10 | enum Strings { 11 | static let imageButtonTitle = "Like Image" 12 | static let imageDescription = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." 13 | } 14 | -------------------------------------------------------------------------------- /LottieAnimationExample/LottieAnimationExample/Sources/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // LottieAnimationExample 4 | // 5 | // Created by Siewert, Laura on 22.12.23. 6 | // 7 | 8 | import SwiftUI 9 | import Lottie 10 | 11 | struct ContentView: View { 12 | /// A state variable which holds the playback mode 13 | @State private var playbackMode: LottiePlaybackMode = LottiePlaybackMode.paused 14 | 15 | /// Body 16 | var body: some View { 17 | VStack(alignment: .leading) { 18 | ZStack { 19 | /// Image 20 | Image(ImageNames.displayedImage) 21 | .resizable() 22 | .frame(height: Dimens.imageHeight) 23 | .scaledToFit() 24 | .opacity(playbackMode != .paused ? Dimens.opacity05 : Dimens.opacity1) 25 | .animation(.linear, value: playbackMode) 26 | /// LottieView 27 | LottieView(animation: .named(ImageNames.heartAnimation)) 28 | .playbackMode(playbackMode) 29 | .animationDidFinish { completed in 30 | /// Change the playback mode to paused, when animation is finished 31 | playbackMode = LottiePlaybackMode.paused 32 | } 33 | .resizable() 34 | /// Define the offset according to the playback mode and pair it with an animation modifier to animate the offset 35 | .offset(y: playbackMode != .paused ? Dimens.offSet0 : Dimens.offSetFull) 36 | .animation(.spring, value: playbackMode) 37 | .frame(height: Dimens.animationHeight) 38 | } 39 | Spacer() 40 | Button(action: { 41 | /// Change the playback mode to playing when the user pressed the button 42 | playbackMode = .playing(.fromProgress(0, toProgress: 1, loopMode: .playOnce)) 43 | }, label: { 44 | HStack { 45 | Text(Strings.imageButtonTitle) 46 | .foregroundStyle(.white) 47 | Image(systemName: ImageNames.likeImage) 48 | .foregroundStyle(.white) 49 | } 50 | .padding(Dimens.padding16) 51 | .background( 52 | RoundedRectangle(cornerRadius: Dimens.cornerRadius) 53 | .fill(Color.black) 54 | ) 55 | }) 56 | Text(Strings.imageDescription) 57 | .foregroundStyle(.white) 58 | .padding(Dimens.padding16) 59 | Spacer() 60 | } 61 | .background(.black) 62 | .edgesIgnoringSafeArea(.all) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /LottieAnimationExample/LottieAnimationExample/Sources/LottieAnimationExampleApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LottieAnimationExampleApp.swift 3 | // LottieAnimationExample 4 | // 5 | // Created by Siewert, Laura on 22.12.23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct LottieAnimationExampleApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /LottieAnimationExample/README.md: -------------------------------------------------------------------------------- 1 | # A guide for seamlessly integrating animations in SwiftUI with LottieView 2 | 3 | ## Description 4 | 5 | This project shows an example of how to use the LottieView in combination with SwiftUI. 6 | 7 | ## Integrate Lottie to your project 8 | 9 | 10 | To incorporate the Lottie Framework in Xcode, you can integrate it by using [Swift Package Manager or Cocoa Pods](https://github.com/airbnb/lottie-ios). Once integrated, simply import Lottie to start using it. 11 | 12 | For an appealing animation, LottieFiles is an excellent resource. You can find and download animations, including some that are [license-free](https://lottiefiles.com/page/license). In our example, we'll use the heart animation by [Biswajit Rout](https://lottiefiles.com/animations/heart-E3NfCZitfh). Import the downloaded JSON file into your Xcode project to kickstart the implementation. 13 | 14 | ## Quick guide to Lottie in SwiftUI 15 | 16 | To load a Lottie animation in SwiftUI, we can use the LottieView. And add the modifier .playing() to start the animation. It’s just that easy. Wwhen you start your app you can see the animation playing. 17 | 18 | ```swift 19 | struct ContentView: View { 20 | var body: some View { 21 | VStack { 22 | LottieView(animation: .named("heartAnimation")) 23 | .playing() 24 | } 25 | } 26 | } 27 | ``` 28 | 29 | But there is more you can do with your animation. You can adjust it to your needs. The playing() modifier can be configured with a LottiePlaybackMode. Here you can specify, from which, frame or progress you want to play your animation or in which loopMode. 30 | 31 | ```swift 32 | LottieView(animation: .named("heartAnimation")) 33 | .playing(.fromFrame(1, toFrame: 5, loopMode: .playOnce)) 34 | ``` 35 | 36 | You can also directly use the playbackMode Modifier like this: 37 | 38 | ```swift 39 | LottieView(animation: .named("heartAnimation")) 40 | .playbackMode(.playing(.fromFrame(1, toFrame: 5, loopMode: .playOnce))) 41 | ``` 42 | 43 | With this modifier you can also pause your animation by setting it to paused. 44 | 45 | ```swift 46 | LottieView(animation: .named("heartAnimation")) 47 | .playbackMode(.paused) 48 | ``` 49 | You can also respond to the completion of the animation by using the animationDidFinish modifier to trigger specific actions. 50 | 51 | ```swift 52 | LottieView(animation: .named("heartAnimation")) 53 | .playing(loopMode: .loop) 54 | .animationDidFinish { completed in 55 | /// Do sth. 56 | } 57 | ``` 58 | ## How to use the project 59 | 60 | The project was tested for iPhone 14 for the iOS Version 17. You need at least the Xcode version 15 to use the project. 61 | 62 | ## Structure of the project 63 | 64 | ### `ContentView` 65 | 66 | Shows the main view an image, button and text. It will also display the lottie animation. 67 | 68 | ### `Constants` 69 | 70 | Contains the files `Dimens` (defines sizes), `Strings` (defines strings) and `ImageNames` (defines image names). 71 | 72 | ### `Ressources` 73 | 74 | Holds the needed assets. The JSON File `heartAnimation.json` and the displayed image in the `Assets`. 75 | 76 | ## Example 77 | 78 | We can create our desired animation. First, we create the needed UI. This contains a ZStack with the image and the LottieView and a Button for liking the image, with some description underneath. 79 | 80 | Now we want to play the animation when the button is pressed. For this, we need a state variable that holds the LottiePlaybackMode. In the beginning, this mode is set to paused. When we click on the button, we set it to playing and when the animation is finished, we set it back to paused again. 81 | 82 | ```swift 83 | @State private var playbackMode: LottiePlaybackMode = LottiePlaybackMode.paused 84 | … 85 | 86 | LottieView(animation: .named("heartAnimation")) 87 | .playbackMode(playbackMode) 88 | .animationDidFinish { completed in 89 | playbackMode = LottiePlaybackMode.paused 90 | } 91 | … 92 | Button(action: { 93 | playbackMode = .playing(.fromProgress(0, toProgress: 1, loopMode: .playOnce)) 94 | }, label: { … }) 95 | … 96 | ``` 97 | 98 | The last step is to animate the LottieView up and down with the offset Modifier and change the opacity of the image. We can achieve this by reacting to the State of the playbackMode. 99 | 100 | ```swift 101 | Image("image") 102 | .opacity(playbackMode != .paused ? 0.5 : 1) 103 | .animation(.linear, value: playbackMode) 104 | … 105 | LottieView(animation: .named("heartAnimation")) 106 | .offset(y: playbackMode != .paused ? 0 : -UIScreen.main.bounds.height) 107 | .animation(.spring, value: playbackMode) 108 | … 109 | ``` 110 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "1x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "2x", 16 | "size" : "16x16" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "1x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "2x", 26 | "size" : "32x32" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "2x", 36 | "size" : "128x128" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "1x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "2x", 46 | "size" : "256x256" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "1x", 51 | "size" : "512x512" 52 | }, 53 | { 54 | "idiom" : "mac", 55 | "scale" : "2x", 56 | "size" : "512x512" 57 | } 58 | ], 59 | "info" : { 60 | "author" : "xcode", 61 | "version" : 1 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Assets.xcassets/Illustrations/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Assets.xcassets/Illustrations/Empty.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "undraw_no_data_re_kwbl-2.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Assets.xcassets/Illustrations/Empty.imageset/undraw_no_data_re_kwbl-2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Assets.xcassets/Illustrations/Error.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "undraw_fixing_bugs_w7gi.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Assets.xcassets/Illustrations/Error.imageset/undraw_fixing_bugs_w7gi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Assets.xcassets/Illustrations/PageLimit.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "undraw_file_manager_re_ms29.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Assets.xcassets/Illustrations/PageLimit.imageset/undraw_file_manager_re_ms29.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/PdfImporter.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Sources/App/PdfImporterApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PdfImporterApp.swift 3 | // PdfImporter 4 | // 5 | // Created by Siewert, Laura on 03.05.24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct PdfImporterApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | PdfImporterView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Sources/Constants/Dimens.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dimens.swift 3 | // PdfImporter 4 | // 5 | // Created by Siewert, Laura on 03.05.24. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Stores the dimens, that are used throughout the app 11 | enum Dimens { 12 | static let unit2 = 2.0 13 | static let unit4 = 4.0 14 | static let unit6 = 6.0 15 | static let unit8 = 8.0 16 | static let unit10 = 10.0 17 | static let unit12 = 12.0 18 | static let unit14 = 14.0 19 | static let unit16 = 16.0 20 | static let unit18 = 18.0 21 | static let unit20 = 20.0 22 | static let unit22 = 22.0 23 | static let unit24 = 24.0 24 | static let unit26 = 26.0 25 | static let unit28 = 28.0 26 | static let unit30 = 30.0 27 | static let unit32 = 32.0 28 | static let unit34 = 34.0 29 | static let unit36 = 36.0 30 | static let unit38 = 38.0 31 | static let unit40 = 40.0 32 | static let unit42 = 42.0 33 | static let unit44 = 44.0 34 | static let unit46 = 46.0 35 | static let unit48 = 48.0 36 | static let unit50 = 50.0 37 | static let unit52 = 52.0 38 | static let unit54 = 54.0 39 | static let unit56 = 56.0 40 | static let unit58 = 58.0 41 | static let unit60 = 60.0 42 | static let unit62 = 62.0 43 | static let unit64 = 64.0 44 | static let unit66 = 66.0 45 | static let unit68 = 68.0 46 | static let unit70 = 70.0 47 | static let unit72 = 72.0 48 | static let unit74 = 74.0 49 | static let unit76 = 76.0 50 | static let unit78 = 78.0 51 | static let unit80 = 80.0 52 | static let unit82 = 82.0 53 | static let unit84 = 84.0 54 | static let unit86 = 86.0 55 | static let unit88 = 88.0 56 | static let unit90 = 90.0 57 | static let unit92 = 92.0 58 | static let unit94 = 94.0 59 | static let imageSize: CGFloat = 200 60 | static let opacity16 = 0.16 61 | } 62 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Sources/Constants/ImageNames.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageNames.swift 3 | // PdfImporter 4 | // 5 | // Created by Siewert, Laura on 06.05.24. 6 | // 7 | 8 | /// Stores the image names used in the app 9 | enum ImageNames { 10 | static let empty = "Empty" 11 | static let error = "Error" 12 | static let pageLimit = "PageLimit" 13 | } 14 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Sources/Constants/Texts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Texts.swift 3 | // PdfImporter 4 | // 5 | // Created by Siewert, Laura on 06.05.24. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Stores the texts used in the app 11 | enum Texts { 12 | static let emptyPdfTitle = "No PDF selected" 13 | static let emptyPdfDescription = "You did not select a PDF click on the button to import a pdf" 14 | static let backToFileImportButtonTitle = "Back to file import" 15 | static let importAnotherPdf = "Import another PDF" 16 | static let loadingPdf = "Your selected PDF is being loaded" 17 | static let importErrorTitle = "PDF could not be imported" 18 | static let importErrorDescription = "Something went wrong while trying to import your selected PDF. Please try to import another PDF Document" 19 | static let pageLimitErrorTitle = "Page Limit reached" 20 | static let pageLimitErrorDescription = "Your PDF has too many pages, please try to import a smaller pdf" 21 | } 22 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Sources/Error/PDFError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PDFError.swift 3 | // PdfImporter 4 | // 5 | // Created by Siewert, Laura on 06.05.24. 6 | // 7 | 8 | import Foundation 9 | 10 | /// A Protocol for every created error 11 | protocol PDFError { 12 | /// A prefix to identify the error 13 | var prexif: String { get } 14 | /// Name of the illustration 15 | var illustrationName: String { get } 16 | /// A title to show to the user 17 | var title: String { get } 18 | /// A description to show to the user 19 | var description: String { get } 20 | /// A title on the button to show to the user 21 | var buttonTitle: String { get } 22 | } 23 | 24 | /// Error for PDF import 25 | struct PDFImportError: PDFError, Error { 26 | var prexif: String = "PDFImportError" 27 | var illustrationName: String = ImageNames.error 28 | var title: String = Texts.importErrorTitle 29 | var description: String = Texts.importErrorDescription 30 | var buttonTitle: String = Texts.importAnotherPdf 31 | } 32 | 33 | /// Error for pdf page limit 34 | struct PDFPageLimitError: PDFError, Error { 35 | var prexif: String = "PDFImportError" 36 | var illustrationName: String = ImageNames.pageLimit 37 | var title: String = Texts.pageLimitErrorTitle 38 | var description: String = Texts.pageLimitErrorDescription 39 | var buttonTitle: String = Texts.importAnotherPdf 40 | } 41 | 42 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Sources/ViewModel/PdfImporterViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PdfImporterViewModel.swift 3 | // PdfImporter 4 | // 5 | // Created by Siewert, Laura on 03.05.24. 6 | // 7 | 8 | import Combine 9 | import PDFKit 10 | 11 | extension PdfImporterViewModel { 12 | /// States for loading pdfs 13 | enum PdfImporterState: Equatable { 14 | /// PDF is loading 15 | case loading 16 | /// An error occured while loading 17 | case error(PDFError) 18 | /// PDF loaded 19 | case loaded(PDFDocument) 20 | /// No pdf selected 21 | case empty 22 | 23 | /// Equatble Func 24 | static func == ( 25 | lhs: PdfImporterViewModel.PdfImporterState, 26 | rhs: PdfImporterViewModel.PdfImporterState 27 | ) -> Bool { 28 | switch (lhs, rhs) { 29 | case (.loading, .loading): 30 | return true 31 | case (.loaded, .loaded): 32 | return false 33 | case (.error(let lhsError), .error(let rhsError)): 34 | return lhsError.prexif == rhsError.prexif 35 | case (.empty, .empty): 36 | return true 37 | default: 38 | return false 39 | } 40 | } 41 | } 42 | } 43 | 44 | /// VIewmodel for edit pdf view 45 | class PdfImporterViewModel: ObservableObject { 46 | 47 | // MARK: - Published Properties 48 | /// Indicates if selectedPdf view should be shown 49 | @Published var isPresentingPdfImport: Bool = true 50 | /// PdfImporterState 51 | @Published var pdfImporterState: PdfImporterState = .empty 52 | 53 | // MARK: - Private Properties 54 | /// Limit for importing pdfs 55 | private let pdfPageLimit = 20 56 | 57 | // MARK: - Public Functions 58 | /// Pdf was selected, set state to loading, import PDF and check given requirements 59 | public func pdfWasSelected(result: Result) { 60 | pdfImporterState = .loading 61 | /// Load pdf in background 62 | switch result { 63 | case .success(let url): 64 | Task { 65 | /// Check Secuirty 66 | guard 67 | url.startAccessingSecurityScopedResource(), 68 | let pdfDocument = PDFDocument(url: url) 69 | else { 70 | /// startAccessingSecurityScopedResource returns false e.g. when trying to upload virus 71 | url.stopAccessingSecurityScopedResource() 72 | Task.detached { @MainActor in 73 | self.pdfImporterState = .error(PDFImportError()) 74 | } 75 | return 76 | } 77 | 78 | /// Check if limit of 20 pages is not exceeded 79 | guard pdfDocument.pageCount <= self.pdfPageLimit else { 80 | Task.detached { @MainActor in 81 | self.pdfImporterState = .error(PDFPageLimitError()) 82 | } 83 | return 84 | } 85 | 86 | Task.detached { @MainActor in 87 | self.pdfImporterState = .loaded(pdfDocument) 88 | } 89 | url.stopAccessingSecurityScopedResource() 90 | } 91 | case .failure: 92 | /// Error from apple fileimporter (corrupt files for e.g.) 93 | pdfImporterState = .error(PDFImportError()) 94 | } 95 | } 96 | 97 | 98 | /// Shows pdf importer 99 | public func showPdfImporter() { 100 | isPresentingPdfImport = true 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Sources/Views/EmptyPdfImporterView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyPdfImporterView.swift 3 | // PdfImporter 4 | // 5 | // Created by Siewert, Laura on 03.05.24. 6 | // 7 | 8 | import SwiftUI 9 | /// View which will be shown, when the user did not select a pdf 10 | struct EmptyPdfImporterView: View { 11 | /// Action to open file importer 12 | let backToFileImportAction: () -> Void 13 | 14 | var body: some View { 15 | VStack(spacing: 0) { 16 | Spacer() 17 | Image(ImageNames.empty) 18 | .resizable() 19 | .frame( 20 | width: Dimens.imageSize, 21 | height: Dimens.imageSize 22 | ) 23 | .scaledToFit() 24 | .padding(.bottom, Dimens.unit40) 25 | Text(Texts.emptyPdfTitle) 26 | .font(.title) 27 | .multilineTextAlignment(.center) 28 | .padding(.bottom, Dimens.unit16) 29 | Text(Texts.emptyPdfDescription) 30 | .multilineTextAlignment(.center) 31 | Spacer() 32 | Button( 33 | Texts.backToFileImportButtonTitle, 34 | action: backToFileImportAction 35 | ) 36 | .buttonStyle(.borderedProminent) 37 | .padding(.bottom, Dimens.unit16) 38 | } 39 | .padding(.horizontal, Dimens.unit20) 40 | .preferredColorScheme(.light) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Sources/Views/PDFImporterBottomSheet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PDFImporterBottomSheet.swift 3 | // PdfImporter 4 | // 5 | // Created by Siewert, Laura on 03.05.24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | /// Bottomsheet displayed above selected pdf 11 | struct PdfImporterBottomSheet: View { 12 | /// BackToFileImportAction 13 | var backToFileImportAction: (() -> Void)? 14 | 15 | /// Body 16 | var body: some View { 17 | VStack(spacing: 0) { 18 | Button( 19 | Texts.importAnotherPdf, 20 | action: backToFileImportAction ?? {} 21 | ) 22 | .buttonStyle(.borderedProminent) 23 | } 24 | .frame(maxWidth: .infinity) 25 | .padding(.vertical, Dimens.unit20) 26 | .padding(.horizontal, Dimens.unit20) 27 | .background(.white) 28 | .background( 29 | Color.white 30 | .shadow( 31 | color: Color.black.opacity(Dimens.opacity16), 32 | radius: Dimens.unit8 33 | ) 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Sources/Views/PDFKitView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PDFKitView.swift 3 | // PdfImporter 4 | // 5 | // Created by Siewert, Laura on 06.05.24. 6 | // 7 | 8 | import SwiftUI 9 | import PDFKit 10 | 11 | /// A Wrapper for UIKit PDFKitView 12 | struct PDFKitView: UIViewRepresentable { 13 | /// PDF Document to display 14 | let pdf: PDFDocument 15 | 16 | /// makeUIView 17 | func makeUIView(context: UIViewRepresentableContext) -> PDFView { 18 | let pdfView = PDFView() 19 | /// Sets the pdf to full width 20 | pdfView.autoScales = true 21 | return pdfView 22 | } 23 | 24 | /// UpdateUIView 25 | func updateUIView(_ uiView: PDFView, context: UIViewRepresentableContext) { 26 | DispatchQueue.main.async { 27 | /// Ensure to always update on main thread 28 | uiView.document = pdf 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Sources/Views/PdfImporterErrorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PdfImporterErrorView.swift 3 | // PdfImporter 4 | // 5 | // Created by Siewert, Laura on 03.05.24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | /// View which will be shown if an error accured while importing a pdf 11 | struct PdfImporterErrorView: View { 12 | /// Error for importing a pdf 13 | let pdfError: PDFError 14 | /// Back to file import action 15 | let backToFileImportAction: () -> Void 16 | 17 | var body: some View { 18 | VStack(spacing: 0) { 19 | Spacer() 20 | Image(pdfError.illustrationName) 21 | .resizable() 22 | .scaledToFit() 23 | .frame(height: Dimens.imageSize) 24 | .padding(.bottom, Dimens.unit40) 25 | Text(pdfError.title) 26 | .font(.title) 27 | .padding(.bottom, Dimens.unit16) 28 | Text(pdfError.description) 29 | .multilineTextAlignment(.center) 30 | Spacer() 31 | Button( 32 | pdfError.buttonTitle, 33 | action: backToFileImportAction 34 | ) 35 | .buttonStyle(.borderedProminent) 36 | .padding(.bottom, Dimens.unit16) 37 | } 38 | .padding(.horizontal, Dimens.unit20) 39 | .background(.white) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Sources/Views/PdfImporterLoadedView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PdfImporterLoadedView.swift 3 | // PdfImporter 4 | // 5 | // Created by Siewert, Laura on 03.05.24. 6 | // 7 | 8 | import SwiftUI 9 | import PDFKit 10 | 11 | /// A View which shows the loaded pdf with a bottomsheet 12 | struct PdfImporterLoadedView: View { 13 | // MARK: - Public Properties 14 | ///Selected PDF from import 15 | let selectedPdf: PDFDocument 16 | /// BackToFileImportAction 17 | let backToFileImportAction: () -> Void 18 | 19 | var body: some View { 20 | VStack(spacing: 0) { 21 | PDFKitView(pdf: selectedPdf) 22 | PdfImporterBottomSheet( 23 | backToFileImportAction: backToFileImportAction 24 | ) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Sources/Views/PdfImporterLoadingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PdfImporterLoadingView.swift 3 | // PdfImporter 4 | // 5 | // Created by Siewert, Laura on 03.05.24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | /// View which will be shown, when pdf is loading 11 | struct PdfImporterLoadingView: View { 12 | var body: some View { 13 | VStack(alignment: .center, spacing: 0) { 14 | Spacer() 15 | ProgressView() 16 | .frame(width: Dimens.unit56, height: Dimens.unit56) 17 | .padding(.vertical, Dimens.unit16) 18 | .padding(.bottom, Dimens.unit8) 19 | Text(Texts.loadingPdf) 20 | Spacer() 21 | PdfImporterBottomSheet() 22 | } 23 | .frame(maxWidth: .infinity) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PdfImporter/PdfImporter/Sources/Views/PdfImporterView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PdfImporterView.swift 3 | // PdfImporter 4 | // 5 | // Created by Siewert, Laura on 03.05.24. 6 | // 7 | 8 | import SwiftUI 9 | import PDFKit 10 | 11 | /// View which manages which view to display by statae 12 | struct PdfImporterView: View { 13 | /// StateObject of view 14 | @StateObject var viewModel: PdfImporterViewModel = PdfImporterViewModel() 15 | 16 | var body: some View { 17 | ZStack { 18 | switch viewModel.pdfImporterState { 19 | case .loading: 20 | PdfImporterLoadingView() 21 | case .loaded(let selecedPdf): 22 | PdfImporterLoadedView( 23 | selectedPdf: selecedPdf, 24 | backToFileImportAction: viewModel.showPdfImporter 25 | ) 26 | case .error(let error): 27 | PdfImporterErrorView( 28 | pdfError: error, 29 | backToFileImportAction: viewModel.showPdfImporter 30 | ) 31 | case .empty: 32 | EmptyPdfImporterView( 33 | backToFileImportAction: viewModel.showPdfImporter 34 | ) 35 | } 36 | } 37 | .fileImporter( 38 | isPresented: $viewModel.isPresentingPdfImport, 39 | allowedContentTypes: [.pdf] 40 | ) { result in 41 | viewModel.pdfWasSelected(result: result) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /PdfImporter/README.md: -------------------------------------------------------------------------------- 1 | # Integrating PDF import in SwiftUI 2 | 3 | ## Description 4 | 5 | This project shows an example of how to use the file importer in SwiftUI and reflecting different states by an enum. 6 | 7 | ![Gif of file importer](./ReadMeResources/GifWithDevices.gif "Gif of file importer") 8 | 9 | 10 | ## How to use the project 11 | 12 | The project was tested for iPhone SE for the iOS Version 17. You need at least the Xcode version 15 to use the project. 13 | 14 | ## Structure of the project 15 | 16 | ### `Views` 17 | 18 | Holds all views that are displayed and used in the app. 19 | 20 | ### `Constants` 21 | 22 | Contains the files `Dimens` (defines sizes), `Texts` (defines strings) and `ImageNames` (defines image names). 23 | 24 | ### `ViewModel` 25 | 26 | Contains the viewModel which controls the view and holds the state. 27 | 28 | ### `Error` 29 | 30 | Contains the defined custom errors. 31 | 32 | ## Example 33 | 34 | The main view `PdfImporterView` is controlled by it's view model. The view model holds a published variable pdfImporterState, which controls which state is the current state of the import. By this published variable it can be decided which view to show. 35 | 36 | ## Errors 37 | 38 | The project implements two error cases: 39 | 1. Error when something went wrong while importing 40 | 2. An additional error, when the PDF has too many pages 41 | 42 | Those are example errors and you can define many more errors to set your own requirements for importing PDFs. 43 | -------------------------------------------------------------------------------- /PdfImporter/ReadMeResources/GifWithDevices.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EXXETA/swiftui-examples/6b6913a1f4c134b92299e1e5f34bd283d3ac63cb/PdfImporter/ReadMeResources/GifWithDevices.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swiftui-examples 2 | 3 | ## Description 4 | 5 | This is a collection of interesting SwiftUI example projects, which we created for our [medium blog](https://medium.com/@mobileatexxeta). Here you can find detailed examples of our code, which we like to share with you. 6 | 7 | ## About us 8 | 9 | We are Mobile@Exxeta, passionate people, who talk about various topics around mobile developing and create great solutions for mobile devices. 10 | 11 | We enjoy: creating | sharing | exchanging. 12 | 13 | Contact: 14 | --------------------------------------------------------------------------------