Skip to content

Instantly share code, notes, and snippets.

@indyfromoz
Forked from IanKeen/Example_Complex.swift
Created July 30, 2020 09:32
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 indyfromoz/3d1bd35f488fbd716df962a10d7388a8 to your computer and use it in GitHub Desktop.
Save indyfromoz/3d1bd35f488fbd716df962a10d7388a8 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 {
private let derived: State<Value>
private let source: Binding<Value>
fileprivate init(source: Binding<Value>) {
self.source = source
self.derived = State(initialValue: source.wrappedValue)
}
public var wrappedValue: Value {
get { derived.wrappedValue }
nonmutating set { derived.wrappedValue = newValue }
}
public var projectedValue: Transaction<Value> { self }
public subscript<T>(dynamicMember keyPath: WritableKeyPath<Value, T>) -> Binding<T> {
return derived.projectedValue[dynamicMember: keyPath]
}
public var binding: Binding<Value> { derived.projectedValue }
public func commit() {
source.wrappedValue = derived.wrappedValue
}
public func rollback() {
derived.wrappedValue = source.wrappedValue
}
}
extension Transaction where Value: Equatable {
public var hasChanges: Bool { return source.wrappedValue != derived.wrappedValue }
}
extension Binding {
public func transaction() -> Transaction<Value> { .init(source: self) }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment