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.