237 Building Custom Views With Swiftui
237 Building Custom Views With Swiftui
Dave Abrahams
John Harper
© 2019 Apple Inc. All rights reserved. Redistribution or public display not permitted without written permission from Apple.
Layout Basics
Text
Layout Basics
ContentView
Text
Layout Basics
ContentView
Text
Layout Basics
ContentView
Text
Layout Basics
ContentView
Text
Layout Basics
ContentView
Text
Layout Basics
ContentView
Text
Layout Basics
ContentView
Text
1. Parent Proposes Size for Child
Root View
ContentView
Text
2. Child Chooses Its Own Size
Root View
ContentView
Text
3. Parent Places Child in Parent’s Coordinate Space
ContentView
Text
Layout Procedure
Text
Delicious Avocado Toast
Text
Delicious Avocado Toast
Background Text
Delicious Avocado Toast
Background
Text
Delicious Avocado Toast
Background
Text Color
Delicious Avocado Toast
Padding Color
Text
Delicious Avocado Toast
Padding Color
Text
Delicious Avocado Toast
Padding Color
Text
Delicious Avocado Toast
Padding Color
Text
Delicious Avocado Toast
Padding Color
Text
Delicious Avocado Toast
Padding Color
Text
Delicious Avocado Toast 10 10
10
Padding Color
10
Text
Delicious Avocado Toast 10 10
10
Padding Color
10
Text
Delicious Avocado Toast
Padding Color
Text
Delicious Avocado Toast
Padding Color
Text
Delicious Avocado Toast
Padding Color
Text
Delicious Avocado Toast
Padding Color
Text
Delicious Avocado Toast
Padding Color
Text
Delicious Avocado Toast
Padding Color
Text
Scrumptious Avocado
Image
Scrumptious Avocado
Frame Image
Scrumptious Avocado
Frame
Image
Scrumptious Avocado
Frame
Image
•
This Is as It Should Be
There’s no wrong way to do it
HStack and VStack
★★★★★
5 stars
Avocado Toast
Ingredients: Avocado, Almond Butter, Bread, Red Pep…
HStack {
VStack {
★★★★★
5 stars
Avocado Toast
Text("★★★★★") Ingredients: Avocado, Almond Butter, Bread, Red Pep…
Text("5 stars")
}.font(.caption)
VStack(alignment: .leading) {
HStack {
Text("Avocado Toast").font(.title)
Spacer()
Image("20x20_avocado")
}
Text("5 stars")
}.font(.caption)
VStack(alignment: .leading) {
HStack {
Text("Avocado Toast").font(.title)
Spacer()
Image("20x20_avocado")
}
Text("5 stars")
}.font(.caption)
VStack(alignment: .leading) {
HStack {
Text("Avocado Toast").font(.title)
Spacer()
Image("20x20_avocado")
}
★★★★★
5 stars
Avocado Toast
Ingredients: Avocado, Almond Butter, Bread, Red Pep…
HStack and VStack
★★★★★
5 stars
Avocado Toast
Ingredients: Avocado, Almond Butter, Bread, Red Pep…
HStack and VStack
★★★★★
5 stars
Avocado Toast
Ingredients: Avocado, Almond Butter, Bread, Red Pep…
HStack and VStack
★★★★★
5 stars
Avocado Toast
Ingredients: Avocado, Almond Butter, Bread, Red Pep…
HStack and VStack
VStack {
ListCell()
Toggle(isOn: $on) { ... }
}
★★★★★
5 stars
Avocado Toast
Ingredients: Avocado, Almond Butter, Bread, Red Pep…
HStack and VStack
VStack {
ListCell()
Toggle(isOn: $on) { ... }
}
★★★★★
5 stars
Avocado Toast
Ingredients: Avocado, Almond Butter, Bread, Red Pep…
HStack and VStack
VStack(spacing: 4) {
ListCell()
Toggle(isOn: $on) { ... }
}
★★★★★
5 stars
Avocado Toast
Ingredients: Avocado, Almond Butter, Bread, Red Pep…
HStack and VStack
★★★★★
5 stars
Avocado Toast
Ingredients: Avocado, Almond Butter, Bread, Red Pep…
HStack and VStack
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack {
Text("Delicious")
Deli… Avocado…
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack { S₀ S₁
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
S₀ + S₁
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
w
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
w
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack { w
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
w
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack { S₀ S₁
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack(alignment: .center) {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1) Parent
HStack
HStack {
Text("Delicious")
Delicious Avocado T…
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1)
How Stacks Work
HStack {
Text("Delicious")
Delicious Avocado T…
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1)
How Stacks Work
HStack {
Text("Delicious")
Deli… Avocado…
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1)
How Stacks Work
HStack {
Text("Delicious")
… Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast")
}
.lineLimit(1)
Layout Priority
HStack {
Text("Delicious")
… Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast").layoutPriority(1)
}
.lineLimit(1)
Layout Priority
w₀+w₁
HStack { w₀
Text("Delicious")
… Avocado Toast
Image("20x20_avocado")
w₁
Text("Avocado Toast").layoutPriority(1)
}
.lineLimit(1)
Alignments
HStack(alignment: .center) {
Text("Delicious")
Delicious Avocado Toast
Image("20x20_avocado")
Text("Avocado Toast").layoutPriority(1)
}
.lineLimit(1)
Alignments
HStack(alignment: .bottom) {
Text("Delicious")
Image("20x20_avocado")
Delicious Avocado Toast
Text("Avocado Toast").layoutPriority(1)
}
.lineLimit(1)
Alignments
HStack(alignment: .bottom) {
Text("Delicious").font(.caption)
Image("20x20_avocado")
Delicious Avocado Toast
Text("Avocado Toast").layoutPriority(1)
}
.lineLimit(1)
Alignments
HStack(alignment: .bottom) {
Text("Delicious").font(.caption)
Image("20x20_avocado")
Text("Avocado Toast").layoutPriority(1)
}
.lineLimit(1)
HStack(alignment: .bottom) {
Text("Delicious").font(.caption)
Image("20x20_avocado")
Text("Avocado Toast").layoutPriority(1)
}
.lineLimit(1)
HStack(alignment: .bottom) {
Text("Delicious").font(.caption)
Image("20x20_avocado")
Text("Avocado Toast").layoutPriority(1)
}
.lineLimit(1)
HStack(alignment: .bottom) {
Text("Delicious").font(.caption)
Image("20x20_avocado")
Text("Avocado Toast").layoutPriority(1)
}
.lineLimit(1)
HStack(alignment: .bottom) {
Text("Delicious").font(.caption)
Image("20x20_avocado")
Text("Avocado Toast").layoutPriority(1)
}
.lineLimit(1)
HStack(alignment: .bottom) {
Text("Delicious").font(.caption)
Image("20x20_avocado")
Text("Avocado Toast").layoutPriority(1)
}
.lineLimit(1)
HStack(alignment: .lastTextBaseline) {
Text("Delicious").font(.caption)
Image("20x20_avocado")
Text("Avocado Toast").layoutPriority(1)
}
.lineLimit(1)
HStack(alignment: .lastTextBaseline) {
Text("Delicious").font(.caption)
Image("20x20_avocado")
Text("Avocado Toast").layoutPriority(1)
}
.lineLimit(1)
HStack(alignment: .lastTextBaseline) {
Text("Delicious").font(.caption)
Image("20x20_avocado")
Text("Avocado Toast").layoutPriority(1)
}
.lineLimit(1)
87.4%
HStack(alignment: .lastTextBaseline) {
Text("Delicious").font(.caption)
Image("20x20_avocado").alignmentGuide(.lastTextBaseline) { d in d[.bottom] * 0.927 }
Text("Avocado Toast").layoutPriority(1)
}
.lineLimit(1)
87.4%
HStack(alignment: .lastTextBaseline) {
Text("Delicious").font(.caption)
Image("20x20_avocado").alignmentGuide(.lastTextBaseline) { d in d[.bottom] * 0.927 }
Text("Avocado Toast").layoutPriority(1)
}
.lineLimit(1)
87.4%
★★★★★
Avocado Toast
5 stars
Ingredients: Avocado, Almond Butter, Bread, Red Pep…
Alignments
★★★★★
Avocado Toast
5 stars
Ingredients: Avocado, Almond Butter, Bread, Red Pep…
Alignments
★★★★★
Avocado Toast
5 stars
Ingredients: Avocado, Almond Butter, Bread, Red Pep…
Alignments
VStack(alignment: .leading) {
HStack {
Text("Avocado Toast").font(.title)
Spacer()
Image("20x20_avocado")
}
VStack(alignment: .leading) {
HStack {
Text("Avocado Toast").font(.title)
Spacer()
Image("20x20_avocado")
}
extension VerticalAlignment {
private enum MidStarAndTitle : AlignmentID {
static func defaultValue(in d: ViewDimensions) -> Length {
return d[.bottom]
}
}
static let midStarAndTitle = VerticalAlignment(MidStarAndTitle.self)
}
Defining a New Vertical Alignment
extension VerticalAlignment {
private enum MidStarAndTitle : AlignmentID {
static func defaultValue(in d: ViewDimensions) -> Length {
return d[.bottom]
}
}
static let midStarAndTitle = VerticalAlignment(MidStarAndTitle.self)
}
Defining a New Vertical Alignment
extension VerticalAlignment {
private enum MidStarAndTitle : AlignmentID {
static func defaultValue(in d: ViewDimensions) -> Length {
return d[.bottom]
}
}
static let midStarAndTitle = VerticalAlignment(MidStarAndTitle.self)
}
Defining a New Vertical Alignment
extension VerticalAlignment {
private enum MidStarAndTitle : AlignmentID {
static func defaultValue(in d: ViewDimensions) -> Length {
return d[.bottom]
}
}
static let midStarAndTitle = VerticalAlignment(MidStarAndTitle.self)
}
Defining a New Vertical Alignment
extension VerticalAlignment {
private enum MidStarAndTitle : AlignmentID {
static func defaultValue(in d: ViewDimensions) -> Length {
return d[.bottom]
}
}
static let midStarAndTitle = VerticalAlignment(MidStarAndTitle.self)
}
Defining a New Vertical Alignment
extension VerticalAlignment {
private enum MidStarAndTitle : AlignmentID {
static func defaultValue(in d: ViewDimensions) -> Length {
return d[.bottom]
}
}
static let midStarAndTitle = VerticalAlignment(MidStarAndTitle.self)
}
Avocado Toast
HStack(alignment: .midStarAndTitle) {
★★★★★
VStack {
5 stars
Text("★★★★★") Ingredients: Avocado, Almond Butter, Bread, Red Pep…
Text("5 stars")
}.font(.caption)
VStack(alignment: .leading) {
HStack {
Text("Avocado Toast").font(.title)
Spacer()
Image("20x20_avocado")
}
VStack(alignment: .leading) {
HStack {
Text("Avocado Toast").font(.title)
Spacer()
Image("20x20_avocado")
}
VStack(alignment: .leading) {
HStack {
Text("Avocado Toast").font(.title)
Spacer()
Image("20x20_avocado")
}
VStack(alignment: .leading) {
HStack {
Text("Avocado Toast").font(.title)
Spacer()
Image("20x20_avocado")
}
VStack(alignment: .leading) {
HStack {
Text("Avocado Toast").font(.title)
.alignmentGuide(.midStarAndTitle) { d in d[.bottom] / 2 }
Spacer()
Image("20x20_avocado")
}
Text("Ingredients: Avocado, Almond Butter, Bread, Red Pepper Flakes")
.font(.caption).lineLimit(1)
}
}
Avocado Toast
HStack(alignment: .midStarAndTitle) {
★★★★★
VStack {
5 stars
Text("★★★★★") Ingredients: Avocado, Almond Butter, Bread, Red Pep…
.alignmentGuide(.midStarAndTitle) { d in d[.bottom] / 2 }
Text("5 stars")
}.font(.caption)
VStack(alignment: .leading) {
HStack {
Text("Avocado Toast").font(.title)
.alignmentGuide(.midStarAndTitle) { d in d[.bottom] / 2 }
Spacer()
Image("20x20_avocado")
}
Text("Ingredients: Avocado, Almond Butter, Bread, Red Pepper Flakes")
.font(.caption).lineLimit(1)
}
}
Avocado Toast
HStack(alignment: .midStarAndTitle) {
★★★★★
VStack {
5 stars Ingredients: Avocado, Almond Butter, Bread, Red Pep…
Text("★★★★★")
.alignmentGuide(.midStarAndTitle) { d in d[.bottom] / 2 }
Text("5 stars")
}.font(.caption)
VStack(alignment: .leading) {
HStack {
Text("Avocado Toast").font(.title)
.alignmentGuide(.midStarAndTitle) { d in d[.bottom] / 2 }
Spacer()
Image("20x20_avocado")
}
Text("Ingredients: Avocado, Almond Butter, Bread, Red Pepper Flakes")
.font(.caption).lineLimit(1)
}
}
•
Graphics in SwiftUI
John Harper
First Steps
}
}
First Steps
shape.op(style) → view
shape.op(style) → view
shape.op(style) → view
shape.op(style) → view
• Tiled images
0 1
}
}
Gradients
}
}
Gradients
return Circle().fill(conic)
}
}
Gradients
width
// Data model.
class Ring {
struct Wedge {
var width: Double depth
var depth: Double
var hue: Double
}
return p
}
}
r1
a1°
r0
a0°
0°
struct WedgeShape: Shape { cen
return p
}
}
struct WedgeShape: Shape {
var wedge: Ring.Wedge
return p
}
}
struct WedgeShape: Shape {
var wedge: Ring.Wedge
return p
}
}
struct WedgeShape: Shape {
var wedge: Ring.Wedge
return p
}
}
struct WedgeShape: Shape {
var wedge: Ring.Wedge
return p
}
}
struct WedgeShape: Shape {
var wedge: Ring.Wedge
return p
}
}
struct WedgeShape: Shape {
var wedge: Ring.Wedge
return p
}
}
struct WedgeShape: Shape {
var wedge: Ring.Wedge
var animatableData: … {
}
}
struct WedgeShape: Shape {
var wedge: Ring.Wedge
}
}
}
struct RingView: View {
@EnvironmentObject var ring: Ring
}
}
}
}
struct RingView: View {
@EnvironmentObject var ring: Ring
}
}
}
}
struct RingView: View {
@EnvironmentObject var ring: Ring
View Inserted
View Idle
View Removed
Custom Transitions
View Inserted
Animation
View Idle
Animation
View Removed
Custom Transitions
isActive: true
isActive: false
isActive: true
isActive: false
struct ScaleAndFade: ViewModifier {
var isActive: Bool
}
}
isActive: true
isActive: false
struct ScaleAndFade: ViewModifier {
var isActive: Bool
isActive: false
struct ScaleAndFade: ViewModifier {
var isActive: Bool
Demo
Graphic Effects
Opacity
Blur effect
Clipping, masking
• Graphics
• Interaction