Home > Net >  How to get access to Location from API? SwiftUI
How to get access to Location from API? SwiftUI

Time:10-04

I just started to learn Swift programing language and have a question. I'm trying to create a simple app with list of characters and some info about them.

API: https://rickandmortyapi.com/api/character

I get a list, and I want to add a NavigationLink to any of them. How to do this? Here is my code source:

** CharacterView**

    import SwiftUI
    
    struct CharacterView: View {
        
        let image: String
        let name: String
        let status: String
        let stats: Color
        let species: String
        let spec: Color
        let gender: String
        let gndr: Color
        
        var body: some View {
            
            VStack (alignment: .leading) {
                
                HStack(spacing: 10) {
                    
                    // Image
                    AsyncImage(url: URL(string: image)) { image in
                        image.resizable()
                    } placeholder: {
                        ProgressView()
                    }
                    .frame(width: 55, height: 55)
                    .cornerRadius(5)
                    
                    VStack (alignment: .leading, spacing: 5) {
                        
                        // Name
                        Text(name)
                            .fontWeight(.bold)
                        
                        // Tags
                        // MARK: - Fix code duplicates
                        HStack {
                            Text(status)
                                .fontWeight(.bold)
                                .foregroundColor(Color.white)
                                .padding(.leading, 5)
                                .padding(.trailing, 5)
                                .background(stats)
                                .cornerRadius(3)
                            
                            Text(species)
                                .fontWeight(.bold)
                                .foregroundColor(Color.white)
                                .padding(.leading, 5)
                                .padding(.trailing, 5)
                                .background(spec)
                                .cornerRadius(3)
                                .lineLimit(1)
                            
                            Text(gender)
                                .fontWeight(.bold)
                                .foregroundColor(Color.white)
                                .padding(.leading, 5)
                                .padding(.trailing, 5)
                                .background(gndr)
                                .cornerRadius(3)
                        }
                    }
                }
            }
            .ignoresSafeArea()
        }
    }
    
    struct CharacterView_Previews: PreviewProvider {
        static var previews: some View {
            CharacterView(image: "", name: "", status: "", stats: Color.clear, species: "", spec: Color.clear, gender: "", gndr: Color.clear)
        }
    }
    

CharacterList import SwiftUI

    struct CharacterList: View {
        
        @ObservedObject var data: RickandMortyDatas
        
        var body: some View {
            
            NavigationView {
                
                List {
                    
                    ForEach(data.responses, id: \.page) { response in
                        
                        ForEach(Array(response.element.results.enumerated()), id: \.element.id) { character in
                            
                            NavigationLink(destination: DetailView(name: Result(id: 1, name: "", status: "", image: "", species: "", gender: ""))) {
                                
                                CharacterView(
                                    image: character.element.image,
                                    name: character.element.name,
                                    status: character.element.status,
                                    stats: character.element.status.stats(),
                                    species: character.element.species,
                                    spec: character.element.species.spec(),
                                    gender: character.element.gender,
                                    gndr: character.element.gender.gndr()
                                )
                                .onAppear {
                                    guard character.offset == response.element.results.count - 1,
                                          let next = response.element.info.next,
                                          let pageString = next.components(separatedBy: "?page=").last,
                                          let nextPage = Int(pageString)
                                    else { return }
                                    data.loadPage(nextPage)
                                }
                            }
                        }
                    }
                }
                .navigationBarTitle(Text("Characters"))
                .listStyle(PlainListStyle())
                .navigationBarTitleDisplayMode(.inline)
            }
            .onAppear{ data.loadPage(1) }
        }
    }
    
    struct CharacterList_Previews: PreviewProvider {
        static var previews: some View {
            CharacterList(data: RickandMortyDatas())
        }
    }

DetailView
import SwiftUI

struct DetailView: View {
    
    var name: Result
    
    var body: some View {
        VStack {
            Text("Location must be here")
        }
    }
}

struct DetailView_Previews: PreviewProvider {
    static var previews: some View {
        DetailView(name: Result(id: 1, name: "", status: "", image: "", species: "", gender: ""))
    }
}

