Pinch-to-Zoom — суть, обработка щипка в мобильных приложениях

Автор: IT Sectr Опубликовано: 2026-02-27 Время чтения: 8 мин
Pinch-to-Zoom (щипок пальцами) — мультитач-жест масштабирования, при котором пользователь сводит или разводит два пальца на экране для уменьшения или увеличения содержимого. Этот жест стал стандартом де-факто для просмотра изображений, карт, веб-страниц и документов на мобильных устройствах. По данным Apple HIG и Google Material Design, Pinch-to-Zoom — базовый жест, который пользователи ожидают во всех приложениях, работающих с визуальным контентом.

Главное

  • Pinch-to-Zoom — мультитач-жест с двумя пальцами для масштабирования контента на мобильных устройствах.
  • В iOS обработка выполняется через UIPinchGestureRecognizer с непрерывным отслеживанием .began → .changed → .ended.
  • В Android используется ScaleGestureDetector (из Support Library) с колбэком onScale для обновления масштаба через Matrix.
  • UIPinchGestureRecognizer имеет свойство scale (коэффициент относительно предыдущего состояния) и velocity (скорость).
  • Для ограничения зума применяется clamp коэффициента scale между заданными min/max значениями (обычно 0.5–3.0).

Что такое Pinch-to-Zoom?

Pinch-to-Zoom (щипок или защемление) — мультитач-жест, использующий два пальца для изменения масштаба отображаемого контента. При сведении пальцев (pinch close) масштаб уменьшается, при разведении (pinch open) — увеличивается. Жест непрерывный: система отслеживает расстояние между пальцами в реальном времени и генерирует промежуточные события, позволяя приложению плавно обновлять масштаб.

Механика жеста основана на вычислении расстояния между двумя точками касания. iOS и Android используют евклидово расстояние: sqrt((x2-x1)² + (y2-y1)²). Текущий scale вычисляется как отношение текущего расстояния к начальному. Если начальное расстояние было 100px, а стало 150px — scale = 1.5 (увеличение на 50%). iOS передаёт scale относительно предыдущего состояния (дельту), Android — накопленный scale через ScaleGestureDetector.

По данным Google Material Design Guidelines, Pinch-to-Zoom обязателен для приложений, отображающих изображения, карты или документы. В IT Sectr мы реализуем этот жест с UIPinchGestureRecognizer и ScaleGestureDetector во всех проектах, работающих с визуальным контентом, — от галерей до редакторов изображений.

Pinch-to-Zoom в iOS: UIPinchGestureRecognizer

UIPinchGestureRecognizer — непрерывный подкласс UIGestureRecognizer для распознавания жеста щипка. В отличие от дискретных распознавателей (UITapGestureRecognizer), он генерирует последовательность событий: .began (пальцы коснулись экрана), .changed (каждое движение пальцев), .ended (пальцы оторваны).

Ключевые свойства: scale (CGFloat) — относительное изменение расстояния между пальцами с начала жеста; velocity (CGFloat) — скорость изменения scale в секунду. Начальное scale = 1.0. При разведении пальцев scale растёт (1.0 → 1.5 → 2.0), при сведении — уменьшается (1.0 → 0.8 → 0.5). Свойство velocity используется для инерции: после .ended можно анимировать продолжение масштабирования с затуханием.

UIPinchGestureRecognizer поддерживает свойство delegate для одновременной работы с другими распознавателями (например, pan + pinch на карте). Метод gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) возвращает true, и оба жеста работают параллельно. Apple рекомендует добавлять UIPinchGestureRecognizer на контейнерную вью, а не на отдельные элементы — это упрощает расчёт pivot-точки масштабирования.

Pinch-to-Zoom в Android: ScaleGestureDetector

ScaleGestureDetector — класс из Android Support Library (android.view), предназначенный для распознавания мультитач-масштабирования. В отличие от GestureDetector.onFling, ScaleGestureDetector специально спроектирован для двухпальцевого зума и автоматически вычисляет focusX, focusY (центр между пальцами) и scaleFactor (дельта масштаба с предыдущего события).

ScaleGestureDetector работает через интерфейс OnScaleGestureListener с тремя методами: onScaleBegin(detector) (аналог .began), onScale(detector) (аналог .changed, возвращает boolean — true, если масштаб принят), onScaleEnd(detector) (аналог .ended). Свойства: scaleFactor — множитель масштаба относительно предыдущего события (~1.01 при медленном разведении), focusX/focusY — координаты центра между пальцами.

