Pinch-to-Zoom — istota, obsługa gestu szczypania w aplikacjach mobilnych

Autor: IT Sectr Opublikowano: 2026-02-27 Czas czytania: 8 min
Pinch-to-Zoom (szczypanie palcami) — wielodotykowy gest powiększania, w którym użytkownik zbliża lub rozsuwa dwa palce na ekranie, aby zmniejszyć lub zwiększyć zawartość. Ten gest stał się standardem de facto do przeglądania obrazów, map, stron internetowych i dokumentów na urządzeniach mobilnych. Według Apple HIG i Google Material Design, Pinch-to-Zoom — podstawowy gest, którego użytkownicy oczekują we wszystkich aplikacjach pracujących z treścią wizualną.

Najważniejsze

  • Pinch-to-Zoom — wielodotykowy gest dwoma palcami do skalowania zawartości na urządzeniach mobilnych.
  • W iOS obsługa odbywa się przez UIPinchGestureRecognizer z ciągłym śledzeniem .began → .changed → .ended.
  • W Android używany jest ScaleGestureDetector (z Support Library) z callbackiem onScale do aktualizacji skali przez Matrix.
  • UIPinchGestureRecognizer ma właściwość scale (współczynnik względem poprzedniego stanu) i velocity (prędkość).
  • Do ograniczenia zoomu stosuje się clamp współczynnika scale między zadanymi wartościami min/max (zwykle 0.5–3.0).

Co to jest Pinch-to-Zoom?

Pinch-to-Zoom (szczypanie lub ściskanie) — wielodotykowy gest używający dwóch palców do zmiany skali wyświetlanej zawartości. Przy zbliżaniu palców (pinch close) skala zmniejsza się, przy rozsuwaniu (pinch open) — zwiększa. Gest jest ciągły: system śledzi odległość między palcami w czasie rzeczywistym i generuje zdarzenia pośrednie, umożliwiając aplikacji płynną aktualizację skali.

Mechanika gestu opiera się na obliczaniu odległości między dwoma punktami dotyku. iOS i Android używają odległości euklidesowej: sqrt((x2-x1)² + (y2-y1)²). Bieżący scale jest obliczany jako stosunek bieżącej odległości do początkowej. Jeśli początkowa odległość wynosiła 100px, a stała się 150px — scale = 1.5 (zwiększenie o 50%). iOS przekazuje scale względem poprzedniego stanu (delta), Android — skumulowany scale przez ScaleGestureDetector.

Według Google Material Design Guidelines, Pinch-to-Zoom jest obowiązkowy dla aplikacji wyświetlających obrazy, mapy lub dokumenty. W IT Sectr implementujemy ten gest z UIPinchGestureRecognizer i ScaleGestureDetector we wszystkich projektach pracujących z treścią wizualną — od galerii po edytory obrazów.

Pinch-to-Zoom w iOS: UIPinchGestureRecognizer

UIPinchGestureRecognizer — ciągła podklasa UIGestureRecognizer do rozpoznawania gestu szczypania. W przeciwieństwie do dyskretnych rozpoznawaczy (UITapGestureRecognizer), generuje sekwencję zdarzeń: .began (palce dotknęły ekranu), .changed (każdy ruch palców), .ended (palce oderwane).

Kluczowe właściwości: scale (CGFloat) — względna zmiana odległości między palcami od początku gestu; velocity (CGFloat) — prędkość zmiany scale na sekundę. Początkowy scale = 1.0. Przy rozsuwaniu palców scale rośnie (1.0 → 1.5 → 2.0), przy zbliżaniu — maleje (1.0 → 0.8 → 0.5). Właściwość velocity jest używana do inercji: po .ended można animować kontynuację skalowania z wygaszaniem.

UIPinchGestureRecognizer obsługuje właściwość delegate do jednoczesnej pracy z innymi rozpoznawaczami (np. pan + pinch na mapie). Metoda gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) zwraca true, a oba gesty działają równolegle. Apple zaleca dodawanie UIPinchGestureRecognizer do widoku kontenera, a nie do poszczególnych elementów — upraszcza to obliczanie punktu pivot skalowania.

