Skip to content

Instantly share code, notes, and snippets.

@mattyoung
Forked from IanKeen/Example_Complex.swift
Last active July 31, 2020 10:31
Show Gist options
  • Save mattyoung/facbde63008ec44278735b3f340cb1ec to your computer and use it in GitHub Desktop.
Save mattyoung/facbde63008ec44278735b3f340cb1ec to your computer and use it in GitHub Desktop.
PropertyWrapper: @transaction binding for SwiftUI to make changes to data supporting commit/rollback
struct User: Equatable {
var firstName: String
var lastName: String
}
@main
struct MyApp: App {
@State var value = User(firstName: "", lastName: "")
@State var showEdit = false
var body: some Scene {
WindowGroup {
VStack {
Text("First Name: \(value.firstName)")
Text("Last Name: \(value.lastName)")
Button("Edit") { showEdit = true }
}
.sheet(isPresented: $showEdit) {
UserEditView(value: $value.transaction())
}
}
}
}
struct UserEditView: View {
@Transaction var value: User
var body: some View {
VStack {
TextField("First Name", text: $value.firstName)
TextField("Last Name", text: $value.lastName)
Divider()
Button("Commit", action: $value.commit).disabled(!$value.hasChanges)
Button("Rollback", action: $value.rollback).disabled(!$value.hasChanges)
}
}
}
@main
struct MyApp: App {
@State var value = 0
var body: some Scene {
WindowGroup {
MyView(value: $value.transaction())
}
}
}
struct MyView: View {
@Transaction var value: Int
var body: some View {
VStack {
Text("Value: \(value)")
Text("HasChanges: \($value.hasChanges ? "yes" : "no")")
Divider()
Button("Increase") { value += 1 }
Button("Commit", action: $value.commit)
Button("Rollback", action: $value.rollback)
}
}
}
@propertyWrapper
@dynamicMemberLookup
public struct Transaction<Value>: DynamicProperty {
@State private var derived: Value
@Binding private var source: Value
fileprivate init(source: Binding<Value>) {
self._source = source
self._derived = State(initialValue: source.wrappedValue)
}
public var wrappedValue: Value {
get { derived }
nonmutating set { derived = newValue }
}
public var projectedValue: Transaction<Value> { self }
public subscript<T>(dynamicMember keyPath: WritableKeyPath<Value, T>) -> Binding<T> {
return $derived[dynamicMember: keyPath]
}
public var binding: Binding<Value> { $derived }
public func commit() {
source = derived
}
public func rollback() {
derived = source
}
}
extension Transaction where Value: Equatable {
public var hasChanges: Bool { return source != derived }
}
extension Binding {
public func transaction() -> Transaction<Value> { .init(source: self) }
}
@mattyoung
Copy link
Author

Use @State @binding instead of State, Binding so code use shorthand notation for .wrappedValue and .projectedValue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment