M5L4-5 : Home is Where the View is

Today’s lesson follows Chris through setting up the main view, whilst using sub-views for duplicated code.

Example:

struct HomeView: View {
    
    @EnvironmentObject var model: ContentModel
    
    var body: some View {
        NavigationView {
            VStack (alignment: .leading) {
                Text("What do you want to do today?")
                    .padding(.leading, 20)
                ScrollView {
                    LazyVStack {
                        ForEach(model.modules) { module in
                            VStack (spacing: 20) {
                                HomeViewRow(image: module.content.image, title: "Learn \(module.category)", description: module.content.description, count: "\(module.content.lessons.count) Lessons", time: module.content.time)
                                HomeViewRow(image: module.test.image, title: " \(module.category) Test", description: module.test.description, count: "\(module.test.questions.count) Questions", time: module.test.time)
                            }
                        }
                    }
                    .padding()
                }
            } 
            .navigationTitle("Get Started")
        }
    }
} 

The main struct HomeView calls up the HomeViewRow twice – first to show a “Lessons” card, then to show a “Test” card.

This pair of views is then called up ForEach lesson stored in modules (populated with JSON data).

The sub-view creates a card with drop-shadow using a ZStack on top of which is a Rectangle (for the drop-shadow), followed by a HStack to contain the Image plus a VStack of the Text elements. The VStack includes another HStack to display the smallprint with related icons side by side.

It’s all about the layering, and envisaging the layout like a grid of Horizontal and Vertical stacks, layered on top of the Rectangle by using a ZStack.

The content that’s being displayed is passed from HomeView to HomeViewRow through the struct properties (the list of var:String at the start of the struct).

The sub-view is coded, thus:

struct HomeViewRow: View {
    
    var image: String
    var title: String
    var description: String
    var count: String
    var time: String
    
    var body: some View {
        ZStack {
            Rectangle()
                .foregroundColor(.white)
                .cornerRadius(10)
                .shadow(radius: 5)
                .aspectRatio(CGSize(width:335, height:175), contentMode: .fit)
            HStack {
                Image(image)
                    .resizable()
                    .frame(width: 116, height: 116)
                    .clipShape(Circle())
                Spacer()
                VStack (alignment: .leading, spacing: 10) { 
                    Text(title)
                        .fontWeight(.bold)
                    Text(description)
                        .padding(.bottom, 20.0)
                        .font(.caption)
                    HStack { 
                        Image(systemName: "text.book.closed")
                            .resizable()
                            .frame(width: 15, height: 15)
                        Text(count)
                            .font(Font.system(size: 10))
                        Spacer()
                        Image(systemName: "clock")
                            .resizable()
                            .frame(width: 15, height: 15)
                        Text(time)
                            .font(Font.system(size: 10))
                    } 
                }
                .padding(.leading, 20)
            }
            .padding(.horizontal, 20.0)
        } 
    }
}

It’s a lot of code for something that’s relatively straightforward, but I’ll leave it here to refresh my memory in the future. I know I’ll need the refresher.

Lesson 6 continues in this vein, setting up the next level down in the hierarchy so that when a Lesson is selected, a list of available content is displayed.

The main HomeView is changed for the appropriate NavigationLink, re:

ForEach ...
    VStack (spacing: 20) {
        NavigationLink(
            destination:
                ContentView()
                .onAppear(perform: { 
                    model.beginModule(module.id)
                }),
                label: {
                    HomeViewRow(image: ...

The sub-view ContentView, is thus:

struct ContentView: View {
    
    @EnvironmentObject var model: ContentModel
    
    var body: some View {
        ScrollView {
            LazyVStack {
                if model.currentModule != nil {
                    ForEach(0..<model.currentModule!.content.lessons.count) { index in                        
                        ContentViewRow(index: index)                        
                    }
                }
            }
            .padding()
            .navigationTitle("Learn \(model.currentModule?.category ?? "")")             
        }        
    }
}

And the ContentViewRow sub-view is thus:

struct ContentViewRow: View {
    
    @EnvironmentObject var model: ContentModel
    var index: Int
    
    var body: some View {
        let lesson = model.currentModule!.content.lessons[index]
        ZStack (alignment: .leading) {
            Rectangle()
                .foregroundColor(.white)
                .cornerRadius(10)
                .shadow(radius: 5)
                .frame(height: 66)
            HStack(spacing: 30.0) {
                Text(String(index + 1))
                    .fontWeight(.bold)
                VStack (alignment: .leading) {
                    Text(lesson.title)
                        .fontWeight(.bold)
                    Text(lesson.duration)
                }
            }
            .padding()
        }
        .padding(.bottom, 5)
    }
}

Note for reference – don’t forget the @EnvironmentObject when moving code to sub-view.