Home > Software engineering >  SwiftUI JSON Decoder Response is 0 for APIResponse
SwiftUI JSON Decoder Response is 0 for APIResponse

Time:11-09

I'm trying to fetch urls from an API based on a search query. If I hardcode the query parameter to some value in the url (i.e. "fitness"), I get a response.

If I set the query parameter to an interpolated value to be inserted at a later date, the app has no images at runtime-- which makes sense.

However, when I enter a search query into my search bar, I cannot fetch the results, either. In fact, my results are 0.

Here's the error:

po jsonResult

▿ APIResponse
  - total : 0
  - results : 0 elements

Here's my code:

Models

import Foundation

struct APIResponse: Codable {
    let total: Int
    let results: [Result]
}

struct Result: Codable {
    let id: String
    let urls: URLS
}

struct URLS: Codable {
    let full: String
}

View

import SwiftUI

struct SimpleView: View {
    @ObservedObject var simpleViewModel = SimpleViewModel.shared
    
    @State private var searchText = ""
    @State private var selected: String? = nil
    
    var filteredResults: [Result] {
        if searchText.isEmpty {
            return simpleViewModel.results
        } else {
            return simpleViewModel.results.filter { $0.urls.full.contains(searchText) }
        }
    }
    
    var body: some View {
        NavigationStack {
            ScrollView {
                VStack(spacing: 0) {
                    ForEach(filteredResults, id: \.id) { result in
                        NavigationLink(destination: SimpleDetailView()) {
                            VStack {
                                AsyncImage(url: URL(string: result.urls.full)) { image in
                                    image.resizable()
                                } placeholder: {
                                    ProgressView()
                                }
                                .scaledToFill()
                                .frame(maxWidth: .infinity)
                                
                                .onTapGesture {
                                    withAnimation(.spring()) {
                                        if self.selected == result.urls.full {
                                            self.selected = nil
                                        } else {
                                            self.selected = result.urls.full
                                        }
                                    }
                                    hideKeyboard()
                                }
                                .scaleEffect(self.selected == result.urls.full ? 3.0 : 1.0)
                            }
                        }
                    }
                }
            }
            .onAppear {
                simpleViewModel.fetchPhotos(query: searchText)
            }
            .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
        }
    }
}

struct SimpleView_Previews: PreviewProvider {
    static var previews: some View {
        SimpleView()
    }
}

ViewModel

import Foundation

class SimpleViewModel: ObservableObject {
    static let shared = SimpleViewModel()

    private init() {}

    @Published var results = [Result]()
    
    func fetchPhotos(query: String) {
        let url = "https://api.unsplash.com/search/photos?page=1&query=\(query)&client_id=blahblahblahblahblahblahblahblah"
        
        guard let url = URL(string: url) else { return }
        let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
            guard let data = data, error == nil else { return }
            
            do {
                let jsonResult = try JSONDecoder().decode(APIResponse.self, from: data)
                DispatchQueue.main.async {
                    self?.results = jsonResult.results
                }
            } catch {
                print("Error: \(error)")
            }
        }
        task.resume()
    }
}

How can I search for images in my SimpleView based on my search query in my SimpleViewModel?

  • Setting breakpoints (how I discovered 0 values)
  • Ternary operators to check for search values or not
  • Setting my computer on fire

UPDATE

I added this to the code as @workingdog suggested, but with an else statement.

.onSubmit(of: .search) {
                if searchText.isEmpty {
                    simpleViewModel.results = filteredResults
                } else if !searchText.isEmpty {
                    simpleViewModel.fetchPhotos(query: searchText)
                }
            }

Here's what happens:

  1. Images are fetched, but not displayed in view
  2. Search query on submit renders nothing
  3. Pressing cancel enacts the query
  4. The images are displayed
  5. Images remain and are displayed. Go back to 2.

CodePudding user response:

In your SimpleView, the .onAppear { simpleViewModel.fetchPhotos(query: searchText } is called only when the view appears, and uses searchText = "". In other words you have an empty query. So remove the .onAppear{...}, it does nothing.

Add something like this, to fetch the photos when the searchText is submitted.

 .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
 .onSubmit(of: .search) {
     if !searchText.isEmpty {
         simpleViewModel.fetchPhotos(query: searchText)
     }
 }
  • Related