0

I'm experiencing an issue with my SwiftUI project that uses Firebase and a CoreML model for image recognition. Despite adding various debug print statements, the process doesn't proceed beyond the validation step. Below are the details of my setup and the relevant code:

Problem Description When I attempt to create a new post, the console logs "Post button tapped" and "Fields validated", but nothing happens afterward. The post is not created, and there are no further console logs to indicate what went wrong. Additionally, I'm getting several CoreGraphics and constraint-related warnings in the console.

Console Output

Model loaded successfully
Loading posts...
User document does not exist or no following data
Loaded 8 fallback posts
> > <0x13de085e0> Gesture: System gesture gate timed out.
> > <0x13de085e0> Gesture: System gesture gate timed out.
Error: this application, or a library it uses, has passed an invalid numeric value (NaN, or not-a-number) to CoreGraphics API and this value is being ignored. Please fix this problem.
If you want to see the backtrace, please set CG_NUMERICS_SHOW_BACKTRACE environmental variable.
Error: this application, or a library it uses, has passed an invalid numeric value (NaN, or not-a-number) to CoreGraphics API and this value is being ignored. Please fix this problem.
If you want to see the backtrace, please set CG_NUMERICS_SHOW_BACKTRACE environmental variable.
Post button tapped
Fields validated

`NewPostAlertState.swift`
import Foundation
import SwiftUI

enum NewPostAlertState: Identifiable {
    case error(String)
    case success(String)
    case confirmation(() -> Void)
    
    var id: String {
        switch self {
        case .error(let message):
            return message
        case .success(let message):
            return message
        case .confirmation:
            return "confirmation"
        }
    }
    
    var alert: Alert {
        switch self {
        case .error(let message):
            return Alert(title: Text("Error"), message: Text(message), dismissButton: .default(Text("OK")))
        case .success(let message):
            return Alert(title: Text("Success"), message: Text(message), dismissButton: .default(Text("OK")))
        case .confirmation(let action):
            return Alert(
                title: Text("Confirmation"),
                message: Text("Are you sure?"),
                primaryButton: .destructive(Text("Delete"), action: action),
                secondaryButton: .cancel()
            )
        }
    }
}

NewPostView.swift


import SwiftUI
import Firebase
import FirebaseFirestore
import FirebaseStorage
import CoreLocation
import Vision
import CoreML

struct NewPostView: View {
    u/EnvironmentObject var userSession: UserSession
    u/EnvironmentObject var viewRouter: ViewRouter
    u/State private var showImagePicker: Bool = false
    u/State private var usingCamera: Bool = false
    u/State private var postImage: UIImage?
    u/State private var postDescription: String = ""
    u/State private var postHashtags: String = ""
    u/State private var postLocation: String = ""
    u/State private var sneakerModel: String = ""
    u/State private var additionalDetails: String = ""
    u/State private var locationManager = CLLocationManager()
    u/State private var activeAlert: NewPostAlertState?
    u/State private var isUploading: Bool = false
    u/State private var showConfirmationAlert: Bool = false
    u/State private var showInvalidImageAlert: Bool = false 

    u/State private var modelLoaded: Bool = false

    var body: some View {
        NavigationView {
            VStack {
                if !modelLoaded {
                    Text("Failed to load the model. Please check your MLModel file.")
                        .foregroundColor(.red)
                        .padding()
                } else {
                    ScrollView {
                        VStack(spacing: 10) {
                            Text("Add Image")
                                .font(.headline)
                                .padding(.top)
                            
                            imageSection
                                .frame(height: 250)
                                .padding(.horizontal)
                            
                            Text("Post Details")
                                .font(.headline)
                                .padding(.top)

                            postDetailsSection
                                .padding(.horizontal)
                        }
                        .padding(.horizontal, 16)
                    }

                    Spacer()

                    postButton
                        .padding()
                        .background(
                            LinearGradient(gradient: Gradient(colors: [Color.white, Color.brown.opacity(0.3)]), startPoint: .top, endPoint: .bottom)
                                .edgesIgnoringSafeArea(.bottom)
                        )
                }
            }
            .navigationTitle("New Post")
            .navigationBarItems(leading: Button(action: {
                viewRouter.goToMain()
            }) {
                HStack {
                    Image(systemName: "chevron.backward")
                    Text("Back")
                }
            })
            .background(
                LinearGradient(gradient: Gradient(colors: [Color.white, Color.brown.opacity(0.3)]), startPoint: .top, endPoint: .bottom)
                    .edgesIgnoringSafeArea(.all)
            )
            .onAppear {
                loadModel()
            }
            .sheet(isPresented: $showImagePicker) {
                ImagePicker(image: $postImage, sourceType: usingCamera ? .camera : .photoLibrary)
            }
            .alert(item: $activeAlert) { alertState in
                alertState.alert
            }
            .alert(isPresented: $showConfirmationAlert) {
                Alert(
                    title: Text("Is this a sneaker image?"),
                    message: Text(""),
                    primaryButton: .default(Text("Yes"), action: validateAndUploadImage),
                    secondaryButton: .cancel(Text("No"), action: {
                        print("Image rejected by user")
                        activeAlert = .error("The selected image is not recognized as a sneaker.")
                    })
                )
            }
            .alert(isPresented: $showInvalidImageAlert) { // Alerta para imagen inválida
                Alert(
                    title: Text("Invalid Image"),
                    message: Text("The selected image is not recognized as a sneaker. Please select another image."),
                    dismissButton: .default(Text("OK"))
                )
            }
        }
    }

