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 автоматично обчислює центроїд двох дотиків і передає його через 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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