diff --git a/BlueprintUI/Sources/BlueprintView/BlueprintView.swift b/BlueprintUI/Sources/BlueprintView/BlueprintView.swift index 09959e076..3b978ed03 100644 --- a/BlueprintUI/Sources/BlueprintView/BlueprintView.swift +++ b/BlueprintUI/Sources/BlueprintView/BlueprintView.swift @@ -118,6 +118,16 @@ public final class BlueprintView: UIView { /// Provides performance metrics about the duration of layouts, updates, etc. public weak var metricsDelegate: BlueprintViewMetricsDelegate? = nil + /// Defaults to `false`. If enabled, Blueprint will pass through any touches + /// not recieved by an element to the view hierarchy behind the `BlueprintView`. + public var passThroughTouches: Bool = false { + didSet { + if oldValue != passThroughTouches { + setNeedsViewHierarchyUpdate() + } + } + } + private var isVisible: Bool = false { didSet { switch (oldValue, isVisible) { @@ -141,7 +151,7 @@ public final class BlueprintView: UIView { rootController = NativeViewController( node: NativeViewNode( - content: UIView.describe { _ in }, + content: PassthroughView.describe { _ in }, // Because no layout update occurs here, passing an empty environment is fine; // the correct environment will be passed during update. environment: .empty, @@ -327,6 +337,17 @@ public final class BlueprintView: UIView { setNeedsViewHierarchyUpdate() } + /// Ignore any touches on this view and (pass through) by returning nil if the default `hitTest` implementation returns this view. + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let result = super.hitTest(point, with: event) + + if passThroughTouches { + return result == self ? nil : result + } else { + return result + } + } + /// Clears any sizing caches, invalidates the `intrinsicContentSize` of the /// view, and marks the view as needing a layout. private func setNeedsViewHierarchyUpdate() { @@ -391,7 +412,9 @@ public final class BlueprintView: UIView { rootController.view.frame = bounds var rootNode = NativeViewNode( - content: UIView.describe { _ in }, + content: PassthroughView.describe { [weak self] config in + config[\.passThroughTouches] = self?.passThroughTouches ?? false + }, environment: environment, layoutAttributes: LayoutAttributes(frame: rootFrame), children: viewNodes diff --git a/BlueprintUI/Sources/Internal/PassthroughView.swift b/BlueprintUI/Sources/Internal/PassthroughView.swift index a791da7e7..e1cc1162f 100644 --- a/BlueprintUI/Sources/Internal/PassthroughView.swift +++ b/BlueprintUI/Sources/Internal/PassthroughView.swift @@ -8,10 +8,16 @@ import UIKit CATransformLayer.self } - /// Ignore any touches on this view and (pass through) by returning nil if the - /// default `hitTest` implementation returns this view. + public var passThroughTouches: Bool = true + + /// Ignore any touches on this view and (pass through) by returning nil if the default `hitTest` implementation returns this view. public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let result = super.hitTest(point, with: event) - return result == self ? nil : result + + if passThroughTouches { + return result == self ? nil : result + } else { + return result + } } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 80261cc1a..7cfcc6131 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `BlueprintView` will now pass through touches to views lower in the view hierarchy if `passThroughTouches` is true. + ### Removed ### Changed