diff --git a/Sources/XUI/DeepLink/DeepLinkable+Receiver.swift b/Sources/XUI/DeepLink/DeepLinkable+Receiver.swift index 6c2b8b3..4641674 100644 --- a/Sources/XUI/DeepLink/DeepLinkable+Receiver.swift +++ b/Sources/XUI/DeepLink/DeepLinkable+Receiver.swift @@ -12,39 +12,25 @@ extension DeepLinkable { as type: Receiver.Type, where filter: (Receiver) -> Bool = { _ in true } ) -> Receiver! { - firstReceiver(ofType: type, where: filter) { $0.children } - ?? firstReceiver(ofType: type, where: filter) { - Mirror(reflecting: $0) - .values { $0 as? DeepLinkable } - } - } - -} - -extension DeepLinkable { - - // MARK: Helpers - DeepLink - - private func firstReceiver( - ofType type: Receiver.Type, - where filter: (Receiver) -> Bool, - children: (DeepLinkable) -> [DeepLinkable] - ) -> Receiver? { var visited = Set() var stack = [DeepLinkable]() return firstReceiver( where: { ($0 as? Receiver).map(filter) ?? false }, visited: &visited, - stack: &stack, - children: children + stack: &stack ) as? Receiver } +} + +extension DeepLinkable { + + // MARK: Helpers - DeepLink + private func firstReceiver( where filter: (Any) -> Bool, visited: inout Set, - stack: inout [DeepLinkable], - children: (DeepLinkable) -> [DeepLinkable] + stack: inout [DeepLinkable] ) -> Any? { visited.insert(ObjectIdentifier(self)) @@ -54,7 +40,7 @@ extension DeepLinkable { } stack.append( - contentsOf: children(self) + contentsOf: children .filter { !visited.contains(ObjectIdentifier($0)) } ) @@ -64,16 +50,8 @@ extension DeepLinkable { stack.removeFirst() return next - .firstReceiver(where: filter, visited: &visited, stack: &stack, children: children) + .firstReceiver(where: filter, visited: &visited, stack: &stack) } } -extension Mirror { - - fileprivate func values(_ compactMap: (Any) -> Value?) -> [Value] { - children.compactMap { compactMap($0.value) } - + (superclassMirror?.values(compactMap) ?? []) - } - -} diff --git a/Sources/XUI/DeepLink/DeepLinkable.swift b/Sources/XUI/DeepLink/DeepLinkable.swift index cb1a266..78d5201 100644 --- a/Sources/XUI/DeepLink/DeepLinkable.swift +++ b/Sources/XUI/DeepLink/DeepLinkable.swift @@ -7,14 +7,33 @@ // public protocol DeepLinkable: AnyObject { - @DeepLinkableBuilder var children: [DeepLinkable] { get } + var children: [DeepLinkable] { get } } extension DeepLinkable { - @DeepLinkableBuilder public var children: [DeepLinkable] { - get {} + Mirror(reflecting: self) + .values(extractDeepLinkable) + } + + private func extractDeepLinkable(from object: Any) -> DeepLinkable? { + if let value = object as? DeepLinkable { + return value + } else if let value = object as? DeepLinkableWrapper { + return extractDeepLinkable(from: value.content) + } else { + return nil + } + } + +} + +extension Mirror { + + fileprivate func values(_ compactMap: (Any) -> Value?) -> [Value] { + children.compactMap { compactMap($0.value) } + + (superclassMirror?.values(compactMap) ?? []) } } diff --git a/Sources/XUI/DeepLink/DeepLinkableBuilder.swift b/Sources/XUI/DeepLink/DeepLinkableBuilder.swift deleted file mode 100644 index bf61fc1..0000000 --- a/Sources/XUI/DeepLink/DeepLinkableBuilder.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// DeepLinkableBuilder.swift -// XUI -// -// Created by Paul Kraft on 01.03.21. -// Copyright © 2021 QuickBird Studios. All rights reserved. -// - -public typealias DeepLinkableBuilder = CollectionBuilder - -@_functionBuilder -public struct CollectionBuilder { - - public static func buildBlock(_ components: [Component]...) -> [Component] { - components.flatMap { $0 } - } - - public static func buildBlock() -> [Component] { - [] - } - - public static func buildExpression(_ expression: Component) -> [Component] { - [expression] - } - - public static func buildExpression(_ expression: Component?) -> [Component] { - [expression].compactMap { $0 } - } - - public static func buildExpression(_ expression: Void) -> [Component] { - [] - } - - public static func buildOptional(_ component: [Component]?) -> [Component] { - component ?? [] - } - - public static func buildEither(first component: Component) -> [Component] { - [component] - } - - public static func buildEither(second component: Component) -> [Component] { - [component] - } - - public static func buildEither(first component: [Component]) -> [Component] { - component - } - - public static func buildEither(second component: [Component]) -> [Component] { - component - } - - public static func buildArray(_ components: [Component]) -> [Component] { - components - } - - public static func buildLimitedAvailability(_ component: [Component]) -> [Component] { - component - } - - public static func buildFinalResult(_ component: [Component]) -> [Component] { - component - } - -} diff --git a/Sources/XUI/DeepLink/DeepLinkableWrapper.swift b/Sources/XUI/DeepLink/DeepLinkableWrapper.swift new file mode 100644 index 0000000..9a18731 --- /dev/null +++ b/Sources/XUI/DeepLink/DeepLinkableWrapper.swift @@ -0,0 +1,47 @@ +// +// DeepLinkableWrapper.swift +// XUI +// +// Created by Paul Kraft on 01.03.21. +// Copyright © 2021 QuickBird Studios. All rights reserved. +// + +public protocol DeepLinkableWrapper { + var content: Any { get } +} + +extension Published: DeepLinkableWrapper { + + public var content: Any { + guard let value = Mirror(reflecting: self).descendant("storage", "value") else { + assertionFailure("We could not extract a wrappedValue from a Published property wrapper.") + return () + } + return value + } + +} + +extension Binding: DeepLinkableWrapper { + public var content: Any { wrappedValue } +} + +extension State: DeepLinkableWrapper { + public var content: Any { wrappedValue } +} + +extension ObservedObject: DeepLinkableWrapper { + public var content: Any { wrappedValue } +} + +extension EnvironmentObject: DeepLinkableWrapper { + public var content: Any { wrappedValue } +} + +extension Store: DeepLinkableWrapper { + public var content: Any { wrappedValue } +} + +extension Optional: DeepLinkableWrapper { + public var content: Any { flatMap { $0 } ?? () } +} diff --git a/Tests/XUITests/DeepLinkTests.swift b/Tests/XUITests/DeepLinkTests.swift new file mode 100644 index 0000000..2c550c8 --- /dev/null +++ b/Tests/XUITests/DeepLinkTests.swift @@ -0,0 +1,28 @@ + +import XCTest +@testable import XUI + +class ParentViewModel: ViewModel, ObservableObject { + @Published var child: ChildViewModel? +} + +class ChildViewModel: ViewModel, ObservableObject { + +} + +final class DeepLinkTests: XCTestCase { + + func testMirror() { + let viewModel = ParentViewModel() + XCTAssertNil(viewModel.firstReceiver(as: ChildViewModel.self)) + viewModel.child = .init() + XCTAssertNotNil(viewModel.firstReceiver(as: ChildViewModel.self)) + viewModel.child = nil + XCTAssertNil(viewModel.firstReceiver(as: ChildViewModel.self)) + } + + static var allTests = [ + ("testMirror", testMirror), + ] + +} diff --git a/Tests/XUITests/XCTestManifests.swift b/Tests/XUITests/XCTestManifests.swift index 0710550..b7af7a6 100644 --- a/Tests/XUITests/XCTestManifests.swift +++ b/Tests/XUITests/XCTestManifests.swift @@ -3,6 +3,7 @@ import XCTest #if !canImport(ObjectiveC) public func allTests() -> [XCTestCaseEntry] { return [ + testCase(DeepLinkTests.allTests), testCase(XUITests.allTests), ] }