Design Exploration iOS 26

This month’s code-alongs are dedicated to introduce you to the new liquid glass design of iOS 26. We will create a TabView including 3 different views and a search view. We will also create an easy MiniPlayer which sits above the TabView and is a simple recreation of the Music Player behaviour in Apple Music:

Let’s get started!

Step 1: Download Xcode 26

For this project you’ll need Xcode 26 beta 4. You can download it here:
Xcode 26 beta 4

Please install it before proceeding.

Step 1: Set up your project

  1. Open Xcode: Launch Xcode and select Create a new Xcode project.
  2. Choose Template: Select App under the iOS tab and click Next.
  3. Name Your Project: Enter a name for your project, like DesignExploration.
    • interface: SwiftUI
    • language: Swift

Click Next, and then save your project.

When you open your project, you’ll see the already familiar standard code presenting a globe and the Text “Hello, world!” in the ContentView.swift.

Step 2: Creating the different default views

Please create the following SwiftUI views by clicking command + N:

  • HomeView
  • ListView
  • ProfileView
  • SearchView
  • MiniPlayerView

Step 3: Creating the TabView

In ContentView we create our TabView as usual:

Swift
@State private var selectedTab: String = "home"

var body: some View {

	TabView(selection: $selectedTab) {

		Tab("Home", systemImage: "house", value: "home") {
			HomeView()
		}

		Tab("Profile", systemImage: "person", value: "profile") {
			ProfileView()
		}

		Tab("List", systemImage: "list.bullet", value: "list") {
			ListView()
		}

		Tab(value: "search", role: .search) {
			SearchView()
		}
    }
}

You’ll see that the usual TabViewalready creates the new liquid glass behaviour. Just move from one to the next tab, and you’ll see the wonderful liquid glass effect.

You’ll also see that by adding role: .search to the last tab, automatically the SearchView is singled out in a separate container and is represented by the magnifier symbol.

Step 4: SearchView

We want to create a list with the entries “Song Title” and adding the numbers from 1 to 50. At the bottom we want to include a search bar and the list is then filtered accordingly.

Therefore, we need to define a variable that holds the searchString and also one that holds the filtered items (filteredItems):

Swift
struct SearchView: View {

    @State private var searchString = ""
    private var filteredItems: [Int] {
	    let allItems = Array(1...50)
	
	    if searchString.isEmpty {
		    return allItems
	    } else {
		    return allItems.filter {
	                "Song Title \($0)".localizedCaseInsensitiveContains(searchString)
		    }
	    }
    }

    var body: some View {

        NavigationStack {
            List(filteredItems, id: \.**self**) { idx in
                Text("Song Title \(idx)")
            }
            .navigationTitle("Search")
            .searchable(text: $searchString)
        }
    }
}

By simply adding .searchable(text: $searchString), a search bar appears at the bottom and we make the list searchable. You can try it by typing e.g. 1and all “Song Title” with a 1 are being filtered. That’s all!

Step 5: Adding liquid glass elements to HomeView

Let’s add to HomeView some liquid glass elements. We define some symbols in symbolSet we want to show. We set a background image (which has the simple name “1016” and comes from one of Apple’s examples) and present our systemName images as usual – for the moment please just take the namespace as it is:

Swift
struct HomeView: View {

    @Namespace private var namespace

    let symbolSet: [String] = ["cloud.bolt.rain.fill", "sun.rain.fill", "moon.stars.fill", "moon.fill"]

    var body: some View {

        ZStack {
	        Image("1016")
		        .resizable()
		        .scaledToFill()
		        .edgesIgnoringSafeArea(.all)

            VStack(spacing: 40.0) {
				HStack(spacing: 40.0) {
					Image(systemName: "scribble.variable")
						.frame(width: 80.0, height: 80.0)
						.font(.system(size: 36))
						.glassEffect()
						.offset(x: 20.0, y: 0.0)

					Image(systemName: "eraser.fill")
						.frame(width: 80.0, height: 80.0)
						.font(.system(size: 36))
						.glassEffect()
						.offset(x: -20.0, y: 0.0)
				}
            }
        }
    }
}

By simply adding the modifier .glassEffect() we create the glass effect around our images. That’s all!

Let’s wrap the HStack into a

Swift
GlassEffectContainer(spacing: 40.0) {

}

The appearance now changes from two separate circular items to an appearance with a slightly overlapping effect since they are now in the same GlassEffectContainer. You can play with the spacing and the .offset

To show this effect again, you can add as well:

Swift
   GlassEffectContainer(spacing: 20.0) {
                    HStack(spacing: 20.0) {
                        ForEach(symbolSet.indices, id: \.**self**) { item **in**
                            Image(systemName: symbolSet[item])
                                .frame(width: 80.0, height: 80.0)
                                .font(.system(size: 36))
                                .glassEffect()
                                .glassEffectUnion(id: item < 2 ? "1" : "2", namespace: namespace)
                        }
                    }
                }

.glassEffectUnion(id: item < 2 ? "1" : "2", namespace: namespace) groups glass effects together. Items with the same id will share one glass background. Here, the first two items (item < 2) get “1”, the others get “2”. namespace is a @Namespace variable that ensures SwiftUI knows which items belong to the same group.

Play around!

Step 6: Create a cool toggle effect

We will now create a cool transition effect that showcases the liquid glass effect: Pressing a toggle button to expand two images and revert back. Let’s start by putting two images (for scribble and eraser) on top of each other in the same GlassEffectContainer:

