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

Day 42 of SwiftUI, Challenge 2: Where to define common structs?

Forums > 100 Days of SwiftUI

The challenge reads:

Extract one or two pieces of view code into their own new SwiftUI views – the horizontal scroll view in MissionView is a great candidate.

I was a bit baffled by how 'simple' this sounded and figured it was probably just for us to get some practice with how views and things relate to one another and also just to get some coding practice. So I merely copy-pasted the ScrollView containing the HStack that displays the crew members along with its title Text("Crew")... into the body of a new SwiftUI view file named CrewView.swift, then I defined CrewMember the same way it is defined in MissionView and passed the crew property of MissionView straight through to it, by instantiating the view where its underlying code used to sit:

CrewView(crew: crew)

But then I got a compiler error saying that the type MissionView.[CrewMember] could not be converted to CrewView.[CrewMember], so I figured even though the two are technically the same type, I have to define the type somewhere outside of both views, in a scope that is common to both. For this reason I moved the definition of the CrewMember struct out into MoonshotApp.swift and everything works as before. But something is not sitting right with me, perhaps because Paul has never touched this file so far or alluded to this practice being common. He only briefly once said that it is where code that runs as soon as the app is launched resides.

Is there anything wrong with my approach? Have I misunderstood the challenge altogether?

2      

Farid wonders if he missed the objective:

Is there anything wrong with my approach? Have I misunderstood the challenge altogether?

If you're testing your boundaries and learning something new, I'd say you get full points for this challenge.

Your example is hard to follow without before and after code bits. So my mind went a different way with @twoStraw's challenge

The Challenge:
Extract one or two pieces of view code into their own new SwiftUI views.

Here's a simple example where I have a complex view, and I break out two pieces of view code into their own SwiftUI views. In this example, I alternate Text views and Rectangles to simulate headlines in a newspaper. But notice all the redundant code. Each view has background colors, opacity, heights, etc.

Can you accomplish the challenge and extract pieces into their own SwiftUI views?

// Paste into Playgrounds!
import SwiftUI
import PlaygroundSupport

struct ComplexView: View {
    var body: some View {
        VStack(alignment: .center, spacing: 20) {
            // Headline ---------------
            Text("Joker Robs Gotham Bank!")
                .padding([.leading, .trailing])
                .font(.largeTitle)
                .foregroundColor(.teal.opacity(0.9))
            // Divider ------------------
            Rectangle().frame(width: 200, height: 10)
                .foregroundColor(.red.opacity(0.7))
            Text("Batman Ignores Batsignal").font(.title)
                .foregroundColor(.indigo.opacity(0.9))
            Rectangle().frame(width: 200, height: 10)
                .foregroundColor(.blue.opacity(0.7))
            Text("Catwoman Cavorts with Robin").font(.title2)
                .foregroundColor(.brown.opacity(1.0))
            Rectangle().frame(width: 300, height: 6)
                .foregroundColor(.orange.opacity(0.7))
        }
        .frame(width: .infinity, height: 300)
    }
}

// Run this line in Playgrounds
PlaygroundPage.current.setLiveView( ComplexView() )

Create a Reusable DividerView()

Try to think in terms of building a reusable Lego brick. It's a unique piece you can easily snap into a solution and know that it fits perfectly. Here's how I see the DividerView().

Requirements Analysis

What are the common properties of a DividerView() ?

  1. Width
  2. Height
  3. Color
  4. Opacity
// Create a reusable Lego brick.
// You'll want to reuse this in your applications.
// Easy to paste this into a new application!
struct DividerView: View {
    // Common Properties with sensible default values
    var width:   CGFloat = 200     // provide a default
    var height:  CGFloat = 10      // nice default
    var color:   Color   = .black  // common default
    var opacity: CGFloat = 0.8     // default

