skip to Main Content

I just tried to make an API app in SwiftUI with loveCalculator from rapidapi.com

The problem is that API first needs names from me before it gives me the results.

My program works but fetching data form API earlier that I want (when I click to show my data in next view, first show default data, then show the data that should be displayed when I click).

Also Is it possible to initialize @Published var loveData (in LoveViewModel) without passing any default data or empty String?
Something like make the data from LoveData optional ?

Can You tell me when I make mistake?

MY CODE IS :

LoveData (for api)

struct LoveData: Codable {
    let percentage: String
    let result: String
}

LoveViewModel

class LoveViewModel: ObservableObject {
    
    @Published var loveData = LoveData(percentage: "50", result: "aaa")
    
    let baseURL = "https://love-calculator.p.rapidapi.com/getPercentage?"
    let myApi = "c6c134a7f0msh980729b528fe273p1f337fjsnd17137cb2f24"
    
    func loveCal (first: String, second: String) async {
        let completedurl = "(baseURL)&rapidapi-key=(myApi)&sname=(first)&fname=(second)"
      
        guard let url = URL(string: completedurl) else {return }
        
        do {
            let decoder = JSONDecoder()
            let (data, _) = try await URLSession.shared.data(from: url)
            
            if let safeData = try? decoder.decode(LoveData.self, from: data) {
                print("succesfully saved data")
                
                    self.loveData = safeData
                
                
            }
        } catch {
            print(error)
        }
    }
     
}

LoveCalculator View

struct LoveCalculator: View {
    @State var firstName = ""
    @State var secondName = ""
    @ObservedObject var loveViewModel = LoveViewModel()
    var body: some View {
        NavigationView {
            ZStack{
                Color(.systemTeal).ignoresSafeArea()
                VStack(alignment: .center) {
                    Text("Love Calculator")
                        .padding(.top, 100)
                    Text("Enter first name:")
                        .padding()
                    TextField("first name", text: $firstName)
                        .textFieldStyle(.roundedBorder)
                        .frame(maxWidth: 300)
                        .padding(.bottom)
                    
                    Text("Enter second name:")
                        .padding()
                    TextField("second name", text: $secondName, onEditingChanged: { isBegin in
                        if isBegin == false {
                            print("finish get names")
                            
                        }
                    })
                        .textFieldStyle(.roundedBorder)
                        .frame(maxWidth: 300)
                        .padding(.bottom)
                    
                    
                    NavigationLink {
                        LoveResults(percentage: loveViewModel.loveData.percentage, description: loveViewModel.loveData.result)
                    } label: {
                        Text("CHECK IT!")
                    }




                    
                    Spacer()
                }
                    
                
            }
            .task {
                
               
               
                await loveViewModel.loveCal(first: firstName, second: secondName)
                
            }
            
        }
    }
}

LoveResults View

struct LoveResults: View {
    var percentage: String
    var description: String
    var body: some View {
        ZStack{
            Color(.green).ignoresSafeArea()
            VStack{
                Text("RESULTS :")
                    .padding()
                Text(percentage)
                    .font(.system(size: 80, weight: .heavy))
                    .foregroundColor(.red)
                    .padding()
                Text(description)
            }
        }
    }
}

Thanks for help!
Regards,
Michal

2

Answers


  1. Chosen as BEST ANSWER

    It was very helpful but still not enough for me, .onChange work even if I was type 1 letter in my Form.

    I find out how to use Task { } and .onCommit on my TextField, now everything working well !

    My code now looks like that :

    LoveCalculator View :

    struct LoveCalculator: View {
        @State var firstName = ""
        @State var secondName = ""
        var namesAreValid: Bool {
            !firstName.isEmpty && !secondName.isEmpty
        }
        
        func makeApiCall() {
            if namesAreValid {
                DispatchQueue.main.async {
                    Task {
                       await self.loveViewModel.loveCal(first: firstName, second: secondName)
                    }
                }
            }
        }
        
     
        @ObservedObject var loveViewModel = LoveViewModel()
        var body: some View {
            NavigationView {
                ZStack{
                    Color(.systemTeal).ignoresSafeArea()
                    VStack(alignment: .center) {
                        Text("Love Calculator")
                            .padding(.top, 100)
                        Text("Enter first name:")
                            .padding()
                        TextField("first name", text: $firstName, onCommit: {makeApiCall()})
                            .textFieldStyle(.roundedBorder)
                            .frame(maxWidth: 300)
                            .padding(.bottom)
                        
                        
                        Text("Enter second name:")
                            .padding()
                        TextField("second name", text: $secondName, onCommit: {
                            makeApiCall()
                        })
                            .textFieldStyle(.roundedBorder)
                            .frame(maxWidth: 300)
                            .padding(.bottom)
    
                        NavigationLink {
                            LoveResults(percentage: loveViewModel.loveData.percentage ?? "", description: loveViewModel.loveData.result ?? "")
                        } label: {
                            Text("CHECK IT!")
                        }
    
    
                        Spacer()
                    }
     
                }
            }
        }
    }
    

    Thank You one more time for very helpful tip! Peace! Michał ;)

    Editing :

    Just look at Apple documentation and I see that they say that .onCommit is deprecated whatever it means.

    So instead of this I use .onSubmit and works the same !

    TextField("second name", text: $secondName)
                            .textFieldStyle(.roundedBorder)
                            .frame(maxWidth: 300)
                            .padding(.bottom)
                            .onSubmit {
                                makeApiCall()
                            }
    

    Peace! :)


  2. .task is the same modifier as .onAppear in terms of view lifecycle, meaning it will fire immediately when the view appears. You have to move your API call to a more controlled place, where you can call it once you have all the required data.

    If you want to fire the API request only after both names are entered, you can create a computed variable, that checks for desired state of TextFields and when that variable turns to true, then you call the API.

    Like in this example:

    struct SomeView: View {
        @State var firstName: String = ""
        @State var secondName: String = ""
    
        var namesAreValid: Bool {
            !firstName.isEmpty && !secondName.isEmpty // << validation logic here
        }
    
        var body: some View {
            Form {
                TextField("Enter first name", text: $firstName)
                TextField("Enter second name", text: $secondName)
            }
            .onChange(of: namesAreValid) { isValid in
                if isValid {
                    // Call API
                }
            }
        }
    }
    

    You can also set your loveData to optional using @Published var loveData: LoveData? and disable/hide the navigation link, until your data is not nil. In that case, you might need to provide default values with ?? to handle optional string errors in LoveResults view initializer

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search