diff --git a/storage/Podfile b/storage/Podfile index 33d7c5d99..0f0b855d5 100644 --- a/storage/Podfile +++ b/storage/Podfile @@ -1,15 +1,29 @@ # StorageExample use_frameworks! -platform :ios, '10.0' - -pod 'Firebase/Analytics' -pod 'Firebase/Auth' -pod 'Firebase/Storage' target 'StorageExample' do + platform :ios, '10.0' + + pod 'Firebase/Analytics' + pod 'Firebase/Auth' + pod 'Firebase/Storage' end target 'StorageExampleSwift' do + platform :ios, '10.0' + + pod 'Firebase/Analytics' + pod 'Firebase/Auth' + pod 'Firebase/Storage' + pod 'FirebaseStorageSwift', "~> 7.0-beta" +end +target 'StorageExampleSwiftUI' do + platform :ios, '13.0' + + pod 'Firebase' + pod 'Firebase/Analytics' + pod 'Firebase/Auth' + pod 'Firebase/Storage' pod 'FirebaseStorageSwift', "~> 7.0-beta" end target 'StorageExampleTests' do diff --git a/storage/Podfile.lock b/storage/Podfile.lock index 1240b4391..98a277d3c 100644 --- a/storage/Podfile.lock +++ b/storage/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - Firebase (7.4.0): + - Firebase/Core (= 7.4.0) - Firebase/Analytics (7.4.0): - Firebase/Core - Firebase/Auth (7.4.0): @@ -81,6 +83,7 @@ PODS: - PromisesObjC (1.2.12) DEPENDENCIES: + - Firebase - Firebase/Analytics - Firebase/Auth - Firebase/Storage @@ -119,6 +122,6 @@ SPEC CHECKSUMS: nanopb: 59221d7f958fb711001e6a449489542d92ae113e PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97 -PODFILE CHECKSUM: 68e67a7f5b716247bf88b244cb2b3cc55d2e53ec +PODFILE CHECKSUM: 25b9e14f35ae5e264d830941bf9f0c8c3b02ce6b COCOAPODS: 1.10.1 diff --git a/storage/StorageExample.xcodeproj/project.pbxproj b/storage/StorageExample.xcodeproj/project.pbxproj index 007a8bfc6..2d07ed1cc 100644 --- a/storage/StorageExample.xcodeproj/project.pbxproj +++ b/storage/StorageExample.xcodeproj/project.pbxproj @@ -23,6 +23,13 @@ 5F99610C1AE0CF4F0034F503 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5F9961071AE0CF4F0034F503 /* LaunchScreen.xib */; }; 5F99610D1AE0CF4F0034F503 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5F9961071AE0CF4F0034F503 /* LaunchScreen.xib */; }; 5FDE055D1B0DAA090037B82F /* AppTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDE055C1B0DAA090037B82F /* AppTests.m */; }; + B5540064262FEC080048875B /* StorageExampleSwiftUIApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5540063262FEC080048875B /* StorageExampleSwiftUIApp.swift */; }; + B554006B262FEC080048875B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B554006A262FEC080048875B /* Preview Assets.xcassets */; }; + B5540076262FEC090048875B /* StorageExampleSwiftUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5540075262FEC090048875B /* StorageExampleSwiftUITests.swift */; }; + B55400A9262FEC960048875B /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5F9961061AE0CF4F0034F503 /* Images.xcassets */; }; + B55400B2262FEDB30048875B /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55400B1262FEDB30048875B /* ImagePicker.swift */; }; + B55400BB262FEE8F0048875B /* ImageStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55400BA262FEE8F0048875B /* ImageStore.swift */; }; + B55400BD262FEECB0048875B /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55400BC262FEECB0048875B /* ImageView.swift */; }; DE8564A823AECBB800611383 /* FIREGSignInHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8564A523AECBB700611383 /* FIREGSignInHelper.m */; }; DE8564A923AECBB800611383 /* FIREGHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8564A623AECBB700611383 /* FIREGHelper.m */; }; /* End PBXBuildFile section */ @@ -49,6 +56,13 @@ remoteGlobalIDString = 5F5A534B1ADE670C00F81DF0; remoteInfo = StorageExample; }; + B5540072262FEC090048875B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5F5A53441ADE670C00F81DF0 /* Project object */; + proxyType = 1; + remoteGlobalIDString = B5540060262FEC080048875B; + remoteInfo = StorageExampleSwiftUI; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -76,6 +90,16 @@ 5F9961071AE0CF4F0034F503 /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; 5FDE05581B0DAA090037B82F /* StorageExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StorageExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5FDE055C1B0DAA090037B82F /* AppTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppTests.m; sourceTree = ""; }; + B5540061262FEC080048875B /* StorageExampleSwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StorageExampleSwiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; }; + B5540063262FEC080048875B /* StorageExampleSwiftUIApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageExampleSwiftUIApp.swift; sourceTree = ""; }; + B554006A262FEC080048875B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + B554006C262FEC080048875B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B5540071262FEC090048875B /* StorageExampleSwiftUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StorageExampleSwiftUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + B5540075262FEC090048875B /* StorageExampleSwiftUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageExampleSwiftUITests.swift; sourceTree = ""; }; + B5540077262FEC090048875B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B55400B1262FEDB30048875B /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = ""; }; + B55400BA262FEE8F0048875B /* ImageStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageStore.swift; sourceTree = ""; }; + B55400BC262FEECB0048875B /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = ""; }; DE8564A423AECBB700611383 /* FIREGHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FIREGHelper.h; sourceTree = ""; }; DE8564A523AECBB700611383 /* FIREGSignInHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIREGSignInHelper.m; sourceTree = ""; }; DE8564A623AECBB700611383 /* FIREGHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIREGHelper.m; sourceTree = ""; }; @@ -118,6 +142,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + B554005E262FEC080048875B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B554006E262FEC090048875B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -144,12 +182,15 @@ children = ( 5F5A534E1ADE670C00F81DF0 /* StorageExample */, 5F5A537A1ADE67D500F81DF0 /* StorageExampleSwift */, + B5540062262FEC080048875B /* StorageExampleSwiftUI */, 5FDE05591B0DAA090037B82F /* StorageExampleTests */, 107347E120315A84004A66D1 /* StorageExampleSwiftUITests */, 1073487B20333C27004A66D1 /* StorageExampleUITests */, DE8564A323AECBB700611383 /* TestUtils */, + B5540074262FEC090048875B /* StorageExampleSwiftUITests */, 5F5A534D1ADE670C00F81DF0 /* Products */, 5F9961041AE0CF4F0034F503 /* Shared */, + 9FC0986D0DDD162782FD51E7 /* Pods */, ); sourceTree = ""; wrapsLines = 0; @@ -162,6 +203,8 @@ 5FDE05581B0DAA090037B82F /* StorageExampleTests.xctest */, 107347E020315A84004A66D1 /* StorageExampleSwiftUITests.xctest */, 1073487A20333C27004A66D1 /* StorageExampleUITests.xctest */, + B5540061262FEC080048875B /* StorageExampleSwiftUI.app */, + B5540071262FEC090048875B /* StorageExampleSwiftUITests.xctest */, ); name = Products; sourceTree = ""; @@ -218,6 +261,43 @@ path = StorageExampleTests; sourceTree = ""; }; + 9FC0986D0DDD162782FD51E7 /* Pods */ = { + isa = PBXGroup; + children = ( + ); + path = Pods; + sourceTree = ""; + }; + B5540062262FEC080048875B /* StorageExampleSwiftUI */ = { + isa = PBXGroup; + children = ( + B5540063262FEC080048875B /* StorageExampleSwiftUIApp.swift */, + B55400BC262FEECB0048875B /* ImageView.swift */, + B55400BA262FEE8F0048875B /* ImageStore.swift */, + B55400B1262FEDB30048875B /* ImagePicker.swift */, + B554006C262FEC080048875B /* Info.plist */, + B5540069262FEC080048875B /* Preview Content */, + ); + path = StorageExampleSwiftUI; + sourceTree = ""; + }; + B5540069262FEC080048875B /* Preview Content */ = { + isa = PBXGroup; + children = ( + B554006A262FEC080048875B /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + B5540074262FEC090048875B /* StorageExampleSwiftUITests */ = { + isa = PBXGroup; + children = ( + B5540075262FEC090048875B /* StorageExampleSwiftUITests.swift */, + B5540077262FEC090048875B /* Info.plist */, + ); + path = StorageExampleSwiftUITests; + sourceTree = ""; + }; DE8564A323AECBB700611383 /* TestUtils */ = { isa = PBXGroup; children = ( @@ -321,13 +401,48 @@ productReference = 5FDE05581B0DAA090037B82F /* StorageExampleTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + B5540060262FEC080048875B /* StorageExampleSwiftUI */ = { + isa = PBXNativeTarget; + buildConfigurationList = B5540089262FEC090048875B /* Build configuration list for PBXNativeTarget "StorageExampleSwiftUI" */; + buildPhases = ( + B554005D262FEC080048875B /* Sources */, + B554005E262FEC080048875B /* Frameworks */, + B554005F262FEC080048875B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StorageExampleSwiftUI; + productName = StorageExampleSwiftUI; + productReference = B5540061262FEC080048875B /* StorageExampleSwiftUI.app */; + productType = "com.apple.product-type.application"; + }; + B5540070262FEC090048875B /* StorageExampleSwiftUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = B554008A262FEC090048875B /* Build configuration list for PBXNativeTarget "StorageExampleSwiftUITests" */; + buildPhases = ( + B554006D262FEC090048875B /* Sources */, + B554006E262FEC090048875B /* Frameworks */, + B554006F262FEC090048875B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + B5540073262FEC090048875B /* PBXTargetDependency */, + ); + name = StorageExampleSwiftUITests; + productName = StorageExampleSwiftUITests; + productReference = B5540071262FEC090048875B /* StorageExampleSwiftUITests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 5F5A53441ADE670C00F81DF0 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0920; + LastSwiftUpdateCheck = 1240; LastUpgradeCheck = 1110; ORGANIZATIONNAME = "Google Inc."; TargetAttributes = { @@ -358,6 +473,15 @@ ProvisioningStyle = Automatic; TestTargetID = 5F5A534B1ADE670C00F81DF0; }; + B5540060262FEC080048875B = { + CreatedOnToolsVersion = 12.4; + ProvisioningStyle = Automatic; + }; + B5540070262FEC090048875B = { + CreatedOnToolsVersion = 12.4; + ProvisioningStyle = Automatic; + TestTargetID = B5540060262FEC080048875B; + }; }; }; buildConfigurationList = 5F5A53471ADE670C00F81DF0 /* Build configuration list for PBXProject "StorageExample" */; @@ -375,9 +499,11 @@ targets = ( 5F5A534B1ADE670C00F81DF0 /* StorageExample */, 5F5A53781ADE67D500F81DF0 /* StorageExampleSwift */, + B5540060262FEC080048875B /* StorageExampleSwiftUI */, 5FDE05571B0DAA090037B82F /* StorageExampleTests */, 107347DF20315A84004A66D1 /* StorageExampleSwiftUITests */, 1073487920333C27004A66D1 /* StorageExampleUITests */, + B5540070262FEC090048875B /* StorageExampleSwiftUITests */, ); }; /* End PBXProject section */ @@ -424,6 +550,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + B554005F262FEC080048875B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B554006B262FEC080048875B /* Preview Assets.xcassets in Resources */, + B55400A9262FEC960048875B /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B554006F262FEC090048875B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -474,6 +616,25 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + B554005D262FEC080048875B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B55400BD262FEECB0048875B /* ImageView.swift in Sources */, + B55400B2262FEDB30048875B /* ImagePicker.swift in Sources */, + B5540064262FEC080048875B /* StorageExampleSwiftUIApp.swift in Sources */, + B55400BB262FEE8F0048875B /* ImageStore.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B554006D262FEC090048875B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B5540076262FEC090048875B /* StorageExampleSwiftUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -492,6 +653,11 @@ target = 5F5A534B1ADE670C00F81DF0 /* StorageExample */; targetProxy = 5FDE055E1B0DAA090037B82F /* PBXContainerItemProxy */; }; + B5540073262FEC090048875B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = B5540060262FEC080048875B /* StorageExampleSwiftUI */; + targetProxy = B5540072262FEC090048875B /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -713,7 +879,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; INFOPLIST_FILE = StorageExample/Info.plist; @@ -729,7 +897,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; INFOPLIST_FILE = StorageExample/Info.plist; @@ -745,7 +915,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/StorageExample/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -763,7 +935,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/StorageExample/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -830,6 +1004,128 @@ }; name = Release; }; + B5540083262FEC090048875B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_ASSET_PATHS = "\"StorageExampleSwiftUI/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = StorageExampleSwiftUI/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.quickstart.StorageExampleSwiftUI; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + B5540084262FEC090048875B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_ASSET_PATHS = "\"StorageExampleSwiftUI/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = StorageExampleSwiftUI/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.quickstart.StorageExampleSwiftUI; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + B5540085262FEC090048875B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = StorageExampleSwiftUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.quickstart.StorageExampleSwiftUI.StorageExampleSwiftUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/StorageExampleSwiftUI.app/StorageExampleSwiftUI"; + }; + name = Debug; + }; + B5540086262FEC090048875B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = StorageExampleSwiftUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.quickstart.StorageExampleSwiftUI.StorageExampleSwiftUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/StorageExampleSwiftUI.app/StorageExampleSwiftUI"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -887,6 +1183,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + B5540089262FEC090048875B /* Build configuration list for PBXNativeTarget "StorageExampleSwiftUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B5540083262FEC090048875B /* Debug */, + B5540084262FEC090048875B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B554008A262FEC090048875B /* Build configuration list for PBXNativeTarget "StorageExampleSwiftUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B5540085262FEC090048875B /* Debug */, + B5540086262FEC090048875B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 5F5A53441ADE670C00F81DF0 /* Project object */; diff --git a/storage/StorageExampleSwiftUI/ImagePicker.swift b/storage/StorageExampleSwiftUI/ImagePicker.swift new file mode 100644 index 000000000..73dfa0551 --- /dev/null +++ b/storage/StorageExampleSwiftUI/ImagePicker.swift @@ -0,0 +1,100 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import SwiftUI +import Firebase + +/// ImagePickerRepresentable wraps a UIImagePickerController, so it is accessible through SwiftUI. +struct ImagePickerRepresentable { + enum Source { + case camera + case photoLibrary + } + + /// Denotes whether the user is taking a photo or selecting one. + var source: Source + + /// Persistent storage which retains the image. + @ObservedObject var store: ImageStore + + /// Binds to whether the image picker is visible. + @Binding var visible: Bool + + /// Completion handler that is invoked when the image picker dismisses. + var completion: () -> Void + + /// Coordinator is an internal class that acts as a delegate for the image picker. + class Coordinator: NSObject { + private var representable: ImagePickerRepresentable + private var store: ImageStore + + init(representable: ImagePickerRepresentable, store: ImageStore) { + self.representable = representable + self.store = store + } + } +} + +extension ImagePickerRepresentable: UIViewControllerRepresentable { + typealias UIViewControllerType = UIImagePickerController + + /// Invoked by the system to setup a coordinator that the UIImagePickerViewController can use. + /// - Returns: The coordinator. + func makeCoordinator() -> Coordinator { + Coordinator(representable: self, store: self.store) + } + + func makeUIViewController(context: Context) -> UIImagePickerController { + let imagePicker = UIImagePickerController() + + switch self.source { + case .camera: + imagePicker.sourceType = .camera + imagePicker.cameraCaptureMode = .photo + case .photoLibrary: + imagePicker.sourceType = .photoLibrary + } + + imagePicker.delegate = context.coordinator + return imagePicker + } + + /// Required to implement, but unnecessary. We do not need to invalidate the SwiftUI canvas. + func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) { } +} + +extension ImagePickerRepresentable.Coordinator: UIImagePickerControllerDelegate { + func imagePickerController( + _ picker: UIImagePickerController, + didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any] + ) { + if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { + // TODO: Consider displaying a progress bar or spinner here + store.saveImage(image) { result in + // TODO: Handle the error + self.representable.visible = false + picker.dismiss(animated: true, completion: self.representable.completion) + } + } + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + self.representable.visible = false + picker.dismiss(animated: true, completion: self.representable.completion) + } +} + +/// The coordinator must implement the UINavigationControllerDelegate protocol in order to be the +/// UIImagePickerController's delegate. +extension ImagePickerRepresentable.Coordinator: UINavigationControllerDelegate { } diff --git a/storage/StorageExampleSwiftUI/ImageStore.swift b/storage/StorageExampleSwiftUI/ImageStore.swift new file mode 100644 index 000000000..4ac1a6e78 --- /dev/null +++ b/storage/StorageExampleSwiftUI/ImageStore.swift @@ -0,0 +1,99 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Firebase +import FirebaseStorageSwift + +/// ImageStore facilitates saving and loading an image. +public class ImageStore: ObservableObject { + /// Reference to Firebase storage. + private var storage: Storage + + /// Quality for JPEG images where 1.0 is the best quality and 0.0 is the worst. + public var compressionQuality: CGFloat = 0.8 + + /// UserDefaults key that will have a value containing the path of the last image. + public let imagePathKey = "imagePath" + + /// Binds to the current image. + @Published var image: UIImage? + + lazy var localImageFileDirectory: String = { + let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) + let documentsDirectory = paths[0] + return "file:\(documentsDirectory)" + }() + + public init(storage: Storage) { + self.storage = storage + } + + /// Saves an image in the store. + /// - Parameter image: The image to save. + public func saveImage(_ image: UIImage, completion: @escaping (Result) -> Void) { + self.image = image + let imagePath = "\(Auth.auth().currentUser!.uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000)).jpg" + uploadImage(image, atPath: imagePath) { result in + UserDefaults.standard.setValue(imagePath, forKey: self.imagePathKey) + completion(result) + } + } + + /// Loads the most recent image. + public func loadImage() { + if let imagePath = UserDefaults.standard.string(forKey: imagePathKey) { + downloadImage(atPath: imagePath) + } + } + + /// Uploads an image to Firebase storage. + /// - Parameters: + /// - image: Image to upload. + /// - imagePath: Path of the image in Firebase storage. + private func uploadImage( + _ image: UIImage, + atPath imagePath: String, + completion: @escaping (Result) -> Void + ) { + guard let imageData = image.jpegData(compressionQuality: compressionQuality) else { return } + + let imageMetadata = StorageMetadata() + imageMetadata.contentType = "image/jpeg" + + let storageRef = storage.reference(withPath: imagePath) + storageRef.putData(imageData, metadata: imageMetadata) { result in + switch result { + case .success: + completion(.success(())) + case let .failure(error): + completion(.failure(error)) + break + } + } + } + + /// Downloads an image from Firebase storage. + /// - Parameter imagePath: Path of the image in Firebase storage. + private func downloadImage(atPath imagePath: String) { + guard let imageURL = URL(string: "\(self.localImageFileDirectory)/\(imagePath)") else { return } + self.storage.reference().child(imagePath).write(toFile: imageURL) { result in + switch result { + case let .success(downloadedFileURL): + self.image = UIImage(contentsOfFile: downloadedFileURL.path) + case let .failure(error): + print("Error downloading: \(error)") + } + } + } +} diff --git a/storage/StorageExampleSwiftUI/ImageView.swift b/storage/StorageExampleSwiftUI/ImageView.swift new file mode 100644 index 000000000..ccc797469 --- /dev/null +++ b/storage/StorageExampleSwiftUI/ImageView.swift @@ -0,0 +1,94 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import SwiftUI +import Firebase + +/// ImageView provides the main content for the app. It displays a current image and provides +/// controls to change it by taking a new one with the camera, selecting one from the photo library +/// or downloading one from Firebase storage. +struct ImageView: View { + /// Manages retrieval and persistence of the current image. + @StateObject private var photoStore = ImageStore(storage: Storage.storage()) + + /// Indicates whether the user is selecting an image from the photo library. + @State var isSelectingImage = false + + /// Indicates whether the user is taking an image using the camera. + @State var isTakingPhoto = false + + /// Indicates whether a submenu that allows the user to choose whether to select or take a photo + /// should be visible. + @State var showUploadMenu = false + + var body: some View { + NavigationView { + VStack { + Image(uiImage: photoStore.image ?? UIImage()) + .resizable() + .aspectRatio(contentMode: .fit) + } + .navigationTitle("Firebase Storage") + .toolbar { + ToolbarItemGroup(placement: .bottomBar) { + if showUploadMenu { + Button("❌") { + showUploadMenu = false + } + + Spacer() + + Button("Take Photo") { + isTakingPhoto = true + }.sheet(isPresented: $isTakingPhoto) { + ImagePickerRepresentable( + source: .camera, + store: photoStore, + visible: $isTakingPhoto + ) { + showUploadMenu = false + } + }.disabled(!UIImagePickerController.isSourceTypeAvailable(.camera)) + + Button("Select Image") { + isSelectingImage = true + }.sheet(isPresented: $isSelectingImage) { + ImagePickerRepresentable( + source: .photoLibrary, + store: photoStore, + visible: $isSelectingImage + ) { + showUploadMenu = false + } + } + } else { + Button("Upload") { + showUploadMenu = true + } + Spacer() + Button("Download") { + photoStore.loadImage() + } + } + } + } + } + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ImageView() + } +} diff --git a/storage/StorageExampleSwiftUI/Info.plist b/storage/StorageExampleSwiftUI/Info.plist new file mode 100644 index 000000000..efc211a0c --- /dev/null +++ b/storage/StorageExampleSwiftUI/Info.plist @@ -0,0 +1,50 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UIApplicationSupportsIndirectInputEvents + + UILaunchScreen + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/storage/StorageExampleSwiftUI/Preview Content/Preview Assets.xcassets/Contents.json b/storage/StorageExampleSwiftUI/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/storage/StorageExampleSwiftUI/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/storage/StorageExampleSwiftUI/StorageExampleSwiftUIApp.swift b/storage/StorageExampleSwiftUI/StorageExampleSwiftUIApp.swift new file mode 100644 index 000000000..d207e3a9f --- /dev/null +++ b/storage/StorageExampleSwiftUI/StorageExampleSwiftUIApp.swift @@ -0,0 +1,36 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import SwiftUI +import Firebase + +@main +struct StorageExampleSwiftUIApp: App { + init() { + FirebaseApp.configure() + if Auth.auth().currentUser == nil { + Auth.auth().signInAnonymously(completion: { (authResult, error) in + if let error = error { + print("Failed to sign in: \(error.localizedDescription)") + } + }) + } + } + + var body: some Scene { + WindowGroup { + ImageView() + } + } +}