← Назад к вопросам

Как увеличить область нажатия для элемента, не изменяя его размеры?

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)
    }
}

Принцип работы:

  1. Мы создаем расширенный прямоугольник на основе текущих границ (bounds)
  2. Используем inset(by:) с отрицательными значениями для увеличения области
  3. Проверяем, попадает ли точка в этот расширенный прямоугольник

Альтернативные методы решения

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
  • Маленьких иконок в таббарах
  • Элементов форм с ограниченным пространством
  • Интерактивных элементов в кастомных интерфейсах

Каждый из представленных методов имеет свои преимущества: первый наиболее прост и производителен, второй лучше интегрируется с существующим кодом, третий предлагает максимальную гибкость. Выбор зависит от конкретных требований проекта и архитектуры приложения.

Как увеличить область нажатия для элемента, не изменяя его размеры? | PrepBro