    private func loadModel() {
        if let _ = try? VNCoreMLModel(for: SneakerClassifier().model) {
            print("Model loaded successfully")
            modelLoaded = true
        } else {
            print("Failed to load the model")
            modelLoaded = false
        }
    }

    private var imageSection: some View {
        Button(action: {
            showImagePicker = true
            usingCamera = false
        }) {
            ZStack {
                RoundedRectangle(cornerRadius: 15)
                    .fill(Color.gray.opacity(0.2))
                    .frame(maxWidth: .infinity)
                    .overlay(RoundedRectangle(cornerRadius: 15).stroke(Color.gray, lineWidth: 2))

                if let image = postImage {
                    Image(uiImage: image)
                        .resizable()
                        .scaledToFit()
                        .clipShape(RoundedRectangle(cornerRadius: 15))
                } else {
                    VStack {
                        Image(systemName: "camera.fill")
                            .resizable()
                            .scaledToFit()
                            .frame(width: 80, height: 80)
                            .foregroundColor(.gray)
                            .shadow(radius: 10)
                        Text("Tap to add image")
                            .foregroundColor(.gray)
                    }
                }
            }
        }
        .padding()
    }

    private var postDetailsSection: some View {
        VStack(spacing: 10) {
            inputField("Write Description", text: $postDescription, icon: "pencil")
            inputField("Sneaker Model", text: $sneakerModel, icon: "tag")
            inputField("Additional Details", text: $additionalDetails, icon: "info.circle")
            
            VStack(alignment: .leading, spacing: 8) {
                HStack {
                    Image(systemName: "number")
                        .foregroundColor(.gray)
                    TextField("Add hashtags", text: $postHashtags, onCommit: addHashtag)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                        .padding(.leading, 5)
                        .frame(height: 30)
                }
                .padding()
                .background(Color.white)
                .cornerRadius(10)
                .shadow(radius: 5)
                
                Text("Hashtags: \(formattedHashtags)")
                    .font(.caption)
                    .foregroundColor(.gray)
                    .padding(.leading)
            }

            locationField
                .padding()
        }
    }

    private func inputField(_ placeholder: String, text: Binding<String>, icon: String) -> some View {
        HStack {
            Image(systemName: icon)
                .foregroundColor(.gray)
            TextField(placeholder, text: text)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .frame(height: 30)
        }
        .padding()
        .background(Color.white)
        .cornerRadius(10)
        .shadow(radius: 5)
    }

    private var locationField: some View {
        Button(action: fetchLocation) {
            HStack {
                Image(systemName: "location.fill")
                Text(postLocation.isEmpty ? "Add Location" : postLocation)
            }
            .padding()
            .foregroundColor(.primary)
            .background(Color.blue.opacity(0.2))
            .cornerRadius(10)
            .shadow(radius: 5)
            .animation(.easeInOut)


        }
    }

    private var postButton: some View {
        Button(action: {
            print("Post button tapped")
            guard validateFields() else {
                print("Validation failed")
                activeAlert = .error("Please fill in all fields.")
                return
            }
            print("Fields validated")
            showConfirmationAlert = true
        }) {
            if isUploading {
                ProgressView()
                    .progressViewStyle(CircularProgressViewStyle(tint: .white))
                    .frame(width: 20, height: 20)
            } else {
                Text("Post")
                    .frame(maxWidth: .infinity)
            }
        }
        .buttonStyle(GradientButtonStyle())
        .padding()
    }

    private func fetchLocation() {
        locationManager.requestWhenInUseAuthorization()
        if let location = locationManager.location {
            let geocoder = CLGeocoder()
            geocoder.reverseGeocodeLocation(location) { places, _ in
                postLocation = places?.first?.locality ?? "Unknown location"
            }
        }
    }

    private func validateAndUploadImage() {
        guard let image = postImage else {
            print("No image to validate")
            activeAlert = .error("Please select an image.")
            return
        }

        print("Validating image")
        // Validar la imagen con el modelo
        validateSneakerImage(image: image) { isValid in
            if isValid {
                print("Image is valid")
                uploadImageAndPost()
            } else {
                print("Image is invalid")
                showInvalidImageAlert = true // Mostrar alerta si la imagen no es válida
            }
        }
    }

