Instruction
As you have seen in previous lessons, Apple has made a giant effort to make Liquid Glass simple to adopt for developers. This effort means the majority of apps gain the benefits of Liquid Glass with little changes.
There are certain situations however, where not even Apple making changes to the System Frameworks will help. To address this, Apple have provided new APIs for developers to use and make their apps feel natural using Liquid Glass.
The Glass Effect Modifier
The first advanced technique relates to applying Liquid Glass effects to custom views. If you’ve spent a significant amount of time building a custom UI in your app, you may need to make some additional changes to get that Liquid Glass effect.
An easy way to do this is to use the .glassEffect() View Modifier. This modifier styles the View with a Liquid Glass effect, whilst also applying a shape using the Liquid Glass material.
To see an example, open the up the Starter project for this lesson, and run the app. Once the app loads, tap the + button to open the create card screen:
In this upcoming example, you will remove the toolbar and replace it with your own implementation. That way you can leverage the use of the .glassEffect() modifier without the toolbar taking over and applying the Liquid Glass effect by itself.
To do that update CardToolbar.swift and remove the contents of the ToolbarItem on line 70:
ToolbarItem(placement: .bottomBar) {
}
Next, open SingleCardView.swift and replace the body property with the following layout:
var body: some View {
NavigationStack {
VStack {
GeometryReader { proxy in
CardDetailView(
card: $card,
viewScale: Settings.calculateScale(proxy.size))
frame(
width: Settings.calculateSize(proxy.size).width,
height: Settings.calculateSize(proxy.size).height)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.modifier(CardToolbar(
currentModal: $currentModal,
card: $card))
}
Spacer()
HStack {
Image(systemName: "eraser.fill")
.frame(width: 40.0, height: 40.0)
.glassEffect(.regular.tint(.blue).interactive())
Image(systemName: "textformat.size")
.frame(width: 40.0, height: 40.0)
.glassEffect()
Text("More")
.padding(8.0)
.glassEffect(in: .rect(cornerRadius: 16.0))
}
.onDisappear {
card.save()
}
.onChange(of: scenePhase) { _, newScenePhase in
if newScenePhase == .inactive {
card.save()
}
}
}
}
}
The layout is mostly kept the same. The only difference is you add a VStack to hold the content, and as part of the VStack you create a HStack containing two Image’s and a Textcomponent. These components are setup and most importantly given a .glassEffect() modifier.
Build and run the app to see how they work for yourself.
As you can see in the code and within the app, the glass effect modifier can provide a variety of effects. The first image glass effect modifier is passed a .regular property, which is the regular variant of Liquid Glass. Then, the glass is tinted with a blue color using .tint() and finally enabled to be interactive using .interactive().
You can see the image is interact by tapping on it, and seeing the Image expanding and contracting to the tap.
Moving onto the second Image, this is using a plain .glassEffect() with no other configuration. This is useful for providing a default Liquid Glass effect. Finally, the Text is passed a .rect into .glassEffect, telling SwiftUI to apply Liquid Glass as a rectangle, setting the cornerRadius to 16 points. With just a few different configurations, you can see how Liquid Glass can morph and change to your particular needs.
There are a few more configurations available to try. You can find these in the Glass API Documentation. It’s time to move on and look at another advanced technique: using Liquid Glass Containers!
Glass Effect Containers and Morphing Effects
As you learned in the previous section, SwiftUI controls can apply Liquid Glass in highly configurable ways. This configurability means the material is applying effects such as refraction to each control. Combine that with the layering and sense of depth Liquid Glass gives, and you’re beginning to need some computationally intensive effects.
To address that need, Apple has created the Glass Effect Container API to group SwiftUI controls together. This allows SwiftUI to render the Liquid Glass effect for each control as if they were one control The container also allows the controls to interact and morph into one another.
Let’s create an example. Open CardToolbar.swift and update the bottom bar toolbar from line 70:
ToolbarItemGroup(placement: .bottomBar) {
GlassEffectContainer(spacing: 60.0) {
HStack(spacing: 40.0) {
Image(systemName: "scribble.variable")
.frame(width: 40.0, height: 40.0)
.glassEffect()
.glassEffectID("pencil", in: namespace)
if isExpanded {
Image(systemName: "eraser.fill")
.frame(width: 40.0, height: 40.0)
.glassEffect()
.glassEffectID("eraser", in: namespace)
Image(systemName: "textformat.size")
.frame(width: 40.0, height: 40.0)
.glassEffect()
.glassEffectID("text", in: namespace)
}
}
}
}
Here, you add a GlassEffectContainer inside the group, and pass in a horizontal stack of images. Each of them are given a .glassEffect() modifier to apply Liquid Glass, they are also given a glassEffectId() modifier. This modifier allows you to individually identify each control within the group using the id passed in. Finally, you set each control with a namespace so SwiftUI knows these controls belong to the same group when applying effects.
You’ll see this namespace in use soon. All you need to know for now is when it is used, it’ll help nicely animate the images within the isExpanded block. Don’t worry about the code errors in Xcode. You’ll fix them soon.
Underneath the ToolbarItemGroup holding the spacer, add the following code:
ToolbarItemGroup(placement: .bottomBar) {
Button {
withAnimation {
isExpanded.toggle()
}
} label: {
Label("More", systemImage: "arrow.up.left.and.arrow.down.right")
}
.buttonStyle(.glass)
}
Here, you provide another toolbar group, and set a button using the .glass button style. When the button is tapped, it toggles the isExpanded property you used earlier. Finally, at the top of the CardToolbar. Add the isExpanded property and namespace in use by the toolbar controls:
// ... other properties ...
@State private var isExpanded: Bool = false
@Namespace private var namespace
Build and run the app, then select or create a card. You’ll see that the bottom toolbar has completed changed:
Only one of the images in the bottom left of the toolbar is visible. The image on the bottom right is visible, just as you set it up! Now watch this part closely. Tap the bottom right image in the app, and watch as the rest of the toolbar animates in!
The Glass Effect Container is helping coordinate the animation to show the full toolbar. You only had to make sure the container know about the controls, and when to show them via an animation. If you tap the button repeatedly, you can see the toolbar expanding and constricting itself.
This is another example of Apple doing the hard for you to easily integrate Liquid Glass into your app. Not only do they help with the UI, they provide an easy way to provide animation that fits right in with the design language!
Cleaning up the UI
To finish, it’s time to clean up after yourself. The UI at the bottom of the Cards app is starting to look busy. Open SingleCardView.swift and remove the HStack added earlier:
NavigationStack {
VStack {
// Keep the other controls in VStack
HStack {
Image(systemName: "eraser.fill")
.frame(width: 40.0, height: 40.0)
.glassEffect(.regular.tint(.blue).interactive())
Image(systemName: "textformat.size")
.frame(width: 40.0, height: 40.0)
.glassEffect()
Text("More")
.padding(8.0)
.glassEffect(in: .rect(cornerRadius: 16.0))
}
}
}
Next, open CardsListView.swift and update the .toolbar modifier on line 112 to the following:
if(selectedCard == nil) {
ToolbarSpacer(.flexible, placement: .bottomBar)
ToolbarItemGroup(placement: .bottomBar) {
createButton
settingsButton
}
}
Here, you tell SwiftUI to only render the bottom toolbar items for the CardsListView if a selectedCard is nil.
Build and run the app again, then tap on a card. You should see the bottom of the screen is looking cleaner!
Refining your UI is of particular importance in Liquid Glass. Remember that controls, particularly toolbars, are rendered above the content. If you have multiple layers of content then you could end up with multiple layers of controls. With the changes you made here, the app knows to hide controls when drilling down into the detail card screen.