Last active
September 10, 2024 11:53
-
-
Save IanKeen/a85e4ed74a10a25341c44a98f43cf386 to your computer and use it in GitHub Desktop.
PropertyWrapper: @transaction binding for SwiftUI to make changes to data supporting commit/rollback
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
@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) } | |
} |
hey @agisilaos , currently this requires you base the transaction off a Binding
so you should be able to do something like:
ChangeMindfulMinutesGoal(goal: Binding.constant(20).transaction())
if you want to provide just a value you could do something like
extension Transaction {
public init(source: Value) {
var source = source
let binding = Binding(get: { source }, set: { source = $0 })
self.init(source: binding)
}
}
then you can do
ChangeMindfulMinutesGoal(goal: .init(source: 20))
but this would be a detached object, commits don't propagate anywhere (which is fine for previews)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello @IanKeen, how that would work in a preview?
And I get this error:
Cannot convert value of type 'Int' to expected argument type 'Transaction<Int>'