Pinch-to-Zoom w Android: ScaleGestureDetector

ScaleGestureDetector — klasa z Android Support Library (android.view), przeznaczona do rozpoznawania wielodotykowego skalowania. W przeciwieństwie do GestureDetector.onFling, ScaleGestureDetector jest specjalnie zaprojektowany do zoomu dwoma palcami i automatycznie oblicza focusX, focusY (środek między palcami) oraz scaleFactor (delta skali z poprzedniego zdarzenia).

ScaleGestureDetector działa przez interfejs OnScaleGestureListener z trzema metodami: onScaleBegin(detector) (odpowiednik .began), onScale(detector) (odpowiednik .changed, zwraca boolean — true, jeśli skala przyjęta), onScaleEnd(detector) (odpowiednik .ended). Właściwości: scaleFactor — mnożnik skali względem poprzedniego zdarzenia (~1.01 przy wolnym rozsuwaniu), focusX/focusY — współrzędne środka między palcami.

Do zastosowania skali do View zwykle używa się Matrix: view.imageMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY). Matrix obsługuje kumulację transformacji — każde wywołanie postScale dodaje nowe przekształcenie do istniejącego. Ważne: ScaleGestureDetector nie ogranicza minimalnej/maksymalnej skali — to zadanie programisty. W IT Sectr stosujemy ScaleGestureDetector z niestandardowymi granicami scale (0.5–3.0) i animacją powrotu przy przekroczeniu.

Ograniczenie zoomu i punkt pivot

Przy implementacji Pinch-to-Zoom krytycznie ważne jest ograniczenie minimalnej i maksymalnej skali — bez tego użytkownik może powiększyć obraz do nieskończoności lub zmniejszyć do niewidoczności. Standardowe wartości: min = 0.5 (50% oryginalnego rozmiaru), max = 3.0 (300%). W iOS ograniczenie wykonuje się przez clamp: newScale = min(max(currentScale * gesture.scale, minScale), maxScale). W Android — przez sprawdzenie po postScale.

Punkt pivot — środek między palcami, względem którego wykonywane jest skalowanie. iOS automatycznie oblicza centroid dwóch dotknięć i przekazuje go przez location(in:) — programista nie musi obliczać pivotu ręcznie. Android udostępnia focusX/focusY w ScaleGestureDetector, ale przy zastosowaniu przez Matrix pivot przekazywany jest jako trzeci i czwarty argument w postScale(scaleFactor, scaleFactor, focusX, focusY). Bez punktu pivot skalowanie będzie wykonywane względem lewego górnego rogu (0,0), co prowadzi do przesunięcia zawartości podczas zoomu.

Z doświadczenia IT Sectr, prawidłowe obliczenie punktu pivot — najczęstsze źródło błędów przy wdrażaniu Pinch-to-Zoom. W 90% przypadków wystarczy użyć wbudowanych współrzędnych GestureRecognizer i ScaleGestureDetector, nie obliczając pivotu ręcznie. Jeśli zawartość przesuwa się podczas skalowania — sprawdź, czy przekazujesz focusX/focusY do matrix.postScale().

Inercja po zoomie

Do płynnego UX po zakończeniu gestu można dodać inercję: w iOS — użyj velocity z UIPinchGestureRecognizer dla UIView.animate z parametrami spring; w Android — OverScroller lub ValueAnimator. Inercja nie powinna wychodzić poza granice min/max scale — końcowa wartość jest przycinana z animacją powrotu spring przy przekroczeniu.

Przykłady kodu w Swift i Kotlin

Przykład 1: Pinch-to-Zoom na iOS (Swift)

Dodaje UIPinchGestureRecognizer do UIImageView z ograniczeniem skali od 0.5 do 3.0. Pokazuje minimalną implementację z zachowaniem skumulowanej skali.

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

