Week 5 of Learning SwiftUI

Codable, Date formater, and Drawing and animating Shapes!

Posted by Joe Speakman on February 17, 2021 · 12 min read



Day 24 (Day 39)

Today we embark on a new project called Moonshot! Today we go over Resizing images with GeometryReader, How to use ScrollView, and more!

We first learned how to display large images with GeometryReader, this allows SwiftUI to properly scale the image to fill the width of the frame.

struct ContentView: View {
    var body: some View {
        VStack{
            GeometryReader { geo in
            Image("Example")
                .resizable()
                .frame(width: geo.size.width)

            }
        }
    }
}

This produces a different result than changing the aspect ratio to .fill, or .fit as they do not adjust the size of the view, just the offset of the content in the frame. The next topic we cover in today’s content is ScrollView. A ScrollView allows you to create a scrollable set of data

Day 25 (Day 40)

We are going to be loading two types of data from JSON. Provided with the project are two files containing astronauts and descriptions of their work. The second is a list of all the Apollo missions. We create structs for each data type, so it is easier to decode in the next lesson. Each struct will need items to conform to Codable and Identifiable:

struct Astronaut: Codable, Identifiable {
    let id: String
    let name: String
    let description: String
}

as we look to decode mission data, a more complex struct is needed to handle crew roles and crew members for each Apollo mission.

This marks the first time we use generics in a project. adding T to the decode function like this

extension Bundle {
    func decode<T: Codable>(_ file: String) -> T {
        guard let url = self.url(forResource: file, withExtension: nil) else {
            fatalError("Failed to locate \(file) in bundle.")
        }
        guard let data = try? Data(contentsOf: url) else {
            fatalError("Failed to load \(file) from bundle ")
        }
        let decoder = JSONDecoder()
        let formatter = DateFormatter()

        formatter.dateFormat = "y-MM-dd"
        decoder.dateDecodingStrategy = .formatted(formatter)

        guard let loaded = try? decoder.decode(T.self, from: data) else {
            fatalError("failed to decode \(file) from bundle")
        }

        return loaded
    }
}

Today we started building the MissionView portion of the app to provide a detailed view of each mission. The content is within a vertical scroll view with a resizable image that is using .scaledToFit(), and from there, we then add the mission description. This contains additional facts based on the mission and details for the crew that flew on each mission. This was to use the workings that we have already started using codable to read data from the JSON files. Most of the code for formatting this view is topics that we have covered already in the course.

Day 26 (Day 41)

Swift has a method for handling arrays called first(where:) that allows you to pass in a condition, and it will send back the first array item that matches the condition or nil if none of the things in the array match. For this project, we are asking first(where:) to give us the first astronaut with the id provided.

A weird bug in the MissionView that we created yesterday where sometimes the text displayed would become truncated due to layout priority. This can be solved by changing the priority of the items displayed in AstronautView this can be done by adding this to the Text view in the VStack:

Text(self.astronaut.description)
                        .padding()
                        .layoutPriority(1)
Day 27 (Day 42)
  • Add the launch date to MissionView, below the mission badge.
  • ModifyAstronautView to show all the missions this astronaut flew on.
  • Make a bar button in ContentView that toggles between showing launch dates and showing crew names.

Challenge one can be accomplished by adding this code just below the image on the mission view

Text(self.mission.formattedLaunchDate)
                        .font(.headline)

we have already pulled the data in creating the main ContentView.

On the project quiz, I scored a respectable 83%. I am incredibly pleased with my learning so far in this program and look forward to the next project.

Day 28 (Day 43)

A Whole new project, to infinity and beyond!!

INSERT GIF HERE

Today we start covering drawing in SwiftUI. “SwiftUI makes high-performance drawing easy,” Paul writes. I’ll be the judge of that good sir. All jokes aside, I think it is imperative to learn how SwiftUI draws shapes and interacts with CoreGraphics.

SwiftUI allows us to draw paths with ease, this can be done by using a Path object that is written out like this:

    Path { path in
        path.move(to: CGPoint(x: 200, y: 100))
        path.addLine(to: CGPoint(x: 100, y: 300))
        path.addLine(to: CGPoint(x: 300, y: 300))
        path.addLine(to: CGPoint(x: 200, y: 100))
    }

as you can probably guess, there is a problem here. Yes, this will draw out the triangle that the lesson teaches; however, locations can change. Using hardcoded x, and y values will make the drawn triangle lose its shape when displayed on various device screens.

We can use Shapes to solve for that inconvenience and reusability. The code outlined that was outlined to create the triangle now looks like this:

struct Triangle: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()

        path.move(to: CGPoint(x: rect.midX, y: rect.minY))
        path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
        path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))

        return path
    }
}

And to daw the trinagle we can use this:

Triangle()
    .fill(Color.blue)
    .frame(width: 300, height: 300)

because we are using CGPoint’s min and max values that allows us to daw the shape in whatever size we want.

Day 29 (Day 44)

Today, we learned more about drawing and how to manipulate shapes via CGAffineTransform and solve performance issues when drawing within SwiftUI.

We created a circle that cycles through various colors and hues. When drawing a LinearGradient, we ran into a significant performance issue as Core animation struggled to animate the individual 100 views. When adding .drawingGroup() to the end of the ZStack, we can hand off drawing to Metal, The hardware-accelerated Graphics processing.

Day 30 (Day 45)

With SwiftUI, we have great control over how a view is rendered, allowing us to apply blurs and other filters. like adding the .blur() modifier to applicable views. This also works with adjusting the object’s saturation and blend mode.

Today we bring back animating shapes! We are animating shapes that we are creating. We learn how to animate shapes that are simple and complex in style. This is done with animatableData in the shape as a variable.

Day 31 (Day 46)

This was another technical project where we didn’t create any new content but learned new skills. I never looked into older animating interfaces or UIKit objects. Taking the end-of-project quiz, I got an 83% missing on two questions.

  • Create an Arrow shape made from a rectangle and a triangle – having it point straight up is fine.
  • Make the line thickness of your Arrow shape animatable.
  • Create a ColorCyclingRectangle shape that is the rectangular cousin of ColorCyclingCircle, allowing us to control the position of the gradient using a property.
Day 32 (Day 47)

Today is a milestone day, where we look back and reflect on the projects that I have embarked on and written about for the past week and a half. There was some essential learning along the way, and I feel confident in all of the things I learned in these past three projects.

Week five.

This week has been a lot of fun. I learned so much about how SwiftUI draws views and a bit of US history. I am not feeling the strain of last week’s projects this week. I look forward to more complex, more extensive projects in the future!