UIImage not passed into function

75 views Asked by At

I'm trying to collect photos from a user's camera roll, display it on screen, and upon clicking a button, execute an upload to Firebase. However, the image is not being uploaded.

I've diagnosed it to be that when passing images into uploadAsJPEG or uploadAsJPEG1x1 (they display similar results), it's passing an empty array rather than an array of UIImages.

When I execute loadImages, shown below in the viewModel, the photos do indeed get appended properly. However, when passing in the images into the ImageStorageService structs, it ends up passing in an empty array of values - why is this occurring, and what's the fix?

I made a simple test view here to isolate the problem, which is:

import SwiftUI

struct TestView: View {
    @StateObject private var viewModel = OnboardingViewModel()
    @State private var isUploading = false
    var body: some View {
        VStack {
            ImageSelectorView()
            Button(action: {
                Task {
                    isUploading = true
                    await viewModel.uploadAndUpdateImageURLs(email: "[email protected]", images: viewModel.images)
                    isUploading = false
                }
            }, label: {
                Text("Save Photos")
            })
            if isUploading {
                ProgressView()
            }
        }
    }
}

#Preview {
    TestView()
}

and imageSelectorView is:

import SwiftUI

struct ImageView: View {
    var uiImage: UIImage
    var body: some View {
        Image(uiImage: uiImage)
            .resizable()
            .scaledToFit()
    }
}

struct ImageSelectorView: View {
    //@State private var images: [UIImage] = []
    //@State private var image: Image?
    //@State private var isShowingImagePicker = false
    //@State private var inputImage: UIImage?
    
    @ObservedObject private var viewModel = OnboardingViewModel()
    //@Binding var savedImages: [UIImage]
    
    var body: some View {
        NavigationView {
            VStack {
                ZStack {
                    Rectangle()
                        .fill(Color.secondary)
                    
                    Text("Tap To Select Photos")
                        .foregroundColor(.white)
                        .font(.headline)
                }
                .onTapGesture {
                    viewModel.isShowingImagePicker = true
                }
                HStack {
                    ForEach(viewModel.images.indices, id: \.self) { index in
                        ImageView(uiImage: viewModel.images[index])
                    }
                }
                

            }
            .padding([.horizontal, .bottom])
            .onChange(of: viewModel.inputImage) { _ in
                viewModel.loadImages()
            }
            .sheet(isPresented: $viewModel.isShowingImagePicker) {
                ImagePicker(image: $viewModel.inputImage)
            }
        }
    }
    
    
}

the relevant viewModel components are:

struct ImageStorageService{
    static private let storage = Storage.storage()
    ///Uploads a multiple images as JPEGs and returns the URLs for the images
    ///Runs the uploads simultaneously
    static func uploadAsJPEG(images: [UIImage], path: String, compressionQuality: CGFloat = 1) async throws -> [URL]{
        return try await withThrowingTaskGroup(of: URL.self, body: { group in
            print("images array incoming is: \(images)")
            print("images array count is: \(images.count)")
            for image in images {
                group.addTask {
                    return try await uploadAsJPEG(image: image, path: path, compressionQuality: compressionQuality)
                }
            }
            var urls: [URL] = []
            for try await url in group{
                urls.append(url)
                print("URLS array is currently: \(urls)")
            }
            
            return urls
        })
    }
    
    ///Uploads a multiple images as JPEGs and returns the URLs for the images
    ///Runs the uploads one by one
    static func uploadAsJPEG1x1(images: [UIImage], path: String, compressionQuality: CGFloat = 1) async throws -> [URL]{
        var urls: [URL] = []
        for image in images {
            let url = try await uploadAsJPEG(image: image, path: path, compressionQuality: compressionQuality)
            urls.append(url)
            print("URLS array is currently: \(urls)")
        }
        return urls
    }
    ///Uploads a single image as a JPG and returns the URL for the image
    ///Runs the uploads simultaneously
    static func uploadAsJPEG(image: UIImage, path: String, compressionQuality: CGFloat = 1) async throws -> URL{
        guard let data = image.jpegData(compressionQuality: compressionQuality) else{
            throw ServiceError.unableToGetData
        }
        
        return try await upload(imageData: data, path: path)
    }
    ///Uploads Data to the designated path in `FirebaseStorage`
    static func upload(imageData: Data, path: String) async throws -> URL{
        let storageRef = storage.reference()
        let imageRef = storageRef.child(path)
        
        let metadata = try await imageRef.putDataAsync(imageData)
        return try await imageRef.downloadURL()
    }
    
    enum ServiceError: LocalizedError{
        case unableToGetData
    }
}

@Published var images: [UIImage] = []
@Published var inputImage: UIImage?

func uploadAndUpdateImageURLs(email: String, images: [UIImage]) async {
        print("uploadAndUpdateImageURLs Reached")
        
        do {
            let imageURLs = try await ImageStorageService.uploadAsJPEG(images: images, path: "users")
            print("ImageURLs is currently: \(imageURLs)")
            print("Count of imageURLs is: \(imageURLs.count)")
            for imageURL in imageURLs {
                await updateImageURLs(email: email, imageURL: imageURL.absoluteString)
            }
        } catch {
            print("error uploading and updating imageURLs: \(error.localizedDescription)")
        }
    }

func loadImages() {
        guard let inputImage = inputImage else { return }
        images.append(inputImage)
        print("images array is currently \(images)")
    }
1

There are 1 answers

1
workingdog support Ukraine On BEST ANSWER

You have multiple OnboardingViewModel(), such as in TestView and then again in ImageSelectorView. These have no relations to each other. One has the images and the other has not.

You should have only one source of truth, in TestView and pass that model to the other views.

For example, in your TestView use:

 ImageSelectorView(viewModel: viewModel)  // <--- here

then

struct ImageSelectorView: View {
   //...
   @ObservedObject private var viewModel: OnboardingViewModel  // <--- here