2023 EssentialsSwiftUI & UI Frameworks
WWDC23 · 34 min · Essentials / SwiftUI & UI Frameworks
What’s new in SwiftUI
Learn how you can use SwiftUI to build great apps for all Apple platforms. Explore the latest updates to SwiftUI and discover new scene types for visionOS. Simplify your data models with the latest data flow options and learn about the Inspector view. We’ll also take you through enhanced animation APIs, powerful ScrollView improvements, and a host of refinements to help you make tidier tables, improve focus and keyboard input, and so much more.
Watch at developer.apple.com ↗Chapters
Code shown on screen · 33 snippets
watchOS 10
import SwiftUI
#if os(watchOS)
struct ContainerBackground_Snippet: View {
private var selection: Int?
var date = Date()
var body: some View {
NavigationSplitView {
List(selection: $selection) {
NavigationLink("Dates", value: -1)
NavigationLink("Zero", value: 0)
NavigationLink("One", value: 1)
NavigationLink("Two", value: 2)
}
.containerBackground(
Color.green.gradient, for: .navigation)
} detail: {
switch selection {
case -1:
DatePicker(
"Time",
selection: $date,
displayedComponents: .hourMinuteAndSecond)
.containerBackground(
Color.yellow.gradient, for: .navigation)
case let value?:
DetailView(value: value)
.containerBackground(
Color.blue.gradient, for: .navigation)
default:
Text("Choose a link")
}
}
}
struct DetailView: View {
var value: Int
var body: some View {
Text("\(value)")
.font(.largeTitle)
}
}
}
#Preview {
ContainerBackground_Snippet()
}
#endif Widget Previews
#Preview(as: .systemSmall) {
CaffeineTrackerWidget()
} timeline: {
CaffeineLogEntry.log1
CaffeineLogEntry.log2
CaffeineLogEntry.log3
CaffeineLogEntry.log4
} SwiftUI Preview
#Preview("good dog") {
ZStack(alignment: .bottom) {
Rectangle()
.fill(Color.blue.gradient)
Text("Riley")
.font(.largeTitle)
.padding()
.background(.thinMaterial, in: .capsule)
.padding()
}
.ignoresSafeArea()
} Mac Preview
import SwiftUI
struct MacPreview_Snippet: View {
private var drinks = Drink.sampleData
private var selection: Drink?
var body: some View {
NavigationSplitView {
List(drinks, selection: $selection) { drink in
NavigationLink(drink.name, value: drink)
}
} detail: {
if let selection {
DrinkCard(drink: selection)
} else {
ContentUnavailableView(
"Select a drink",
systemImage: "cup.and.saucer.fill")
}
}
}
}
struct DrinkCard: View {
var drink: Drink
var body: some View {
ZStack(alignment: .top) {
Rectangle()
.fill(Color.blue.gradient)
Text(drink.name)
.padding([.leading, .trailing], 16)
.padding([.top, .bottom], 4)
.background(.thinMaterial, in: .capsule)
.padding()
}
}
}
struct Drink: Identifiable, Hashable {
let id = UUID()
var name: String
static let sampleData: [Drink] = [
Drink(name: "Cappuccino"),
Drink(name: "Coffee"),
Drink(name: "Espresso"),
Drink(name: "Latte"),
Drink(name: "Macchiato"),
]
}
#Preview {
MacPreview_Snippet()
} MapKit
import SwiftUI
import MapKit
struct Maps_Snippet: View {
private let location = CLLocationCoordinate2D(
latitude: CLLocationDegrees(floatLiteral: 37.3353),
longitude: CLLocationDegrees(floatLiteral: -122.0097))
var body: some View {
Map {
Marker("Pond", coordinate: location)
UserAnnotation()
}
.mapControls {
MapUserLocationButton()
MapCompass()
}
}
}
#Preview {
Maps_Snippet()
} Scrolling Charts
import SwiftUI
import Charts
struct ScrollingChart_Snippet: View {
private var scrollPosition = SalesData.last365Days.first!
private var selection: SalesData?
var body: some View {
VStack(alignment: .leading) {
VStack(alignment: .leading) {
Text("""
Scrolled to: \
\(scrollPosition.day,
format: .dateTime.day().month().year())
""")
Text("""
Selected: \
\(selection?.day ?? .now,
format: .dateTime.day().month().year())
""")
.opacity(selection != nil ? 1.0 : 0.0)
}
.padding([.leading, .trailing])
Chart {
ForEach(SalesData.last365Days, id: \.day) {
BarMark(
x: .value("Day", $0.day, unit: .day),
y: .value("Sales", $0.sales))
}
.foregroundStyle(.blue)
}
.chartScrollableAxes(.horizontal)
.chartXVisibleDomain(length: 3600 * 24 * 30)
.chartScrollPosition(x: $scrollPosition)
.chartXSelection(value: $selection)
}
}
}
struct SalesData: Plottable {
var day: Date
var sales: Int
var primitivePlottable: Date { day }
init?(primitivePlottable: Date) {
self.day = primitivePlottable
self.sales = 0
}
init(day: Date, sales: Int) {
self.day = day
self.sales = sales
}
static let last365Days: [SalesData] = buildSalesData()
private static func buildSalesData() -> [SalesData] {
var result: [SalesData] = []
var date = Date.now
for _ in 0..<365 {
result.append(SalesData(day: date, sales: Int.random(in: 150...250)))
date = Calendar.current.date(
byAdding: .day, value: -1, to: date)!
}
return result.reversed()
}
}
#Preview {
ScrollingChart_Snippet()
} Donut and Pie Charts
import SwiftUI
import Charts
struct DonutChart_Snippet: View {
var sales = Bagel.salesData
var body: some View {
NavigationStack {
Chart(sales, id: \.name) { element in
SectorMark(
angle: .value("Sales", element.sales),
innerRadius: .ratio(0.6),
angularInset: 1.5)
.cornerRadius(5)
.foregroundStyle(by: .value("Name", element.name))
}
.padding()
.navigationTitle("Bagel Sales")
.toolbarTitleDisplayMode(.inlineLarge)
}
}
}
struct Bagel {
var name: String
var sales: Int
static var salesData: [Bagel] = buildSalesData()
static func buildSalesData() -> [Bagel] {
[
Bagel(name: "Blueberry", sales: 60),
Bagel(name: "Everything", sales: 120),
Bagel(name: "Choc. Chip", sales: 40),
Bagel(name: "Cin. Raisin", sales: 100),
Bagel(name: "Plain", sales: 140),
Bagel(name: "Onion", sales: 70),
Bagel(name: "Sesame Seed", sales: 110),
]
}
}
#Preview {
DonutChart_Snippet()
} StoreKit
import SwiftUI
import StoreKit
struct SubscriptionStore_Snippet {
var body: some View {
SubscriptionStoreView(groupID: passGroupID) {
PassMarketingContent()
.lightMarketingContentStyle()
.containerBackground(for: .subscriptionStoreFullHeight) {
SkyBackground()
}
}
.backgroundStyle(.clear)
.subscriptionStoreButtonLabel(.multiline)
.subscriptionStorePickerItemBackground(.thinMaterial)
.storeButton(.visible, for: .redeemCode)
}
} Observable Model
import Foundation
import SwiftUI
class Dog: Identifiable {
var id = UUID()
var name = ""
var age = 1
var breed = DogBreed.mutt
var owner: Person? = nil
}
class Person: Identifiable {
var id = UUID()
var name = ""
}
enum DogBreed {
case mutt
} Observable View Integration
import Foundation
import SwiftUI
struct DogCard: View {
var dog: Dog
var body: some View {
DogImage(dog: dog)
.overlay(alignment: .bottom) {
HStack {
Text(dog.name)
Spacer()
Image(systemName: "heart")
.symbolVariant(dog.isFavorite ? .fill : .none)
}
.font(.headline)
.padding(.horizontal, 22)
.padding(.vertical, 12)
.background(.thinMaterial)
}
.clipShape(.rect(cornerRadius: 16))
}
struct DogImage: View {
var dog: Dog
var body: some View {
Rectangle()
.fill(Color.green)
.frame(width: 400, height: 400)
}
}
class Dog: Identifiable {
var id = UUID()
var name = ""
var isFavorite = false
}
} Observable State Integration
import Foundation
import SwiftUI
struct AddSightingView: View {
private var model = DogDetails()
var body: some View {
Form {
Section {
TextField("Name", text: $model.dogName)
DogBreedPicker(selection: $model.dogBreed)
}
Section {
TextField("Location", text: $model.location)
}
}
}
struct DogBreedPicker: View {
var selection: DogBreed
var body: some View {
Picker("Breed", selection: $selection) {
ForEach(DogBreed.allCases) {
Text($0.rawValue.capitalized)
.tag($0.id)
}
}
}
}
class DogDetails {
var dogName = ""
var dogBreed = DogBreed.mutt
var location = ""
}
enum DogBreed: String, CaseIterable, Identifiable {
case mutt
case husky
case beagle
var id: Self { self }
}
}
#Preview {
AddSightingView()
} Observable Environment Integration
import SwiftUI
@main
private struct WhatsNew2023: App {
private var currentUser: User?
var body: some Scene {
WindowGroup {
ContentView()
.environment(currentUser)
}
}
struct ContentView: View {
var body: some View {
Color.clear
}
}
struct ProfileView: View {
(User.self) private var currentUser: User?
var body: some View {
if let currentUser {
UserDetails(user: currentUser)
} else {
Button("Log In") { }
}
}
}
struct UserDetails: View {
var user: User
var body: some View {
Text("Hello, \(user.name)")
}
}
class User: Identifiable {
var id = UUID()
var name = ""
}
} SwiftData Model Container
import Foundation
import SwiftUI
import SwiftData
@main
private struct WhatsNew2023: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: Dog.self)
}
struct ContentView: View {
var body: some View {
Color.clear
}
}
class Dog {
var name = ""
var age = 1
}
} SwiftData Query
import Foundation
import SwiftUI
import SwiftData
struct RecentDogsView: View {
(sort: \.dateSpotted) private var dogs: [Dog]
var body: some View {
ScrollView(.vertical) {
LazyVStack {
ForEach(dogs) { dog in
DogCard(dog: dog)
}
}
}
}
struct DogCard: View {
var dog: Dog
var body: some View {
DogImage(dog: dog)
.overlay(alignment: .bottom) {
HStack {
Text(dog.name)
Spacer()
Image(systemName: "heart")
.symbolVariant(dog.isFavorite ? .fill : .none)
}
.font(.headline)
.padding(.horizontal, 22)
.padding(.vertical, 12)
.background(.thinMaterial)
}
.clipShape(.rect(cornerRadius: 16))
}
}
struct DogImage: View {
var dog: Dog
var body: some View {
Rectangle()
.fill(Color.green)
.frame(width: 400, height: 400)
}
}
class Dog: Identifiable {
var name = ""
var isFavorite = false
var dateSpotted = Date.now
}
}
#Preview {
RecentDogsView()
} SwiftData DocumentGroup
import SwiftUI
import SwiftData
import UniformTypeIdentifiers
@main
private struct WhatsNew2023: App {
var body: some Scene {
DocumentGroup(editing: DogTag.self, contentType: .dogTag) {
ContentView()
}
}
struct ContentView: View {
var body: some View {
Color.clear
}
}
class DogTag {
var text = ""
}
}
extension UTType {
static var dogTag: UTType {
UTType(exportedAs: "com.apple.SwiftUI.dogTag")
}
} Inspector
import SwiftUI
struct InspectorContentView: View {
private var inspectorPresented = true
var body: some View {
DogTagEditor()
.inspector(isPresented: $inspectorPresented) {
DogTagInspector()
}
}
struct DogTagEditor: View {
var body: some View {
Color.clear
}
}
struct DogTagInspector: View {
private var fontName = FontName.sfHello
private var fontColor: Color = .white
var body: some View {
Form {
Section("Text Formatting") {
Picker("Font", selection: $fontName) {
ForEach(FontName.allCases) {
Text($0.name).tag($0)
}
}
ColorPicker("Font Color", selection: $fontColor)
}
}
}
}
enum FontName: Identifiable, CaseIterable {
case sfHello
case arial
case helvetica
var id: Self { self }
var name: String {
switch self {
case .sfHello: return "SF Hello"
case .arial: return "Arial"
case .helvetica: return "Helvetica"
}
}
}
}
#Preview {
InspectorContentView()
} File Export Dialog Customization
import Foundation
import SwiftUI
import UniformTypeIdentifiers
struct ExportDialogCustomization: View {
private var isExporterPresented = true
private var selectedItem = ""
var body: some View {
Color.clear
.fileExporter(
isPresented: $isExporterPresented, item: selectedItem,
contentTypes: [.plainText], defaultFilename: "ExportedData.txt")
{ result in
handleDataExport(result: result)
}
.fileExporterFilenameLabel("Export Data")
.fileDialogConfirmationLabel("Export Data")
}
func handleDataExport(result: Result<URL, Error>) {
}
struct Data: Codable, Transferable {
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .plainText)
}
var text = "Exported Data"
}
} Confirmation Dialog Customization
import Foundation
import SwiftUI
import UniformTypeIdentifiers
struct ConfirmationDialogCustomization: View {
private var showDeleteDialog = false
("dialogIsSuppressed") private var dialogIsSuppressed = false
var body: some View {
Button("Show Dialog") {
if !dialogIsSuppressed {
showDeleteDialog = true
}
}
.confirmationDialog(
"Are you sure you want to delete the selected dog tag?",
isPresented: $showDeleteDialog)
{
Button("Delete dog tag", role: .destructive) { }
HelpLink { }
}
.dialogSeverity(.critical)
.dialogSuppressionToggle(isSuppressed: $dialogIsSuppressed)
}
} Table Column Customization
import SwiftUI
struct DogSightingsTable: View {
private var dogSightings: [DogSighting] = (1..<50).map {
.init(
name: "Sighting \($0)",
date: .now + Double((Int.random(in: -5..<5) * 86400)))
}
("columnCustomization")
private var columnCustomization: TableColumnCustomization<DogSighting>
private var selectedSighting: DogSighting.ID?
var body: some View {
Table(
dogSightings, selection: $selectedSighting,
columnCustomization: $columnCustomization)
{
TableColumn("Dog Name", value: \.name)
.customizationID("name")
TableColumn("Date") {
Text($0.date, style: .date)
}
.customizationID("date")
}
}
struct DogSighting: Identifiable {
var id = UUID()
var name: String
var date: Date
}
} DisclosureTableRow
import SwiftUI
struct DogGenealogyTable: View {
private static let dogToys = ["🦴", "🧸", "👟", "🎾", "🥏"]
private var dogs: [DogGenealogy] = (1..<10).map {
.init(
name: "Parent \($0)", age: Int.random(in: 8..<12) * 7,
favoriteToy: dogToys[Int.random(in: 0..<5)],
children: (1..<10).map {
.init(
name: "Child \($0)", age: Int.random(in: 1..<5) * 7,
favoriteToy: dogToys[Int.random(in: 0..<5)])
}
)
}
var body: some View {
Table(of: DogGenealogy.self) {
TableColumn("Dog Name", value: \.name)
TableColumn("Age (Dog Years)") {
Text($0.age, format: .number)
}
TableColumn("Favorite Toy", value: \.favoriteToy)
} rows: {
ForEach(dogs) { dog in
DisclosureTableRow(dog) {
ForEach(dog.children) { child in
TableRow(child)
}
}
}
}
}
struct DogGenealogy: Identifiable {
var id = UUID()
var name: String
var age: Int
var favoriteToy: String
var children: [DogGenealogy] = []
}
} Programmatic Section Expansion
import SwiftUI
struct ExpandableSectionsView: View {
private var selection: Int?
var body: some View {
NavigationSplitView {
Sidebar(selection: $selection)
} detail: {
Detail(selection: selection)
}
}
struct Sidebar: View {
var selection: Int?
private var isSection1Expanded = true
private var isSection2Expanded = false
var body: some View {
List(selection: $selection) {
Section("First Section", isExpanded: $isSection1Expanded) {
ForEach(1..<6, id: \.self) {
Text("Item \($0)")
}
}
Section("Second Section", isExpanded: $isSection2Expanded) {
ForEach(6..<11, id: \.self) {
Text("Item \($0)")
}
}
}
}
}
struct Detail: View {
var selection: Int?
var body: some View {
Text(selection.map { "Selection: \($0)" } ?? "No Selection")
}
}
} Table Display Customization And Background Prominence
import SwiftUI
struct TableDisplayCustomizationView: View {
private var dogSightings: [DogSighting] = (1..<10).map {
.init(
name: "Dog Breed \($0)", sightings: Int.random(in: 1..<5),
rating: Int.random(in: 1..<6))
}
private var selection: DogSighting.ID?
var body: some View {
Table(dogSightings, selection: $selection) {
TableColumn("Name", value: \.name)
TableColumn("Sightings") {
Text($0.sightings, format: .number)
}
TableColumn("Rating") {
StarRating(rating: $0.rating)
.foregroundStyle(.starRatingForeground)
}
}
.alternatingRowBackgrounds(.disabled)
.tableColumnHeaders(.hidden)
}
struct StarRating: View {
var rating: Int
var body: some View {
HStack(spacing: 1) {
ForEach(1...5, id: \.self) { n in
Image(systemName: "star")
.symbolVariant(n <= rating ? .fill : .none)
}
}
.imageScale(.small)
}
}
struct StarRatingForegroundStyle: ShapeStyle {
func resolve(in environment: EnvironmentValues) -> some ShapeStyle {
if environment.backgroundProminence == .increased {
return AnyShapeStyle(.secondary)
} else {
return AnyShapeStyle(.yellow)
}
}
}
struct DogSighting: Identifiable {
var id = UUID()
var name: String
var sightings: Int
var rating: Int
}
}
extension ShapeStyle where Self ==
TableDisplayCustomizationView.StarRatingForegroundStyle
{
static var starRatingForeground: TableDisplayCustomizationView.StarRatingForegroundStyle {
.init()
}
} Keyframe Animator
import SwiftUI
struct KeyframeAnimator_Snippet: View {
var body: some View {
Logo(color: .blue)
Text("Tap the shape")
}
}
struct Logo: View {
var color: Color
private var runPlan = 0
var body: some View {
VStack(spacing: 100) {
KeyframeAnimator(
initialValue: AnimationValues(), trigger: runPlan
) { values in
LogoField(color: color)
.scaleEffect(values.scale)
.rotationEffect(values.rotation, anchor: .bottom)
.offset(y: values.verticalTranslation)
.frame(width: 240, height: 240)
} keyframes: { _ in
KeyframeTrack(\.verticalTranslation) {
SpringKeyframe(30, duration: 0.25, spring: .smooth)
CubicKeyframe(-120, duration: 0.3)
CubicKeyframe(-120, duration: 0.5)
CubicKeyframe(10, duration: 0.3)
SpringKeyframe(0, spring: .bouncy)
}
KeyframeTrack(\.scale) {
SpringKeyframe(0.98, duration: 0.25, spring: .smooth)
SpringKeyframe(1.2, duration: 0.5, spring: .smooth)
SpringKeyframe(1.0, spring: .bouncy)
}
KeyframeTrack(\.rotation) {
LinearKeyframe(Angle(degrees:0), duration: 0.45)
CubicKeyframe(Angle(degrees: 0), duration: 0.1)
CubicKeyframe(Angle(degrees: -15), duration: 0.1)
CubicKeyframe(Angle(degrees: 15), duration: 0.1)
CubicKeyframe(Angle(degrees: -15), duration: 0.1)
SpringKeyframe(Angle(degrees: 0), spring: .bouncy)
}
}
.onTapGesture {
runPlan += 1
}
}
}
struct AnimationValues {
var scale = 1.0
var verticalTranslation = 0.0
var rotation = Angle(degrees: 0.0)
}
struct LogoField: View {
var color: Color
var body: some View {
ZStack(alignment: .bottom) {
RoundedRectangle(cornerRadius: 48)
.fill(.shadow(.drop(radius: 5)))
.fill(color.gradient)
}
}
}
}
#Preview {
KeyframeAnimator_Snippet()
} Phase Animator
import SwiftUI
struct PhaseAnimator_Snippet: View {
private var sightingCount = 0
var body: some View {
VStack {
Spacer()
HappyDog()
.phaseAnimator(
SightingPhases.allCases, trigger: sightingCount
) { content, phase in
content
.rotationEffect(phase.rotation)
.scaleEffect(phase.scale)
} animation: { phase in
switch phase {
case .shrink: .snappy(duration: 0.1)
case .spin: .bouncy
case .grow: .spring(
duration: 0.2, bounce: 0.1, blendDuration: 0.1)
case .reset: .linear(duration: 0.0)
}
}
.sensoryFeedback(.increase, trigger: sightingCount)
Spacer()
Button("There’s One!", action: recordSighting)
.zIndex(-1.0)
}
}
func recordSighting() {
sightingCount += 1
}
enum SightingPhases: CaseIterable {
case reset
case shrink
case spin
case grow
var rotation: Angle {
switch self {
case .spin, .grow: Angle(degrees: 360)
default: Angle(degrees: 0)
}
}
var scale: Double {
switch self {
case .reset: 1.0
case .shrink: 0.75
case .spin: 0.85
case .grow: 1.0
}
}
}
}
struct HappyDog: View {
var body: some View {
ZStack(alignment: .center) {
Rectangle()
.fill(.blue.gradient)
Text("🐶")
.font(.system(size: 58))
}
.clipShape(.rect(cornerRadius: 12))
.frame(width: 96, height: 96)
}
}
#Preview {
PhaseAnimator_Snippet()
} Haptic Feedback
https://developer.apple.com/design/human-interface-guidelines/playing-haptics Visual Effects
import SwiftUI
struct VisualEffects_Snippet: View {
private var dogs: [Dog] = manySampleDogs
private var simulation = Simulation()
private var showFocalPoint = false
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: itemSpacing) {
ForEach(dogs) { dog in
DogCircle(dog: dog, focalPoint: simulation.point)
}
}
.opacity(showFocalPoint ? 0.3 : 1.0)
.overlay(alignment: .topLeading) {
DebugDot(focalPoint: simulation.point)
.opacity(showFocalPoint ? 1.0 : 0.0)
}
.compositingGroup()
}
.coordinateSpace(.dogGrid)
.onTapGesture {
withAnimation {
showFocalPoint.toggle()
}
}
}
var columns: [GridItem] {
[GridItem(
.adaptive(
minimum: imageLength,
maximum: imageLength
),
spacing: itemSpacing
)]
}
struct DebugDot: View {
var focalPoint: CGPoint
var body: some View {
Circle()
.fill(.red)
.frame(width: 10, height: 10)
.visualEffect { content, proxy in
content.offset(position(in: proxy))
}
}
func position(in proxy: GeometryProxy) -> CGSize {
guard let backgroundSize = proxy.bounds(of: .dogGrid)?.size else {
return .zero
}
let frame = proxy.frame(in: .dogGrid)
let center = CGPoint(
x: (frame.minX + frame.maxX) / 2.0,
y: (frame.minY + frame.maxY) / 2.0
)
let xOffset = focalPoint.x * backgroundSize.width - center.x
let yOffset = focalPoint.y * backgroundSize.height - center.y
return CGSize(width: xOffset, height: yOffset)
}
}
/// A self-updating simulation of a point bouncing inside a unit square.
class Simulation: ObservableObject {
var point = CGPoint(
x: Double.random(in: 0.001..<1.0),
y: Double.random(in: 0.001..<1.0)
)
private var velocity = CGVector(dx: 0.0048, dy: 0.0028)
private var updateTask: Task<Void, Never>?
private var isUpdating = true
init() {
updateTask = Task.detached {
do {
while true {
try await Task.sleep(for: .milliseconds(16))
await self.updateLocation()
}
} catch {
// fallthrough and exit
}
}
}
func toggle() {
isUpdating.toggle()
}
private func updateLocation() {
guard isUpdating else { return }
point.x += velocity.dx
point.y += velocity.dy
if point.x < 0 || point.x >= 1.0 {
velocity.dx *= -1
point.x += 2 * velocity.dx
}
if point.y < 0 || point.y >= 1.0 {
velocity.dy *= -1
point.y += 2 * velocity.dy
}
}
}
}
extension CoordinateSpaceProtocol where Self == NamedCoordinateSpace {
fileprivate static var dogGrid: Self { .named("dogGrid") }
}
private func magnitude(dx: Double, dy: Double) -> Double {
sqrt(dx * dx + dy * dy)
}
private struct DogCircle: View {
var dog: Dog
var focalPoint: CGPoint
var body: some View {
ZStack {
DogImage(dog: dog)
.visualEffect { content, geometry in
content
.scaleEffect(contentScale(in: geometry))
.saturation(contentSaturation(in: geometry))
.opacity(contentOpacity(in: geometry))
}
}
}
}
private struct DogImage: View {
var dog: Dog
var body: some View {
Circle()
.fill(.shadow(.drop(
color: .black.opacity(0.4),
radius: 4,
x: 0, y: 2)))
.fill(dog.color)
.strokeBorder(.secondary, lineWidth: 3)
.frame(width: imageLength, height: imageLength)
}
}
extension DogCircle {
func contentScale(in geometry: GeometryProxy) -> Double {
guard let gridSize = geometry.bounds(of: .dogGrid)?.size else { return 0 }
let frame = geometry.frame(in: .dogGrid)
let center = CGPoint(x: (frame.minX + frame.maxX) / 2.0, y: (frame.minY + frame.maxY) / 2.0)
let xOffset = focalPoint.x * gridSize.width - center.x
let yOffset = focalPoint.y * gridSize.height - center.y
let unitMagnitude =
magnitude(dx: xOffset, dy: yOffset) / magnitude(dx: gridSize.width, dy: gridSize.height)
if unitMagnitude < 0.2 {
let d = 3 * (unitMagnitude - 0.2)
return 1.0 + 1.2 * d * d * (1 + d)
} else {
return 1.0
}
}
func contentOpacity(in geometry: GeometryProxy) -> Double {
opacity(for: displacement(in: geometry))
}
func contentSaturation(in geometry: GeometryProxy) -> Double {
opacity(for: displacement(in: geometry))
}
func opacity(for displacement: Double) -> Double {
if displacement < 0.3 {
return 1.0
} else {
return 1.0 - (displacement - 0.3) * 1.43
}
}
func displacement(in proxy: GeometryProxy) -> Double {
guard let backgroundSize
= proxy.bounds(of: .dogGrid)?.size
else {
return 0
}
let frame = proxy.frame(in: .dogGrid)
let center = CGPoint(
x: (frame.minX + frame.maxX) / 2.0,
y: (frame.minY + frame.maxY) / 2.0
)
let xOffset = focalPoint.x * backgroundSize.width - center.x
let yOffset = focalPoint.y * backgroundSize.height - center.y
return magnitude(dx: xOffset, dy: yOffset)
/ magnitude(
dx: backgroundSize.width,
dy: backgroundSize.height)
}
}
private struct Dog: Identifiable {
let id = UUID()
var color: Color
}
private let imageLength = 100.0
private let itemSpacing = 20.0
private let possibleColors: [Color] =
[.red, .orange, .yellow, .green, .blue, .indigo, .purple]
private let manySampleDogs: [Dog] =
(0..<100).map {
Dog(color: possibleColors[$0 % possibleColors.count])
}
#Preview {
VisualEffects_Snippet()
} Metal Shader
import SwiftUI
struct ShaderUse_Snippet: View {
private var stripeSpacing: Float = 10.0
private var stripeAngle: Float = 0.0
var body: some View {
VStack {
Text(
"""
\(
Text("Furdinand")
.foregroundStyle(stripes)
.fontWidth(.expanded)
) \
is a good dog!
"""
)
.font(.system(size: 56, weight: .heavy).width(.condensed))
.lineLimit(...4)
.multilineTextAlignment(.center)
Spacer()
controls
Spacer()
}
.padding()
}
var stripes: Shader {
ShaderLibrary.angledFill(
.float(stripeSpacing),
.float(stripeAngle),
.color(.blue)
)
}
var controls: some View {
Grid(alignment: .trailing) {
GridRow {
spacingSlider
ZStack(alignment: .trailing) {
Text("50.0 PX").hidden() // maintains size
Text("""
\(stripeSpacing,
format: .number.precision(.fractionLength(1))) \
\(Text("PX").textScale(.secondary))
""")
.foregroundStyle(.secondary)
}
}
GridRow {
angleSlider
ZStack(alignment: .trailing) {
Text("-0.09π RAD").hidden() // maintains size
Text("""
\(stripeAngle / .pi,
format: .number.precision(.fractionLength(2)))π \
\(Text("RAD").textScale(.secondary))
""")
.foregroundStyle(.secondary)
}
}
}
.labelsHidden()
}
var spacingSlider: some View {
Slider(
value: $stripeSpacing, in: Float(10.0)...50.0) {
Text("Spacing")
} minimumValueLabel: {
Image(
systemName:
"arrow.down.forward.and.arrow.up.backward")
} maximumValueLabel: {
Image(
systemName:
"arrow.up.backward.and.arrow.down.forward")
}
}
var angleSlider: some View {
Slider(
value: $stripeAngle, in: (-.pi / 2)...(.pi / 2)) {
Text("Angle")
} minimumValueLabel: {
Image(
systemName: "arrow.clockwise")
} maximumValueLabel: {
Image(
systemName: "arrow.counterclockwise")
}
}
}
// NOTE: create a .metal file in your project and add the following to it:
/*
#include <metal_stdlib>
using namespace metal;
[[ stitchable ]] half4
angledFill(float2 position, float width, float angle, half4 color)
{
float pMagnitude = sqrt(position.x * position.x + position.y * position.y);
float pAngle = angle +
(position.x == 0.0f ? (M_PI_F / 2.0f) : atan(position.y / position.x));
float rotatedX = pMagnitude * cos(pAngle);
float rotatedY = pMagnitude * sin(pAngle);
return (color + color * fmod(abs(rotatedX + rotatedY), width) / width) / 2;
}
*/
#Preview {
ShaderUse_Snippet()
} Symbol Effect
import SwiftUI
struct SymbolEffect_Snippet: View {
private var downloadCount = -2
private var isPaused = false
var scaleUpActive: Bool {
(downloadCount % 2) == 0
}
var isHidden: Bool { scaleUpActive }
var isShown: Bool { scaleUpActive }
var isPlaying: Bool { scaleUpActive }
var body: some View {
ScrollView {
VStack(spacing: 48) {
Image(systemName: "rectangle.inset.filled.and.person.filled")
.symbolEffect(.pulse)
.frame(maxWidth: .infinity)
Image(systemName: "arrow.down.circle")
.symbolEffect(.bounce, value: downloadCount)
Image(systemName: "wifi")
.symbolEffect(.variableColor.iterative.reversing)
Image(systemName: "bubble.left.and.bubble.right.fill")
.symbolEffect(.scale.up, isActive: scaleUpActive)
Image(systemName: "cloud.sun.rain.fill")
.symbolEffect(.disappear, isActive: isHidden)
Image(systemName: isPlaying ? "play.fill" : "pause.fill")
.contentTransition(.symbolEffect(.replace.downUp))
}
.padding()
}
.font(.system(size: 64))
.frame(maxWidth: .infinity)
.symbolRenderingMode(.multicolor)
.preferredColorScheme(.dark)
.task {
do {
while true {
try await Task.sleep(for: .milliseconds(1500))
if !isPaused {
downloadCount += 1
}
}
} catch {
print("exiting")
}
}
}
}
#Preview {
SymbolEffect_Snippet()
} Metal Shader (cont.)
import SwiftUI
struct ShaderUse_Snippet: View {
private var stripeSpacing: Float = 10.0
private var stripeAngle: Float = 0.0
var body: some View {
VStack {
Text(
"""
\(
Text("Furdinand")
.foregroundStyle(stripes)
.fontWidth(.expanded)
) \
is a good dog!
"""
)
.font(.system(size: 56, weight: .heavy).width(.condensed))
.lineLimit(...4)
.multilineTextAlignment(.center)
Spacer()
controls
Spacer()
}
.padding()
}
var stripes: Shader {
ShaderLibrary.angledFill(
.float(stripeSpacing),
.float(stripeAngle),
.color(.blue)
)
}
var controls: some View {
Grid(alignment: .trailing) {
GridRow {
spacingSlider
ZStack(alignment: .trailing) {
Text("50.0 PX").hidden() // maintains size
Text("""
\(stripeSpacing,
format: .number.precision(.fractionLength(1))) \
\(Text("PX").textScale(.secondary))
""")
.foregroundStyle(.secondary)
}
}
GridRow {
angleSlider
ZStack(alignment: .trailing) {
Text("-0.09π RAD").hidden() // maintains size
Text("""
\(stripeAngle / .pi,
format: .number.precision(.fractionLength(2)))π \
\(Text("RAD").textScale(.secondary))
""")
.foregroundStyle(.secondary)
}
}
}
.labelsHidden()
}
var spacingSlider: some View {
Slider(
value: $stripeSpacing, in: Float(10.0)...50.0) {
Text("Spacing")
} minimumValueLabel: {
Image(
systemName:
"arrow.down.forward.and.arrow.up.backward")
} maximumValueLabel: {
Image(
systemName:
"arrow.up.backward.and.arrow.down.forward")
}
}
var angleSlider: some View {
Slider(
value: $stripeAngle, in: (-.pi / 2)...(.pi / 2)) {
Text("Angle")
} minimumValueLabel: {
Image(
systemName: "arrow.clockwise")
} maximumValueLabel: {
Image(
systemName: "arrow.counterclockwise")
}
}
}
// NOTE: create a .metal file in your project and add the following to it:
/*
#include <metal_stdlib>
using namespace metal;
[[ stitchable ]] half4
angledFill(float2 position, float width, float angle, half4 color)
{
float pMagnitude = sqrt(position.x * position.x + position.y * position.y);
float pAngle = angle +
(position.x == 0.0f ? (M_PI_F / 2.0f) : atan(position.y / position.x));
float rotatedX = pMagnitude * cos(pAngle);
float rotatedY = pMagnitude * sin(pAngle);
return (color + color * fmod(abs(rotatedX + rotatedY), width) / width) / 2;
}
*/
#Preview {
ShaderUse_Snippet()
} Typesetting Language
import SwiftUI
struct TypesettingLanguage_Snippet: View {
var dog = Dog(
name: "ไมโล",
language: .init(languageCode: .thai),
imageName: "Puppy_Pitbull")
func phrase(for name: Text) -> Text {
Text(
"Who's a good dog, \(name)?"
)
}
var body: some View {
HStack(spacing: 54) {
VStack {
phrase(for: Text("Milo"))
}
VStack {
phrase(for: Text(dog.name))
}
VStack {
phrase(for: dog.nameText)
}
}
.font(.title)
.lineLimit(...5)
.multilineTextAlignment(.leading)
.padding()
}
struct Dog {
var name: String
var language: Locale.Language
var imageName: String
var nameText: Text {
Text(name).typesettingLanguage(language)
}
}
}
#Preview {
TypesettingLanguage_Snippet()
} ScrollView Transitions And Behaviors
import SwiftUI
struct ScrollingRecentDogsView: View {
private static let colors: [Color] = [.red, .blue, .brown, .yellow, .purple]
private var dogs: [Dog] = (1..<10).map {
.init(
name: "Dog \($0)", color: colors[Int.random(in: 0..<5)],
isFavorite: false)
}
private var parks: [Park] = (1..<10).map { .init(name: "Park \($0)") }
private var scrolledID: Dog.ID?
var body: some View {
ScrollView {
LazyVStack {
ForEach(dogs) { dog in
DogCard(dog: dog, isTop: scrolledID == dog.id)
.scrollTransition { content, phase in
content
.scaleEffect(phase.isIdentity ? 1 : 0.8)
.opacity(phase.isIdentity ? 1 : 0)
}
}
}
}
.scrollPosition(id: $scrolledID)
.safeAreaInset(edge: .top) {
ScrollView(.horizontal) {
LazyHStack {
ForEach(parks) { park in
ParkCard(park: park)
.aspectRatio(3.0 / 2.0, contentMode: .fill)
.containerRelativeFrame(
.horizontal, count: 5, span: 2, spacing: 8)
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
.padding(.vertical, 8)
.fixedSize(horizontal: false, vertical: true)
.background(.thinMaterial)
}
.safeAreaPadding(.horizontal, 16.0)
}
struct DogCard: View {
var dog: Dog
var isTop: Bool
var body: some View {
DogImage(dog: dog)
.overlay(alignment: .bottom) {
HStack {
Text(dog.name)
Spacer()
if isTop {
TopDog()
}
Spacer()
Image(systemName: "heart")
.symbolVariant(dog.isFavorite ? .fill : .none)
}
.font(.headline)
.padding(.horizontal, 22)
.padding(.vertical, 12)
.background(.thinMaterial)
}
.clipShape(.rect(cornerRadius: 16))
}
}
struct DogImage: View {
var dog: Dog
var body: some View {
Rectangle()
.fill(dog.color.gradient)
.frame(height: 400)
}
}
struct TopDog: View {
var body: some View {
HStack {
Image(systemName: "trophy.fill")
Text("Top Dog")
Image(systemName: "trophy.fill")
}
}
}
struct ParkCard: View {
var park: Park
var body: some View {
RoundedRectangle(cornerRadius: 8)
.fill(.green.gradient)
.overlay {
Text(park.name)
.padding()
}
}
}
struct Dog: Identifiable {
var id = UUID()
var name: String
var color: Color
var isFavorite: Bool
}
struct Park: Identifiable {
var id = UUID()
var name: String
}
} Menu Enhancements
import SwiftUI
struct DogTagEditMenu: View {
private var selectedColor = TagColor.blue
var body: some View {
Menu {
ControlGroup {
Button {
} label: {
Label("Cut", systemImage: "scissors")
}
Button {
} label: {
Label("Copy", systemImage: "doc.on.doc")
}
Button {
} label: {
Label("Paste", systemImage: "doc.on.clipboard.fill")
}
Button {
} label: {
Label("Duplicate", systemImage: "plus.square.on.square")
}
}
.controlGroupStyle(.compactMenu)
Picker("Tag Color", selection: $selectedColor) {
ForEach(TagColor.allCases) {
Label($0.rawValue.capitalized, systemImage: "tag")
.tint($0.color)
.tag($0)
}
}
.paletteSelectionEffect(.symbolVariant(.fill))
.pickerStyle(.palette)
} label: {
Label("Edit", systemImage: "ellipsis.circle")
}
.menuStyle(.button)
}
enum TagColor: String, CaseIterable, Identifiable {
case blue
case brown
case green
case yellow
var id: Self { self }
var color: Color {
switch self {
case .blue: return .blue
case .brown: return .brown
case .green: return .green
case .yellow: return .yellow
}
}
}
} Highlight Hover Effect
import SwiftUI
struct DogGalleryCard: View {
private var isFocused: Bool
var body: some View {
Button {
} label: {
VStack {
RoundedRectangle(cornerRadius: 8)
.fill(.blue)
.frame(width: 888, height: 500)
.hoverEffect(.highlight)
Text("Name")
.opacity(isFocused ? 1 : 0)
}
}
.buttonStyle(.borderless)
.focused($isFocused)
}
} Resources
Related sessions
-
37 min -
16 min -
10 min -
27 min -
26 min -
19 min -
13 min -
19 min -
30 min -
18 min -
23 min -
15 min -
13 min -
23 min -
43 min -
9 min -
27 min -
17 min