100 Days of SwiftUI Day 42

100 Days of SwiftUI – Day 42

It’s day 42 of the 100 Days of SwiftUI! Slowly but surely, we’re nearing the halfway point. Yesterday, we finished our Moonshot app and it turned out quite well. Today, we’re completing a few challenges to solidify what we’ve learned. Let’s take a look!

Moonshot SwiftUI challenge #1

The first challenge is to add the launch date to MissionView, below the mission badge. This was quite simple, because we already have a formatted launch date.

Text(mission.formattedLaunchDate)
                        .foregroundColor(.secondary)
                        .padding(.top)Text(mission.formattedLaunchDate)
                        .foregroundColor(.secondary)
                        .padding(.top)

Moonshot SwiftUI challenge #2

The second challenge is to extract one or two views into their own new SwiftUI view. I took the easy way out due to time constraints and set up a new view for the divider.

//
//  ViewDivider.swift
//  Moonshot
//

import SwiftUI

struct ViewDivider: View {
    var body: some View {
        Rectangle() // Used as a custom divider to divide sectons in the app
            .frame(height: 2)
            .foregroundColor(.lightBackground)
            .padding(.vertical)
    }
}

struct ViewDivider_Previews: PreviewProvider {
    static var previews: some View {
        ViewDivider()
    }
}
// some code...

    var body: some View {
        GeometryReader { geometry in
            ScrollView {
                VStack {
                    Image(mission.image)
                        .resizable()
                        .scaledToFit()
                        .frame(maxWidth: geometry.size.width * 0.6)
                        .padding(.top)
                    
                    Text(mission.formattedLaunchDate)
                        .foregroundColor(.secondary)
                        .padding(.top)
                                    
                    VStack(alignment: .leading) {
//                        Rectangle() // Used as a custom divider to divide sectons in the app
//                            .frame(height: 2)
//                            .foregroundColor(.lightBackground)
//                            .padding(.vertical)
                        ViewDivider()

// some more code...

Moonshot SwiftUI challenge #3

The third challenge is to add a toolbar item to ContentView that toggles between showing missions as a grid and as a list.

To start, we move the entire ScrollView from the ContentView to a new view called GridLayout. For the list, we created a new view called ListLayout.

//
//  GridLayout.swift
//  Moonshot
//

import SwiftUI

struct GridLayout: View {
    
    let missions: [Mission] // Creates an instance of the Mission array
    let astronauts: [String: Astronaut] // Creates an instance of the String/Astronaut dictionary
    
    let columns = [
        GridItem(.adaptive(minimum: 150)) // creates an adaptive, column based grid
    ]
    
    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns) { // sets our specified columns grid as columns
                ForEach(missions) { mission in
                    NavigationLink {
                        MissionView(mission: mission, astronauts: astronauts)
                    } label: {
                        VStack {
                            Image(mission.image)
                                .resizable()
                                .scaledToFit()
                                .frame(width: 100, height: 100)
                                .padding()
                            
                            VStack {
                                Text(mission.displayName) // displays the mission name
                                    .font(.headline)
                                    .foregroundColor(.white)
                                
                                Text(mission.formattedLaunchDate) // displays the mission date
                                    .font(.caption)
                                    .foregroundColor(.white.opacity(0.5))
                            }
                            .padding(.vertical)
                            .frame(maxWidth: .infinity)
                            .background(.lightBackground) // sets the background color as specified in the Color-Theme.swift file
                        }
                        .clipShape(RoundedRectangle(cornerRadius: 10))
                        .overlay(
                            RoundedRectangle(cornerRadius: 10)
                                .stroke(.lightBackground)
                        )
                    }
                }
            }
            .padding([.horizontal, .bottom])
        }
    }
}

struct GridLayout_Previews: PreviewProvider {
    static var previews: some View {
        GridLayout(missions: Bundle.main.decode("missions.json"), astronauts: Bundle.main.decode("astronauts.json")) // decode the JSON files. We don't have to declare the type because SwiftUI already knows what types they are
            .preferredColorScheme(.dark)
    }
}
//
//  ListLayout.swift
//  Moonshot
//

import SwiftUI

struct ListLayout: View {
    
    let missions: [Mission]
    let astronauts: [String: Astronaut]
    
    var body: some View {
        List(missions) { mission in
            NavigationLink {
                MissionView(mission: mission, astronauts: astronauts)
            } label: {
                HStack {
                    Image(mission.image)
                        .resizable()
                        .scaledToFit()
                        .frame(width: 40, height: 40)
                        .padding()
                    
                    VStack(alignment: .leading) {
                        Text(mission.displayName)
                            .font(.headline)
                        
                        Text(mission.formattedLaunchDate)
                    }
                }
            }
            .listRowBackground(Color.darkBackground) // Sets our custom dark background color as background for the list
        }
        .listStyle(.plain) // Makes the list take the full width of the screen
    }
}

struct ListLayout_Previews: PreviewProvider {
    static var previews: some View {
        ListLayout(missions: Bundle.main.decode("missions.json"), astronauts: Bundle.main.decode("astronauts.json"))
            .preferredColorScheme(.dark)
    }
}

Then, we update our ContentView to implement the new views.

//
//  ContentView.swift
//  Moonshot
//

import SwiftUI

struct ContentView: View {
    
    let astronauts: [String: Astronaut] = Bundle.main.decode("astronauts.json") // decodes the astronauts.json file and puts it in an array Strings containing Astronauts
    let missions: [Mission] = Bundle.main.decode("missions.json") // decodes the missions.json file and puts it in an array of Missions
    
    @AppStorage("showingGrid") private var showingGrid = true // Saves the users preference and remembers which type of grid to load
    
    var body: some View {
        NavigationView {
            Group { // use a group to wrap your conditions in a view, otherwise they won't work, which you can then add modifiers to.
                if showingGrid {
                    GridLayout(missions: missions, astronauts: astronauts) // if true, show the GridLayout view
                } else {
                    ListLayout(missions: missions, astronauts: astronauts) // if false, show the ListLayout view
                }
                ListLayout(missions: missions, astronauts: astronauts)
            }
            .toolbar {
                Button {
                    showingGrid.toggle() // Toggles showingGrid to false or true
                } label: {
                    if showingGrid {
                        Label("Show as table", systemImage: "list.dash")
                    } else {
                        Label("Show as grid", systemImage: "square.grid.2x2")
                    }
                }
            }
            
            .navigationTitle("Moonshot")
            .background(.darkBackground) // sets the background color as specified in the Color-Theme.swift file
            .preferredColorScheme(.dark) // sets the view to display as if the device is in Dark mode, even if it is not
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Wrap up

And that’s it for day 42 and the Moonshot project! We’ll be diving into project #9 tomorrow, to high time to rest and recharge. Until then!

Darryl

Hi! My name is Darryl and this is my personal blog where I write about my journey as I learn programming! You'll also find articles about other things that interest me including games, tech and anime.

Post navigation

1 Comment

  • Thank you so much for the solution.
    Looks like with Xcode 14, you just need to use “GridLayout()” instead of “GridLayout(missions: missions, astronauts: astronauts)”.

    I wish you could have a solution to split the horizontal scroll view in MissionView like Paul asked.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

100 Days of SwiftUI – Day 38

100 Days of SwiftUI – Day 32

100 Days of SwiftUI – Day 83

100 Days of SwiftUI – Day 89