WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

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

   

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 ?

   

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?

   

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

   

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

1      

Save 50% in my Black Friday sale.

SAVE 50% To celebrate WWDC22, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

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

Reply to this topic…

You need to create an account or log in to reply.

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.