Created
June 18, 2025 15:41
-
-
Save drewolbrich/568c59f591374753556393c1097db0c4 to your computer and use it in GitHub Desktop.
Replacement for UIView.hitTest(_:with:) that detects hits on subviews outside the bounds of the parent view
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UIView+HitTest.swift | |
// | |
// Created by Drew Olbrich on 2/5/20. | |
// Copyright © 2022 Lunar Skydiving LLC. All rights reserved. | |
// | |
// MIT License | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in all | |
// copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
// | |
import UIKit | |
public extension UIView { | |
/// Returns the frontmost visible subview of the receiver that contains `point`, or | |
/// the receiver itself if it contains `point`. | |
/// | |
/// This method differs from the default implementation of `hitTest(_:with:)` in | |
/// that points that lie outside the receiver’s bounds are reported as hits if they | |
/// actually lie within one of the receiver’s subviews. | |
/// | |
/// See https://developer.apple.com/library/archive/qa/qa2013/qa1812.html | |
/// | |
/// Example usage: | |
/// | |
/// override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { | |
/// return hitTestIncludingSubviewsWithoutBoundsCheck(point, with: event) | |
/// } | |
/// | |
/// - Parameters: | |
/// - point: A point specified in the receiver’s local coordinate system (bounds). | |
/// - event: The event that warranted a call to this method. If you are calling this | |
/// method from outside your event-handling code, you may specify nil. | |
/// - Returns: The frontmost visible subview of the receiver, or the receiver, | |
/// that contains `point`, even if that subview lies outside the receiver's bounds. | |
/// Returns `nil` if `point` does not lie within any of the receiver's subviews. | |
func hitTestIncludingSubviewsWithoutBoundsCheck(_ point: CGPoint, with event: UIEvent?) -> UIView? { | |
// It might be tempting to refactor this function to apply its test recursively, | |
// but this would this interact badly with system controls that contain internal | |
// views that have different expectations about the hit test. For example, | |
// `UISlider` manages an internal view for its thumb whose `hitTest` method always | |
// returns `nil`, and if it returns otherwise, the slider will no longer work. | |
// | |
// Instead, this method should be manually applied to each problematic view in a | |
// hierarchy. | |
guard !isHidden && isUserInteractionEnabled && alpha >= 0.01 else { | |
return nil | |
} | |
// Test the frontmost subview first. | |
for subview in subviews.reversed() { | |
let pointInSubview = subview.convert(point, from: self) | |
if let hitSubview = subview.hitTest(pointInSubview, with: event) { | |
return hitSubview | |
} | |
} | |
if self.point(inside: point, with: event) { | |
return self | |
} | |
return nil | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment