Mastering SwiftUI: Building Beautiful and Interactive User Interfaces: Part 3

Modabbir Tarique
9 min readAug 5, 2023

--

Mastering Dynamic Views in SwiftUI

Hola! Welcome to our comprehensive guide to SwiftUI, Apple’s declarative UI framework for building delightful and interactive user interfaces on iOS, macOS, watchOS, and tvOS. In this article, we will embark on an exciting journey into the world of SwiftUI, exploring its core concepts, advantages, and lifecycle.

In the first part of our exploration, we will delve into the fundamental aspects of SwiftUI. We will begin by understanding what SwiftUI is and how it revolutionizes the way we design and create user interfaces. Discover the numerous advantages it brings, such as enhanced readability, reusability, and adaptability across Apple’s platforms. Moreover, we will gain insights into SwiftUI’s lifecycle, enabling us to grasp how views are created, updated, and discarded. If you missed it, don’t worry — you can catch up right here!

As we progress to the second part, we will focus on building strong foundations in layout design. We’ll explore SwiftUI’s powerful layout system, including Stacks, which allow us to arrange views vertically or horizontally, and delve into various layout techniques to achieve beautiful user interfaces. Additionally, we will learn the essentials of List and NavigationStacks, fundamental components for building dynamic lists and navigation flows. In case if you missed that, don’t worry — you can read it from here!

In the third part of the series, we will dive deeper. So, fasten your seat-belts, as we embark on an immersive adventure through the depths of SwiftUI, and equip ourselves with the knowledge and skills to create stunning and engaging user interfaces for Apple’s ecosystem. Let’s get started!

Topics that we are going to discuss in today’s blog:

  1. Scroll View
  2. Dynamic List View
  3. Complex Lists with dynamic and static elements
  4. Hierarchical List
  5. Customising lists using different styling options
  6. SwiftUI Gestures

Scroll View

ScrollView is a container view that allows you to create a scrollable area for its content. It's used when you have a group of views that may not fit on the screen at once, and you want to enable the user to scroll through them.

The basic syntax for ScrollView is as follows:

ScrollView {
// Content views go here
}

We can place multiple views inside scroll view. SwiftUI automatically arrange them vertically by default. But, we want to arrange them horizontally then we can use HStack inside scroll view.

Lets, look into one example, we use ScrollView to create a scrollable area containing 20 Text views, each displaying "Item 1", "Item 2", and so on. The VStack inside the ScrollView arranges the views vertically.

struct ContentView: View {
var body: some View {
ScrollView {
VStack(spacing: 20) {
ForEach(1...20, id: \.self) { index in
Text("Item \(index)")
.font(.title)
}
}
.padding()
}
}
}

By default, ScrollView will enable both vertical and horizontal scrolling if the content exceeds the available space in both dimensions. You can also customize the scrolling behavior using the Axis.Set parameter of the ScrollView initializer.

Keep in mind that ScrollView should be used when you need to scroll through a limited number of views. For very large datasets or long lists, SwiftUI provides specialised views like List, which is optimised for handling large collections efficiently.

Dynamic List View

In the previous part, we explored the foundational concepts of lists and static list.

Now, lets look into Dynamic List View.

Dynamic lists in SwiftUI are a powerful way to display a collection of data that can change at runtime. SwiftUI provides a convenient way to create dynamic lists using the List view along with the ForEach construct.

Lets look into the example, here we use the @State property wrapper to manage the dynamic elements, which are stored in the dynamicElements array. The list is created using the List view, and the dynamic elements are displayed using ForEach. The onDelete modifier enables swipe-to-delete functionality to remove items from the list.

import SwiftUI

