├── .github └── workflows │ └── manual.yml ├── .gitignore ├── CODEOWNERS ├── GifMaker_Swift_Template.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── GifMaker_Swift_Template ├── AVAssetImageGeneratorTimePoints.swift ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── ios_app_icons_AppIcon29x29@2x.png │ │ ├── ios_app_icons_AppIcon29x29@3x.png │ │ ├── ios_app_icons_AppIcon40x40@2x.png │ │ ├── ios_app_icons_AppIcon40x40@3x.png │ │ ├── ios_app_icons_AppIcon60x60@2x.png │ │ └── ios_app_icons_AppIcon60x60@3x.png │ ├── Close.imageset │ │ ├── Contents.json │ │ ├── Delete-22-2.png │ │ ├── Delete-44-2.png │ │ └── Delete-66-2.png │ ├── Contents.json │ ├── LaunchLogo.imageset │ │ ├── Contents.json │ │ └── LaunchLogo.pdf │ ├── Settings.imageset │ │ ├── Contents.json │ │ ├── Settings-22.png │ │ ├── Settings-44.png │ │ └── Settings-66.png │ ├── createButton.imageset │ │ ├── Contents.json │ │ └── createButton.png │ ├── graphicCollectionEmpty.imageset │ │ ├── Contents.json │ │ ├── graphicCollectionEmpty.png │ │ ├── graphicCollectionEmpty@2x.png │ │ └── graphicCollectionEmpty@3x.png │ └── splashLogo.imageset │ │ ├── Contents.json │ │ ├── splashLogo.png │ │ ├── splashLogo@2x.png │ │ └── splashLogo@3x.png ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Info.plist ├── Regift.swift ├── RegiftCaption.swift ├── hotlineBling.gif └── tinaFeyHiFive.gif ├── LICENSE └── README.md /.github/workflows/manual.yml: -------------------------------------------------------------------------------- 1 | # Workflow to ensure whenever a Github PR is submitted, 2 | # a JIRA ticket gets created automatically. 3 | name: Manual Workflow 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on pull request events but only for the master branch 8 | pull_request_target: 9 | types: [opened, reopened] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | jobs: 15 | test-transition-issue: 16 | name: Convert Github Issue to Jira Issue 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@master 21 | 22 | - name: Login 23 | uses: atlassian/gajira-login@master 24 | env: 25 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} 26 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 27 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} 28 | 29 | - name: Create NEW JIRA ticket 30 | id: create 31 | uses: atlassian/gajira-create@master 32 | with: 33 | project: CONUPDATE 34 | issuetype: Task 35 | summary: | 36 | Github PR [Assign the ND component] | Repo: ${{ github.repository }} | PR# ${{github.event.number}} 37 | description: | 38 | Repo link: https://github.com/${{ github.repository }} 39 | PR no. ${{ github.event.pull_request.number }} 40 | PR title: ${{ github.event.pull_request.title }} 41 | PR description: ${{ github.event.pull_request.description }} 42 | In addition, please resolve other issues, if any. 43 | fields: '{"components": [{"name":"Github PR"}], "customfield_16449":"https://classroom.udacity.com/", "customfield_16450":"Resolve the PR", "labels": ["github"], "priority":{"id": "4"}}' 44 | 45 | - name: Log created issue 46 | run: echo "Issue ${{ steps.create.outputs.issue }} was created" 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | 28 | # Carthage 29 | # 30 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 31 | # Carthage/Checkouts 32 | 33 | Carthage/Build 34 | 35 | # Ignore those pesky DS_Store files! 36 | .DS_Store 37 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @udacity/active-public-content -------------------------------------------------------------------------------- /GifMaker_Swift_Template.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A93B3B081D2D3F7900BE1662 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = A93B3B041D2D3F7900BE1662 /* LaunchScreen.xib */; }; 11 | A93B3B091D2D3F7900BE1662 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A93B3B061D2D3F7900BE1662 /* Main.storyboard */; }; 12 | EDDFC4DC1CE261A600A586CA /* tinaFeyHiFive.gif in Resources */ = {isa = PBXBuildFile; fileRef = EDDFC4DB1CE261A600A586CA /* tinaFeyHiFive.gif */; }; 13 | EDDFC4DE1CE261C400A586CA /* hotlineBling.gif in Resources */ = {isa = PBXBuildFile; fileRef = EDDFC4DD1CE261C400A586CA /* hotlineBling.gif */; }; 14 | EDFAA8ED1CDA99A500D2CB08 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFAA8EC1CDA99A500D2CB08 /* AppDelegate.swift */; }; 15 | EDFAA8F41CDA99A500D2CB08 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EDFAA8F31CDA99A500D2CB08 /* Assets.xcassets */; }; 16 | EDFAA91F1CDA9C3100D2CB08 /* AVAssetImageGeneratorTimePoints.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFAA91C1CDA9C3100D2CB08 /* AVAssetImageGeneratorTimePoints.swift */; }; 17 | EDFAA9201CDA9C3100D2CB08 /* Regift.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFAA91D1CDA9C3100D2CB08 /* Regift.swift */; }; 18 | EDFAA9211CDA9C3100D2CB08 /* RegiftCaption.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFAA91E1CDA9C3100D2CB08 /* RegiftCaption.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | A93B3B051D2D3F7900BE1662 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 23 | A93B3B071D2D3F7900BE1662 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 24 | EDDFC4DB1CE261A600A586CA /* tinaFeyHiFive.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = tinaFeyHiFive.gif; sourceTree = ""; }; 25 | EDDFC4DD1CE261C400A586CA /* hotlineBling.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = hotlineBling.gif; sourceTree = ""; }; 26 | EDFAA8E91CDA99A500D2CB08 /* GifMaker_Swift_Template.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GifMaker_Swift_Template.app; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | EDFAA8EC1CDA99A500D2CB08 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 28 | EDFAA8F31CDA99A500D2CB08 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 29 | EDFAA8F81CDA99A500D2CB08 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | EDFAA91C1CDA9C3100D2CB08 /* AVAssetImageGeneratorTimePoints.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AVAssetImageGeneratorTimePoints.swift; sourceTree = ""; }; 31 | EDFAA91D1CDA9C3100D2CB08 /* Regift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Regift.swift; sourceTree = ""; }; 32 | EDFAA91E1CDA9C3100D2CB08 /* RegiftCaption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegiftCaption.swift; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | EDFAA8E61CDA99A500D2CB08 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | A94CC3D61D21C84C00DC758E /* Supporting Files */ = { 47 | isa = PBXGroup; 48 | children = ( 49 | EDFAA8F81CDA99A500D2CB08 /* Info.plist */, 50 | ); 51 | name = "Supporting Files"; 52 | sourceTree = ""; 53 | }; 54 | A94CC3D71D21C85A00DC758E /* Assets */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | EDFAA8F31CDA99A500D2CB08 /* Assets.xcassets */, 58 | A93B3B061D2D3F7900BE1662 /* Main.storyboard */, 59 | A93B3B041D2D3F7900BE1662 /* LaunchScreen.xib */, 60 | EDDFC4DB1CE261A600A586CA /* tinaFeyHiFive.gif */, 61 | EDDFC4DD1CE261C400A586CA /* hotlineBling.gif */, 62 | ); 63 | name = Assets; 64 | sourceTree = ""; 65 | }; 66 | EDFAA8E01CDA99A500D2CB08 = { 67 | isa = PBXGroup; 68 | children = ( 69 | EDFAA8EB1CDA99A500D2CB08 /* GifMaker_Swift_Template */, 70 | EDFAA8EA1CDA99A500D2CB08 /* Products */, 71 | ); 72 | sourceTree = ""; 73 | }; 74 | EDFAA8EA1CDA99A500D2CB08 /* Products */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | EDFAA8E91CDA99A500D2CB08 /* GifMaker_Swift_Template.app */, 78 | ); 79 | name = Products; 80 | sourceTree = ""; 81 | }; 82 | EDFAA8EB1CDA99A500D2CB08 /* GifMaker_Swift_Template */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | EDFAA8EC1CDA99A500D2CB08 /* AppDelegate.swift */, 86 | A94CC3D71D21C85A00DC758E /* Assets */, 87 | A94CC3D61D21C84C00DC758E /* Supporting Files */, 88 | EDFAA9221CDA9C4700D2CB08 /* Regift Files */, 89 | ); 90 | path = GifMaker_Swift_Template; 91 | sourceTree = ""; 92 | }; 93 | EDFAA9221CDA9C4700D2CB08 /* Regift Files */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | EDFAA91C1CDA9C3100D2CB08 /* AVAssetImageGeneratorTimePoints.swift */, 97 | EDFAA91D1CDA9C3100D2CB08 /* Regift.swift */, 98 | EDFAA91E1CDA9C3100D2CB08 /* RegiftCaption.swift */, 99 | ); 100 | name = "Regift Files"; 101 | sourceTree = ""; 102 | }; 103 | /* End PBXGroup section */ 104 | 105 | /* Begin PBXNativeTarget section */ 106 | EDFAA8E81CDA99A500D2CB08 /* GifMaker_Swift_Template */ = { 107 | isa = PBXNativeTarget; 108 | buildConfigurationList = EDFAA9111CDA99A500D2CB08 /* Build configuration list for PBXNativeTarget "GifMaker_Swift_Template" */; 109 | buildPhases = ( 110 | EDFAA8E51CDA99A500D2CB08 /* Sources */, 111 | EDFAA8E61CDA99A500D2CB08 /* Frameworks */, 112 | EDFAA8E71CDA99A500D2CB08 /* Resources */, 113 | ); 114 | buildRules = ( 115 | ); 116 | dependencies = ( 117 | ); 118 | name = GifMaker_Swift_Template; 119 | productName = GifMaker_Swift_Template; 120 | productReference = EDFAA8E91CDA99A500D2CB08 /* GifMaker_Swift_Template.app */; 121 | productType = "com.apple.product-type.application"; 122 | }; 123 | /* End PBXNativeTarget section */ 124 | 125 | /* Begin PBXProject section */ 126 | EDFAA8E11CDA99A500D2CB08 /* Project object */ = { 127 | isa = PBXProject; 128 | attributes = { 129 | LastSwiftUpdateCheck = 0730; 130 | LastUpgradeCheck = 0800; 131 | ORGANIZATIONNAME = "Gabrielle Miller-Messner"; 132 | TargetAttributes = { 133 | EDFAA8E81CDA99A500D2CB08 = { 134 | CreatedOnToolsVersion = 7.3; 135 | LastSwiftMigration = 0800; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = EDFAA8E41CDA99A500D2CB08 /* Build configuration list for PBXProject "GifMaker_Swift_Template" */; 140 | compatibilityVersion = "Xcode 3.2"; 141 | developmentRegion = English; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = EDFAA8E01CDA99A500D2CB08; 148 | productRefGroup = EDFAA8EA1CDA99A500D2CB08 /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | EDFAA8E81CDA99A500D2CB08 /* GifMaker_Swift_Template */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | EDFAA8E71CDA99A500D2CB08 /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | A93B3B081D2D3F7900BE1662 /* LaunchScreen.xib in Resources */, 163 | A93B3B091D2D3F7900BE1662 /* Main.storyboard in Resources */, 164 | EDDFC4DC1CE261A600A586CA /* tinaFeyHiFive.gif in Resources */, 165 | EDDFC4DE1CE261C400A586CA /* hotlineBling.gif in Resources */, 166 | EDFAA8F41CDA99A500D2CB08 /* Assets.xcassets in Resources */, 167 | ); 168 | runOnlyForDeploymentPostprocessing = 0; 169 | }; 170 | /* End PBXResourcesBuildPhase section */ 171 | 172 | /* Begin PBXSourcesBuildPhase section */ 173 | EDFAA8E51CDA99A500D2CB08 /* Sources */ = { 174 | isa = PBXSourcesBuildPhase; 175 | buildActionMask = 2147483647; 176 | files = ( 177 | EDFAA9201CDA9C3100D2CB08 /* Regift.swift in Sources */, 178 | EDFAA9211CDA9C3100D2CB08 /* RegiftCaption.swift in Sources */, 179 | EDFAA8ED1CDA99A500D2CB08 /* AppDelegate.swift in Sources */, 180 | EDFAA91F1CDA9C3100D2CB08 /* AVAssetImageGeneratorTimePoints.swift in Sources */, 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | }; 184 | /* End PBXSourcesBuildPhase section */ 185 | 186 | /* Begin PBXVariantGroup section */ 187 | A93B3B041D2D3F7900BE1662 /* LaunchScreen.xib */ = { 188 | isa = PBXVariantGroup; 189 | children = ( 190 | A93B3B051D2D3F7900BE1662 /* Base */, 191 | ); 192 | name = LaunchScreen.xib; 193 | sourceTree = ""; 194 | }; 195 | A93B3B061D2D3F7900BE1662 /* Main.storyboard */ = { 196 | isa = PBXVariantGroup; 197 | children = ( 198 | A93B3B071D2D3F7900BE1662 /* Base */, 199 | ); 200 | name = Main.storyboard; 201 | sourceTree = ""; 202 | }; 203 | /* End PBXVariantGroup section */ 204 | 205 | /* Begin XCBuildConfiguration section */ 206 | EDFAA90F1CDA99A500D2CB08 /* Debug */ = { 207 | isa = XCBuildConfiguration; 208 | buildSettings = { 209 | ALWAYS_SEARCH_USER_PATHS = NO; 210 | CLANG_ANALYZER_NONNULL = YES; 211 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 212 | CLANG_CXX_LIBRARY = "libc++"; 213 | CLANG_ENABLE_MODULES = YES; 214 | CLANG_ENABLE_OBJC_ARC = YES; 215 | CLANG_WARN_BOOL_CONVERSION = YES; 216 | CLANG_WARN_CONSTANT_CONVERSION = YES; 217 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 218 | CLANG_WARN_EMPTY_BODY = YES; 219 | CLANG_WARN_ENUM_CONVERSION = YES; 220 | CLANG_WARN_INT_CONVERSION = YES; 221 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 222 | CLANG_WARN_UNREACHABLE_CODE = YES; 223 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 224 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 225 | COPY_PHASE_STRIP = NO; 226 | DEBUG_INFORMATION_FORMAT = dwarf; 227 | ENABLE_STRICT_OBJC_MSGSEND = YES; 228 | ENABLE_TESTABILITY = YES; 229 | GCC_C_LANGUAGE_STANDARD = gnu99; 230 | GCC_DYNAMIC_NO_PIC = NO; 231 | GCC_NO_COMMON_BLOCKS = YES; 232 | GCC_OPTIMIZATION_LEVEL = 0; 233 | GCC_PREPROCESSOR_DEFINITIONS = ( 234 | "DEBUG=1", 235 | "$(inherited)", 236 | ); 237 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 238 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 239 | GCC_WARN_UNDECLARED_SELECTOR = YES; 240 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 241 | GCC_WARN_UNUSED_FUNCTION = YES; 242 | GCC_WARN_UNUSED_VARIABLE = YES; 243 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 244 | MTL_ENABLE_DEBUG_INFO = YES; 245 | ONLY_ACTIVE_ARCH = YES; 246 | SDKROOT = iphoneos; 247 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 248 | }; 249 | name = Debug; 250 | }; 251 | EDFAA9101CDA99A500D2CB08 /* Release */ = { 252 | isa = XCBuildConfiguration; 253 | buildSettings = { 254 | ALWAYS_SEARCH_USER_PATHS = NO; 255 | CLANG_ANALYZER_NONNULL = YES; 256 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 257 | CLANG_CXX_LIBRARY = "libc++"; 258 | CLANG_ENABLE_MODULES = YES; 259 | CLANG_ENABLE_OBJC_ARC = YES; 260 | CLANG_WARN_BOOL_CONVERSION = YES; 261 | CLANG_WARN_CONSTANT_CONVERSION = YES; 262 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 263 | CLANG_WARN_EMPTY_BODY = YES; 264 | CLANG_WARN_ENUM_CONVERSION = YES; 265 | CLANG_WARN_INT_CONVERSION = YES; 266 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 267 | CLANG_WARN_UNREACHABLE_CODE = YES; 268 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 269 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 270 | COPY_PHASE_STRIP = NO; 271 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 272 | ENABLE_NS_ASSERTIONS = NO; 273 | ENABLE_STRICT_OBJC_MSGSEND = YES; 274 | GCC_C_LANGUAGE_STANDARD = gnu99; 275 | GCC_NO_COMMON_BLOCKS = YES; 276 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 277 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 278 | GCC_WARN_UNDECLARED_SELECTOR = YES; 279 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 280 | GCC_WARN_UNUSED_FUNCTION = YES; 281 | GCC_WARN_UNUSED_VARIABLE = YES; 282 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 283 | MTL_ENABLE_DEBUG_INFO = NO; 284 | SDKROOT = iphoneos; 285 | VALIDATE_PRODUCT = YES; 286 | }; 287 | name = Release; 288 | }; 289 | EDFAA9121CDA99A500D2CB08 /* Debug */ = { 290 | isa = XCBuildConfiguration; 291 | buildSettings = { 292 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 293 | INFOPLIST_FILE = GifMaker_Swift_Template/Info.plist; 294 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 295 | PRODUCT_BUNDLE_IDENTIFIER = "Udacity.GifMaker-Swift-Template"; 296 | PRODUCT_NAME = "$(TARGET_NAME)"; 297 | SWIFT_VERSION = 3.0; 298 | }; 299 | name = Debug; 300 | }; 301 | EDFAA9131CDA99A500D2CB08 /* Release */ = { 302 | isa = XCBuildConfiguration; 303 | buildSettings = { 304 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 305 | INFOPLIST_FILE = GifMaker_Swift_Template/Info.plist; 306 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 307 | PRODUCT_BUNDLE_IDENTIFIER = "Udacity.GifMaker-Swift-Template"; 308 | PRODUCT_NAME = "$(TARGET_NAME)"; 309 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 310 | SWIFT_VERSION = 3.0; 311 | }; 312 | name = Release; 313 | }; 314 | /* End XCBuildConfiguration section */ 315 | 316 | /* Begin XCConfigurationList section */ 317 | EDFAA8E41CDA99A500D2CB08 /* Build configuration list for PBXProject "GifMaker_Swift_Template" */ = { 318 | isa = XCConfigurationList; 319 | buildConfigurations = ( 320 | EDFAA90F1CDA99A500D2CB08 /* Debug */, 321 | EDFAA9101CDA99A500D2CB08 /* Release */, 322 | ); 323 | defaultConfigurationIsVisible = 0; 324 | defaultConfigurationName = Release; 325 | }; 326 | EDFAA9111CDA99A500D2CB08 /* Build configuration list for PBXNativeTarget "GifMaker_Swift_Template" */ = { 327 | isa = XCConfigurationList; 328 | buildConfigurations = ( 329 | EDFAA9121CDA99A500D2CB08 /* Debug */, 330 | EDFAA9131CDA99A500D2CB08 /* Release */, 331 | ); 332 | defaultConfigurationIsVisible = 0; 333 | defaultConfigurationName = Release; 334 | }; 335 | /* End XCConfigurationList section */ 336 | }; 337 | rootObject = EDFAA8E11CDA99A500D2CB08 /* Project object */; 338 | } 339 | -------------------------------------------------------------------------------- /GifMaker_Swift_Template.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /GifMaker_Swift_Template/AVAssetImageGeneratorTimePoints.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AVAssetImageGeneratorTimePoints.swift 3 | // GifMaker_ObjC 4 | // 5 | // Created by Gabrielle Miller-Messner on 3/1/16. 6 | // Copyright © 2016 Gabrielle Miller-Messner. All rights reserved. 7 | // 8 | 9 | import AVFoundation 10 | 11 | public extension AVAssetImageGenerator { 12 | public func generateCGImagesAsynchronouslyForTimePoints(_ timePoints: [TimePoint], completionHandler: @escaping AVAssetImageGeneratorCompletionHandler) { 13 | let times = timePoints.map {timePoint in 14 | return NSValue(time: timePoint) 15 | } 16 | self.generateCGImagesAsynchronously(forTimes: times, completionHandler: completionHandler) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /GifMaker_Swift_Template/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // GifMaker_Swift_Template 4 | // 5 | // Created by Gabrielle Miller-Messner on 5/4/16. 6 | // Copyright © 2016 Gabrielle Miller-Messner. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: - AppDelegate: UIResponder, UIApplicationDelegate 12 | 13 | @UIApplicationMain 14 | class AppDelegate: UIResponder, UIApplicationDelegate { 15 | 16 | // MARK: Properties 17 | 18 | var window: UIWindow? 19 | 20 | // MARK: UIApplicationDelegate 21 | 22 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 23 | // Override point for customization after application launch. 24 | return true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "ios_app_icons_AppIcon29x29@2x.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "ios_app_icons_AppIcon29x29@3x.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "ios_app_icons_AppIcon40x40@2x.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "ios_app_icons_AppIcon40x40@3x.png", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "size" : "60x60", 39 | "idiom" : "iphone", 40 | "filename" : "ios_app_icons_AppIcon60x60@2x.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "ios_app_icons_AppIcon60x60@3x.png", 47 | "scale" : "3x" 48 | } 49 | ], 50 | "info" : { 51 | "version" : 1, 52 | "author" : "xcode" 53 | } 54 | } -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/AppIcon.appiconset/ios_app_icons_AppIcon29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/AppIcon.appiconset/ios_app_icons_AppIcon29x29@2x.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/AppIcon.appiconset/ios_app_icons_AppIcon29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/AppIcon.appiconset/ios_app_icons_AppIcon29x29@3x.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/AppIcon.appiconset/ios_app_icons_AppIcon40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/AppIcon.appiconset/ios_app_icons_AppIcon40x40@2x.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/AppIcon.appiconset/ios_app_icons_AppIcon40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/AppIcon.appiconset/ios_app_icons_AppIcon40x40@3x.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/AppIcon.appiconset/ios_app_icons_AppIcon60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/AppIcon.appiconset/ios_app_icons_AppIcon60x60@2x.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/AppIcon.appiconset/ios_app_icons_AppIcon60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/AppIcon.appiconset/ios_app_icons_AppIcon60x60@3x.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/Close.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Delete-22-2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Delete-44-2.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Delete-66-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/Close.imageset/Delete-22-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/Close.imageset/Delete-22-2.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/Close.imageset/Delete-44-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/Close.imageset/Delete-44-2.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/Close.imageset/Delete-66-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/Close.imageset/Delete-66-2.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/LaunchLogo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchLogo.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/LaunchLogo.imageset/LaunchLogo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/LaunchLogo.imageset/LaunchLogo.pdf -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/Settings.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Settings-22.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Settings-44.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Settings-66.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/Settings.imageset/Settings-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/Settings.imageset/Settings-22.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/Settings.imageset/Settings-44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/Settings.imageset/Settings-44.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/Settings.imageset/Settings-66.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/Settings.imageset/Settings-66.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/createButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "createButton.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/createButton.imageset/createButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/createButton.imageset/createButton.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/graphicCollectionEmpty.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "graphicCollectionEmpty.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "graphicCollectionEmpty@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "graphicCollectionEmpty@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/graphicCollectionEmpty.imageset/graphicCollectionEmpty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/graphicCollectionEmpty.imageset/graphicCollectionEmpty.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/graphicCollectionEmpty.imageset/graphicCollectionEmpty@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/graphicCollectionEmpty.imageset/graphicCollectionEmpty@2x.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/graphicCollectionEmpty.imageset/graphicCollectionEmpty@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/graphicCollectionEmpty.imageset/graphicCollectionEmpty@3x.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/splashLogo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "splashLogo.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "splashLogo@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "splashLogo@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/splashLogo.imageset/splashLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/splashLogo.imageset/splashLogo.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/splashLogo.imageset/splashLogo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/splashLogo.imageset/splashLogo@2x.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Assets.xcassets/splashLogo.imageset/splashLogo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/Assets.xcassets/splashLogo.imageset/splashLogo@3x.png -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 97 | 98 | 99 | 100 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 231 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 282 | 283 | 284 | 285 | 286 | 287 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 340 | 347 | 348 | 349 | 350 | 351 | 372 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSMicrophoneUsageDescription 6 | GIFMaker would like to use your microphone to create GIFs. 7 | NSCameraUsageDescription 8 | GIFMaker would like to use your camera to create GIFs. 9 | NSPhotoLibraryUsageDescription 10 | GIFMaker would like to use your photo library to create GIFs. 11 | CFBundleDevelopmentRegion 12 | en 13 | CFBundleExecutable 14 | $(EXECUTABLE_NAME) 15 | CFBundleIdentifier 16 | $(PRODUCT_BUNDLE_IDENTIFIER) 17 | CFBundleInfoDictionaryVersion 18 | 6.0 19 | CFBundleName 20 | $(PRODUCT_NAME) 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | 1.0 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | 1 29 | LSRequiresIPhoneOS 30 | 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Main 35 | UIRequiredDeviceCapabilities 36 | 37 | armv7 38 | 39 | UISupportedInterfaceOrientations 40 | 41 | UIInterfaceOrientationPortrait 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /GifMaker_Swift_Template/Regift.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Regift.swift 3 | // GifMaker_Swift_Template 4 | // 5 | // Modified by Gabrielle Miller-Messner on 3/2/16. 6 | // Created by Matthew Palmer on 27/12/2014. 7 | // Copyright (c) 2014 Matthew Palmer. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | import MobileCoreServices 12 | import ImageIO 13 | import AVFoundation 14 | 15 | public typealias TimePoint = CMTime 16 | 17 | /// Errors thrown by Regift 18 | public enum RegiftError: NSInteger, Error { 19 | case destinationNotFound 20 | case sourceFormatInvalid 21 | case addFrameToDestination 22 | case destinationFinalize 23 | } 24 | 25 | // Convenience struct for managing dispatch groups. 26 | private struct Group { 27 | let group = DispatchGroup() 28 | func enter() { group.enter() } 29 | func leave() { group.leave() } 30 | func wait() { let _ = group.wait(timeout: DispatchTime.distantFuture) } 31 | } 32 | 33 | /// Easily convert a video to a GIF. It can convert the whole thing, or you can choose a section to trim out. 34 | /// 35 | /// Synchronous Usage: 36 | /// 37 | /// let regift = Regift(sourceFileURL: movieFileURL, frameCount: 24, delayTime: 0.5, loopCount: 7) 38 | /// print(regift.createGif()) 39 | /// 40 | /// // OR 41 | /// 42 | /// let trimmedRegift = Regift(sourceFileURL: movieFileURL, startTime: 30, duration: 15, frameRate: 15) 43 | /// print(trimmedRegift.createGif()) 44 | /// 45 | /// Asynchronous Usage: 46 | /// 47 | /// let regift = Regift.createGIFFromSource(movieFileURL, frameCount: 24, delayTime: 0.5, loopCount: 7) { (result) in 48 | /// print(result) 49 | /// } 50 | /// 51 | /// // OR 52 | /// 53 | /// let trimmedRegift = Regift.createGIFFromSource(movieFileURL, startTime: 30, duration: 15, frameRate: 15) { (result) in 54 | /// print(result) 55 | /// } 56 | /// 57 | @objc open class Regift: NSObject { 58 | 59 | // Constants removed from struct 60 | static let FileName = "regift.gif" 61 | static let TimeInterval: Int32 = 600 62 | static let Tolerance = 0.01 63 | 64 | 65 | // Static conversion methods, for convenient and easy-to-use API: 66 | 67 | /** 68 | Create a GIF from a movie stored at the given URL. This converts the whole video to a GIF meeting the requested output parameters. 69 | - parameters: 70 | - sourceFileURL: The source file to create the GIF from. 71 | - destinationFileURL: An optional destination file to write the GIF to. If you don't include this, a default path will be provided. 72 | - frameCount: The number of frames to include in the gif; each frame has the same duration and is spaced evenly over the video. 73 | - delayTime: The amount of time each frame exists for in the GIF. 74 | - loopCount: The number of times the GIF will repeat. This defaults to `0`, which means that the GIF will repeat infinitely. 75 | - completion: A block that will be called when the GIF creation is completed. The `result` parameter provides the path to the file, or will be `nil` if there was an error. 76 | */ 77 | open static func createGIFFromSource( 78 | _ sourceFileURL: URL, 79 | destinationFileURL: URL? = nil, 80 | frameCount: Int, 81 | delayTime: Float, 82 | loopCount: Int = 0, 83 | completion: (_ result: URL?) -> Void) { 84 | let gift = Regift( 85 | sourceFileURL: sourceFileURL, 86 | destinationFileURL: destinationFileURL, 87 | frameCount: frameCount, 88 | delayTime: delayTime, 89 | loopCount: loopCount 90 | ) 91 | 92 | completion(gift.createGif(nil, font: nil)) 93 | } 94 | 95 | /** 96 | Create a GIF from a movie stored at the given URL. This allows you to choose a start time and duration in the source material that will be used to create the GIF which meets the output parameters. 97 | - parameters: 98 | - sourceFileURL: The source file to create the GIF from. 99 | - destinationFileURL: An optional destination file to write the GIF to. If you don't include this, a default path will be provided. 100 | - startTime: The time in seconds in the source material at which you want the GIF to start. 101 | - duration: The duration in seconds that you want to pull from the source material. 102 | - frameRate: The desired frame rate of the outputted GIF. 103 | - loopCount: The number of times the GIF will repeat. This defaults to `0`, which means that the GIF will repeat infinitely. 104 | - completion: A block that will be called when the GIF creation is completed. The `result` parameter provides the path to the file, or will be `nil` if there was an error. 105 | */ 106 | open static func createGIFFromSource( 107 | _ sourceFileURL: URL, 108 | destinationFileURL: URL? = nil, 109 | startTime: Float, 110 | duration: Float, 111 | frameRate: Int, 112 | loopCount: Int = 0, 113 | completion: (_ result: URL?) -> Void) { 114 | let gift = Regift( 115 | sourceFileURL: sourceFileURL, 116 | destinationFileURL: destinationFileURL, 117 | startTime: startTime, 118 | duration: duration, 119 | frameRate: frameRate, 120 | loopCount: loopCount 121 | ) 122 | 123 | completion(gift.createGif(nil, font: nil)) 124 | } 125 | 126 | /// A reference to the asset we are converting. 127 | fileprivate var asset: AVAsset 128 | 129 | /// The url for the source file. 130 | fileprivate let sourceFileURL: URL 131 | 132 | /// The point in time in the source which we will start from. 133 | fileprivate var startTime: Float = 0 134 | 135 | /// The desired duration of the gif. 136 | fileprivate var duration: Float 137 | 138 | /// The total length of the movie, in seconds. 139 | fileprivate var movieLength: Float 140 | 141 | /// The number of frames we are going to use to create the gif. 142 | fileprivate let frameCount: Int 143 | 144 | /// The amount of time each frame will remain on screen in the gif. 145 | fileprivate let delayTime: Float 146 | 147 | /// The number of times the gif will loop (0 is infinite). 148 | fileprivate let loopCount: Int 149 | 150 | /// The destination path for the generated file. 151 | fileprivate var destinationFileURL: URL? 152 | 153 | /** 154 | Create a GIF from a movie stored at the given URL. This converts the whole video to a GIF meeting the requested output parameters. 155 | - parameters: 156 | - sourceFileURL: The source file to create the GIF from. 157 | - destinationFileURL: An optional destination file to write the GIF to. If you don't include this, a default path will be provided. 158 | - frameCount: The number of frames to include in the gif; each frame has the same duration and is spaced evenly over the video. 159 | - delayTime: The amount of time each frame exists for in the GIF. 160 | - loopCount: The number of times the GIF will repeat. This defaults to `0`, which means that the GIF will repeat infinitely. 161 | */ 162 | public init(sourceFileURL: URL, destinationFileURL: URL? = nil, frameCount: Int, delayTime: Float, loopCount: Int = 0) { 163 | self.sourceFileURL = sourceFileURL 164 | self.asset = AVURLAsset(url: sourceFileURL, options: nil) 165 | self.movieLength = Float(asset.duration.value) / Float(asset.duration.timescale) 166 | self.duration = movieLength 167 | self.delayTime = delayTime 168 | self.loopCount = loopCount 169 | self.destinationFileURL = destinationFileURL 170 | self.frameCount = frameCount 171 | } 172 | 173 | /** 174 | Create a GIF from a movie stored at the given URL. This allows you to choose a start time and duration in the source material that will be used to create the GIF which meets the output parameters. 175 | - parameters: 176 | - sourceFileURL: The source file to create the GIF from. 177 | - destinationFileURL: An optional destination file to write the GIF to. If you don't include this, a default path will be provided. 178 | - startTime: The time in seconds in the source material at which you want the GIF to start. 179 | - duration: The duration in seconds that you want to pull from the source material. 180 | - frameRate: The desired frame rate of the outputted GIF. 181 | - loopCount: The number of times the GIF will repeat. This defaults to `0`, which means that the GIF will repeat infinitely. 182 | */ 183 | public init(sourceFileURL: URL, destinationFileURL: URL? = nil, startTime: Float, duration: Float, frameRate: Int, loopCount: Int = 0) { 184 | self.sourceFileURL = sourceFileURL 185 | self.asset = AVURLAsset(url: sourceFileURL, options: nil) 186 | self.destinationFileURL = destinationFileURL 187 | self.startTime = startTime 188 | self.duration = duration 189 | 190 | // The delay time is based on the desired framerate of the gif. 191 | self.delayTime = (1.0 / Float(frameRate)) 192 | 193 | // The frame count is based on the desired length and framerate of the gif. 194 | self.frameCount = Int(duration * Float(frameRate)) 195 | 196 | // The total length of the file, in seconds. 197 | self.movieLength = Float(asset.duration.value) / Float(asset.duration.timescale) 198 | 199 | self.loopCount = loopCount 200 | } 201 | 202 | /** 203 | Get the URL of the GIF created with the attributes provided in the initializer. 204 | - returns: The path to the created GIF, or `nil` if there was an error creating it. 205 | */ 206 | 207 | open func createGif(_ caption : String?, font : UIFont?) -> URL? { 208 | 209 | let fileProperties = [kCGImagePropertyGIFDictionary as String:[ 210 | kCGImagePropertyGIFLoopCount as String: NSNumber(value: Int32(loopCount))], 211 | kCGImagePropertyGIFHasGlobalColorMap as String: NSValue(nonretainedObject: true) 212 | ] as [String : Any] 213 | 214 | let frameProperties = [ 215 | kCGImagePropertyGIFDictionary as String:[ 216 | kCGImagePropertyGIFDelayTime as String:delayTime 217 | ] 218 | ] 219 | 220 | // How far along the video track we want to move, in seconds. 221 | let increment = Float(duration) / Float(frameCount) 222 | 223 | // Add each of the frames to the buffer 224 | var timePoints: [TimePoint] = [] 225 | 226 | for frameNumber in 0 ..< frameCount { 227 | let seconds: Float64 = Float64(startTime) + (Float64(increment) * Float64(frameNumber)) 228 | let time = CMTimeMakeWithSeconds(seconds, Regift.TimeInterval) 229 | 230 | timePoints.append(time) 231 | } 232 | 233 | do { 234 | 235 | if let caption = caption, let font = font { 236 | return try createGIFForTimePointsAndCaption(timePoints, fileProperties: fileProperties as [String : AnyObject], frameProperties: frameProperties as [String : AnyObject], frameCount: frameCount, caption: caption as NSString, font:font) 237 | }else{ 238 | return try createGIFForTimePoints(timePoints, fileProperties: fileProperties as [String : AnyObject], frameProperties: frameProperties as [String : AnyObject], frameCount: frameCount) 239 | } 240 | } catch { 241 | return nil 242 | } 243 | } 244 | 245 | // Helper function 246 | open func createGif() -> URL? { 247 | return createGif(nil, font: nil) 248 | } 249 | 250 | /** 251 | Create a GIF using the given time points in a movie file stored in this Regift's `asset`. 252 | 253 | - parameters: 254 | - timePoints: timePoints An array of `TimePoint`s (which are typealiased `CMTime`s) to use as the frames in the GIF. 255 | - fileProperties: The desired attributes of the resulting GIF. 256 | - frameProperties: The desired attributes of each frame in the resulting GIF. 257 | - frameCount: The desired number of frames for the GIF. *NOTE: This seems redundant to me, as `timePoints.count` should really be what we are after, but I'm hesitant to change the API here.* 258 | - returns: The path to the created GIF, or `nil` if there was an error creating it. 259 | */ 260 | open func createGIFForTimePoints(_ timePoints: [TimePoint], fileProperties: [String: AnyObject], frameProperties: [String: AnyObject], frameCount: Int) throws -> URL { 261 | // Ensure the source media is a valid file. 262 | guard asset.tracks(withMediaCharacteristic: AVMediaCharacteristicVisual).count > 0 else { 263 | throw RegiftError.sourceFormatInvalid 264 | } 265 | 266 | var fileURL:URL? 267 | if self.destinationFileURL != nil { 268 | fileURL = self.destinationFileURL 269 | } else { 270 | let temporaryFile = (NSTemporaryDirectory() as NSString).appendingPathComponent(Regift.FileName) 271 | fileURL = URL(fileURLWithPath: temporaryFile) 272 | } 273 | 274 | guard let destination = CGImageDestinationCreateWithURL(fileURL! as CFURL, kUTTypeGIF, frameCount, nil) else { 275 | throw RegiftError.destinationNotFound 276 | } 277 | 278 | CGImageDestinationSetProperties(destination, fileProperties as CFDictionary) 279 | 280 | let generator = AVAssetImageGenerator(asset: asset) 281 | 282 | generator.appliesPreferredTrackTransform = true 283 | 284 | let tolerance = CMTimeMakeWithSeconds(Regift.Tolerance, Regift.TimeInterval) 285 | generator.requestedTimeToleranceBefore = tolerance 286 | generator.requestedTimeToleranceAfter = tolerance 287 | 288 | // Transform timePoints to times for the async asset generator method. 289 | var times = [NSValue]() 290 | for time in timePoints { 291 | times.append(NSValue(time: time)) 292 | } 293 | 294 | // Create a dispatch group to force synchronous behavior on an asynchronous method. 295 | let gifGroup = Group() 296 | var dispatchError: Bool = false 297 | gifGroup.enter() 298 | 299 | generator.generateCGImagesAsynchronously(forTimes: times, completionHandler: { (requestedTime, image, actualTime, result, error) in 300 | guard let imageRef = image , error == nil else { 301 | print("An error occurred: \(error), image is \(image)") 302 | dispatchError = true 303 | gifGroup.leave() 304 | return 305 | } 306 | 307 | CGImageDestinationAddImage(destination, imageRef, frameProperties as CFDictionary) 308 | 309 | if requestedTime == times.last?.timeValue { 310 | gifGroup.leave() 311 | } 312 | }) 313 | 314 | // Wait for the asynchronous generator to finish. 315 | gifGroup.wait() 316 | 317 | // If there was an error in the generator, throw the error. 318 | if dispatchError { 319 | throw RegiftError.addFrameToDestination 320 | } 321 | 322 | CGImageDestinationSetProperties(destination, fileProperties as CFDictionary) 323 | 324 | // Finalize the gif 325 | if !CGImageDestinationFinalize(destination) { 326 | throw RegiftError.destinationFinalize 327 | } 328 | 329 | return fileURL! 330 | } 331 | 332 | 333 | 334 | /// Create a GIF using the given time points in a movie file stored at the URL provided and a caption to be overlayed. 335 | /// 336 | /// :param: timePoints An array of `TimePoint`s (which are typealiased `CMTime`s) to use as the frames in the GIF. 337 | /// :param: URL The URL of the video file to convert 338 | /// :param: fileProperties The desired attributes of the resulting GIF. 339 | /// :param: frameProperties The desired attributes of each frame in the resulting GIF. 340 | /// :param: caption 341 | /// 342 | open func createGIFForTimePointsAndCaption(_ timePoints: [TimePoint], fileProperties: [String: AnyObject], frameProperties: [String: AnyObject], frameCount: Int, caption: NSString, font: UIFont) throws -> URL { 343 | let temporaryFile = (NSTemporaryDirectory() as NSString).appendingPathComponent(Regift.FileName) 344 | let fileURL = URL(fileURLWithPath: temporaryFile) 345 | 346 | guard let destination = CGImageDestinationCreateWithURL(fileURL as CFURL, kUTTypeGIF, frameCount, nil) else { 347 | print("An error occurred.") 348 | throw RegiftError.destinationNotFound 349 | } 350 | 351 | CGImageDestinationSetProperties(destination, fileProperties as CFDictionary) 352 | 353 | let asset = AVURLAsset(url: sourceFileURL) 354 | let generator = AVAssetImageGenerator(asset: asset) 355 | 356 | generator.appliesPreferredTrackTransform = true 357 | 358 | let tolerance = CMTimeMakeWithSeconds(Regift.Tolerance, Regift.TimeInterval) 359 | generator.requestedTimeToleranceBefore = tolerance 360 | generator.requestedTimeToleranceAfter = tolerance 361 | 362 | for time in timePoints { 363 | do { 364 | let imageRef = try generator.copyCGImage(at: time, actualTime: nil) 365 | let imageRefWithCaption = addCaption(imageRef,text:caption, font:font) 366 | CGImageDestinationAddImage(destination, imageRefWithCaption, frameProperties as CFDictionary) 367 | } catch let error as NSError { 368 | print("An error occurred: \(error)") 369 | throw RegiftError.addFrameToDestination 370 | } 371 | } 372 | 373 | CGImageDestinationSetProperties(destination, fileProperties as CFDictionary) 374 | 375 | // Finalize the gif 376 | if !CGImageDestinationFinalize(destination) { 377 | throw RegiftError.destinationFinalize 378 | } 379 | 380 | return fileURL 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /GifMaker_Swift_Template/RegiftCaption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RegiftCaption.swift 3 | // GifMaker_Swift_Template 4 | // 5 | // Created by Gabrielle Miller-Messner on 4/22/16. 6 | // Modified from http://stackoverflow.com/questions/6992830/how-to-write-text-on-image-in-objective-c-iphone 7 | // 8 | 9 | import UIKit 10 | import Foundation 11 | import CoreGraphics 12 | 13 | extension Regift { 14 | 15 | 16 | func addCaption(_ image: CGImage, text: NSString, font: UIFont) -> CGImage { 17 | let image = UIImage(cgImage:image) 18 | 19 | // Text attributes 20 | let color = UIColor.white 21 | var attributes = [NSForegroundColorAttributeName:color, NSFontAttributeName:font, NSStrokeColorAttributeName : UIColor.black, NSStrokeWidthAttributeName : -4] as [String : Any] 22 | 23 | // Get scale factor 24 | let testSize:CGSize = text.size(attributes: attributes) 25 | let scaleFactor = testSize.height/360 26 | 27 | // Apply scale factor to attributes 28 | let scaledFont: UIFont = UIFont(name: "HelveticaNeue-CondensedBlack", size:image.size.height * scaleFactor)! 29 | attributes[NSFontAttributeName] = scaledFont 30 | 31 | // Text size 32 | let size:CGSize = text.size(attributes: attributes) 33 | let adjustedWidth = ceil(size.width) 34 | let adjustedHeight = ceil(size.height) 35 | 36 | // Draw image 37 | UIGraphicsBeginImageContext(image.size) 38 | let firstRect = CGRect(x: 0,y: 0,width: image.size.width,height: image.size.height) 39 | image.draw(in: firstRect) 40 | 41 | // Draw text 42 | let sideMargin = (image.size.width - adjustedWidth)/2.0 43 | let bottomMargin = image.size.height/6.0 44 | let textOrigin = CGPoint(x: sideMargin, y: image.size.height - bottomMargin) 45 | let secondRect = CGRect(x: textOrigin.x,y: textOrigin.y, width: adjustedWidth, height: adjustedHeight) 46 | text.draw(with: secondRect, options:.usesLineFragmentOrigin, attributes: attributes, context:nil) 47 | 48 | // Capture combined image and text 49 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 50 | UIGraphicsEndImageContext() 51 | return newImage!.cgImage! 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /GifMaker_Swift_Template/hotlineBling.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/hotlineBling.gif -------------------------------------------------------------------------------- /GifMaker_Swift_Template/tinaFeyHiFive.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/ios-nd-gif-maker-swift/15df2e807ebc7f501fa7bb99b466fbdc34f9fa6d/GifMaker_Swift_Template/tinaFeyHiFive.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Udacity 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | iOS Developer Nanodegree logo 2 | 3 | # GifMaker (Swift) 4 | 5 | ![Platform iOS](https://img.shields.io/badge/nanodegree-iOS-blue.svg) 6 | 7 | This repository contains resources for the GifMaker (Swift) project. 8 | 9 | ## Overview 10 | 11 | GifMaker is an app that lets users create simple GIF animations from their iOS device. It is used as an example throughout Udacity's Objective-C for Swift Developers course. 12 | 13 | ## Setup 14 | 15 | GifMaker should run without any additional setup. 16 | 17 | ## Maintainers 18 | 19 | @GabrielleM 20 | --------------------------------------------------------------------------------