    private func validateSneakerImage(image: UIImage, completion: u/escaping (Bool) -> Void) {
        guard let model = try? VNCoreMLModel(for: SneakerClassifier().model) else {
            print("Failed to load model for validation")
            completion(false)
            return
        }

        let request = VNCoreMLRequest(model: model) { request, error in
            guard let results = request.results as? [VNClassificationObservation],
                  let firstResult = results.first else {
                print("No valid results from model")
                completion(false)
                return
            }
            print("Validation result: \(firstResult.identifier) with confidence \(firstResult.confidence)")
            completion(firstResult.identifier == "sneaker" && firstResult.confidence > 0.8)
        }

        guard let ciImage = CIImage(image: image) else {
            print("Failed to convert UIImage to CIImage")
            completion(false)
            return
        }

        let handler = VNImageRequestHandler(ciImage: ciImage, options: [:])
        DispatchQueue.global(qos: .userInitiated).async {
            do {
                try handler.perform([request])
                print("Image classification request performed")
            } catch {
                print("Failed to perform classification: \(error.localizedDescription)")
                completion(false)
            }
        }
    }

    private func uploadImageAndPost() {
        guard let image = postImage, let imageData = image.jpegData(compressionQuality: 0.8) else {
            print("Failed to prepare image for upload")
            activeAlert = .error("Failed to prepare image.")
            return
        }

        print("Uploading image...")
        isUploading = true

        let storageRef = Storage.storage().reference().child("sneaker_images/\(UUID().uuidString).jpg")
        storageRef.putData(imageData, metadata: nil) { metadata, error in
            if let error = error {
                print("Error uploading image: \(error.localizedDescription)")
                activeAlert = .error("Error uploading image.")
                isUploading = false
                return
            }

            print("Image uploaded, fetching download URL...")
            storageRef.downloadURL { url, error in
                if let error = error {
                    print("Error retrieving image URL: \(error.localizedDescription)")
                    activeAlert = .error("Error retrieving image URL.")
                    isUploading = false
                    return
                }

                guard let downloadURL = url else {
                    print("No download URL found")
                    activeAlert = .error("No download URL found.")
                    isUploading = false
                    return
                }
                print("Image download URL retrieved: \(downloadURL.absoluteString)")
                createPost(imageUrl: downloadURL.absoluteString)
            }
        }
    }

    private func createPost(imageUrl: String) {
        guard let user = userSession.user else {
            print("User session not found")
            activeAlert = .error("User session not found.")
            isUploading = false
            return
        }

        let hashtagsArray = postHashtags.split(separator: " ").map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) }
        let newPost = SneakerPost(
            imageUrl: imageUrl,
            uploader: user.nickname,
            nickname: user.nickname,
            sneakerModel: sneakerModel,
            description: postDescription,
            hashtags: hashtagsArray,
            uploadDate: Date(),
            replies: [],
            additionalDetails: additionalDetails,
            likes: [],
            comments: []
        )

        print("Creating post...")
        userSession.addNewPost(newPost) { success in
            isUploading = false
            if success {
                print("Post created successfully")
                viewRouter.goToMain()
            } else {
                print("Failed to save post")
                activeAlert = .error("Failed to save post.")
            }
        }
    }

    private func addHashtag() {
        let trimmedHashtag = postHashtags.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmedHashtag.isEmpty else { return }

        var hashtags = postHashtags.split(separator: " ").map(String.init)

        if !trimmedHashtag.hasPrefix("#") {
            hashtags.append("#" + trimmedHashtag)
        } else {
            hashtags.append(trimmedHashtag)
        }

        postHashtags = hashtags.filter { !$0.isEmpty }.joined(separator: " ")
    }

    private func validateFields() -> Bool {
        return !postDescription.isEmpty &&
            !postHashtags.isEmpty &&
            !postLocation.isEmpty &&
            !sneakerModel.isEmpty &&
            !additionalDetails.isEmpty &&
            postImage != nil
    }

    private var formattedHashtags: String {
        postHashtags.split(separator: " ").map { $0.hasPrefix("#") ? $0 : "#\($0)" }.joined(separator: " ")
    }
}

struct NewPostView_Previews: PreviewProvider {
    static var previews: some View {
        NewPostView().environmentObject(UserSession()).environmentObject(ViewRouter())
    }
}

Model Loading: The model seems to load correctly based on the console output "Model loaded successfully". Is there any additional configuration or verification required for the model?

Post Creation Flow: Despite reaching the validation step ("Fields validated"), the post creation process does not proceed. What could be causing the blockage, and how can I debug this further?

CoreGraphics Warnings: There are several warnings about passing an invalid numeric value to CoreGraphics. Could this be related to my issue, and how can I resolve these warnings?

Firebase Setup: Are there any specific Firebase configurations or permissions that might be preventing the post creation from succeeding?

Any help or insights would be greatly appreciated. Thank you!

2

0

Browse other questions tagged or ask your own question.