Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to get access to Location from API? SwiftUI

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

** 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
    }
    
}

>Solution :

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
 }
 
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading