Skip to content

Instantly share code, notes, and snippets.

@berikv
Created November 10, 2021 13:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save berikv/ec3b184ab29e66253e74b22c1a50825a to your computer and use it in GitHub Desktop.
Save berikv/ec3b184ab29e66253e74b22c1a50825a to your computer and use it in GitHub Desktop.
SwiftUI Bindings playground
// Drop this code in an xcode playground for iOS
// Open the canvas to see the view rendered
// Cmd+Enter to Run
import PlaygroundSupport
import SwiftUI
// Wrap your application state in observable objects
// Adhering to ObservableObject is needed for @ObservedObject, @StateObject and @EnvironmentObject
final class Clock: ObservableObject {
static let shared = Clock()
// @Published properties update the view when they change
@Published var hour: Int = 0
@Published var minute: Int = 0
@Published var second: Int = 0
init() {
// Add an offset to see which clocks exist
let timeOffset = Double.random(in: 0 ..< 4) * 60 * 60
Timer.scheduledTimer(withTimeInterval: 1.0/4, repeats: true, block: { _ in
let now = Date().advanced(by: timeOffset)
// Setting @Published properties will trigger the objectWillChange publisher
self.hour = Calendar.current.component(.hour, from: now)
self.minute = Calendar.current.component(.minute, from: now)
self.second = Calendar.current.component(.second, from: now)
})
}
}
struct ModelOwnedClockView: View {
// @ObservedObject tracks changes on an object that is owned outside of the view
@ObservedObject var clock = Clock.shared
var body: some View {
Text("\(clock.hour):\(clock.minute):\(clock.second)")
}
}
struct PassedInClockView: View {
// @ObservedObject tracks changes on an object passed through init
@ObservedObject var clock: Clock
// Whenever a @State or other binding changes, the whole subtree is re-rendered.
// So it is not needed to observe value types: `var font: Font` would work fine here too.
// @Binding makes a value type mutable: Setting font in this view will also update the value in the parent view
@Binding var font: Font
var body: some View {
Text("\(clock.hour):\(clock.minute):\(clock.second)")
.font(font)
.onChange(of: clock.second) { newValue in
if newValue.isMultiple(of: 10) {
font = .system(size: 25)
}
}
}
}
struct EnvironmentClockView: View {
// @EnvironmentObject tracks changes in an object, provided by ancester views
@EnvironmentObject var clock: Clock
// @Environment(...) tracks changes in value types, provided by ancester views
@Environment(\.font) var font
var body: some View {
Text("\(clock.hour):\(clock.minute):\(clock.second)")
.font(font)
}
}
// A generic wrapper, to show how @Environment and @EnvironmentObject are passed through
struct WrapperView<Wrapped: View>: View {
private let subView: Wrapped
init(_ subView: Wrapped) { self.subView = subView }
var body: some View { subView.foregroundColor(.red) }
}
struct ParentView: View {
@StateObject var clock = Clock()
@State var font = Font.body
var body: some View {
VStack(spacing: 4) {
Text("The time is")
ModelOwnedClockView()
.font(font) // SwiftUI internally uses @Environment(...) to make "inheritance" work
PassedInClockView(clock: clock, font: $font)
WrapperView(EnvironmentClockView())
.environmentObject(clock)
.environment(\.font, font)
Button("Toggle bold") {
if font == .body {
font = .body.bold()
} else {
font = .body
}
}
}
}
}
// Types required to support @Environment(\.font)
enum FontKey: EnvironmentKey {
static var defaultValue: Font { Font.body }
}
extension EnvironmentValues {
var font: Font {
get { self[FontKey.self] }
set { self[FontKey.self] = newValue }
}
}
PlaygroundPage.current.setLiveView(ParentView().frame(width: 200, height: 300))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment