🔴 Let's Build Twitter with SwiftUI (iOS 15, Xcode 13, Firebase, SwiftUI 3.0)
PHPickerViewController
를 통한 이미지 등록 뷰 구현func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
guard
let itemProvider = results.first?.itemProvider,
itemProvider.canLoadObject(ofClass: UIImage.self) else { return }
itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
guard
let image = image as? UIImage,
error == nil else { return }
self?.parent.selectedImage = image
}
}
UIViewControllerRepresentable
클래스.sheet(isPresented: $showImagePicker, onDismiss: loadImage) {
ImagePicker(selectedImage: $selectedImage)
}
onDismiss
함수에서 적용private func loadImage() {
guard let selectedImage = selectedImage else { return }
profileImage = Image(uiImage: selectedImage)
}
profileImage
에 값을 줌func uploadProfileImage(with image: UIImage) {
guard let uid = tempUserSession?.uid else { return }
ImageUploader.uploadImage(image: image) { result in
switch result {
case .success(let urlString):
let data = ["profileImageURL": urlString]
Firestore.firestore().collection("users")
.document(uid)
.setData(data, merge: true) { [weak self] error in
if let error = error {
print(error.localizedDescription)
print("Profile Upload Did Fail")
} else {
self?.userSession = self?.tempUserSession
self?.tempUserSession = nil
}
}
case .failure(let error):
print(error.localizedDescription)
}
}
}
import SwiftUI
import PhotosUI
struct ImagePicker: UIViewControllerRepresentable {
@Binding var selectedImage: UIImage?
@Environment(\.dismiss) var dismiss
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
func makeUIViewController(context: Context) -> some UIViewController {
let configuration = PHPickerConfiguration()
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
extension ImagePicker {
class Coordinator: NSObject, UINavigationControllerDelegate, PHPickerViewControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
guard
let itemProvider = results.first?.itemProvider,
itemProvider.canLoadObject(ofClass: UIImage.self) else { return }
itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
guard
let image = image as? UIImage,
error == nil else { return }
self?.parent.selectedImage = image
}
}
}
}
Coordinator
등 프로토콜을 따르는 데 필요한 함수를 적용한 뒤 서용selectedImage
값을 변경함으로써 해당 뷰에서 이미지를 사용 가능import SwiftUI
struct ProfilePhotoSelectorView: View {
@State private var showImagePicker = false
@State private var selectedImage: UIImage?
@State private var profileImage: Image?
@EnvironmentObject private var viewModel: AuthViewModel
var body: some View {
VStack {
AuthenticationHeaderView(title: "Create your account\nAdd a profile photo")
Button {
showImagePicker.toggle()
} label: {
if let profileImage = profileImage {
profileImage
.resizable()
.scaledToFill()
.frame(width: 180, height: 180)
.clipShape(Circle())
} else {
Image("tweet_plus")
.resizable()
.scaledToFit()
.frame(width: 180, height: 180)
}
}
.sheet(isPresented: $showImagePicker, onDismiss: loadImage) {
ImagePicker(selectedImage: $selectedImage)
}
.padding(.top, 44)
if let selectedImage = selectedImage {
Button {
viewModel.uploadProfileImage(with: selectedImage)
} label: {
Text("Continue")
.font(.headline)
.foregroundColor(.white)
.frame(width: 340, height: 50)
.background(Color(.systemBlue))
.clipShape(Capsule())
.padding()
}
.shadow(color: .gray.opacity(0.5), radius: 10, x: 0, y: 0)
}
Spacer()
}
.ignoresSafeArea()
}
}
extension ProfilePhotoSelectorView {
private func loadImage() {
guard let selectedImage = selectedImage else { return }
profileImage = Image(uiImage: selectedImage)
}
}
func uploadProfileImage(with image: UIImage) {
guard let uid = tempUserSession?.uid else { return }
ImageUploader.uploadImage(image: image) { result in
switch result {
case .success(let urlString):
let data = ["profileImageURL": urlString]
Firestore.firestore().collection("users")
.document(uid)
.setData(data, merge: true) { [weak self] error in
if let error = error {
print(error.localizedDescription)
print("Profile Upload Did Fail")
} else {
self?.userSession = self?.tempUserSession
self?.tempUserSession = nil
}
}
case .failure(let error):
print(error.localizedDescription)
}
}
}
userSession
값으로 넘겨받은 임시 변수를 통해 데이터베이스에 접근 가능import Foundation
import SwiftUI
import FirebaseStorage
struct ImageUploader {
static func uploadImage(image: UIImage, completion: @escaping(Result<String, Error>) -> Void) {
guard let imageData = image.jpegData(compressionQuality: 0.80) else { return }
let fileName = UUID().uuidString
let ref = Storage.storage().reference(withPath: "/profile_image/\(fileName)")
ref.putData(imageData) { _, error in
if let error = error {
print(error.localizedDescription)
print("Failed to upload image with error")
completion(.failure(error))
return
}
ref.downloadURL { imageURL, error in
if let error = error {
completion(.failure(error))
return
} else if let imageURLString = imageURL?.absoluteString {
completion(.success(imageURLString))
return
} else {
completion(.failure(URLError(.badURL)))
return
}
}
}
}
}
static
함수를 통해 해당 클래스의 타입으로 접근 가능Result<>
타입을 리턴하는 컴플리션 핸들러파이어베이스 스토리지에 사진을 업로드하는 중 인터렉션을 막기 위해 버튼 클릭 이벤트를 막거나, 도중 스피너(
ProgressView
) 등을 사용할 수 있을 것이다.