    // This is what gets drawn to your screen
    var body: some View {
        // Create a rectangle using the parameters you pass in.
        Rectangle()
            .frame(width: width, height: height)
            .foregroundColor( color.opacity(opacity))
    }
}

Simplify the ComplexView()

With this reusable Lego brick, you can simplify the Complex view.

struct SimplerView: View {
    // Simplify your view by using your DividerView.
    var body: some View {
        VStack(alignment: .center, spacing: 20) {
            Text("Joker Robs Gotham Bank!")
                .padding([.leading, .trailing])
                .font(.largeTitle)
                .foregroundColor(.teal.opacity(0.9))
            // Use your Lego brick! Customize your divider --------------
            DividerView(width: 200, height: 10, color: .red, opacity: 0.7)
            Text("Batman Ignores Batsignal").font(.title)
                .foregroundColor(.indigo.opacity(0.9))
            // Use your Lego brick. Rely on defaults.
            DividerView(color: .blue, opacity: 0.7)
            Text("Catwoman Cavorts with Robin").font(.title2)
                .foregroundColor(.brown.opacity(1.0))
            DividerView(width: 300, height: 6, color: .orange, opacity: 0.7)
        }
        .frame(width: .infinity, height: 300)
    }
}

// Run this line in Playgrounds
PlaygroundPage.current.setLiveView( SimplerView() )

HeadlineView()

The next step is to pull out the Text views and create another reusable Lego brick. I call this one HeadlineView().
What are the common properties of a HeadlineView ?

  1. Text
  2. Font Style
  3. Color
  4. Opacity
// Create another Lego brick for Headlines
// Easy to paste this into a new application!
struct HeadlineView: View {
    var headline         = "Placeholder"     // provide a default
    var style            = Font.largeTitle   // nice default
    var color:   Color   = .black            // common default
    var opacity: CGFloat = 1.0               // default

    // This is what is drawn to your screen
    var body: some View {
        // Create a headline using the parameters you pass in.
        Text( headline )
            .font( style )
            .foregroundColor( color.opacity(opacity)) // You specify this look
    }
}

Refactored View

Now that you've created two Lego bricks, you can further simplify the original complex view. Please note: Because these two bricks can be put into their own .swift files, it will be easy to reuse this code in other applications you write. Think about common buttons you might want in different applications. Break your complex views into smaller, specialized Lego bricks.

Also note, when you use one of your Lego bricks, you can supply your own customizations, or just rely on the default values. This makes the bricks very flexible.

// Simplify your view by using your DividerView and your HeadlineView
struct RefactoredView: View {
    var body: some View {
        VStack(alignment: .center, spacing: 20) {
        // Create your view by snapping together custom Lego bricks. Easy!
            HeadlineView(headline: "Joker Robs Gotham Bank",      style: .largeTitle, color: .teal,   opacity: 0.9)
            DividerView(width: 200, height: 15, color: .red, opacity: 0.7)
            HeadlineView(headline: "Batman Ignores Batsignal",    style: .title,      color: .indigo, opacity: 0.9)
            DividerView(color: .blue, opacity: 0.7)
            HeadlineView(headline: "Batwoman Dines with Alfred",  style: .title2,     color: .brown)
            DividerView(width: 300, height: 3, color: .orange, opacity: 0.7)
        }
        .frame(width: .infinity, height: 300)
    }
}

// Run this line in Playgrounds
PlaygroundPage.current.setLiveView( RefactoredView() )

Hope this helps.

Keep coding!

4      

@Obelix Thank you so much for this excellent, detailed example and your in-depth explanations. I was able to play around with this in the Sandbox and I learned a few things. However, my question still stands, which is: When two separate structs (MissionView and CrewView, in this case) independently define an identical struct in a nested manner (MissionView.CrewMember and CrewView.CrewMember, in this case), what is the best approach? Should the common struct definition be extracted into the xyzApp.swift file or is there a better place to define the sub-struct such that we can use and reference it in two different views/files? Thank you again, as always.

2      

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.