Skip to content

Commit

Permalink
Add passThroughTouches to BlueprintView, PassthroughView (#511)
Browse files Browse the repository at this point in the history
Without this, Blueprint will eat any touches that are meant for views
behind it – in particular relevant if you're layering them in the
z-index and the one on "top" is largely visually transparent.
  • Loading branch information
kyleve authored Sep 18, 2024
1 parent 84865ae commit 3b88ea6
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 5 deletions.
27 changes: 25 additions & 2 deletions BlueprintUI/Sources/BlueprintView/BlueprintView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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,
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand Down
12 changes: 9 additions & 3 deletions BlueprintUI/Sources/Internal/PassthroughView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 3b88ea6

Please sign in to comment.