New in SwiftUI 3: SafeAreaInset in SwiftUI 3 and iOS 15

DevTechie Inc
Apr 6, 2022

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.