Preparing the subviews

Preparing the subviews 🖌

We start by preparing the subviews that our TabView should contain later. Create a new SwiftUI view file and name it “Subview”. Delete the “Hello, world!” Text view.

Each Subview should consist of an Image view and two Text views. Since these views should be arranged vertically, we use a VStack. Let’s insert our Image view and use – for example – the “meditating” image file. In order to scale our Image view properly, we must first apply the .resizable modifier.

struct Subview: View {
    var body: some View {
        VStack {
            Image("meditating")
                .resizable()
        }
    }
}

Our Image view should always be half as high as the screen of the device our app is running on. But how can we find out how wide and high this screen is? After all, the screen of an iPhone 13 Pro Max has completely different dimensions than that of an iPhone SE.

This is the perfect case for using a GeometryReader. We can embed views into such a GeometryReader. The GeometryReader has access to the dimensions of the outer view. The embedded view can then access this information through the GeometryReader.  

This should become more comprehensible when you see how we use a GeometryReader for our purposes. Our Image view should always be half as high as the entire Subview. Therefore, we have to access the body of our Subview. To do this, wrap the VStack below it in a GeometryReader as follows:

struct Subview: View {
    var body: some View {
        GeometryReader { geometry in
            VStack {
                Image("meditating")
                    .resizable()
            }
        }
    }
}

The views embedded into the GeometryReader can now access the dimensions of the overall Subview by using the GeometryReader’s geometry property. 

Now we can easily frame our Image view as follows:

GeometryReader { geometry in
    VStack {
        Image("meditating")
            .resizable()
            .frame(height: geometry.size.height/2)
    }
}

Great! We have now ensured that our Image view is always half the height of the entire Subview. Let’s apply the common modifiers for framing our Image view properly and add some padding to it.

Image("meditating")
    .resizable()
    .frame(height: geometry.size.height/2)
    .aspectRatio(contentMode: .fit)
    .clipped()
    .padding(.top, 70)
    .padding()

Your preview simulator should now look like this:

Now we have to insert the two Text views below our Image view.

VStack {
    //...
    Text("Take some time out")
        .font(.title)
        .padding()
    Text("Take your time out and bring awareness into your everyday life")
        .font(.subheadline)
        .foregroundColor(.gray)
        .padding()
}

Next, we make sure that all views are aligned on the left. For this purpose, we change the alignment mode of our VStack. Furthermore, we push all views to the top by adding a Spacer view.

VStack(alignment: .leading) {
    //...
    Spacer()
}

We are finished with preparing our Subview!

Defining the data model for our subviews ⚙️

Each Subview of our onboarding app should show a different Image and Text. Therefore, we create a suitable data model that we can use later on. Create a new Swift file named “Helper” and declare a “SubviewModel” struct that conforms to the Identifiable protocol.

struct SubviewModel: Identifiable {
    
}

For each Subview, we need properties for the corresponding Image asset, the title and caption Text. We also need an id property to satisfy the Identifiable protocol.

struct SubviewModel: Identifiable {
    let imageString: String
    let title: String
    let caption: String
    let id = UUID()
}

In order for our TabView to be able to distinguish between the different Subview instances, we need another property. But first, we create a new enum below our SubviewModel struct called “Tab” that describes the different subview cases.

enum Tab: Hashable {
    case meditating
    case running
    case walking
}

Now we can extend our SubviewModel with a “tag” property:

struct SubviewModel: Identifiable {
    let imageString: String
    let title: String
    let caption: String
    let id = UUID()
    let tag: Tab
}

Next, we create some SubviewModel instances which we can use later on to initialize the corresponding Subview instances in the TabView. For this purpose, you can add the following array to your Helper.swift file.

let subviewData = [
    SubviewModel(imageString: "meditating", title: "Take some time out", caption: "Take your time out and bring awareness into your everyday life", tag: .meditating),
    SubviewModel(imageString: "running", title: "Conquer personal hindrances", caption: "Meditating helps you dealing with anxiety and bringing calmness into your life", tag: .running),
    SubviewModel(imageString: "walking", title: "Create a peaceful mind", caption: "Regular meditation sessions create a peaceful inner mind", tag: .walking)
]

Updating our Subview

Instead of fixed values, our Subview should use the information from a given SubviewModel instance. Therefore, we change our Subview accordingly:

struct Subview: View {
    
    let subviewModel: SubviewModel
    
    var body: some View {
        GeometryReader { geometry in
            VStack(alignment: .leading) {
                Image(subviewModel.imageString)
                    //...
                Text(subviewModel.title)
                    //...
                Text(subviewModel.caption)
                    //...
                Spacer()
            }
        }
    }
}

Our Subview_Previews struct now requires a corresponding instance. For example, we can use the second element in our subviewData array.

struct Subview_Previews: PreviewProvider {
    static var previews: some View {
        Subview(subviewModel: subviewData[1])
    }
}

One reply on “Preparing the subviews”

Leave a Reply

Your email address will not be published. Required fields are marked *