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
}