Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use better binding transformations where possible #141

Merged
merged 2 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
.package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.1.0"),
.package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.2.2"),
.package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.0.0"),
.package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.0.0"),
],
Expand Down
102 changes: 69 additions & 33 deletions Sources/SwiftUINavigation/Binding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,7 @@
dynamicMember keyPath: KeyPath<Value.AllCasePaths, AnyCasePath<Value, Member>>
) -> Binding<Member>?
where Value: CasePathable {
let casePath = Value.allCasePaths[keyPath: keyPath]
return Binding<Member>(
unwrapping: Binding<Member?>(
get: { casePath.extract(from: self.wrappedValue) },
set: { newValue, transaction in
guard let newValue else { return }
self.transaction(transaction).wrappedValue = casePath.embed(newValue)
}
)
)
Binding<Member>(unwrapping: self[keyPath])
}

/// Returns a binding to the associated value of a given case key path.
Expand All @@ -36,20 +27,7 @@
dynamicMember keyPath: KeyPath<Enum.AllCasePaths, AnyCasePath<Enum, Member>>
) -> Binding<Member?>
where Value == Enum? {
let casePath = Enum.allCasePaths[keyPath: keyPath]
return Binding<Member?>(
get: {
guard let wrappedValue = self.wrappedValue else { return nil }
return casePath.extract(from: wrappedValue)
},
set: { newValue, transaction in
guard let newValue else {
self.transaction(transaction).wrappedValue = nil
return
}
self.transaction(transaction).wrappedValue = casePath.embed(newValue)
}
)
self[keyPath]
}
#endif

Expand All @@ -68,7 +46,8 @@
/// - Parameter base: A value to project to an unwrapped value.
/// - Returns: A new binding or `nil` when `base` is `nil`.
public init?(unwrapping base: Binding<Value?>) {
self.init(unwrapping: base, case: AnyCasePath(\.some))
guard let value = base.wrappedValue else { return nil }
self = base[default: DefaultSubscript(value)]
}

/// Creates a binding by projecting the current optional value to a boolean describing if it's
Expand All @@ -79,14 +58,7 @@
/// - Returns: A binding to a boolean. Returns `true` if non-`nil`, otherwise `false`.
public func isPresent<Wrapped>() -> Binding<Bool>
where Value == Wrapped? {
.init(
get: { self.wrappedValue != nil },
set: { isPresent, transaction in
if !isPresent {
self.transaction(transaction).wrappedValue = nil
}
}
)
self._isPresent
}

/// Creates a binding that ignores writes to its wrapped value when equivalent to the new value.
Expand Down Expand Up @@ -139,4 +111,68 @@
)
}
}

extension Optional {
fileprivate var _isPresent: Bool {
get { self != nil }
set {
guard !newValue else { return }
self = nil
}
}

fileprivate subscript(default defaultSubscript: DefaultSubscript<Wrapped>) -> Wrapped {
get {
defaultSubscript.value = self ?? defaultSubscript.value
return defaultSubscript.value
}
set {
defaultSubscript.value = newValue
if self != nil { self = newValue }
}
}
}

private final class DefaultSubscript<Value>: Hashable {
var value: Value
init(_ value: Value) {
self.value = value
}
static func == (lhs: DefaultSubscript, rhs: DefaultSubscript) -> Bool {
lhs === rhs
}
func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
}

extension CasePathable {
fileprivate subscript<Member>(
keyPath: KeyPath<Self.AllCasePaths, AnyCasePath<Self, Member>>
) -> Member? {
get { Self.allCasePaths[keyPath: keyPath].extract(from: self) }
set {
guard let newValue else { return }
self = Self.allCasePaths[keyPath: keyPath].embed(newValue)
}
}
}

extension Optional where Wrapped: CasePathable {
fileprivate subscript<Member>(
keyPath: KeyPath<Wrapped.AllCasePaths, AnyCasePath<Wrapped, Member>>
) -> Member? {
get {
guard let wrapped = self else { return nil }
return Wrapped.allCasePaths[keyPath: keyPath].extract(from: wrapped)
}
set {
guard let newValue else {
self = nil
return
}
self = Wrapped.allCasePaths[keyPath: keyPath].embed(newValue)
}
}
}
#endif // canImport(SwiftUI)
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"repositoryURL": "https://github.com/pointfreeco/swift-case-paths",
"state": {
"branch": null,
"revision": "bba1111185863c9288c5f047770f421c3b7793a4",
"version": "1.1.3"
"revision": "8cc3bc05d0cc956f7374c6c208a11f66a7cac3db",
"version": "1.2.2"
}
},
{
Expand Down
Loading