ライブラリから画像を選択してFirebaseStorageにアップロードする方法
この記事では画像ライブラリから画像を選択して、Firebase Storageにアップロードする方法を書いていきます。さらにはアップロード後にFirestoreへ画像URLを保存する方法も書いていきます。
1.準備
まずはStoryboardの設定からやっていきます。
UIパーツの役割はそれぞれ以下のようになっています。
- 選択した画像を表示するUIImageView
- 画像ライブラリを呼び出す青いボタン
- アップロードを開始する赤いボタン
2. 画像ライブラリから任意の画像を選択
まずは画像ライブラリから選択した画像をUIImageViewに表示させる部分からやっていきます。
ViewControllerに下記を追加します。
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 |
import UIKit class ViewController: UIViewController { @IBOutlet weak var imageView: UIImageView! let imagePicker = UIImagePickerController() override func viewDidLoad() { super.viewDidLoad() imagePicker.delegate = self } @IBAction func presentImagePicker(_ sender: Any) { imagePicker.allowsEditing = true //画像の切り抜きが出来るようになります。 imagePicker.sourceType = .photoLibrary //画像ライブラリを呼び出します present(imagePicker, animated: true, completion: nil) } } extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { if let pickedImage = info[UIImagePickerController.InfoKey.editedImage] as? UIImage { imageView.contentMode = .scaleAspectFit imageView.image = pickedImage } dismiss(animated: true, completion: nil) } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { dismiss(animated: true, completion: nil) } } |
選択した画像は allowsEditingオプションによって正方形に切り抜くことが出来ます。アスペクト比は変更出来ないので、16:9などに切り抜きたい場合は外部ライブラリなどを利用すると良いと思います。
画像選択後に呼び出させる didFinishPickingMediaWithInfoから編集後の画像を取得するには info[UIImagePickerController.InfoKey.editedImage] を指定する必要があります。編集前の画像は info[UIImagePickerController.InfoKey.originalImage]から取得することが出来ます。
3. 選択した画像をFirebase Storageへアップロード
次に選択した画像をFirebase Storageへアップロードする方法を書いていきます。FirebaseStorageの初期設定などは公式ページ に詳しく書いてあります。
ViewControllerに以下を加えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
@IBAction func startUpload(_ sender: Any) { upload() } fileprivate func upload() { let date = NSDate() let currentTimeStampInSecond = UInt64(floor(date.timeIntervalSince1970 * 1000)) let storageRef = Storage.storage().reference().child("images").child("\(currentTimeStampInSecond).jpg") let metaData = StorageMetadata() metaData.contentType = "image/jpg" if let uploadData = self.imageView.image?.jpegData(compressionQuality: 0.9) { storageRef.putData(uploadData, metadata: metaData) { (metadata , error) in if error != nil { print("error: \(error?.localizedDescription)") } storageRef.downloadURL(completion: { (url, error) in if error != nil { print("error: \(error?.localizedDescription)") } print("url: \(url?.absoluteString)") }) } } } |
この時点でViewControllerは下記のようになっています。
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 |
import UIKit import FirebaseStorage class ViewController: UIViewController { @IBOutlet weak var imageView: UIImageView! let imagePicker = UIImagePickerController() override func viewDidLoad() { super.viewDidLoad() imagePicker.delegate = self } @IBAction func presentImagePicker(_ sender: Any) { imagePicker.allowsEditing = true //画像の切り抜きが出来るようになります。 imagePicker.sourceType = .photoLibrary //画像ライブラリを呼び出します present(imagePicker, animated: true, completion: nil) } @IBAction func startUpload(_ sender: Any) { upload() } fileprivate func upload() { let date = NSDate() let currentTimeStampInSecond = UInt64(floor(date.timeIntervalSince1970 * 1000)) let storageRef = Storage.storage().reference().child("images").child("\(currentTimeStampInSecond).jpg") let metaData = StorageMetadata() metaData.contentType = "image/jpg" if let uploadData = self.imageView.image?.jpegData(compressionQuality: 0.9) { storageRef.putData(uploadData, metadata: metaData) { (metadata , error) in if error != nil { print("error: \(error?.localizedDescription)") } storageRef.downloadURL(completion: { (url, error) in if error != nil { print("error: \(error?.localizedDescription)") } print("url: \(url?.absoluteString)") }) } } } } extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { if let pickedImage = info[UIImagePickerController.InfoKey.editedImage] as? UIImage { imageView.contentMode = .scaleAspectFit imageView.image = pickedImage } dismiss(animated: true, completion: nil) } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { dismiss(animated: true, completion: nil) } } |
おまけその①:選択した画像を自由に切り抜きできるようにする
UIImagePickerControllerでは自由に切り抜きが出来ないので、前述した通りに外部ライブラリを使います。
下記のライブラリを使います:
まずはStoryboardに選択した画像を切り抜くためのボタンを追加します。
画像の切り抜きを外部ライブラリで行うので、UIImagePickerControllerのallowsEditingをfalseに変更し、選択後の画像もinfo[UIImagePickerController.InfoKey.originalImage]から取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@IBAction func presentImagePicker(_ sender: Any) { imagePicker.allowsEditing = false //画像の切り抜きが出来ないように imagePicker.sourceType = .photoLibrary //画像ライブラリを呼び出します present(imagePicker, animated: true, completion: nil) } func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { if let pickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { imageView.contentMode = .scaleAspectFit imageView.image = pickedImage } dismiss(animated: true, completion: nil) } |
そして、ViewControllerに以下を追加します。
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 |
@IBAction func presentCropViewController(_ sender: Any) { guard let chosenImage = self.imageView.image else { dismiss(animated: true, completion: nil) return } let cropViewController = CropViewController(image: chosenImage) cropViewController.doneButtonTitle = "切り抜く" cropViewController.cancelButtonTitle = "キャンセル" cropViewController.delegate = self self.present(cropViewController, animated: true, completion: nil) } extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { if let pickedImage = info[UIImagePickerController.InfoKey.editedImage] as? UIImage { imageView.contentMode = .scaleAspectFit imageView.image = pickedImage } dismiss(animated: true, completion: nil) } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { dismiss(animated: true, completion: nil) } } extension ViewController: CropViewControllerDelegate { func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) { imageView.contentMode = .scaleAspectFit imageView.image = image dismiss(animated: true, completion: nil) } func cropViewController(_ cropViewController: CropViewController, didFinishCancelled cancelled: Bool) { dismiss(animated: true, completion: nil) } } |
最終的にViewControllerは下記のようになっています。
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 |
import UIKit import FirebaseStorage import CropViewController class ViewController: UIViewController { @IBOutlet weak var imageView: UIImageView! let imagePicker = UIImagePickerController() override func viewDidLoad() { super.viewDidLoad() imagePicker.delegate = self } @IBAction func presentImagePicker(_ sender: Any) { imagePicker.allowsEditing = false //画像の切り抜きが出来ないように imagePicker.sourceType = .photoLibrary //画像ライブラリを呼び出します present(imagePicker, animated: true, completion: nil) } @IBAction func startUpload(_ sender: Any) { upload() } @IBAction func presentCropViewController(_ sender: Any) { guard let chosenImage = self.imageView.image else { dismiss(animated: true, completion: nil) return } let cropViewController = CropViewController(image: chosenImage) cropViewController.doneButtonTitle = "切り抜く" cropViewController.cancelButtonTitle = "キャンセル" cropViewController.delegate = self self.present(cropViewController, animated: true, completion: nil) } fileprivate func upload() { let date = NSDate() let currentTimeStampInSecond = UInt64(floor(date.timeIntervalSince1970 * 1000)) let storageRef = Storage.storage().reference().child("images").child("\(currentTimeStampInSecond).jpg") let metaData = StorageMetadata() metaData.contentType = "image/jpg" if let uploadData = self.imageView.image?.jpegData(compressionQuality: 0.9) { storageRef.putData(uploadData, metadata: metaData) { (metadata , error) in if error != nil { print("error: \(error?.localizedDescription)") } storageRef.downloadURL(completion: { (url, error) in if error != nil { print("error: \(error?.localizedDescription)") } print("url: \(url?.absoluteString)") }) } } } } extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { if let pickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { imageView.contentMode = .scaleAspectFit imageView.image = pickedImage } dismiss(animated: true, completion: nil) } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { dismiss(animated: true, completion: nil) } } extension ViewController: CropViewControllerDelegate { func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) { imageView.contentMode = .scaleAspectFit imageView.image = image dismiss(animated: true, completion: nil) } func cropViewController(_ cropViewController: CropViewController, didFinishCancelled cancelled: Bool) { dismiss(animated: true, completion: nil) } } |
おまけその②:画像アップロード後にFirestoreに画像URLを保存する
画像アップロード後に返ってきた保存先URLをFirestoreへ保存する方法を書いていきます。
まずはアップロードが完了したタイミングがわからないといけないので、upload functionにcompletion handlerを付け加えます。upload functionを以下のように変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
fileprivate func upload(completed: @escaping(_ url: String?) -> Void) { let date = NSDate() let currentTimeStampInSecond = UInt64(floor(date.timeIntervalSince1970 * 1000)) let storageRef = Storage.storage().reference().child("images").child("\(currentTimeStampInSecond).jpg") let metaData = StorageMetadata() metaData.contentType = "image/jpg" if let uploadData = self.imageView.image?.jpegData(compressionQuality: 0.9) { storageRef.putData(uploadData, metadata: metaData) { (metadata , error) in if error != nil { completed(nil) print("error: \(error?.localizedDescription)") } storageRef.downloadURL(completion: { (url, error) in if error != nil { completed(nil) print("error: \(error?.localizedDescription)") } completed(url?.absoluteString) }) } } } |
次に、返ってきたURLをFirestoreへ保存します。ViewControllerに以下のfunctionを加えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
fileprivate func saveToFireStore(){ var data: [String : Any] = [:] upload(){ url in guard let url = url else {return } data["image"] = url Firestore.firestore().collection("images").document().setData(data){ error in if error != nil { print("error: \(error?.localizedDescription)") } print("image saved!") } } } |
そして最後に、アップロードボタンから呼び出すfunctionをsaveToFirestore functionに変更します。
1 2 3 |
@IBAction func startUpload(_ sender: Any) { saveToFireStore() } |
最終的にViewControllerは以下のようになっています。
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 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
import UIKit import FirebaseStorage import FirebaseFirestore import CropViewController class ViewController: UIViewController { @IBOutlet weak var imageView: UIImageView! let imagePicker = UIImagePickerController() override func viewDidLoad() { super.viewDidLoad() imagePicker.delegate = self } @IBAction func presentImagePicker(_ sender: Any) { imagePicker.allowsEditing = false //画像の切り抜きが出来ないように imagePicker.sourceType = .photoLibrary //画像ライブラリを呼び出します present(imagePicker, animated: true, completion: nil) } @IBAction func startUpload(_ sender: Any) { saveToFireStore() } @IBAction func presentCropViewController(_ sender: Any) { guard let chosenImage = self.imageView.image else { dismiss(animated: true, completion: nil) return } let cropViewController = CropViewController(image: chosenImage) cropViewController.doneButtonTitle = "切り抜く" cropViewController.cancelButtonTitle = "キャンセル" cropViewController.delegate = self self.present(cropViewController, animated: true, completion: nil) } fileprivate func upload(completed: @escaping(_ url: String?) -> Void) { let date = NSDate() let currentTimeStampInSecond = UInt64(floor(date.timeIntervalSince1970 * 1000)) let storageRef = Storage.storage().reference().child("images").child("\(currentTimeStampInSecond).jpg") let metaData = StorageMetadata() metaData.contentType = "image/jpg" if let uploadData = self.imageView.image?.jpegData(compressionQuality: 0.9) { storageRef.putData(uploadData, metadata: metaData) { (metadata , error) in if error != nil { completed(nil) print("error: \(error?.localizedDescription)") } storageRef.downloadURL(completion: { (url, error) in if error != nil { completed(nil) print("error: \(error?.localizedDescription)") } completed(url?.absoluteString) }) } } } fileprivate func saveToFireStore(){ var data: [String : Any] = [:] upload(){ url in guard let url = url else {return } data["image"] = url Firestore.firestore().collection("images").document().setData(data){ error in if error != nil { print("error: \(error?.localizedDescription)") } print("image saved!") } } } } extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { if let pickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { imageView.contentMode = .scaleAspectFit imageView.image = pickedImage } dismiss(animated: true, completion: nil) } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { dismiss(animated: true, completion: nil) } } extension ViewController: CropViewControllerDelegate { func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) { imageView.contentMode = .scaleAspectFit imageView.image = image dismiss(animated: true, completion: nil) } func cropViewController(_ cropViewController: CropViewController, didFinishCancelled cancelled: Bool) { dismiss(animated: true, completion: nil) } } |