From d5a6c094e9b3facfa676f1e401033829b2bf9bb7 Mon Sep 17 00:00:00 2001 From: Paul Kraft Date: Wed, 3 Mar 2021 14:39:52 +0100 Subject: [PATCH 1/2] Add more extensions, standardize use of MARKs --- Sources/XUI/Binding/Binding+Change.swift | 5 ++-- Sources/XUI/Binding/Binding+Force.swift | 7 +++++ Sources/XUI/Combine/AnyPublisher+Init.swift | 24 +++++++++++++++++ Sources/XUI/Combine/CancellableBuilder.swift | 8 ++++++ .../XUI/Internal/ErasedObservableObject.swift | 6 +++++ Sources/XUI/Internal/ObjectIdentifiable.swift | 4 +++ Sources/XUI/Store/Store.swift | 4 +-- .../XUI/ViewModifiers/CustomModifier.swift | 27 +++++++++++++++++++ Sources/XUI/Views/NavigationLink.swift | 2 ++ 9 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 Sources/XUI/Combine/AnyPublisher+Init.swift create mode 100644 Sources/XUI/ViewModifiers/CustomModifier.swift diff --git a/Sources/XUI/Binding/Binding+Change.swift b/Sources/XUI/Binding/Binding+Change.swift index 1ac1455..8a16366 100644 --- a/Sources/XUI/Binding/Binding+Change.swift +++ b/Sources/XUI/Binding/Binding+Change.swift @@ -54,10 +54,11 @@ extension Binding { ) } - public func map(get: @escaping (Value) -> V, set: @escaping (V) -> Value) -> Binding { + public func map(get: @escaping (Value) -> V, + set: @escaping (inout Value, V) -> Void) -> Binding { .init( get: { get(self.wrappedValue) }, - set: { self.wrappedValue = set($0) } + set: { set(&self.wrappedValue, $0) } ) } diff --git a/Sources/XUI/Binding/Binding+Force.swift b/Sources/XUI/Binding/Binding+Force.swift index 5807832..2b03289 100644 --- a/Sources/XUI/Binding/Binding+Force.swift +++ b/Sources/XUI/Binding/Binding+Force.swift @@ -20,5 +20,12 @@ extension Binding { ) } + public func `default`(_ value: Wrapped) -> Binding where Value == Wrapped? { + .init( + get: { self.wrappedValue ?? value }, + set: { self.wrappedValue = $0 } + ) + } + } diff --git a/Sources/XUI/Combine/AnyPublisher+Init.swift b/Sources/XUI/Combine/AnyPublisher+Init.swift new file mode 100644 index 0000000..6ab0404 --- /dev/null +++ b/Sources/XUI/Combine/AnyPublisher+Init.swift @@ -0,0 +1,24 @@ +// +// AnyPublisher+Init.swift +// XUI +// +// Created by Paul Kraft on 01.03.21. +// Copyright © 2021 QuickBird Studios. All rights reserved. +// + +extension AnyPublisher { + + // MARK: Factory Methods + + public static func just(_ value: Output) -> AnyPublisher { + Just(value) + .mapError { _ -> Failure in } + .eraseToAnyPublisher() + } + + public static func failure(_ error: Failure) -> AnyPublisher { + Fail(error: error) + .eraseToAnyPublisher() + } + +} diff --git a/Sources/XUI/Combine/CancellableBuilder.swift b/Sources/XUI/Combine/CancellableBuilder.swift index e23a310..909efab 100644 --- a/Sources/XUI/Combine/CancellableBuilder.swift +++ b/Sources/XUI/Combine/CancellableBuilder.swift @@ -70,6 +70,10 @@ extension Cancellable { extension RangeReplaceableCollection where Element == AnyCancellable { + public init(@CancellableBuilder _ builder: () -> [AnyCancellable]) { + self.init(builder()) + } + /// This method can be used to store multiple `Cancellable` objects /// from different subscriptions in a collection of `AnyCancellable`. public mutating func insert(@CancellableBuilder _ builder: () -> [AnyCancellable]) { @@ -80,6 +84,10 @@ extension RangeReplaceableCollection where Element == AnyCancellable { extension Set where Element == AnyCancellable { + public init(@CancellableBuilder _ builder: () -> [AnyCancellable]) { + self.init(builder()) + } + /// This method can be used to store multiple `Cancellable` objects /// from different subscriptions in a set of `AnyCancellable`. public mutating func insert(@CancellableBuilder _ builder: () -> [AnyCancellable]) { diff --git a/Sources/XUI/Internal/ErasedObservableObject.swift b/Sources/XUI/Internal/ErasedObservableObject.swift index 03a3677..d4ae3e9 100644 --- a/Sources/XUI/Internal/ErasedObservableObject.swift +++ b/Sources/XUI/Internal/ErasedObservableObject.swift @@ -8,12 +8,18 @@ class ErasedObservableObject: ObservableObject { + // MARK: Stored Properties + let objectWillChange: AnyPublisher + // MARK: Initialization + init(objectWillChange: AnyPublisher) { self.objectWillChange = objectWillChange } + // MARK: Factory Methods + static func empty() -> ErasedObservableObject { .init(objectWillChange: Empty().eraseToAnyPublisher()) } diff --git a/Sources/XUI/Internal/ObjectIdentifiable.swift b/Sources/XUI/Internal/ObjectIdentifiable.swift index 51cd2d1..cf02237 100644 --- a/Sources/XUI/Internal/ObjectIdentifiable.swift +++ b/Sources/XUI/Internal/ObjectIdentifiable.swift @@ -8,8 +8,12 @@ struct ObjectIdentifiable: Identifiable { + // MARK: Stored Properties + var id: ObjectIdentifier + // MARK: Initialization + init(_ object: Any) { self.id = ObjectIdentifier(object as AnyObject) } diff --git a/Sources/XUI/Store/Store.swift b/Sources/XUI/Store/Store.swift index c535129..05b7ff8 100644 --- a/Sources/XUI/Store/Store.swift +++ b/Sources/XUI/Store/Store.swift @@ -9,7 +9,7 @@ @propertyWrapper public struct Store: DynamicProperty { - // MARK: Nested types + // MARK: Nested Types @dynamicMemberLookup public struct Wrapper { @@ -23,7 +23,7 @@ public struct Store: DynamicProperty { } - // MARK: Stored properties + // MARK: Stored Properties public let wrappedValue: Model diff --git a/Sources/XUI/ViewModifiers/CustomModifier.swift b/Sources/XUI/ViewModifiers/CustomModifier.swift new file mode 100644 index 0000000..a11f2d6 --- /dev/null +++ b/Sources/XUI/ViewModifiers/CustomModifier.swift @@ -0,0 +1,27 @@ +// +// NavigationModifier.swift +// XUI +// +// Created by Paul Kraft on 01.03.21. +// Copyright © 2021 QuickBird Studios. All rights reserved. +// + +public struct CustomModifier: ViewModifier { + + // MARK: Stored Properties + + private let _body: (Content) -> Result + + // MARK: Initialization + + public init(@ViewBuilder body: @escaping (Content) -> Result) { + self._body = body + } + + // MARK: Methods + + public func body(content: Content) -> some View { + _body(content) + } + +} diff --git a/Sources/XUI/Views/NavigationLink.swift b/Sources/XUI/Views/NavigationLink.swift index 63e50a1..b91132b 100644 --- a/Sources/XUI/Views/NavigationLink.swift +++ b/Sources/XUI/Views/NavigationLink.swift @@ -8,6 +8,8 @@ extension NavigationLink { + // MARK: Initialization + public init( model: Binding, @ViewBuilder destination: (Model) -> _Destination, From 2c9f3a8681d5e616834c23ad29c22ee78ef4677e Mon Sep 17 00:00:00 2001 From: Paul Kraft Date: Thu, 4 Mar 2021 18:07:06 +0100 Subject: [PATCH 2/2] Add Binding.onlySetOnChange --- Sources/XUI/Binding/Binding+Change.swift | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Sources/XUI/Binding/Binding+Change.swift b/Sources/XUI/Binding/Binding+Change.swift index 8a16366..8abc00f 100644 --- a/Sources/XUI/Binding/Binding+Change.swift +++ b/Sources/XUI/Binding/Binding+Change.swift @@ -76,4 +76,27 @@ extension Binding { ) } + public func onlySetOnChange(_ isEqual: @escaping (Value, Value) -> Bool) -> Binding { + .init( + get: { self.wrappedValue }, + set: { newValue in + guard !isEqual(self.wrappedValue, newValue) else { + return + } + self.wrappedValue = newValue + } + ) + } + + public func onlySetOnChange( + of keyPath: KeyPath + ) -> Binding where V: Equatable { + + onlySetOnChange { $0[keyPath: keyPath] == $1[keyPath: keyPath] } + } + + public func onlySetOnChange() -> Binding where Value: Equatable { + onlySetOnChange(==) + } + }