It’s day 41 of the 100 Days of SwiftUI! Yesterday, we started coding our Moonshot app. Today, we’re finishing up the app by adding more views and functionality to read from our JSON files. Let’s dive in!
Showing mission details
When the user selects a mission from our list, they should get an overview of all the relevant mission details, meaning the name, logo, description and the crew. To accomplish this, we create a new view called MissionView
. You’ll find the code a bit further down below.
Merging codable structs
Below our mission description we want to show the pictures, names, and roles of each crew member, which means matching up data that came from two different JSON files.
what we need to do is make our MissionView
accept the mission that got tapped, along with our full astronauts dictionary, then have it figure out which astronauts actually took part in the launch. Here’s the complete MissionView
, showing how we accomplished this.
//
// MissionView.swift
// Moonshot
//
import SwiftUI
struct MissionView: View {
struct CrewMember {
let role: String
let astronaut: Astronaut
}
let mission: Mission
let crew: [CrewMember]
var body: some View {
GeometryReader { geometry in
ScrollView {
VStack {
Image(mission.image)
.resizable()
.scaledToFit()
.frame(maxWidth: geometry.size.width * 0.6)
.padding(.top)
VStack(alignment: .leading) {
Rectangle() // Used as a custom divider to divide sectons in the app
.frame(height: 2)
.foregroundColor(.lightBackground)
.padding(.vertical)
Text("Mission Highlights")
.font(.title.bold())
.padding(.bottom, 5)
Text(mission.description)
Rectangle() // Used as a custom divider to divide sectons in the app
.frame(height: 2)
.foregroundColor(.lightBackground)
.padding(.vertical)
Text("Crew")
.font(.title.bold())
.padding(.bottom, 5)
}
.padding(.horizontal)
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(Capsule())
.overlay(
Capsule()
.strokeBorder(.white, lineWidth: 1)
)
VStack(alignment: .leading) {
Text(crewMember.astronaut.name)
.foregroundColor(.white)
.font(.headline)
Text(crewMember.role)
.foregroundColor(crewMember.role == "Commander" ? .red : .secondary )
.font(crewMember.role == "Commander" ? .title3.bold() : nil)
}
}
.padding(.horizontal)
}
}
}
}
}
.padding(.bottom)
}
}
.navigationTitle(mission.displayName)
.navigationBarTitleDisplayMode(.inline)
.background(.darkBackground)
}
init(mission: Mission, astronauts: [String: Astronaut]) {
self.mission = mission
self.crew = mission.crew.map { member in
if let astronaut = astronauts[member.name] { // if the astronaut's name is found, return the role.
return CrewMember(role: member.role, astronaut: astronaut)
} else {
fatalError("Missing \(member.name)")
}
}
}
}
struct MissionView_Previews: PreviewProvider {
static let missions: [Mission] = Bundle.main.decode("missions.json") // Create an array of Missions and put the decoded JSON in the array
static let astronauts: [String: Astronaut] = Bundle.main.decode("astronauts.json") // Create a dictionary of Strings with the Astronaut as key.
static var previews: some View {
MissionView(mission: missions[0], astronauts: astronauts) // Expects an isntance of mission, so we pass in the first item in our Missions array for preview purposes
.preferredColorScheme(.dark) // The preview doesn't know that we force the app in dark mode, so we have to set it here so the preview is correct.
}
}
Finishing the app with the AstronautView
Now that the MissionView
is completed, we have to create one final view
. When in the MissionView
, selecting an astronaut should lead to a new view
where the astronaut’s picture, name and description are displayed.
import SwiftUI
struct AstronautView: View {
let astronaut: Astronaut
var body: some View {
ScrollView {
VStack {
Image(astronaut.id)
.resizable()
.scaledToFit()
Text(astronaut.description)
.padding()
}
}
.background(.darkBackground)
.navigationTitle(astronaut.name)
.navigationBarTitleDisplayMode(.inline)
}
}
struct AstronautView_Previews: PreviewProvider {
static let astronauts: [String: Astronaut] = Bundle.main.decode("astronauts.json")
static var previews: some View {
AstronautView(astronaut: astronauts["armstrong"]!)
.preferredColorScheme(.dark)
}
}
Screenshots of the finished SwiftUI Moonshot app



Wrap up
And that’s it for the Moonshot app! We’ve made great strides and delivered an app that works smoothly while looking good. It’s time to recharge as tomorrow, we’ll be reviewing the project as well as completing a few challenges. Until then!
100 Days of SwiftUI – Day 41