Canvas and Shape
Canvas View
Canvas provides high-performance 2D drawing similar to UIKit CGContext but declarative:
struct GraphView: View {
let dataPoints: [CGFloat]
var body: some View {
Canvas { context, size in
let width = size.width / CGFloat(dataPoints.count - 1)
var path = Path()
for (index, value) in dataPoints.enumerated() {
let point = CGPoint(x: CGFloat(index) * width, y: size.height * (1 - value))
if index == 0 { path.move(to: point) }
else { path.addLine(to: point) }
}
context.stroke(path, with: .color(.blue), lineWidth: 2)
}
}
}
Custom Shapes with Shape Protocol
Create reusable vector shapes:
struct StarShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let center = CGPoint(x: rect.midX, y: rect.midY)
let outerRadius = min(rect.width, rect.height) / 2
let innerRadius = outerRadius * 0.4
for i in 0..<10 {
let angle = Double(i) * .pi / 5 - .pi / 2
let radius = i.isMultiple(of: 2) ? outerRadius : innerRadius
let point = CGPoint(x: center.x + CGFloat(cos(angle)) * radius, y: center.y + CGFloat(sin(angle)) * radius)
if i == 0 { path.move(to: point) } else { path.addLine(to: point) }
}
path.closeSubpath()
return path
}
}
Animatable Shapes
Use animatableData for smooth shape transitions:
struct AnimatedArc: Shape {
var progress: CGFloat
var animatableData: CGFloat {
get { progress }
set { progress = newValue }
}
func path(in rect: CGRect) -> Path {
var path = Path()
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.width / 2, startAngle: .degrees(0), endAngle: .degrees(progress * 360), clockwise: false)
return path
}
}
Advanced Layout
Custom Layout Protocol
SwiftUI Layout protocol creates custom layout containers:
struct WaterfallLayout: Layout {
var columns: Int
var spacing: CGFloat
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let result = layout(proposal: proposal, subviews: subviews)
return result.size
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
let result = layout(proposal: proposal, subviews: subviews)
for (index, subview) in subviews.enumerated() {
subview.place(at: CGPoint(x: bounds.minX + result.positions[index].x, y: bounds.minY + result.positions[index].y), proposal: .unspecified)
}
}
private func layout(proposal: ProposedViewSize, subviews: Subviews) -> (size: CGSize, positions: [CGPoint]) {
let columnWidth = (proposal.width ?? 0) / CGFloat(columns)
var heights = Array(repeating: CGFloat(0), count: columns)
var positions: [CGPoint] = []
for subview in subviews {
let size = subview.sizeThatFits(.unspecified)
let minHeight = heights.min() ?? 0
let col = heights.firstIndex(of: minHeight) ?? 0
positions.append(CGPoint(x: CGFloat(col) * columnWidth, y: heights[col]))
heights[col] += size.height + spacing
}
return (CGSize(width: proposal.width ?? 0, height: heights.max() ?? 0), positions)
}
}
Parallax Header
Create parallax scrolling effects:
struct ParallaxHeader: View {
var body: some View {
ScrollView {
GeometryReader { geometry in
let offset = geometry.frame(in: .global).minY
Image("header")
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width, height: 200 + max(0, -offset))
.offset(y: min(0, offset))
.clipped()
}
.frame(height: 200)
}
}
}
Custom Animations
AnimatableModifier
Create custom animated transitions:
struct PulseModifier: ViewModifier, Animatable {
var scale: CGFloat = 1.0
var opacity: Double = 1.0
var animatableData: CGFloat {
get { scale }
set { scale = newValue }
}
func body(content: Content) -> some View {
content.scaleEffect(scale).opacity(opacity)
}
}
GeometryEffect
Custom geometric transformations like 3D flip:
struct FlipEffect: GeometryEffect {
var angle: Double
var animatableData: Double {
get { angle }
set { angle = newValue }
}
func effectValue(size: CGSize) -> ProjectionTransform {
let a = CGFloat(Angle(degrees: angle).radians)
var t = CATransform3DIdentity
t.m34 = 1 / -500
t = CATransform3DRotate(t, a, 0, 1, 0)
t = CATransform3DTranslate(t, -size.width / 2, -size.height / 2, 0)
let aff = ProjectionTransform(CGAffineTransform(translationX: size.width / 2, y: size.height / 2))
return ProjectionTransform(t).concatenating(aff)
}
}
Custom Transitions
Define reusable transitions:
struct ScaleAndFade: ViewModifier {
func body(content: Content) -> some View {
content.scaleEffect(0.8).opacity(0)
}
}
extension AnyTransition {
static var scaleAndFade: AnyTransition {
.modifier(active: ScaleAndFade(), identity: IdentityModifier())
}
}
if showView {
ContentView().transition(.scaleAndFade)
}
Quiz
1. What does the Canvas view provide in SwiftUI?
2. What protocol enables animated shape morphing?
3. What does the Layout protocol allow you to create?
4. What is GeometryEffect used for?
Flashcards
Question
What is the difference between Canvas and Shape?
Click to reveal answer
Answer
Canvas provides low-level drawing commands for complex graphics. Shape provides a path-based approach for creating reusable vector shapes.
Question
How do you create a custom layout in SwiftUI?
Click to reveal answer
Answer
Implement the Layout protocol with sizeThatFits and placeSubviews methods to define your custom layout algorithm.
Question
What is animatableData used for?
Click to reveal answer
Answer
It tells SwiftUI which properties should be interpolated during animation, enabling smooth transitions between view states.
Revision Notes
Key Takeaways
- 1. Canvas provides CGContext-like drawing in SwiftUI
- 2. Shape protocol creates reusable vector shapes
- 3. Layout protocol enables custom layout algorithms
- 4. Animatable protocol enables smooth shape transitions
- 5. GeometryEffect handles complex view transformations
Interview Tips
- • Explain when to use Canvas vs individual SwiftUI views
- • Describe creating a custom shape with the Shape protocol
- • Walk through building a custom Layout implementation
- • Discuss creating custom animations with Animatable
Cheat Sheet
Advanced SwiftUI Quick Reference
- Canvas: high-performance 2D drawing
- Shape: reusable vector shapes via path
- Layout protocol: custom layout containers
- Animatable: animatableData for transitions
- GeometryEffect: 3D transformations
- Custom transitions with ViewModifier