2025 SwiftUI & UI FrameworksSwift
WWDC25 · 25 min · SwiftUI & UI Frameworks / Swift
Explore concurrency in SwiftUI
Discover how SwiftUI leverages Swift concurrency to build safe and responsive apps. Explore how SwiftUI uses the main actor by default and offloads work to other actors. Learn how to interpret concurrency annotations and manage async tasks with SwiftUI’s event loop for smooth animations and UI updates. You’ll leave knowing how to avoid data races and write code fearlessly.
Watch at developer.apple.com ↗Chapters
Code shown on screen · 8 snippets
UI for extracting colors
// UI for extracting colors
struct ColorScheme: Identifiable, Hashable {
var id = UUID()
let imageName: String
var colors: [Color]
}
final class ColorExtractor {
var imageName: String
var scheme: ColorScheme?
var isExtracting: Bool = false
var colorCount: Float = 5
func extractColorScheme() async {}
}
struct ColorExtractorView: View {
private var model = ColorExtractor()
var body: some View {
ImageView(
imageName: model.imageName,
isLoading: model.isExtracting
)
EqualWidthVStack {
ColorSchemeView(
isLoading: model.isExtracting,
colorScheme: model.scheme,
extractCount: Int(model.colorCount)
)
.onTapGesture {
guard !model.isExtracting else { return }
withAnimation { model.isExtracting = true }
Task {
await model.extractColorScheme()
withAnimation { model.isExtracting = false }
}
}
Slider(value: $model.colorCount, in: 3...10, step: 1)
.disabled(model.isExtracting)
}
}
}
} AppKit and UIKit require @MainActor: an example
// AppKit and UIKit require @MainActor
// Example: UIViewRepresentable
struct FancyUILabel: UIViewRepresentable {
func makeUIView(context: Context) -> UILabel {
let label = UILabel()
// customize the label...
return label
}
} UI for extracting colors
// UI for extracting colors
struct ColorScheme: Identifiable, Hashable {
var id = UUID()
let imageName: String
var colors: [Color]
}
final class ColorExtractor {
var imageName: String
var scheme: ColorScheme?
var isExtracting: Bool = false
var colorCount: Float = 5
func extractColorScheme() async {}
}
struct ColorExtractorView: View {
private var model = ColorExtractorModel()
var body: some View {
ImageView(
imageName: model.imageName,
isLoading: model.isExtracting
)
EqualWidthVStack(spacing: 30) {
ColorSchemeView(
isLoading: model.isExtracting,
colorScheme: model.scheme,
extractCount: Int(model.colorCount)
)
.onTapGesture {
guard !model.isExtracting else { return }
withAnimation { model.isExtracting = true }
Task {
await model.extractColorScheme()
withAnimation { model.isExtracting = false }
}
}
Slider(value: $model.colorCount, in: 3...10, step: 1)
.disabled(model.isExtracting)
}
}
}
} Animated circle, part of color scheme view
// Part of color scheme view
struct SchemeContentView: View {
let isLoading: Bool
private var pulse: Bool = false
var body: some View {
ZStack {
// Color wheel …
Circle()
.scaleEffect(isLoading ? 1.5 : 1)
VStack {
Text(isLoading ? "Please wait" : "Extract")
if !isLoading {
Text("^[\(extractCount) color](inflect: true)")
}
}
.visualEffect { [pulse] content, _ in
content
.blur(radius: pulse ? 2 : 0)
}
.onChange(of: isLoading) { _, newValue in
withAnimation(newValue ? kPulseAnimation : nil) {
pulse = newValue
}
}
}
}
} UI for extracting colors
// UI for extracting colors
struct ColorExtractorView: View {
private var model = ColorExtractor()
var body: some View {
ImageView(
imageName: model.imageName,
isLoading: model.isExtracting
)
EqualWidthVStack {
ColorSchemeView(
isLoading: model.isExtracting,
colorScheme: model.scheme,
extractCount: Int(model.colorCount)
)
.onTapGesture {
guard !model.isExtracting else { return }
withAnimation { model.isExtracting = true }
Task {
await model.extractColorScheme()
withAnimation { model.isExtracting = false }
}
}
Slider(value: $model.colorCount, in: 3...10, step: 1)
.disabled(model.isExtracting)
}
}
}
} Part of color scheme view
// Part of color scheme view
struct SchemeContentView: View {
let isLoading: Bool
private var pulse: Bool = false
var body: some View {
ZStack {
// Color wheel …
Circle()
.scaleEffect(isLoading ? 1.5 : 1)
VStack {
Text(isLoading ? "Please wait" : "Extract")
if !isLoading {
Text("^[\(extractCount) color](inflect: true)")
}
}
.visualEffect { [pulse] content, _ in
content
.blur(radius: pulse ? 2 : 0)
}
.onChange(of: isLoading) { _, newValue in
withAnimation(newValue ? kPulseAnimation : nil) {
pulse = newValue
}
}
}
}
} UI for extracting colors
// UI for extracting colors
struct ColorExtractorView: View {
private var model = ColorExtractor()
var body: some View {
ImageView(
imageName: model.imageName,
isLoading: model.isExtracting
)
EqualWidthVStack {
ColorSchemeView(
isLoading: model.isExtracting,
colorScheme: model.scheme,
extractCount: Int(model.colorCount)
)
.onTapGesture {
guard !model.isExtracting else { return }
withAnimation { model.isExtracting = true }
Task {
await model.extractColorScheme()
withAnimation { model.isExtracting = false }
}
}
Slider(value: $model.colorCount, in: 3...10, step: 1)
.disabled(model.isExtracting)
}
}
}
} Animate colors as they appear by scrolling
// Animate colors as they appear by scrolling
struct SchemeHistoryItemView: View {
let scheme: ColorScheme
private var isShown: Bool = false
var body: some View {
HStack(spacing: 0) {
ForEach(scheme.colors) { color in
color
.offset(x: 0, y: isShown ? 0 : 60)
}
}
.onScrollVisibilityChange(threshold: 0.9) {
guard !isShown else { return }
withAnimation {
isShown = $0
}
}
}
} Resources
Related sessions
-
33 min -
28 min -
30 min