zeem
zeem

Reputation: 105

Perform a API call with value from TextField and display the results in SwiftUI

Looking to perform an API call with the value from a Textfield and display the results in a list. Having trouble to use the $name value from the textfield into the url to then make the API call

My code so far

WebService.swift

import Foundation

public class UserFetcher: ObservableObject {

    @Published var shoes: [Shoe] = []
    @Published private var name: String = ""
    
    init(){
        load()
    }
    
    func load() {
       // let url = URL(string: "https://api.thesneakerdatabase.com/v1/sneakers?limit=10&name=wolf%20in%20sheeps%20clothing")! // the format the API requires
        let url = URL(string: "https://api.thesneakerdatabase.com/v1/sneakers?limit=10&name=" + name)!
    
        URLSession.shared.dataTask(with: url) {(data,response,error) in
            do {
                if let d = data {
                    let decodedLists = try JSONDecoder().decode(APIResponse.self, from: d)
                    DispatchQueue.main.async {
                      //  self.users = decodedLists
                        self.shoes = decodedLists.shoeResults
                    }
                }else {
                    print("No Data")
                }
            } catch {
                print ("Error")
            }
            
        }.resume()
       
    }
}

ContentView.swift

import Foundation
import SwiftUI
import Combine
import SDWebImage
import SDWebImageSwiftUI
 

    struct ContentView: View {
        @ObservedObject var fetcher = UserFetcher()
        @State private var name: String = ""
        
        var body: some View {
            NavigationView  {
            VStack {
                VStack(alignment: .leading) {
                           TextField("Search", text: $name)
                       }
                List(fetcher.shoes) { Shoe in
                   
                    VStack (alignment: .leading) {
                        Text(Shoe.name)
                        Text(Shoe.shoe)
                            .font(.system(size: 11))
                            .foregroundColor(Color.gray)
                        Text(Shoe.brand)
                            .font(.system(size: 11))
                            .foregroundColor(Color.gray)
                        Text(Shoe.styleId)
                            .font(.system(size: 11))
                            .foregroundColor(Color.gray)
                        Text(".\(Shoe.year)")
                            .font(.system(size: 11))
                            .foregroundColor(Color.gray)
                        WebImage(url: URL(string: "\(Shoe.media.thumbUrl)"))
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                    }
                      
                }
         
            }
             .navigationBarTitle(Text("Search"))
        }
        
    }

I'm sure its something pretty obvious but have been staring at it for the last two hours and cant seem to work it. Any directions would be greatly appreciated thank you

Upvotes: 1

Views: 1845

Answers (2)

jnpdx
jnpdx

Reputation: 52397

If you want the normal behavior of typing in the TextField and seeing the results update, you may want something like this (see inline comments about what is changed):

import SwiftUI
import Combine

public class UserFetcher: ObservableObject {
    
    @Published var shoes: [Shoe] = []
    @Published var name: String = "" // Not private any more
    
    private var cancellable : AnyCancellable?
    
    init() {
        cancellable = $name
            .debounce(for: .seconds(1), scheduler: RunLoop.main) //only run once the text has been static for 1 second
            .sink { (newSearch) in
                self.load(searchTerm: newSearch)
            }
    }
    
    func load(searchTerm: String) { //take the search term as a parameter
        guard !searchTerm.isEmpty, let url = URL(string: "https://api.thesneakerdatabase.com/v1/sneakers?limit=10&name=\(searchTerm)") else {
            return //don't search if the term is empty or the URL is invalid
        }
        
        URLSession.shared.dataTask(with: url) {(data,response,error) in
            do {
                if let d = data {
                    let decodedLists = try JSONDecoder().decode(APIResponse.self, from: d)
                    DispatchQueue.main.async {
                        self.shoes = decodedLists.shoeResults
                    }
                }else {
                    print("No Data")
                }
            } catch {
                print ("Error: \(error)") //print your error here
            }
            
        }.resume()
        
    }
}



struct ContentView: View {
    @ObservedObject var fetcher = UserFetcher()
    
    var body: some View {
        NavigationView  {
            VStack {
                VStack(alignment: .leading) {
                    TextField("Search", text: $fetcher.name) //using the name property from fetcher now
                }
                List(fetcher.shoes) { shoe in //don't use capital letters for variable names -- not idiomatically correct Swift and it can get confused with your `Shoe` type
                    VStack (alignment: .leading) {
                        Text(shoe.name)
                        Text(shoe.shoe)
                            .font(.system(size: 11))
                            .foregroundColor(Color.gray)
                        Text(shoe.brand)
                            .font(.system(size: 11))
                            .foregroundColor(Color.gray)
                        Text(shoe.styleId)
                            .font(.system(size: 11))
                            .foregroundColor(Color.gray)
                        Text(".\(shoe.year)")
                            .font(.system(size: 11))
                            .foregroundColor(Color.gray)
                    }
                    
                }
                
            }
            .navigationBarTitle(Text("Search"))
        }
    }
}

I also noticed while testing that you were getting errors from your API using the Shoe struct from a previous question you asked -- make sure that retailPrice is an Int? and not Int since sometimes the API doesn't return a value for that field.

I'll also note that you could make this even more slick by turning your load question into something that you can chain into the Combine call by using Future, but that's something worth other research or a different question (out of scope here).

Upvotes: 0

foolbear
foolbear

Reputation: 964

try following:

public class UserFetcher: ObservableObject {

    @Published var shoes: [Shoe] = []
    
    init(){
        //load() <- remove this
    }
    
    func load(_ name: String) { //<-- add parameter
        ....
    }
struct ContentView: View {
    @ObservedObject var fetcher = UserFetcher()
    @State private var name: String = ""
        
    var body: some View {
        NavigationView  {
            VStack {
                VStack(alignment: .leading) {
                    TextField("Search", text: $name)
                }
                List(fetcher.shoes) { Shoe in
                ...
                }
            }.onAppear(fetcher.load(name)) //<--- begin to load
        }
    }

Upvotes: 1

Related Questions