struct ContentView: View {
// Dynamic elements
@State var dynamicElements = ["Item 1", "Item 2", "Item 3"]

var body: some View {
NavigationView {
List {
Section(header: Text("Dynamic Elements")) {
ForEach(dynamicElements, id: \.self) { item in
Text(item)
}
.onDelete(perform: deleteItem)
}
}
.navigationTitle("Dynamic List")
.navigationBarItems(trailing: addButton)
}
}

func deleteItem(at indexSet: IndexSet) {
dynamicElements.remove(atOffsets: indexSet)
}

var addButton: some View {
Button(action: {
addNewItem()
}) {
Image(systemName: "plus")
}
}

func addNewItem() {
dynamicElements.append("New Item")
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

We can use dynamic lists with more complex view hierarchies, and even combine them with navigation, allowing users to interact with the elements in the list and navigate to different screens. SwiftUI’s powerful data-driven approach simplifies the process of working with dynamic content and allows you to create dynamic and responsive user interfaces with ease.

We also have “Add” button in the navigation bar to demonstrate how to add new items to the list dynamically. When the button is tapped, the addNewItem function appends a new item to the dynamicElements array, and SwiftUI automatically updates the list.

Lets see the final output:

Complex Lists with dynamic and static elements

In SwiftUI, you can create complex lists with dynamic and static elements using the List view and combining it with ForEach and other SwiftUI constructs. SwiftUI is a declarative framework, so you'll use SwiftUI views to represent your data elements.

Let’s see how to create a complex list with dynamic and static elements in SwiftUI:

import SwiftUI

struct ContentView: View {
// Static elements
let staticElements = ["Item 1", "Item 2", "Item 3"]

// Dynamic elements
@State var dynamicElements = [Int]()

var body: some View {
NavigationView {
List {
Section(header: Text("Static Elements")) {
ForEach(staticElements, id: \.self) { item in
Text(item)
}
}

Section(header: Text("Dynamic Elements")) {
ForEach(dynamicElements, id: \.self) { value in
Text("Value: \(value)")
}
}
}
.navigationTitle("Complex List")
.onAppear {
// Populate dynamicElements with some data (e.g., 0 to 9)
dynamicElements = Array(0..<10)
}
}
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

In this example, we define a ContentView with two sections: one for static elements and another for dynamic elements. The static elements are hardcoded in the staticElements array, while the dynamic elements are generated using the dynamicElements array. We use @State to manage the dynamic data so that any changes in dynamicElements will trigger a UI refresh.

The List view in SwiftUI automatically adapts to the elements you provide, whether they are static or dynamic. The ForEach view helps to iterate through elements and display them accordingly.

With SwiftUI’s declarative approach, creating complex lists with dynamic and static elements becomes intuitive and efficient, enabling you to build sophisticated and interactive user interfaces with ease.

Lets see the final output:

Hierarchical List

When we want to show tree structure like data with certain depth then we need hierarchical list.

Let’s create an iOS app in SwiftUI that represents a hierarchical file explorer with subfolders. The app will display a list of folders and subfolders, allowing the user to navigate through the file structure.

First, we’ll define a data model to represent the folders and subfolders:

import Foundation

struct FolderItem: Identifiable {
let id = UUID()
var name: String
var subfolders: [FolderItem]?
}

let rootFolderData = [
FolderItem(name: "Documents", subfolders: [
FolderItem(name: "Text Files"),
FolderItem(name: "Images", subfolders: [
FolderItem(name: "Photos"),
FolderItem(name: "Screenshots"),
]),
]),
FolderItem(name: "Downloads"),
FolderItem(name: "Desktop", subfolders: [
FolderItem(name: "Projects"),
FolderItem(name: "Miscellaneous"),
]),
]

Now, lets create the SwiftUI views for the hierarchical file explorer:

import SwiftUI

struct FolderView: View {
let folder: FolderItem

var body: some View {
NavigationLink(destination: SubfolderView(folder: folder)) {
Text(folder.name)
}
}
}

struct SubfolderView: View {
let folder: FolderItem

var body: some View {
List {
ForEach(folder.subfolders ?? []) { subfolder in
if let subfolders = subfolder.subfolders {
Section(header: Text(subfolder.name)) {
ForEach(subfolders) { nestedSubfolder in
FolderView(folder: nestedSubfolder)
}
}
} else {
FolderView(folder: subfolder)
}
}
}
.navigationTitle(folder.name)
}
}

struct ContentView: View {
var body: some View {
NavigationView {
List {
ForEach(rootFolderData) { folder in
FolderView(folder: folder)
}
}
.navigationTitle("File Explorer")
}
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Lets breakdown what we have written:

  1. FolderView: Represents an individual folder and acts as a navigation link to its subfolders.
  2. SubfolderView: Displays the list of subfolders for a given folder.
  3. ContentView: The root view displaying the list of top-level folders.

The rootFolderData contains the sample folder structure with subfolders.

The FolderView and SubfolderView are recursively used to handle nested subfolders and display them accordingly. Each time the user taps on a folder, the app navigates to its subfolders.

Lets see the final output:

Customising lists using different styling options

In SwiftUI, you have several options for customizing the appearance of lists. Here are some common styling options you can use to create unique and visually appealing lists:

listStyle modifier: SwiftUI provides different list styles that you can apply to your lists.

List {
// List content
}
.listStyle(DefaultListStyle()) // Default style
.listStyle(GroupedListStyle()) // Grouped style
.listStyle(PlainListStyle()) // Plain style
.listStyle(InsetListStyle()) // Inset style

onAppear modifier: You can add animations and transitions when the list appears on the screen.

List {
// List content
}
.onAppear {
// Add animation or transition code here
}

Custom Row Styling: You can customize the appearance of each row in the list using custom SwiftUI views.

struct CustomRow: View {
var item: YourModelType

var body: some View {
// Customize the row view based on the item data
Text(item.title)
.foregroundColor(.blue)
.padding()
}
}

List {
ForEach(yourData) { item in
CustomRow(item: item)
}
}

Section view: You can group list items into sections to create a structured and organized layout.

List {
Section(header: Text("Section 1")) {
// Items for Section 1
}

Section(header: Text("Section 2")) {
// Items for Section 2
}
}

Background and Divider customization: You can customize the background color and divider of the list.

List {
// List content
}
.listRowBackground(Color.blue) // Background color
.listRowSeparator(.visible) // Show dividers

ScrollViewReader and scrollTo: You can programmatically scroll to a specific row using the ScrollViewReader and scrollTo method.

ScrollViewReader { scrollView in
List {
Button("Scroll to Bottom") {
withAnimation {
scrollView.scrollTo(yourLastItemID)
}
}
}
}

DisclosureGroup: You can use a DisclosureGroup to create expandable and collapsible rows in the list.

List {
DisclosureGroup("Expandable Row") {
// Nested content when expanded
}
}

SwiftUI Gestures

In SwiftUI, gestures enable us to add interactivity and user interactions to your app’s user interface. SwiftUI provides several built-in gesture recognizers that we can use to handle various touch and tap events. Here are some commonly used SwiftUI gestures:

TapGesture: Recognizes single taps on a view.

Image("<exampleImage>")
.gesture(TapGesture().onEnded {
// Handle tap event
})

LongPressGesture: Recognizes long press events.

Text("Hold me!")
.gesture(LongPressGesture().onEnded { _ in
// Handle long press event
})

DragGesture: Recognizes dragging or panning gestures.

Image("<exampleImage>")
.gesture(DragGesture().onChanged { gesture in
let translation = gesture.translation
// Handle drag event with the translation
})

MagnificationGesture: Recognizes pinch-to-zoom or magnification gestures.

Image("<exampleImage>")
.gesture(MagnificationGesture().onChanged { scale in
// Handle zoom event with the scale value
})

RotationGesture: Recognizes rotation gestures.

Image("<exampleImage>")
.gesture(RotationGesture().onChanged { angle in
// Handle rotation event with the angle value
})

SimultaneousGesture: Allows combining multiple gestures.

Image("<exampleImage>")
.gesture(SimultaneousGesture(
TapGesture(),
LongPressGesture(),
DragGesture(),
RotationGesture()
).onChanged { value in
// Handle the simultaneous gestures
})

ExclusiveGesture: Allows using multiple gestures but only executes one.

Image("<exampleImage>")
.gesture(ExclusiveGesture(
SimultaneousGesture(TapGesture(), LongPressGesture()),
DragGesture()
).onChanged { value in
if value.isFirst {
// Handle exclusive gestures
}
})

SequenceGesture: Combines multiple gestures in a sequence.

Image("<exampleImage>")
.gesture(SequenceGesture(
DragGesture(),
LongPressGesture()
).onChanged { value in
switch value {
case .first(_, let second):
// Handle the first gesture in the sequence
case .second(_, let first):
// Handle the second gesture in the sequence
default:
break
}
})

We can attach these gesture recognizers to any SwiftUI view, enabling us to respond to user interactions in a highly customizable manner. Additionally, we can combine gestures, use them in sequences, or make them exclusive to provide a rich and interactive user experience in our SwiftUI app.

In this SwiftUI blog series, we covered essential topics like ScrollView, Dynamic List View, Complex Lists with dynamic and static elements, Hierarchical List, Customizing lists with different styling options, and SwiftUI Gestures.

We explored the power of SwiftUI’s views and modifiers to create dynamic and interactive user interfaces. From simple lists to complex hierarchies, we learned how to organize and display data effectively.

Moreover, we customized the appearance of our lists, making them visually appealing with various styles and colors. SwiftUI’s gesture recognizers allowed us to add interactivity and user interactions to our apps.

Now, equipped with theoretical knowledge, it’s time to dive into hands-on projects. Let’s start building exciting SwiftUI apps and turn our creative ideas into reality. So, from our next parts of the series we picking small projects and along with that we started using animation to our swiftUI apps.

Thank you for joining me on this SwiftUI journey. Happy coding, and let’s have fun building amazing apps with SwiftUI!

Check out the next part of the series: SwiftUI — Part4

Feel free to connect me on LinkedIn

--

--

Modabbir Tarique

Full time Software Developer | IIT Guwahati Graduate | Tech Enthusiast