UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Project 8 Challenge 2: Why does what I did work?

Forums > 100 Days of SwiftUI

I know this sounds silly, but why does this work?

Confusion: when I wrote:

static let crew : [MissionView.CrewMember] = Bundle.main.decode("astronauts.json")

How do I know that Swift will decode and give me the exact [MissionView.CrewMember] type I need? As a counterpoint to this, in AstronautView, I had:

static let astronauts : [String : Astronaut] = Bundle.main.decode("astronauts.json")

or does Swift automatically give back the data type that I want? It just does this kind of magic?

After some thinking, the answer might be this: CrewMember, as a struct, has 2 properties, one is a String called role and the other is an Astronaut called astronaut. That is essentially telling Swift that for an instance of CrewMember, for each role, you have an Astronaut. That is essentially a Dictionary where the role is the String key and the astronaut is the Astronaut value, unique to the role String key.

However, what I got when I wrote [MissionView.CrewMember] = Bundle... does not give back a Dictionary type. It gives back an Array of MissionView.CrewMember, which is essentially a Dictionary but not precisely.

Code Below:

Challenge 2: I replaced the ScrollView in MissionView with an independent SwiftUI View called HoriScroll, and the code is like this:

HoriScroll:

import SwiftUI

struct HoriScroll: View {
    let crew : [MissionView.CrewMember]

    var body: some View {

        ScrollView(.horizontal, showsIndicators: false) {
            HStack {
                ForEach(crew, id : \.role) { crewMember in
                    NavigationLink {
                        AstronautView(astronaut: crewMember.astronaut)
                    } label: {
                        HStack {
                            Image(crewMember.astronaut.id)
                                .resizable()
                                .frame(width: 104, height: 72)
                                .clipShape(Circle())
                                .overlay(Circle().strokeBorder(.regularMaterial))

                            VStack(alignment: .leading) {
                                Text(crewMember.astronaut.name)
                                    .foregroundColor(.white)
                                    .font(.headline)
                                Text(crewMember.role)
                                    .foregroundColor(crewMember.role == "Commander" ? .mint : .secondary)
                                    .font(crewMember.role == "Commander" ? .largeTitle : .headline)
                            }
                        }
                        .padding(.horizontal)
                    }
                }
            }
        }

    }
}

struct HoriScroll_Previews: PreviewProvider {
    static let crew : [MissionView.CrewMember] = Bundle.main.decode("astronauts.json")

    static var previews: some View {
        HoriScroll(crew: crew)
    }
}

MissionView:

HoriScroll(crew: crew)

// struct CrewMember conforms to the Codable protocol

2      

Is it because of the custom initializer of MissionView? Where it takes two parameters, one is a mission of type Mission, the other is an Astronaut which is a Dictionary of [String: Astronaut] type but returns a CrewMember type that has 2 properties, role, and astronaut? That is where equivalency between [String : Astronaut] and CrewMember is established ?

2      

Does this actually work for you:

static let crew : [MissionView.CrewMember] = Bundle.main.decode("astronauts.json")

without generating errors?

Also, can you post your MissionView?

2      

Yes, that works for me without generating errors. I let CrewMember to conform to the Codable protocol.

MissionView:

import SwiftUI

struct MissionView: View {
    // a mission property so we can show the mission badge and description
    let mission : Mission

    struct CrewMember : Codable {
        let role : String
        let astronaut : Astronaut
    }

    let crew : [CrewMember]

    init(mission: Mission, astronauts: [String : Astronaut]) {
        self.mission = mission

        self.crew = mission.crew.map { member in
            if let astronaut = astronauts[member.name] {
                return CrewMember(role: member.role, astronaut: astronaut)
            }
            else {
                fatalError("Missing \(member.name)")
            }
        }
    }

    var body: some View {
        GeometryReader {geometry in
            ScrollView {
                VStack {
                    Image(mission.image)
                        .resizable()
                        .scaledToFit()
                        .frame(maxWidth: geometry.size.width * 0.6)

                    Text("\(mission.formattedLaunchDate)")
                        .font(.title.bold())
                        .foregroundColor(.yellow)

                    VStack(alignment: .leading) {
                        Rectangle()
                            .frame(height: 2)
                            .foregroundColor(.lightBackground)
                            .padding(.vertical)

                        Text("Mission Highlights")
                            .font(.title.bold())
                            .padding(.bottom, 5)

                        Text(mission.description)

                        Rectangle()
                            .frame(height: 2)
                            .foregroundColor(.lightBackground)
                            .padding(.vertical)

                        Text("Crew")
                            .font(.title.bold())
                            .padding(.bottom, 5)
                    }
                    .padding(.horizontal)

                    HoriScroll(crew: crew)

                }
                .padding(.bottom)
            }
        }
        .navigationTitle(mission.displayName)
        .navigationBarTitleDisplayMode(.inline)
        .background(.darkBackground)
    }
}

struct MissionView_Previews: PreviewProvider {
    static let missions : [Mission] = Bundle.main.decode("missions.json")
    static let astronauts : [String : Astronaut] = Bundle.main.decode("astronauts.json")

    static var previews: some View {
        MissionView(mission: missions[0], astronauts: astronauts)
            .preferredColorScheme(.dark)
    }
}

2      

I don't see you actually accessing this property anywhere in your listed code, even though you created it:

static let crew : [MissionView.CrewMember] = Bundle.main.decode("astronauts.json")

It does show up in the Previews section but that gets pulled out when the app is built.

To use this you would have to specify something like Self.crew or YourStructName.crew.

For instance if you tried the following:

struct TestView: View {
    struct CrewMember: Codable {
        let role: String
        let astronaut: Astronaut
    }
    static let crew: [TestView.CrewMember] = Bundle.main.decode("astronauts.json")

    var body: some View {
        Form {
            Button("Test") {
                for item in Self.crew {
                    print(item)
                }
            }
        }
    }
}

It will crash your code when you press the Test button. At that point, SwiftUI tries to load the file from the bundle and decode it and it's unable to decode it because the json is formatted as a [String: Astronaut] dictionary and not a [TestView.CrewMember] array.

My guess is your ContentView has this:

let astronauts: [String: Astronaut] = Bundle.main.decode("astronauts.json")
let missions: [Mission] = Bundle.main.decode("missions.json")

and those are the properties you're passing among your views.

I could create this property:

static let whatever: [Int] = Bundle.main.decode("astronauts.json")

and it will compile and build and everything will work fine until I run some code that tries to access it, then the whole thing will crash because the type [Int] doesn't match the [String: Astronaut] type in the json file.

So, the answer to this:

I know this sounds silly, but why does this work?

Static properties aren't initialized when an instance of the struct is created, they belong the struct type itself. It works because you never tried accessing the property.

https://www.hackingwithswift.com/read/0/18/static-properties-and-methods

3      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.