Swift
struct ProfileView: View {

    @State private var isExpanded: Bool = false
    @Namespace private var namespace

    var body: some View {

        ZStack {
            Image("1016")
                .resizable()
                .scaledToFill()
                .edgesIgnoringSafeArea(.all)

            VStack {
                GlassEffectContainer(spacing: 80.0) {
                    HStack(spacing: 80.0) {
                        Image(systemName: "scribble.variable")
                            .frame(width: 80.0, height: 80.0)
                            .font(.system(size: 36))
                            .glassEffect()
                            .glassEffectID("scribble", in: namespace)

                        if isExpanded {
                            Image(systemName: "eraser.fill")
                                .frame(width: 80.0, height: 80.0)
                                .font(.system(size: 36))
                                .glassEffect()
                                .glassEffectID("eraser", in: namespace)
                        }
                    }
                }
                .padding(.bottom, 40)
            }
        }
    }
}

We wrapped the eraser image in an if statement because the eraser should only be visible when a button has been toggled. Therefore let’s define the button:

Below the GlassEffectContainer (after the .padding(.bottom, 40)), insert the following:

Swift
Button("Toggle") {
	withAnimation(.easeInOut(duration: 0.25)) {
		isExpanded.toggle()
	}
}
.buttonStyle(.glass)

Press the toggle button and watch the effect! Doesn’t it look beautiful? You can also use other animations (e.g. .bouncy instead of .easeInOut).

Step 7: A toolbar effect

For the last – pretty cool – effect, we will use our ListViewand our MiniPlayerView. Let’s start with the ListView:

ListView is just a simple list of 50 items, nothing special:

Swift
struct ListView: View {

    var body: some View {

        NavigationStack {
            VStack {
                List(1...50, id: \.**self**) { idx **in**
                    Text("Item \(idx)")
                }
                .listStyle(.plain)
            }
            .navigationTitle("Simple List")
        }
    }
}

Now let’s create a MiniPlayerView:

Swift
struct MiniPlayerView: View {

    var body: some View {

        HStack(spacing: 15) {

            Image(systemName: "music.note.list")
                .font(.title2)
                .foregroundStyle(.secondary)
                .frame(width: 48, height: 48)
                .background(Color(.systemGray5))
                .clipShape(RoundedRectangle(cornerRadius: 8))

            VStack(alignment: .leading) {
                Text("Song Title")
                    .font(.headline)
                    .fontWeight(.semibold)

                Text("Artist Name")
                    .font(.subheadline)
                    .foregroundStyle(.secondary)
            }

            Spacer()

            HStack(spacing: 20) {
                Button(action: { }) {
                    Image(systemName: "play.fill")
                        .font(.title2)
                        .foregroundStyle(.primary)
                }

                Button(action: { }) {
                    Image(systemName: "forward.fill")
                        .font(.title2)
                        .foregroundStyle(.primary)
                }
            }
        }
    }
}

For simplicity, the buttons do not include an action but of course you can add an action of your choice.

So far, nothing special. You can go to ContentView and click on the list tab and you’ll see our list.

Now the cool part comes:

In ContentView add the following modifiers to TabView:

Swift
.searchToolbarBehavior(.minimize)
.tabBarMinimizeBehavior(.onScrollDown)
.tabViewBottomAccessory {
	MiniPlayerView()
		.padding()
}

What do they do?

  • .searchToolbarBehavior(.minimize): This modifier controls how the search toolbar behaves when scrolling. .minimize means that when the user scrolls down, the search bar collapses into a small icon in the navigation bar. It saves space and makes the UI feel cleaner. Without this modifier, the search bar would stay expanded, taking up more space.
  • .tabBarMinimizeBehavior(.onScrollDown): This controls how the tab bar behaves while scrolling. .onScrollDown means that when the user scrolls down, the tab bar hides automatically, giving more space to the content. When the user scrolls back up, the tab bar reappears. This is great for apps with lists or feeds where users scroll through a lot of content.
  • .tabViewBottomAccessory: This lets you add an extra view on top of the tab bar, like a floating accessory panel. Here, it adds a MiniPlayerView()—think of a small music player, similar to Apple Music. And you’ll see that this MiniPlayerView moves to the bottom when you are in our ListView tab and you scroll down. Nice.

Congratulations!

You’ve successfully created views with new design features of iOS 26! 🎉

What you have learned

In this code-along, you’ve learned how to:

  • Create a tab view with multiple tabs, including a dedicated search tab using role: .search and featuring the new liquid glass design.
  • Build a searchable list with real-time filtering using .searchable(text:).
  • Use the new liquid glass design in iOS 26 with .glassEffect(), GlassEffectContainer, and .glassEffectUnion() to group elements together.
  • Apply matched glass effects with .glassEffectID() to create smooth transitions when toggling elements.
  • Enhance navigation with the new iOS 26 modifiers:
    • .searchToolbarBehavior(.minimize) to collapse the search bar on scroll.
    • .tabBarMinimizeBehavior(.onScrollDown) to hide the tab bar while scrolling.
    • .tabViewBottomAccessory to attach a custom view above the tab bar.

That’s a wrap!

Keep learning, keep building, and let your curiosity guide you. Happy coding! ✨

You are never too old to set another goal or to dream a new dream. — Les Brown


Download the full project on GitHub: https://github.com/swiftandcurious/DesignExploration