Reputation: 105
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
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
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