Logika: w .began/.changed stosowana jest skala przez CGAffineTransform z clamp. W .ended scale jest resetowany do 1.0, a skumulowana wartość zapisywana w currentScale. Zapobiega to „dryfowaniu" skali przy wielokrotnych gestach — każdy nowy gest zaczyna od bieżącego stanu. Dla obrazów zamiast CGAffineTransform zaleca się UIScrollView z minimumZoomScale/maximumZoomScale.

Przykład 2: Pinch-to-Zoom na Android (Kotlin)

Implementuje skalowanie ImageView przez ScaleGestureDetector z Matrix. Pokazuje poprawną obsługę punktu pivot i ograniczenie skali.

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

Niestandardowy ImageView nadpisuje onTouchEvent, przekazując zdarzenia do ScaleGestureDetector. OnScaleListener.onScale() jest wywoływany przy każdym ruchu palców. detector.scaleFactor — delta (1.01 przy wolnym rozsuwaniu). currentScale kumuluje się z coerceIn dla clamp. focusX/focusY — środek między palcami, przekazany do postScale dla poprawnego skalowania względem pivotu.

Przykład 3: UIScrollView z obsługą zoomu (iOS)

UIScrollView ma wbudowaną obsługę Pinch-to-Zoom przez delegata. Jest to preferowany sposób dla obrazów i dokumentów — ScrollView zarządza skalą, inercją i przewijaniem automatycznie.

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 samodzielnie tworzy UIPinchGestureRecognizer, zarządza skalą przez transformację CALayer i dodaje efekt bounce przy przekroczeniu granic. Kod sprowadza się do ustawienia min/maxZoomScale i zwrócenia viewForZooming. W IT Sectr używamy UIScrollView dla wszystkich pełnoekranowych obrazów — to 5-krotnie zmniejsza ilość kodu w porównaniu z ręczną implementacją przez UIPinchGestureRecognizer.

Często zadawane pytania

Czym różni się pinch od szczypania na gładziku?

Na ekranie dotykowym pinch używa dwóch palców ze śledzeniem odległości między nimi. Na gładziku MacBook powiększanie jest realizowane przez UIPinchGestureRecognizer z tymi samymi stanami (.began → .changed → .ended), ale źródłem dotknięć jest gładzik z Force Touch, a nie ekran pojemnościowy. API jest takie samo, mechanika identyczna.

Jak ograniczyć minimalny i maksymalny zoom?

W iOS: po otrzymaniu scale z UIPinchGestureRecognizer pomnóż przez currentScale i zastosuj clamp(min: 0.5, max: 3.0). W Android: przez coerceIn(minScale, maxScale) przed matrix.postScale(). W UIScrollView — przez właściwości minimumZoomScale i maximumZoomScale. Bez ograniczenia skala może uciec do nieskończoności lub do zera.

Czy Pinch-to-Zoom działa z jednym palcem?

Nie, pinch wymaga co najmniej dwóch palców. Do zoomu jednym palcem (double-tap + przeciągnięcie w górę/dół) wymagana jest niestandardowa implementacja przez UIPanGestureRecognizer lub GestureDetector.onScroll. To inny gest z inną logiką — zwykle używany do zoomu w kamerze lub mapach.

Podsumowanie

  • Pinch-to-Zoom — wielodotykowy gest dwoma palcami do skalowania zawartości, standard dla obrazów, map i dokumentów.
  • UIPinchGestureRecognizer w iOS — ciągły rozpoznawacz z scale (delta) i velocity (prędkość).
  • ScaleGestureDetector w Android — wyspecjalizowany detektor z onScale(), focusX/focusY dla punktu pivot.
  • Dla UIScrollView zoom wbudowuje się przez minimumZoomScale/maximumZoomScale — prościej niż ręczna implementacja.
  • Obowiązkowo ograniczaj skalę (0.5–3.0) przez clamp — bez tego zawartość może stać się niewidoczna.
  • Punkt pivot skalowania — środek między palcami; bez niego zawartość przesuwa się podczas powiększania.
  • W IT Sectr używamy UIScrollView dla obrazów i niestandardowego ScaleGestureDetector dla edytorów — wybór zależy od złożoności transformacji.

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również