I’m having trouble filtering when using the searchable modifier. I am retrieving data from an API and I have my web service function with my ViewModel. I am getting an error of "Value of type ‘[CharacterResults]’ has no member ‘name’ " even though I do have the property within my CharacterResults. Any help would be appreciated!
Model
struct Character: Codable {
var results: [CharacterResults]
}
struct CharacterResults: Codable, Identifiable {
let id: Int
var name: String?
var status: String?
var species: String?
var origin: Origin?
var location: Location?
var image: String?
}
struct Origin: Codable {
var name: String
}
struct Location: Codable {
var name: String
}
ViewModel
@MainActor
class CharacterViewModel: ObservableObject {
@Published var characterData: Character?
init() {
fetchallCharacters(page: 1) { [weak self] char in
self?.characterData = char
if let data = UserDefaults.standard.data(forKey: "SaveFavorites") {
if let decoded = try? JSONDecoder().decode(Character.self, from: data) {
self?.characterData = decoded
return
}
}
}
}
//MARK: - Fetch all Characters
//Closure is going to provide all the character data for that particular page number
func fetchallCharacters(page: Int, completion: @escaping ((Character) -> ())) {
guard let url = URL(string: "https://rickandmortyapi.com/api/character/?page=\(page)") else {
fatalError("Bad URL")
}
let request = URLRequest(url: url)
let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Request error:", error)
return
}
do {
let decoder = JSONDecoder()
let result = try decoder.decode(Character.self, from: data!)
DispatchQueue.main.async {
//Calling our completion closure which passes in the result
completion(result)
}
} catch {
print(error)
}
}
dataTask.resume()
}
//MARK: - Next page
func nextPage(page: Int) {
//adding weak self since it is calling on the background
fetchallCharacters(page: page) { [weak self ] char in
self?.characterData = char
}
}
//MARK: - Previous page
func previousPage(page: Int) {
fetchallCharacters(page: page) { [weak self ] char in
self?.characterData = char
}
}
}
ContentView
I know I will need to replace the parameter of List with “filteredCharacters” once solved.
struct ContentView: View {
@StateObject var model = CharacterViewModel()
@StateObject var favorites = Favorites()
@State private var searchText = ""
@State var filterResults = CharacterViewModel().characterData
@State private var page = 1
func filteredCharacters() {
if searchText.isEmpty {
filterResults = model.characterData
} else {
filterResults = model.characterData.map {
$0.results.name.localizedCaseInsensitiveContains(searchText)
}
}
}
var body: some View {
NavigationView {
List(model.characterData?.results ?? []){
character in
NavigationLink {
CharacterDetailsView(character: character)
} label: {
HStack{
CharacterRowView(imageUrlString: character.image, name: character.name, species: character.species)
if favorites.contains(character) {
Spacer()
Image(systemName: "heart.fill")
.accessibilityLabel("This is a favorite character")
.foregroundColor(.red)
}
}
}
}
.navigationTitle("Characters")
.searchable(text: $searchText, prompt: "Search for a character")
.navigationBarItems(leading:
self.page > 1 ?
Button("Previous") {
self.page -= 1
model.previousPage(page: self.page)
} : nil,
trailing:
self.page <= 43 ?
Button("Next") {
self.page += 1
model.nextPage(page: self.page)
} : nil )
}
.phoneOnlyNavigationView()
.environmentObject(favorites)
}
}