-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP Add SwiftUI Quickstart for Storage
- Loading branch information
1 parent
5694c29
commit ac7f271
Showing
9 changed files
with
1,085 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// | ||
// ImagePicker.swift | ||
// StorageExampleSwiftUI | ||
// | ||
// Created by Ben Reed on 3/25/21. | ||
// Copyright © 2021 Google Inc. All rights reserved. | ||
// | ||
|
||
import SwiftUI | ||
import Firebase | ||
|
||
struct ImagePickerRepresentable { | ||
enum Source { | ||
case camera | ||
case photoLibrary | ||
} | ||
|
||
var source: Source | ||
@ObservedObject var store: ImageStore | ||
@Binding var visible: Bool | ||
var completion: () -> Void | ||
|
||
class Coordinator: NSObject { | ||
private var representable: ImagePickerRepresentable | ||
private var store: ImageStore | ||
|
||
init(representable: ImagePickerRepresentable, store: ImageStore) { | ||
self.representable = representable | ||
self.store = store | ||
} | ||
} | ||
} | ||
|
||
extension ImagePickerRepresentable.Coordinator: UIImagePickerControllerDelegate { | ||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { | ||
guard let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { return } | ||
|
||
let imagePath = "\(Auth.auth().currentUser!.uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000)).jpg" | ||
self.store.uploadImage(image, atPath: imagePath) | ||
UserDefaults.standard.setValue(imagePath, forKey: self.store.imagePathKey) | ||
|
||
self.store.image = image | ||
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) | ||
} | ||
} | ||
|
||
extension ImagePickerRepresentable.Coordinator: UINavigationControllerDelegate { } | ||
|
||
extension ImagePickerRepresentable: UIViewControllerRepresentable { | ||
typealias UIViewControllerType = UIImagePickerController | ||
|
||
func makeCoordinator() -> Coordinator { | ||
Coordinator(representable: self, store: self.store) | ||
} | ||
|
||
func makeUIViewController(context: Context) -> UIImagePickerController { | ||
guard UIImagePickerController.isSourceTypeAvailable(.camera) else { return UIImagePickerController() } | ||
let imagePicker = UIImagePickerController() | ||
|
||
if self.source == .camera { | ||
imagePicker.sourceType = .camera | ||
imagePicker.cameraCaptureMode = .photo | ||
} else { | ||
imagePicker.sourceType = .photoLibrary | ||
} | ||
|
||
imagePicker.delegate = context.coordinator | ||
return imagePicker | ||
} | ||
|
||
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) { } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// | ||
// ImageStore.swift | ||
// StorageExampleSwiftUI | ||
// | ||
// Created by Ben Reed on 3/25/21. | ||
// Copyright © 2021 Google Inc. All rights reserved. | ||
// | ||
|
||
import Firebase | ||
import FirebaseStorageSwift | ||
|
||
public class ImageStore: ObservableObject { | ||
private var storage: Storage | ||
|
||
public var compressionQuality: CGFloat = 0.8 | ||
|
||
public let imagePathKey = "imagePath" | ||
|
||
@Published var image: UIImage? | ||
|
||
lazy var photoFileDirectory: String = { | ||
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) | ||
let documentsDirectory = paths[0] | ||
return "file:\(documentsDirectory)" | ||
}() | ||
|
||
public init(withStorage storage: Storage) { | ||
self.storage = storage | ||
} | ||
|
||
public func uploadImage(_ image: UIImage, atPath imagePath: String) { | ||
guard let imageData = image.jpegData(compressionQuality: 0.8) 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: | ||
break | ||
case let .failure(error): | ||
_ = error | ||
break | ||
} | ||
} | ||
} | ||
|
||
public func downloadImage(atPath imagePath: String) { | ||
guard let imageURL = URL(string: "\(self.photoFileDirectory)/\(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)") | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// | ||
// ImageView.swift | ||
// StorageExampleSwiftUI | ||
// | ||
// Created by Ben Reed on 2/10/21. | ||
// Copyright © 2021 Google Inc. All rights reserved. | ||
// | ||
|
||
import SwiftUI | ||
import Firebase | ||
|
||
struct ImageView: View { | ||
@StateObject private var photoStore = ImageStore(withStorage: Storage.storage()) | ||
|
||
@State var isSelectingImage = false | ||
@State var isTakingPhoto = false | ||
@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("Upload") { | ||
showUploadMenu = true | ||
} | ||
Spacer() | ||
Button("Download") { | ||
self.downloadImage() | ||
} | ||
} else { | ||
Button("❌") { | ||
showUploadMenu = false | ||
} | ||
|
||
Spacer() | ||
|
||
Button("Take Photo") { | ||
isTakingPhoto = true | ||
}.sheet(isPresented: $isTakingPhoto) { | ||
ImagePickerRepresentable(source: .camera, store: photoStore, visible: $isTakingPhoto, completion: { | ||
showUploadMenu = false | ||
}) | ||
}.disabled(!UIImagePickerController.isSourceTypeAvailable(.camera)) | ||
|
||
Button("Select Image") { | ||
isSelectingImage = true | ||
}.sheet(isPresented: $isSelectingImage) { | ||
ImagePickerRepresentable(source: .photoLibrary, store: photoStore, visible: $isSelectingImage, completion: { | ||
showUploadMenu = false | ||
}) | ||
} | ||
} | ||
} | ||
} | ||
}.onAppear { | ||
downloadImage() | ||
} | ||
} | ||
|
||
func downloadImage() { | ||
UserDefaults.standard.synchronize() | ||
guard let imagePath = UserDefaults.standard.string(forKey: self.photoStore.imagePathKey) else { return } | ||
self.photoStore.downloadImage(atPath: imagePath) | ||
} | ||
} | ||
|
||
struct ContentView_Previews: PreviewProvider { | ||
static var previews: some View { | ||
ImageView() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>CFBundleDevelopmentRegion</key> | ||
<string>$(DEVELOPMENT_LANGUAGE)</string> | ||
<key>CFBundleExecutable</key> | ||
<string>$(EXECUTABLE_NAME)</string> | ||
<key>CFBundleIdentifier</key> | ||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||
<key>CFBundleInfoDictionaryVersion</key> | ||
<string>6.0</string> | ||
<key>CFBundleName</key> | ||
<string>$(PRODUCT_NAME)</string> | ||
<key>CFBundlePackageType</key> | ||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> | ||
<key>CFBundleShortVersionString</key> | ||
<string>1.0</string> | ||
<key>CFBundleVersion</key> | ||
<string>1</string> | ||
<key>LSRequiresIPhoneOS</key> | ||
<true/> | ||
<key>NSCameraUsageDescription</key> | ||
<string>Take and upload photos to cloud storage</string> | ||
<key>NSPhotoLibraryUsageDescription</key> | ||
<string>Upload photos to cloud storage</string> | ||
<key>UIApplicationSceneManifest</key> | ||
<dict> | ||
<key>UIApplicationSupportsMultipleScenes</key> | ||
<true/> | ||
</dict> | ||
<key>UIApplicationSupportsIndirectInputEvents</key> | ||
<true/> | ||
<key>UILaunchScreen</key> | ||
<dict/> | ||
<key>UIRequiredDeviceCapabilities</key> | ||
<array> | ||
<string>armv7</string> | ||
</array> | ||
<key>UISupportedInterfaceOrientations</key> | ||
<array> | ||
<string>UIInterfaceOrientationPortrait</string> | ||
<string>UIInterfaceOrientationLandscapeLeft</string> | ||
<string>UIInterfaceOrientationLandscapeRight</string> | ||
</array> | ||
<key>UISupportedInterfaceOrientations~ipad</key> | ||
<array> | ||
<string>UIInterfaceOrientationPortrait</string> | ||
<string>UIInterfaceOrientationPortraitUpsideDown</string> | ||
<string>UIInterfaceOrientationLandscapeLeft</string> | ||
<string>UIInterfaceOrientationLandscapeRight</string> | ||
</array> | ||
</dict> | ||
</plist> |
6 changes: 6 additions & 0 deletions
6
storage/StorageExampleSwiftUI/Preview Content/Preview Assets.xcassets/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
storage/StorageExampleSwiftUI/StorageExampleSwiftUIApp.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// | ||
// StorageExampleSwiftUIApp.swift | ||
// StorageExampleSwiftUI | ||
// | ||
// Created by Ben Reed on 2/10/21. | ||
// Copyright © 2021 Google Inc. All rights reserved. | ||
// | ||
|
||
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() | ||
} | ||
} | ||
} |