Для применения масштаба к View обычно используется Matrix: view.imageMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY). Matrix поддерживает накопление трансформаций — каждый вызов postScale добавляет новое преобразование к существующему. Важно помнить: ScaleGestureDetector не ограничивает минимальный/максимальный масштаб — это задача разработчика. В IT Sectr мы применяем ScaleGestureDetector с кастомными границами scale (0.5–3.0) и анимацией возврата при превышении.

Ограничение зума и pivot-точка

При реализации Pinch-to-Zoom критически важно ограничить минимальный и максимальный масштаб — без этого пользователь может увеличить изображение до бесконечности или уменьшить до невидимости. Стандартные значения: min = 0.5 (50% исходного размера), max = 3.0 (300%). На iOS ограничение выполняется через clamp: newScale = min(max(currentScale * gesture.scale, minScale), maxScale). На Android — через проверку после postScale.

Pivot-точка — центр между пальцами, относительно которого выполняется масштабирование. iOS автоматически вычисляет centroid двух касаний и передаёт его через location(in:) — разработчику не нужно рассчитывать pivot вручную. Android предоставляет focusX/focusY в ScaleGestureDetector, но при применении через Matrix pivot передаётся третьим и четвёртым аргументом в postScale(scaleFactor, scaleFactor, focusX, focusY). Без pivot-точки масштабирование будет выполняться относительно левого верхнего угла (0,0), что приводит к смещению контента при зуме.

По опыту IT Sectr, правильный расчёт pivot-точки — самый частый источник багов при внедрении Pinch-to-Zoom. В 90% случаев достаточно использовать встроенные координаты GestureRecognizer и ScaleGestureDetector, не вычисляя pivot вручную. Если контент смещается при масштабировании — проверьте, передаёте ли вы focusX/focusY в matrix.postScale().

Инерция после зума

Для плавного UX после завершения жеста можно добавить инерцию: на iOS — используйте velocity из UIPinchGestureRecognizer для UIView.animate с spring-параметрами; на Android — OverScroller или ValueAnimator. Инерция не должна выходить за границы min/max scale — финальное значение обрезается с анимацией spring-возврата при превышении.

Примеры кода на Swift и Kotlin

Пример 1: Pinch-to-Zoom на iOS (Swift)

Добавляет UIPinchGestureRecognizer на UIImageView с ограничением масштаба от 0.5 до 3.0. Показывает минимальную реализацию с сохранением накопленного масштаба.

swift
import UIKit

class ZoomableImageViewController: UIViewController {

    @IBOutlet private var imageView: UIImageView!
    private var currentScale: CGFloat = 1.0
    private let minScale: CGFloat = 0.5
    private let maxScale: CGFloat = 3.0

    override func viewDidLoad() {
        super.viewDidLoad()

        let pinch = UIPinchGestureRecognizer(
            target: self,
            action: #selector(handlePinch(_:))
        )
        imageView.addGestureRecognizer(pinch)
        imageView.isUserInteractionEnabled = true
    }

    @objc private func handlePinch(_: UIPinchGestureRecognizer) {
        if gestureRecognizer.state == .began
            || gestureRecognizer.state == .changed {

            let newScale = currentScale * gestureRecognizer.scale
            imageView.transform = CGAffineTransform(
                scaleX: min(max(newScale, minScale), maxScale),
                y: min(max(newScale, minScale), maxScale)
            )
        }
        if gestureRecognizer.state == .ended {
            currentScale = min(max(
                currentScale * gestureRecognizer.scale,
                minScale
            ), maxScale)
            gestureRecognizer.scale = 1.0
        }
    }
}

Логика: в .began/.changed применяется масштаб через CGAffineTransform с clamp. В .ended scale сбрасывается на 1.0, а накопленное значение сохраняется в currentScale. Это предотвращает «дрейф» масштаба при многократных жестах — каждый новый жест начинает с текущего состояния. Для изображений вместо CGAffineTransform рекомендуется UIScrollView с minimumZoomScale/maximumZoomScale.

Пример 2: Pinch-to-Zoom на Android (Kotlin)