RickandMorty import Foundation

struct RickandMorty: Codable {
    let info: Info
    let results: [Result]
}

struct Info: Codable {
    let count, pages: Int
    let next: String?
    let prev: String?
}

struct Result: Codable {
    let id: Int
    let name: String
    let status: String
    let image: String
    let species: String
    let gender: String
}

RickandMortyDatas import Foundation import Combine

class RickandMortyDatas: ObservableObject {
    
    private var cancellables = Set<AnyCancellable>()
    @Published var responses: [(page: Int, element: RickandMorty)] = []
    
    func loadPage(_ page: Int) {
        
        guard !responses.contains(where: { $0.page == page }) else { return }
        var request = URLRequest(url: URL(string: "https://rickandmortyapi.com/api/character/?page=\(page == 1 ? 1 : page)")!)
        request.httpMethod = "GET"
        request.addValue("application/json", forHTTPHeaderField: "Accept")
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        URLSession.shared.dataTaskPublisher(for: request)
            .map{ $0.data }
            .removeDuplicates()
            .decode(type: RickandMorty.self, decoder: JSONDecoder())
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: { completion in
                if case .failure(let error) = completion {
                    print("\(error)")
                }
            }, receiveValue: { response in
                self.responses.insert((page, response), at: page - 1)
            }).store(in: &cancellables)
    }
}

Extension import Foundation import SwiftUI

extension String {
    
    func stats() -> Color {
        
        var color = Color.clear
        
        switch self {
        case "Alive":
            color = .green
        case "Dead":
            color = .red
        case "unknown":
            color = .orange
        default:
            color = Color.clear
        }
        return color
    }
    
    func spec() -> Color {
        
        var color = Color.clear
        
        switch self {
        case "Human":
            color = .blue
        default:
            color = .brown
        }
        return color
    }
    
    func gndr() -> Color {
        
        var color = Color.clear
        
        switch self {
        case "Male":
            color = .cyan
        case "unknown":
            color = .black
        case "Female":
            color = .pink
        default:
            color = Color.clear
        }
        return color
    }
    
}

CodePudding user response:

try this approach, using a struct Result: Codable {...} that actualy contains the Location, and a appropriatly modified DetailView(..).

 struct CharacterList: View {
     
     @ObservedObject var data: RickandMortyDatas
     
     var body: some View {
         
         NavigationView {
             
             List {
                 
                 ForEach(data.responses, id: \.page) { response in
                     
                     ForEach(Array(response.element.results.enumerated()), id: \.element.id) { character in
                         
                         // -- here
                         NavigationLink(destination: DetailView(character: character.element)) {
                             
                             CharacterView(
                                 image: character.element.image,
                                 name: character.element.name,
                                 status: character.element.status,
                                 stats: character.element.status.stats(),
                                 species: character.element.species,
                                 spec: character.element.species.spec(),
                                 gender: character.element.gender,
                                 gndr: character.element.gender.gndr()
                             )
                             .onAppear {
                                 guard character.offset == response.element.results.count - 1,
                                       let next = response.element.info.next,
                                       let pageString = next.components(separatedBy: "?page=").last,
                                       let nextPage = Int(pageString)
                                 else { return }
                                 data.loadPage(nextPage)
                             }
                         }
                     }
                 }
             }
             .navigationBarTitle(Text("Characters"))
             .listStyle(PlainListStyle())
             .navigationBarTitleDisplayMode(.inline)
         }
         .onAppear{ data.loadPage(1) }
     }
 }
 
 // -- here
 struct DetailView: View {
     var character: Result
     
     var body: some View {
         VStack {
             Text("Location info")
             Text(character.location.name)
             Text(character.location.url)
         }
     }
 }
 
 // -- here
 struct Result: Codable {
     let id: Int
     let name: String
     let status: String
     let species: String
     let type: String
     let gender: String
     let origin, location: Location  // <-- here
     let image: String
     let episode: [String]
     let url: String
     let created: String
 }

 // -- here
 struct Location: Codable {
     let name: String
     let url: String
 }
 
  • Related