Skip to content

Instantly share code, notes, and snippets.

@MarqueIV
Forked from IanKeen/Example_Complex.swift
Last active August 20, 2023 21:00
Show Gist options
  • Save MarqueIV/33d0636efa95477351b02af58632ba60 to your computer and use it in GitHub Desktop.
Save MarqueIV/33d0636efa95477351b02af58632ba60 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)
}
}
}
import SwiftUI
import Combine
@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(wrappedValue: 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) }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment