← Назад к вопросам
Как увеличить область нажатия для элемента, не изменяя его размеры?
1.7 Middle🔥 181 комментариев
#SwiftUI#UIKit и верстка
Комментарии (1)
🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Расширение области отклика (hit-test area) элемента в iOS
Основная задача решается через переопределение метода point(inside:with:) в классе UIView или его подклассов. Этот метод отвечает за определение, попадает ли заданная точка в границы представления для целей обработки касаний (hit-testing).
Основной подход: переопределение point(inside:with:)
class ExtendedTapView: UIView {
/// Отступ для расширения области нажатия (может быть отрицательным)
var hitTestEdgeInsets: UIEdgeInsets = .zero
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
guard hitTestEdgeInsets != .zero else {
return super.point(inside: point, with: event)
}
let expandedRect = bounds.inset(by: hitTestEdgeInsets.inverted())
return expandedRect.contains(point)
}
}
extension UIEdgeInsets {
/// Инвертирует отступы (например, UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10))
func inverted() -> UIEdgeInsets {
return UIEdgeInsets(top: -top, left: -left, bottom: -bottom, right: -right)
}
}
Принцип работы:
- Мы создаем расширенный прямоугольник на основе текущих границ (
bounds) - Используем
inset(by:)с отрицательными значениями для увеличения области - Проверяем, попадает ли точка в этот расширенный прямоугольник
Альтернативные методы решения
1. Использование прозрачного UIView-контейнера
class ExtendedTapButton: UIButton {
private let extendedView = UIView()
override func layoutSubviews() {
super.layoutSubviews()
extendedView.frame = bounds.insetBy(dx: -20, dy: -20)
extendedView.backgroundColor = .clear
if extendedView.superview == nil {
addSubview(extendedView)
sendSubviewToBack(extendedView)
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if extendedView.frame.contains(point) {
return self
}
return super.hitTest(point, with: event)
}
}
2. Категория/Extension для UIButton (Objective-C совместимость)
extension UIButton {
private struct AssociatedKeys {
static var hitTestEdgeInsets = "hitTestEdgeInsets"
}
var hitTestEdgeInsets: UIEdgeInsets? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.hitTestEdgeInsets) as? UIEdgeInsets
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.hitTestEdgeInsets, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
guard let insets = hitTestEdgeInsets else {
return super.point(inside: point, with: event)
}
let expandedRect = bounds.insetBy(dx: -insets.left, dy: -insets.top)
return expandedRect.contains(point)
}
}
3. Использование UIGestureRecognizer как обертка
func addExtendedTapArea(to view: UIView, extraInset: CGFloat = 20, action: @escaping () -> Void) {
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleExtendedTap))
// Создаем контейнер для хранения данных
objc_setAssociatedObject(view, "extendedTapAction", action, .OBJC_ASSOCIATION_RETAIN)
// Расширяем область жеста
let extendedView = UIView(frame: view.bounds.insetBy(dx: -extraInset, dy: -extraInset))
extendedView.addGestureRecognizer(gestureRecognizer)
view.superview?.addSubview(extendedView)
}
Важные аспекты и рекомендации
Производительность:
- Метод
point(inside:with:)вызывается часто, поэтому избегайте сложных вычислений - Используйте простые проверки прямоугольников для оптимальной производительности
Доступность (Accessibility):
- Расширение области касания улучшает UX для пользователей
- Особенно полезно для маленьких элементов навигации
- Соответствует рекомендациям Apple по минимальному размеру касания (44x44 точки)
Интеграция с Auto Layout:
- При использовании Auto Layout изменения должны происходить после расчета layout
- Рекомендуется применять расширение в
layoutSubviews()или после него
class ExtendedTapLabel: UILabel {
var extraTapPadding: CGFloat = 20
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let expandedRect = bounds.insetBy(dx: -extraTapPadding, dy: -extraTapPadding)
return expandedRect.contains(point)
}
override func layoutSubviews() {
super.layoutSubviews()
// Обновляем frame если нужно
}
}
Практическое применение
На практике этот прием часто используется для:
- Кнопок навигации в UINavigationBar
- Маленьких иконок в таббарах
- Элементов форм с ограниченным пространством
- Интерактивных элементов в кастомных интерфейсах
Каждый из представленных методов имеет свои преимущества: первый наиболее прост и производителен, второй лучше интегрируется с существующим кодом, третий предлагает максимальную гибкость. Выбор зависит от конкретных требований проекта и архитектуры приложения.