Swift Refresh 2025 – Day 3: Liquid Glass, animations, SwiftData, and Xcode as an agent
A recap of Day 3 from Swift Refresh Workshop 2025: state, Liquid Glass, declarative animations, SwiftData, and the new role of Xcode as an agent.
Introduction
On Day 3 of the Swift Refresh Workshop 2025, Apple stops teaching isolated APIs and starts showing something much more interesting:
how all the pieces of the modern stack fit together.
Declarative animations, Liquid Glass, SwiftData, strict concurrency, and Xcode as an active agent are not separate topics.
They are different faces of the same idea: state, identity, and ownership.
In this article I am not going to list APIs.
I will explain what we actually learned and why Apple is pushing this model.
1. Everything starts with state (not animations)
Before talking about Liquid Glass or SwiftData, the workshop makes one thing very clear:
SwiftUI does not animate views.
SwiftUI animates state transitions.
A simple boolean like this:
@State private var expanded = false
does not “turn on” an animation.
It describes a new reality.
When that state changes, SwiftUI:
- recalculates the view tree
- detects what changed
- decides what can be animated
if expanded does not hide views
if expanded {
SomeView()
}
This creates and destroys views.
It is not the same as changing .opacity.
That is why inserting views dynamically can make the layout “jump.”
It is not a bug: it is SwiftUI being honest.
2. Layout vs animation: the first reality check
When adding content above a button, we saw the button get pushed down:
VStack {
if expanded { icons }
Button("Options")
}
SwiftUI recalculates the container size.
Result: the button moves.
The workaround used in the workshop
.frame(height: 150, alignment: .bottom)
This is not animation.
It is layout control.
We freeze the space so the button does not “drop.”
In production, you would probably use
overlayorZStack.
Here the goal was to understand why it happens, not to hide it.
3. Animations in SwiftUI: the minimum needed
To animate state changes, you only need one of these two forms:
.animation(.spring, value: expanded)
or
withAnimation(.spring) {
expanded.toggle()
}
SwiftUI will animate everything that can be animated:
- layout
- opacity
- scale
- glass effects
Transitions
.transition(.scale.combined(with: .opacity))
Transitions only work when a view:
- enters
- or exits
(usually usingif)
4. Liquid Glass: not an effect, a system
Liquid Glass is not just another visual modifier.
It is a system that combines:
- material
- identity
- interaction
- animation
Basic glass
.glassEffect(.regular)
.glassEffect(.clear)
.glassEffect(.regular.tint(.green))
.glassEffect(.clear.interactive())
.regular→ visible glass.clear→ subtle glass.interactive()→ responds to input.tint→ colors the surface
Applying it to an HStack creates one single surface.
Applying it to each Image creates individual chips.
5. Visual identity: @Namespace and glassEffectID
@Namespace var namespace
@Namespace does not store data.
It creates a shared identity space.
Mental model
- Namespace = universe
- ID = identity inside the universe
.glassEffectID("icon", in: namespace)
.glassEffectTransition(.identity)
This allows glass to morph between states instead of disappearing and reappearing.
For it to work:
- same
id - same
namespace - the view must exist before and after the change
6. glassEffectUnion: one surface, many views
.glassEffectUnion(id: "g1", namespace: namespace)
This tells SwiftUI:
“These surfaces are one single piece of glass.”
Visually:
- separate icons
- turn into a continuous capsule
Important rule
One
glassEffectUnion= one glass surface
That is why:
- only one tint per union
- if you mix colors, one wins and the others are ignored
7. SwiftData: when the ViewModel is no longer mandatory
@Query private var employees: [Employee]
@Query:
- performs the fetch
- observes changes
- refreshes the UI automatically
In many CRUD screens, this already fulfills the ViewModel role.
Optimization with FetchDescriptor
var fetch = FetchDescriptor<Employee>()
fetch.fetchLimit = 20
fetch.sortBy = [SortDescriptor(\.lastName), SortDescriptor(\.firstName)]
fetch.relationshipKeyPathsForPrefetching = [\.department, \.gender]
_employees = Query(fetch)
fetchLimit→ performancesortBy→ stable orderingprefetch→ avoids N+1 queries
8. Relationships in SwiftData: the most common mistake
After switching to:
@Query private var departments: [Department]
and then using:
department.employees
nothing showed up.
The problem was not the query.
It was the relationship.
Always define the inverse
@Relationship(deleteRule: .deny, inverse: \Employee.department)
var employees: [Employee]
Without inverse, SwiftData cannot build the graph correctly.
9. @ModelActor: the real magic trick
SwiftData is actor-aware.
The mainContext lives on the MainActor.
Using it for heavy operations is a common mistake.
@ModelActor
A @ModelActor:
- has its own
ModelContext - executes serialized operations
- respects strict concurrency
This is the right place for:
- remote fetch
- upserts
- synchronization
- DTO merges
10. DTOs, Sendable, and the predicate error
employee.id == dto.id // ❌
SwiftData does not allow navigating properties inside a #Predicate.
The correct fix:
let id = dto.id
employee.id == id // ✅
Mental rule
In #Predicate:
- the left side is the model
- the right side must be a plain value
11. Xcode Coding Intelligence: the IDE as an agent
Xcode 26 introduces a quiet but profound change.
Coding Intelligence:
- does not browse the internet
- does not use external RAG
- reasons only about your project
- can apply changes and roll them back
Claude works better here by design, not by hype:
- better reasoning with limited context
- better reading of incomplete code
Local models via LM Studio reinforce the idea:
the IDE stops being a passive editor.
12. Final architecture of the day
SwiftUI View
↓ @Query (reactive reads)
SwiftData
↑
ViewModel (@Observable, actions)
↓
ModelActor (concurrent persistence)
↓
Repository (DTOs / Network)
Each layer has a clear responsibility.
Each boundary respects concurrency.
Conclusion
Day 3 is not about learning new APIs.
It is about understanding how Apple thinks about the modern stack.
- State is in charge
- Identity matters
- The database is reactive
- Concurrency is explicit
- The IDE starts collaborating with you
And when all of that lines up, SwiftUI stops feeling “magical”
and starts feeling coherent.
Notes taken during the Swift Developer Workshop 2025 (Apple Coding Academy) and reinterpreted from a practical, real-world perspective.