Another new feature introduced in SwiftUI 3 is safeAreaInset(edge:alignment:spacing:content:) . It provides a way for views to show view beside the modified view.
Last sentence made me dizzy đ”âđ« so letâs look at an example.
In this example, we will take another look at DevTechie Course app where our home page displays courses from DevTechie(again thatâs me đ)
Letâs quickly add model:
struct DevTechieCourse: Identifiable {
var id = UUID()
var name: String
var chapters: Int
}
Sample data
extension DevTechieCourse {
static var sampleData: [DevTechieCourse] {
[
DevTechieCourse(name: "Machine Learning in iOS", chapters: 10),
DevTechieCourse(name: "CollectionViews in iOS", chapters: 12),
DevTechieCourse(name: "SwiftUI DeepDive", chapters: 14),
DevTechieCourse(name: "Goals App in SwiftUI", chapters: 16),
DevTechieCourse(name: "Firebase and SwiftUI", chapters: 8),
DevTechieCourse(name: "Disney Plus Clone in SwiftUI", chapters: 15),
DevTechieCourse(name: "Strava Clone in UIKit, iOS and Swift", chapters: 11),
DevTechieCourse(name: "Data Structures and Algorithms in Swift", chapters: 21),
DevTechieCourse(name: "Machine Learning in iOS", chapters: 10),
DevTechieCourse(name: "CollectionViews in iOS", chapters: 12),
DevTechieCourse(name: "SwiftUI DeepDive", chapters: 14),
DevTechieCourse(name: "Goals App in SwiftUI", chapters: 16),
DevTechieCourse(name: "Firebase and SwiftUI", chapters: 8),
DevTechieCourse(name: "Disney Plus Clone in SwiftUI", chapters: 15),
DevTechieCourse(name: "Strava Clone in UIKit, iOS and Swift", chapters: 11),
DevTechieCourse(name: "Data Structures and Algorithms in Swift", chapters: 21)
]
}
}
View model for model
struct DevTechieCourseViewModel: Identifiable {
var dtCourse: DevTechieCourse
var id: UUID {
dtCourse.id
}
var name: String {
dtCourse.name
}
var chapters: String {
String(dtCourse.chapters)
}
}
Course list view model
class DevTechieCourseListViewModel: ObservableObject {
@Published var dtCourses = [DevTechieCourseViewModel]()
func getAll() {
dtCourses = DevTechieCourse.sampleData.map(DevTechieCourseViewModel.init)
}
}
View cell
struct DevTechieCourseCell: View {
var course: DevTechieCourseViewModel
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text(course.name)
.font(.title2)
.bold()
Text("There are \(course.chapters) chapters in this course.")
.font(.subheadline)
.foregroundColor(.secondary)
}
}
}
and home view
struct DevTechieCourseHomeView: View {
@ObservedObject private var vm = DevTechieCourseListViewModel()
var body: some View {
NavigationView {
ScrollView(showsIndicators: false) {
LazyVStack(alignment: .leading) {
ForEach(vm.dtCourses) { course in
DevTechieCourseCell(course: course)
.padding(.vertical)
}
}
}
.task {
vm.getAll()
}
.overlay(alignment: .bottom, content: {
Button {
} label: {
Text("Enroll Now")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)})
.padding()
.navigationTitle("DevTechie Courses")
}
}
}
Build and run
Notice that we have âEnroll Nowâ button added as an overlay to the scrollview. All looks good until we reach the end of scrollview and then you realize that you canât really see the last row properly đČ Itâs because scrollview doesnât have idea about the button.
Letâs replace overlay with safeAreaInset and alignment parameter with edge
struct DevTechieCourseHomeView: View {
@ObservedObject private var vm = DevTechieCourseListViewModel()
var body: some View {
NavigationView {
ScrollView(showsIndicators: false) {
LazyVStack(alignment: .leading) {
ForEach(vm.dtCourses) { course in
DevTechieCourseCell(course: course)
.padding(.vertical)
}
}
}
.task {
vm.getAll()
}
.safeAreaInset(edge: .bottom, content: {
Button {
} label: {
Text("Enroll Now")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)})
.padding()
.navigationTitle("DevTechie Courses")
}
}
}
Like any other modifier in SwiftUI, safeAreaInset modifier can be applied in chain as shown below:
struct DevTechieCourseHomeView: View {
@ObservedObject private var vm = DevTechieCourseListViewModel()
var body: some View {
NavigationView {
ScrollView(showsIndicators: false) {
LazyVStack(alignment: .leading) {
ForEach(vm.dtCourses) { course in
DevTechieCourseCell(course: course)
.padding(.vertical)
}
}
}
.task {
vm.getAll()
}
.safeAreaInset(edge: .bottom, content: {
Button {
} label: {
Text("Enroll Now")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)})
.safeAreaInset(edge: .bottom, content: {
Button {
} label: {
Text("Enroll Later")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.tint(.orange)})
.padding()
.navigationTitle("DevTechie Courses")
}
}
}
safeAreaInset works for both horizontal and vertical axis. So letâs add safeAreaInset views on leading, trailing, top and bottom edges as well.
struct DevTechieCourseHomeView: View {
@ObservedObject private var vm = DevTechieCourseListViewModel()
var body: some View {
NavigationView {
ScrollView(showsIndicators: false) {
LazyVStack(alignment: .leading) {
ForEach(vm.dtCourses) { course in
DevTechieCourseCell(course: course)
.padding(.vertical)
}
}
}
.task {
vm.getAll()
}
.safeAreaInset(edge: .bottom, content: {
Button {
} label: {
Text("Enroll Now")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)})
.safeAreaInset(edge: .bottom, content: {
Button {
} label: {
Text("Enroll Later")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.tint(.orange)})
.safeAreaInset(edge: .bottom, content: {
Color.cyan
.frame(height: 10)
})
.safeAreaInset(edge: .leading, content: {
Color.teal
.frame(width: 10)
})
.safeAreaInset(edge: .trailing, content: {
Color.blue
.frame(width: 10)
})
.safeAreaInset(edge: .top, content: {
Color.pink
.frame(height: 10)
})
.padding()
.navigationTitle("DevTechie Courses")
}
}
}
With that, we have reached the end of this article. Thank you once again for reading, if you liked it, donât forget to subscribe our newsletter.