Реализует масштабирование ImageView через ScaleGestureDetector с Matrix. Показывает корректную обработку pivot-точки и ограничение масштаба.

kotlin
import android.content.Context
import android.graphics.Matrix
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.widget.ImageView

class PinchZoomImageView(context: Context) : ImageView(context) {

    private val matrix = Matrix()
    private var currentScale = 1f
    private val minScale = 0.5f
    private val maxScale = 3.0f

    private val scaleDetector =
        ScaleGestureDetector(context, ScaleListener())

    private inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            currentScale = (currentScale * detector.scaleFactor)
                .coerceIn(minScale, maxScale)
            matrix.postScale(
                currentScale,
                currentScale,
                detector.focusX,
                detector.focusY
            )
            imageMatrix = matrix
            return true
        }
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        scaleDetector.onTouchEvent(event)
        return true
    }
}

Кастомный ImageView переопределяет onTouchEvent, передавая события ScaleGestureDetector. OnScaleListener.onScale() вызывается при каждом движении пальцев. detector.scaleFactor — дельта (1.01 при медленном разведении). currentScale накапливается с coerceIn для clamp. focusX/focusY — центр между пальцами, переданный в postScale для корректного pivot-масштабирования.

Пример 3: UIScrollView с поддержкой зума (iOS)

UIScrollView имеет встроенную поддержку Pinch-to-Zoom через делегат. Это предпочтительный способ для изображений и документов — ScrollView управляет масштабом, инерцией и прокруткой автоматически.

swift
class ScrollableImageViewController: UIViewController,
                                    UIScrollViewDelegate {

    @IBOutlet private var scrollView: UIScrollView!
    @IBOutlet private var imageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()
        scrollView.minimumZoomScale = 0.5
        scrollView.maximumZoomScale = 3.0
        scrollView.delegate = self
    }

    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return imageView
    }
}

UIScrollView самостоятельно создаёт UIPinchGestureRecognizer, управляет масштабом через CALayer трансформацию и добавляет bounce-эффект при превышении границ. Код сводится к установке min/maxZoomScale и возврату viewForZooming. В IT Sectr мы используем UIScrollView для всех полноэкранных изображений — это в 5 раз сокращает объём кода по сравнению с ручной реализацией через UIPinchGestureRecognizer.

Часто задаваемые вопросы

Чем pinch отличается от щипка на трекпаде?

На сенсорном экране pinch использует два пальца с отслеживанием расстояния между ними. На трекпаде MacBook масштабирование реализовано через UIPinchGestureRecognizer с теми же состояниями (.began → .changed → .ended), но источник касаний — трекпад с Force Touch, а не ёмкостный экран. API одинаков, механика идентична.

Как ограничить минимальный и максимальный зум?

В iOS: после получения scale от UIPinchGestureRecognizer умножьте на currentScale и примените clamp(min: 0.5, max: 3.0). В Android: через coerceIn(minScale, maxScale) перед matrix.postScale(). В UIScrollView — через свойства minimumZoomScale и maximumZoomScale. Без ограничения масштаб может уйти в бесконечность или в ноль.

Pinch-to-Zoom работает с одним пальцем?

Нет, pinch требует минимум двух пальцев. Для однопальцевого зума (double-tap + drag вверх/вниз) требуется кастомная реализация через UIPanGestureRecognizer или GestureDetector.onScroll. Это другой жест с другой логикой — обычно используется для зума в камере или картах.

Итоги

  • Pinch-to-Zoom — мультитач-жест двумя пальцами для масштабирования контента, стандарт для изображений, карт и документов.
  • UIPinchGestureRecognizer в iOS — непрерывный распознаватель с scale (дельта) и velocity (скорость).
  • ScaleGestureDetector в Android — специализированный детектор с onScale(), focusX/focusY для pivot-точки.
  • Для UIScrollView зум встраивается через minimumZoomScale/maximumZoomScale — проще, чем ручная реализация.
  • Обязательно ограничивайте масштаб (0.5–3.0) через clamp — без этого контент может стать невидимым.
  • Pivot-точка масштабирования — центр между пальцами; без неё контент смещается при увеличении.
  • В IT Sectr мы используем UIScrollView для изображений и кастомный ScaleGestureDetector для редакторов — выбор зависит от сложности трансформаций.

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также