diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..97a5e00 --- /dev/null +++ b/.gitignore @@ -0,0 +1,130 @@ +# Created by https://www.toptal.com/developers/gitignore/api/osx,swift,xcode +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,swift,xcode + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Swift ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +### Xcode ### + +## Xcode 8 and earlier + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcodeproj/project.xcworkspace/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno +**/xcshareddata/WorkspaceSettings.xcsettings + +# End of https://www.toptal.com/developers/gitignore/api/osx,swift,xcode diff --git a/Swipe That Pic.xcodeproj/project.pbxproj b/Swipe That Pic.xcodeproj/project.pbxproj new file mode 100644 index 0000000..e63df07 --- /dev/null +++ b/Swipe That Pic.xcodeproj/project.pbxproj @@ -0,0 +1,421 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 670F9C902C558799008BDD2F /* PhotoTumbnailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 670F9C8F2C558799008BDD2F /* PhotoTumbnailView.swift */; }; + 670F9C922C558E0F008BDD2F /* Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 670F9C912C558E0F008BDD2F /* Extension.swift */; }; + 670F9C942C56680C008BDD2F /* IgnoredPictures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 670F9C932C56680C008BDD2F /* IgnoredPictures.swift */; }; + 670F9C982C566EF6008BDD2F /* PhotoTumbnailFromStringView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 670F9C972C566EF6008BDD2F /* PhotoTumbnailFromStringView.swift */; }; + 670F9C9A2C5678B9008BDD2F /* BigPicture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 670F9C992C5678B9008BDD2F /* BigPicture.swift */; }; + 67672FEA2C5915F6003AE9A6 /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = 67672FE92C5915F6003AE9A6 /* .gitignore */; }; + 67E5F04E2C53D93E005DDD7A /* Swipe_That_PicApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E5F04D2C53D93E005DDD7A /* Swipe_That_PicApp.swift */; }; + 67E5F0502C53D93E005DDD7A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E5F04F2C53D93E005DDD7A /* ContentView.swift */; }; + 67E5F0522C53D93E005DDD7A /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E5F0512C53D93E005DDD7A /* Item.swift */; }; + 67E5F0542C53D93F005DDD7A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 67E5F0532C53D93F005DDD7A /* Assets.xcassets */; }; + 67E5F0572C53D93F005DDD7A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 67E5F0562C53D93F005DDD7A /* Preview Assets.xcassets */; }; + 67E5F0692C551489005DDD7A /* PhotosService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E5F0682C551489005DDD7A /* PhotosService.swift */; }; + 67E5F06B2C5514DD005DDD7A /* PhotosUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 67E5F06A2C5514DD005DDD7A /* PhotosUI.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 670F9C8F2C558799008BDD2F /* PhotoTumbnailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoTumbnailView.swift; sourceTree = ""; }; + 670F9C912C558E0F008BDD2F /* Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extension.swift; sourceTree = ""; }; + 670F9C932C56680C008BDD2F /* IgnoredPictures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IgnoredPictures.swift; sourceTree = ""; }; + 670F9C972C566EF6008BDD2F /* PhotoTumbnailFromStringView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoTumbnailFromStringView.swift; sourceTree = ""; }; + 670F9C992C5678B9008BDD2F /* BigPicture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BigPicture.swift; sourceTree = ""; }; + 67672FE92C5915F6003AE9A6 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; + 67E5F04A2C53D93E005DDD7A /* Swipe That Pic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Swipe That Pic.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 67E5F04D2C53D93E005DDD7A /* Swipe_That_PicApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Swipe_That_PicApp.swift; sourceTree = ""; }; + 67E5F04F2C53D93E005DDD7A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 67E5F0512C53D93E005DDD7A /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = ""; }; + 67E5F0532C53D93F005DDD7A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 67E5F0562C53D93F005DDD7A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 67E5F0642C550FCE005DDD7A /* Swipe-That-Pic-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Swipe-That-Pic-Info.plist"; sourceTree = SOURCE_ROOT; }; + 67E5F0662C5511F7005DDD7A /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; }; + 67E5F0682C551489005DDD7A /* PhotosService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotosService.swift; sourceTree = ""; }; + 67E5F06A2C5514DD005DDD7A /* PhotosUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PhotosUI.framework; path = System/Library/Frameworks/PhotosUI.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 67E5F0472C53D93E005DDD7A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 67E5F06B2C5514DD005DDD7A /* PhotosUI.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 67E5F0412C53D93E005DDD7A = { + isa = PBXGroup; + children = ( + 67672FE92C5915F6003AE9A6 /* .gitignore */, + 67E5F04C2C53D93E005DDD7A /* Swipe That Pic */, + 67E5F04B2C53D93E005DDD7A /* Products */, + 67E5F0652C5511F7005DDD7A /* Frameworks */, + ); + sourceTree = ""; + }; + 67E5F04B2C53D93E005DDD7A /* Products */ = { + isa = PBXGroup; + children = ( + 67E5F04A2C53D93E005DDD7A /* Swipe That Pic.app */, + ); + name = Products; + sourceTree = ""; + }; + 67E5F04C2C53D93E005DDD7A /* Swipe That Pic */ = { + isa = PBXGroup; + children = ( + 67E5F0682C551489005DDD7A /* PhotosService.swift */, + 67E5F0642C550FCE005DDD7A /* Swipe-That-Pic-Info.plist */, + 67E5F0612C53DF09005DDD7A /* Models */, + 67E5F0602C53DF01005DDD7A /* Views */, + 67E5F04D2C53D93E005DDD7A /* Swipe_That_PicApp.swift */, + 67E5F0532C53D93F005DDD7A /* Assets.xcassets */, + 67E5F0552C53D93F005DDD7A /* Preview Content */, + ); + path = "Swipe That Pic"; + sourceTree = ""; + }; + 67E5F0552C53D93F005DDD7A /* Preview Content */ = { + isa = PBXGroup; + children = ( + 67E5F0562C53D93F005DDD7A /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 67E5F0602C53DF01005DDD7A /* Views */ = { + isa = PBXGroup; + children = ( + 67E5F04F2C53D93E005DDD7A /* ContentView.swift */, + 670F9C8F2C558799008BDD2F /* PhotoTumbnailView.swift */, + 670F9C972C566EF6008BDD2F /* PhotoTumbnailFromStringView.swift */, + 670F9C932C56680C008BDD2F /* IgnoredPictures.swift */, + 670F9C992C5678B9008BDD2F /* BigPicture.swift */, + ); + path = Views; + sourceTree = ""; + }; + 67E5F0612C53DF09005DDD7A /* Models */ = { + isa = PBXGroup; + children = ( + 67E5F0512C53D93E005DDD7A /* Item.swift */, + 670F9C912C558E0F008BDD2F /* Extension.swift */, + ); + path = Models; + sourceTree = ""; + }; + 67E5F0652C5511F7005DDD7A /* Frameworks */ = { + isa = PBXGroup; + children = ( + 67E5F06A2C5514DD005DDD7A /* PhotosUI.framework */, + 67E5F0662C5511F7005DDD7A /* Photos.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 67E5F0492C53D93E005DDD7A /* Swipe That Pic */ = { + isa = PBXNativeTarget; + buildConfigurationList = 67E5F05A2C53D93F005DDD7A /* Build configuration list for PBXNativeTarget "Swipe That Pic" */; + buildPhases = ( + 67E5F0462C53D93E005DDD7A /* Sources */, + 67E5F0472C53D93E005DDD7A /* Frameworks */, + 67E5F0482C53D93E005DDD7A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Swipe That Pic"; + packageProductDependencies = ( + ); + productName = "Swipe That Pic"; + productReference = 67E5F04A2C53D93E005DDD7A /* Swipe That Pic.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 67E5F0422C53D93E005DDD7A /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1540; + LastUpgradeCheck = 1540; + TargetAttributes = { + 67E5F0492C53D93E005DDD7A = { + CreatedOnToolsVersion = 15.4; + }; + }; + }; + buildConfigurationList = 67E5F0452C53D93E005DDD7A /* Build configuration list for PBXProject "Swipe That Pic" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 67E5F0412C53D93E005DDD7A; + packageReferences = ( + ); + productRefGroup = 67E5F04B2C53D93E005DDD7A /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 67E5F0492C53D93E005DDD7A /* Swipe That Pic */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 67E5F0482C53D93E005DDD7A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 67E5F0572C53D93F005DDD7A /* Preview Assets.xcassets in Resources */, + 67672FEA2C5915F6003AE9A6 /* .gitignore in Resources */, + 67E5F0542C53D93F005DDD7A /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 67E5F0462C53D93E005DDD7A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 670F9C982C566EF6008BDD2F /* PhotoTumbnailFromStringView.swift in Sources */, + 67E5F0692C551489005DDD7A /* PhotosService.swift in Sources */, + 67E5F0502C53D93E005DDD7A /* ContentView.swift in Sources */, + 670F9C942C56680C008BDD2F /* IgnoredPictures.swift in Sources */, + 670F9C9A2C5678B9008BDD2F /* BigPicture.swift in Sources */, + 67E5F0522C53D93E005DDD7A /* Item.swift in Sources */, + 670F9C922C558E0F008BDD2F /* Extension.swift in Sources */, + 670F9C902C558799008BDD2F /* PhotoTumbnailView.swift in Sources */, + 67E5F04E2C53D93E005DDD7A /* Swipe_That_PicApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 67E5F0582C53D93F005DDD7A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 67E5F0592C53D93F005DDD7A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 67E5F05B2C53D93F005DDD7A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Swipe That Pic/Preview Content\""; + DEVELOPMENT_TEAM = SH3XZXB7R8; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Swipe-That-Pic-Info.plist"; + INFOPLIST_KEY_NSPhotoLibraryUsageDescription = ""; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "apps.amine.bou.Swipe-That-Pic"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 67E5F05C2C53D93F005DDD7A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Swipe That Pic/Preview Content\""; + DEVELOPMENT_TEAM = SH3XZXB7R8; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Swipe-That-Pic-Info.plist"; + INFOPLIST_KEY_NSPhotoLibraryUsageDescription = ""; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "apps.amine.bou.Swipe-That-Pic"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 67E5F0452C53D93E005DDD7A /* Build configuration list for PBXProject "Swipe That Pic" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 67E5F0582C53D93F005DDD7A /* Debug */, + 67E5F0592C53D93F005DDD7A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 67E5F05A2C53D93F005DDD7A /* Build configuration list for PBXNativeTarget "Swipe That Pic" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 67E5F05B2C53D93F005DDD7A /* Debug */, + 67E5F05C2C53D93F005DDD7A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 67E5F0422C53D93E005DDD7A /* Project object */; +} diff --git a/Swipe That Pic.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Swipe That Pic.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Swipe That Pic.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Swipe That Pic.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Swipe That Pic.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Swipe That Pic.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Swipe That Pic.xcodeproj/xcuserdata/amine.xcuserdatad/xcschemes/xcschememanagement.plist b/Swipe That Pic.xcodeproj/xcuserdata/amine.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..8931a81 --- /dev/null +++ b/Swipe That Pic.xcodeproj/xcuserdata/amine.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + Swipe That Pic.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/Swipe That Pic/Assets.xcassets/AccentColor.colorset/Contents.json b/Swipe That Pic/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Swipe That Pic/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Swipe That Pic/Assets.xcassets/AppIcon.appiconset/Contents.json b/Swipe That Pic/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..363cd1c --- /dev/null +++ b/Swipe That Pic/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "Frame 1(2).png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Swipe That Pic/Assets.xcassets/AppIcon.appiconset/Frame 1(2).png b/Swipe That Pic/Assets.xcassets/AppIcon.appiconset/Frame 1(2).png new file mode 100644 index 0000000..bb2cc02 Binary files /dev/null and b/Swipe That Pic/Assets.xcassets/AppIcon.appiconset/Frame 1(2).png differ diff --git a/Swipe That Pic/Assets.xcassets/Contents.json b/Swipe That Pic/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Swipe That Pic/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Swipe That Pic/Models/Extension.swift b/Swipe That Pic/Models/Extension.swift new file mode 100644 index 0000000..7731c43 --- /dev/null +++ b/Swipe That Pic/Models/Extension.swift @@ -0,0 +1,16 @@ +// +// Extension.swift +// Swipe That Pic +// +// Created by Amine Bou on 27/07/2024. +// + +import Foundation +import Photos + +extension Array { + func random() -> Element { + let randomIndex = Int(arc4random()) % self.count + return self[randomIndex] + } +} diff --git a/Swipe That Pic/Models/Item.swift b/Swipe That Pic/Models/Item.swift new file mode 100644 index 0000000..f3516b4 --- /dev/null +++ b/Swipe That Pic/Models/Item.swift @@ -0,0 +1,17 @@ +// +// Item.swift +// Swipe That Pic +// +// Created by Amine Bou on 26/07/2024. +// + +import Foundation +import SwiftData + +@Model +final class Item { + @Attribute(.unique) var localIdentfier: String + init(localIdentfier: String) { + self.localIdentfier = localIdentfier + } +} diff --git a/Swipe That Pic/PhotosService.swift b/Swipe That Pic/PhotosService.swift new file mode 100644 index 0000000..7e7ad99 --- /dev/null +++ b/Swipe That Pic/PhotosService.swift @@ -0,0 +1,98 @@ +// +// PhotosService.swift +// Swipe That Pic +// +// Created by Amine Bou on 27/07/2024. +// + +import Foundation +import Photos +import UIKit +import SwiftData +import SwiftUI + +class PhotosService: ObservableObject { + var notKept: PHFetchResult = PHFetchResult() + @Published var one: PHAsset? + var modelContainer: ModelContainer + + init(modelContainer: ModelContainer) { + self.modelContainer = modelContainer + } + + var imageCachingManager = PHCachingImageManager() + + @MainActor func fetchNotKeptPhotos() { + PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in + switch status { + case .authorized: + self.fetchNotKeptPhotosAuthorized() + break; + @unknown default: + fatalError() + } + } + } + + @MainActor func fetchNotKeptPhotosAuthorized() { + imageCachingManager.allowsCachingHighQualityImages = false + let fetchOptions = PHFetchOptions() + fetchOptions.includeHiddenAssets = false + do { + let items = try self.modelContainer.mainContext.fetch(FetchDescriptor()) + if (items.count > 0) { + fetchOptions.predicate = NSPredicate(format: "NOT localIdentifier IN %@", + argumentArray: [items.map {$0.localIdentfier}]) + } + } catch { + print("Can't get model") + } + fetchOptions.sortDescriptors = [ + NSSortDescriptor(key: "creationDate", ascending: false) + ] + DispatchQueue.main.async { + self.notKept = PHAsset.fetchAssets(with: .image, options: fetchOptions) + if (self.notKept.count > 0) { + self.one = self.notKept.object(at: Int.random(in: 0.. UIImage? { + let results = PHAsset.fetchAssets( + withLocalIdentifiers: [localId], + options: nil + ) + guard let asset = results.firstObject else { + fatalError("No asset") + } + let options = PHImageRequestOptions() + options.deliveryMode = .opportunistic + options.resizeMode = .fast + options.isNetworkAccessAllowed = true + options.isSynchronous = true + return try await withCheckedThrowingContinuation { [weak self] continuation in + /// Use the imageCachingManager to fetch the image + self?.imageCachingManager.requestImage( + for: asset, + targetSize: targetSize, + contentMode: contentMode, + options: options, + resultHandler: { image, info in + /// image is of type UIImage + if let error = info?[PHImageErrorKey] as? Error { + continuation.resume(throwing: error) + return + } + continuation.resume(returning: image) + } + ) + } + } +} diff --git a/Swipe That Pic/Preview Content/Preview Assets.xcassets/Contents.json b/Swipe That Pic/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Swipe That Pic/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Swipe That Pic/Swipe_That_PicApp.swift b/Swipe That Pic/Swipe_That_PicApp.swift new file mode 100644 index 0000000..aac6ebb --- /dev/null +++ b/Swipe That Pic/Swipe_That_PicApp.swift @@ -0,0 +1,34 @@ +// +// Swipe_That_PicApp.swift +// Swipe That Pic +// +// Created by Amine Bou on 26/07/2024. +// + +import SwiftUI +import SwiftData +import Photos + +@main +struct Swipe_That_PicApp: App { + var sharedModelContainer: ModelContainer = { + let schema = Schema([ + Item.self, + ]) + let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) + + do { + return try ModelContainer(for: schema, configurations: [modelConfiguration]) + } catch { + fatalError("Could not create ModelContainer: \(error)") + } + }() + + var body: some Scene { + WindowGroup { + ContentView().environmentObject(PhotosService(modelContainer: sharedModelContainer)) + + } + .modelContainer(sharedModelContainer) + } +} diff --git a/Swipe That Pic/Views/BigPicture.swift b/Swipe That Pic/Views/BigPicture.swift new file mode 100644 index 0000000..4c8f77e --- /dev/null +++ b/Swipe That Pic/Views/BigPicture.swift @@ -0,0 +1,79 @@ +// +// PhotoTumbnailView.swift +// Swipe That Pic +// +// Created by Amine Bou on 27/07/2024. +// + +import Foundation +import Photos +import SwiftUI + +struct BigPicture: View { + private var image: Image + + @State var scale = 1.0 + @State var lastScale = 0.0 + @State var offset: CGSize = .zero + @State var lastOffset: CGSize = .zero + + init(image: Image) { + self.image = image + } + + var body: some View { + GeometryReader { proxy in + image + .resizable() + .scaledToFill() + .scaleEffect(scale) + .offset(offset) + .frame(width: proxy.size.width, height: proxy.size.height) + .gesture( + MagnificationGesture(minimumScaleDelta: 0) + .onChanged({ value in + withAnimation(.interactiveSpring()) { + scale = handleScaleChange(value) + } + }) + .onEnded({ _ in + lastScale = scale + }).simultaneously( + with: DragGesture(minimumDistance: 0) + .onChanged({ value in + withAnimation(.interactiveSpring()) { + offset = handleOffsetChange(value.translation) + } + }) + .onEnded({ _ in + lastOffset = offset + }) + + ).simultaneously(with: TapGesture(count: 2).onEnded({ Void in + scale = 1.0 + lastScale = 0.0 + offset = .zero + lastOffset = .zero + })) + ) + + } + // We'll also make sure that the photo will + // be square + .aspectRatio(1, contentMode: .fit) + + } + + private func handleScaleChange(_ zoom: CGFloat) -> CGFloat { + lastScale + zoom - (lastScale == 0 ? 0 : 1) + } + + private func handleOffsetChange(_ offset: CGSize) -> CGSize { + var newOffset: CGSize = .zero + + newOffset.width = offset.width + lastOffset.width + newOffset.height = offset.height + lastOffset.height + + return newOffset + } +} diff --git a/Swipe That Pic/Views/ContentView.swift b/Swipe That Pic/Views/ContentView.swift new file mode 100644 index 0000000..f060114 --- /dev/null +++ b/Swipe That Pic/Views/ContentView.swift @@ -0,0 +1,70 @@ +// +// ContentView.swift +// Swipe That Pic +// +// Created by Amine Bou on 26/07/2024. +// + +import SwiftUI +import SwiftData +import Photos + +struct ContentView: View { + @Environment(\.modelContext) private var modelContext + @EnvironmentObject var photoLibraryService: PhotosService + @Query private var items: [Item] + + var body: some View { + NavigationSplitView { + VStack { + PhotoThumbnailView(asset: $photoLibraryService.one) + Spacer() + HStack { + Button(role: .destructive, action: { + PHPhotoLibrary.shared().performChanges({ + PHAssetChangeRequest.deleteAssets([photoLibraryService.one as Any] as NSArray) + }, completionHandler: { success, error in + if success { + photoLibraryService.fetchNotKeptPhotos() + + } else { + }}) + }) { + Label("Supprimer", systemImage: "minus.circle.fill") + }.buttonStyle(.borderedProminent) + + Spacer() + Button(action: { + let newItem = Item(localIdentfier: photoLibraryService.one!.localIdentifier) + modelContext.insert(newItem) + photoLibraryService.fetchNotKeptPhotos() + }) { + Label("Garder", systemImage: "plus.circle.fill") + }.buttonStyle(.borderedProminent) + }.disabled(photoLibraryService.one == nil) + } + + .toolbar { + if (items.count > 0) { + ToolbarItem { + NavigationLink(destination: IgnoredPictures()) { + Label("Handle kept", systemImage: "tray.and.arrow.up.fill") + } + } + } + + } + } detail: { + Text("Select an item") + }.onAppear(perform: { + photoLibraryService.fetchNotKeptPhotos() + }) + } + +} + +#Preview { + ContentView() + .modelContainer(for: Item.self, inMemory: true) +} + diff --git a/Swipe That Pic/Views/IgnoredPictures.swift b/Swipe That Pic/Views/IgnoredPictures.swift new file mode 100644 index 0000000..96c364b --- /dev/null +++ b/Swipe That Pic/Views/IgnoredPictures.swift @@ -0,0 +1,53 @@ +// +// ContentView.swift +// Swipe That Pic +// +// Created by Amine Bou on 26/07/2024. +// + +import SwiftUI +import SwiftData +import Photos + +struct IgnoredPictures: View { + @Environment(\.modelContext) private var modelContext + @Query private var items: [Item] + + var body: some View { + NavigationSplitView { + ScrollView { + LazyVGrid(columns: [GridItem(.adaptive(minimum: 200)), GridItem(.adaptive(minimum: 200))], alignment: .leading) { + ForEach(items) { item in + PhotoThumbnailFromStringView(localIdentifier: item.localIdentfier) + } + } + .toolbar { + ToolbarItem { + Button(action: clearItems) { + Label("Clear", systemImage: "bubbles.and.sparkles.fill") + } + } + } + } + } detail : { + Text("Ignored pictures") + } + } + + private func clearItems() { + withAnimation { + do { + try modelContext.delete(model: Item.self) + } catch { + print("did not work") + } + } + } + +} + +#Preview { + ContentView() + .modelContainer(for: Item.self, inMemory: true) +} + diff --git a/Swipe That Pic/Views/PhotoTumbnailFromStringView.swift b/Swipe That Pic/Views/PhotoTumbnailFromStringView.swift new file mode 100644 index 0000000..90a8005 --- /dev/null +++ b/Swipe That Pic/Views/PhotoTumbnailFromStringView.swift @@ -0,0 +1,73 @@ +// +// PhotoTumbnailView.swift +// Swipe That Pic +// +// Created by Amine Bou on 27/07/2024. +// + +import Foundation +import Photos +import SwiftUI + +struct PhotoThumbnailFromStringView: View { + @EnvironmentObject var photoLibraryService: PhotosService + @State private var image: Image? + @State var localIdentifier: String? + + func loadImageAsset( + targetSize: CGSize = PHImageManagerMaximumSize + ) async { + if (localIdentifier != nil) { + guard let uiImage = try? await photoLibraryService + .fetchImage( + byLocalIdentifier: localIdentifier!, + targetSize: targetSize + ) else { + image = nil + return + } + image = Image(uiImage: uiImage) + } else { + image = nil + } + } + + var body: some View { + ZStack { + // Show the image if it's available + if let image = image { + GeometryReader { proxy in + image + .resizable() + .aspectRatio(contentMode: .fill) + .frame( + width: proxy.size.width, + height: proxy.size.width + ) + .clipped() + } + // We'll also make sure that the photo will + // be square + .aspectRatio(1, contentMode: .fit) + } else { + // Otherwise, show a gray rectangle with a + // spinning progress view + Rectangle() + .foregroundColor(.gray) + .aspectRatio(1, contentMode: .fit) + ProgressView() + } + } + // We need to use the task to work on a concurrent request to + // load the image from the photo library service, which + // is asynchronous work. + .task(id: localIdentifier) { + await loadImageAsset() + } + // Finally, when the view disappears, we need to free it + // up from the memory + .onDisappear { + image = nil + } + } +} diff --git a/Swipe That Pic/Views/PhotoTumbnailView.swift b/Swipe That Pic/Views/PhotoTumbnailView.swift new file mode 100644 index 0000000..1cba3e4 --- /dev/null +++ b/Swipe That Pic/Views/PhotoTumbnailView.swift @@ -0,0 +1,73 @@ +// +// PhotoTumbnailView.swift +// Swipe That Pic +// +// Created by Amine Bou on 27/07/2024. +// + +import Foundation +import Photos +import SwiftUI + +struct PhotoThumbnailView: View { + @EnvironmentObject var photoLibraryService: PhotosService + @State private var image: Image? + @Binding var asset: PHAsset? + + func loadImageAsset( + targetSize: CGSize = PHImageManagerMaximumSize + ) async { + if (asset != nil) { + guard let uiImage = try? await photoLibraryService + .fetchImage( + byLocalIdentifier: asset!.localIdentifier, + targetSize: targetSize + ) else { + image = nil + return + } + image = Image(uiImage: uiImage) + } else { + image = nil + } + } + + var body: some View { + ZStack { + // Show the image if it's available + if let image = image { + GeometryReader { proxy in + NavigationLink(destination: BigPicture(image: image)) { + image + .resizable() + .aspectRatio(contentMode: .fill) + .frame( + width: proxy.size.width, + height: proxy.size.width + ) + .clipped() + } + + } + } else { + // Otherwise, show a gray rectangle with a + // spinning progress view + Rectangle() + .foregroundColor(.gray) + .aspectRatio(1, contentMode: .fit) + ProgressView() + } + } + // We need to use the task to work on a concurrent request to + // load the image from the photo library service, which + // is asynchronous work. + .task(id: asset?.localIdentifier) { + await loadImageAsset() + } + // Finally, when the view disappears, we need to free it + // up from the memory + .onDisappear { + image = nil + } + } +} diff --git a/Swipe-That-Pic-Info.plist b/Swipe-That-Pic-Info.plist new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/Swipe-That-Pic-Info.plist @@ -0,0 +1,5 @@ + + + + +