From 44c1f87b8af4dfe121e34033030a61399826fcaf Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 27 Feb 2023 09:46:21 -0800 Subject: [PATCH] Swift 5.6 / Xcode 13.4.1 fixes --- .../Internal/NavigationID.swift | 15 +- .../Internal/OpenExistential.swift | 30 ++++ .../Reducer/Reducers/Presentation.swift | 3 +- .../SwiftUI/NavigationDestination.swift | 137 +++++++++--------- 4 files changed, 109 insertions(+), 76 deletions(-) diff --git a/Sources/ComposableArchitecture/Internal/NavigationID.swift b/Sources/ComposableArchitecture/Internal/NavigationID.swift index f92b7970f658..048580f3dd86 100644 --- a/Sources/ComposableArchitecture/Internal/NavigationID.swift +++ b/Sources/ComposableArchitecture/Internal/NavigationID.swift @@ -66,19 +66,20 @@ struct AnyID: Hashable, Identifiable, Sendable { @usableFromInline init(_ base: Base) { - func id(_ identifiable: some Identifiable) -> AnyHashableSendable { + func id(_ identifiable: T) -> AnyHashableSendable { AnyHashableSendable(identifiable.id) } self.objectIdentifier = ObjectIdentifier(Base.self) self.tag = EnumMetadata(Base.self)?.tag(of: base) - if let base = base as? any Identifiable { - self.identifier = id(base) - } else if let metadata = EnumMetadata(type(of: base)), - metadata.associatedValueType(forTag: metadata.tag(of: base)) is any Identifiable.Type - { - // TODO: Extract enum payload and assign id + if let id = _id(base) { + self.identifier = AnyHashableSendable(id) } + // TODO: Extract identifiable enum payload and assign id + // else if let metadata = EnumMetadata(type(of: base)), + // metadata.associatedValueType(forTag: metadata.tag(of: base)) is any Identifiable.Type + // { + // } } @usableFromInline diff --git a/Sources/ComposableArchitecture/Internal/OpenExistential.swift b/Sources/ComposableArchitecture/Internal/OpenExistential.swift index 8aba38085e47..2f8afc8e3307 100644 --- a/Sources/ComposableArchitecture/Internal/OpenExistential.swift +++ b/Sources/ComposableArchitecture/Internal/OpenExistential.swift @@ -11,6 +11,16 @@ self == other as? Self } } + + // MARK: Identifiable + + func _id(_ value: Any) -> AnyHashable? { + func open(_ value: some Identifiable) -> AnyHashable { + value.id + } + guard let value = value as? any Identifiable else { return nil } + return open(value) + } #else // MARK: - // MARK: swift(<5.7) @@ -39,4 +49,24 @@ return lhs == rhs } } + + // MARK: Identifiable + + func _id(_ value: Any) -> AnyHashable? { + func open(_: T.Type) -> AnyHashable? { + (Witness.self as? AnyIdentifiable.Type)?.id(value) + } + return _openExistential(type(of: value), do: open) + } + + private protocol AnyIdentifiable { + static func id(_ value: Any) -> AnyHashable? + } + + extension Witness: AnyIdentifiable where T: Identifiable { + fileprivate static func id(_ value: Any) -> AnyHashable? { + guard let value = value as? T else { return nil } + return value.id + } + } #endif diff --git a/Sources/ComposableArchitecture/Reducer/Reducers/Presentation.swift b/Sources/ComposableArchitecture/Reducer/Reducers/Presentation.swift index ec0c591572f9..a24ad561387a 100644 --- a/Sources/ComposableArchitecture/Reducer/Reducers/Presentation.swift +++ b/Sources/ComposableArchitecture/Reducer/Reducers/Presentation.swift @@ -172,8 +172,7 @@ extension ReducerProtocol { file: StaticString = #file, fileID: StaticString = #fileID, line: UInt = #line - ) -> _PresentationReducer> - where DestinationState: _EphemeralState { + ) -> _PresentationReducer> { self.ifLet( toPresentationState, action: toPresentationAction, diff --git a/Sources/ComposableArchitecture/SwiftUI/NavigationDestination.swift b/Sources/ComposableArchitecture/SwiftUI/NavigationDestination.swift index 02c5278fcd8d..fa9e3cb8295f 100644 --- a/Sources/ComposableArchitecture/SwiftUI/NavigationDestination.swift +++ b/Sources/ComposableArchitecture/SwiftUI/NavigationDestination.swift @@ -1,78 +1,81 @@ import SwiftUI -extension View { - @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) - public func navigationDestination( - store: Store, PresentationAction>, - @ViewBuilder destination: @escaping (Store) -> Destination - ) -> some View { - self.navigationDestination( - store: store, state: { $0 }, action: { $0 }, destination: destination - ) - } +#if swift(>=5.7) + extension View { + @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) + public func navigationDestination( + store: Store, PresentationAction>, + @ViewBuilder destination: @escaping (Store) -> Destination + ) -> some View { + self.navigationDestination( + store: store, state: { $0 }, action: { $0 }, destination: destination + ) + } - @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) - public func navigationDestination< - State, Action, DestinationState, DestinationAction, Destination: View - >( - store: Store, PresentationAction>, - state toDestinationState: @escaping (State) -> DestinationState?, - action fromDestinationAction: @escaping (DestinationAction) -> Action, - @ViewBuilder destination: @escaping (Store) -> Destination - ) -> some View { - self.modifier( - PresentationNavigationDestinationModifier( - store: store, - state: toDestinationState, - action: fromDestinationAction, - content: destination + @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) + public func navigationDestination< + State, Action, DestinationState, DestinationAction, Destination: View + >( + store: Store, PresentationAction>, + state toDestinationState: @escaping (State) -> DestinationState?, + action fromDestinationAction: @escaping (DestinationAction) -> Action, + @ViewBuilder destination: @escaping (Store) -> + Destination + ) -> some View { + self.modifier( + PresentationNavigationDestinationModifier( + store: store, + state: toDestinationState, + action: fromDestinationAction, + content: destination + ) ) - ) + } } -} -@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) -private struct PresentationNavigationDestinationModifier< - State, - Action, - DestinationState, - DestinationAction, - CoverContent: View ->: ViewModifier { - let store: Store, PresentationAction> - @StateObject var viewStore: ViewStore> - let toDestinationState: (State) -> DestinationState? - let fromDestinationAction: (DestinationAction) -> Action - let coverContent: (Store) -> CoverContent + @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) + private struct PresentationNavigationDestinationModifier< + State, + Action, + DestinationState, + DestinationAction, + CoverContent: View + >: ViewModifier { + let store: Store, PresentationAction> + @StateObject var viewStore: ViewStore> + let toDestinationState: (State) -> DestinationState? + let fromDestinationAction: (DestinationAction) -> Action + let coverContent: (Store) -> CoverContent - init( - store: Store, PresentationAction>, - state toDestinationState: @escaping (State) -> DestinationState?, - action fromDestinationAction: @escaping (DestinationAction) -> Action, - content coverContent: @escaping (Store) -> CoverContent - ) { - self.store = store - self._viewStore = StateObject( - wrappedValue: ViewStore( - store - .filter { state, _ in state.wrappedValue != nil } - .scope(state: { $0.wrappedValue.flatMap(toDestinationState) != nil }) + init( + store: Store, PresentationAction>, + state toDestinationState: @escaping (State) -> DestinationState?, + action fromDestinationAction: @escaping (DestinationAction) -> Action, + content coverContent: @escaping (Store) -> CoverContent + ) { + self.store = store + self._viewStore = StateObject( + wrappedValue: ViewStore( + store + .filter { state, _ in state.wrappedValue != nil } + .scope(state: { $0.wrappedValue.flatMap(toDestinationState) != nil }) + ) ) - ) - self.toDestinationState = toDestinationState - self.fromDestinationAction = fromDestinationAction - self.coverContent = coverContent - } + self.toDestinationState = toDestinationState + self.fromDestinationAction = fromDestinationAction + self.coverContent = coverContent + } - func body(content: Content) -> some View { - content.navigationDestination(isPresented: self.viewStore.binding(send: .dismiss)) { - IfLetStore( - self.store.scope( - state: returningLastNonNilValue { $0.wrappedValue.flatMap(self.toDestinationState) }, - action: { .presented(self.fromDestinationAction($0)) } - ), - then: self.coverContent - ) + func body(content: Content) -> some View { + content.navigationDestination(isPresented: self.viewStore.binding(send: .dismiss)) { + IfLetStore( + self.store.scope( + state: returningLastNonNilValue { $0.wrappedValue.flatMap(self.toDestinationState) }, + action: { .presented(self.fromDestinationAction($0)) } + ), + then: self.coverContent + ) + } } } -} +#endif