
🔴 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) 등을 사용할 수 있을 것이다.