Pinch-to-Zoom — esența, procesarea gestului de ciupire în aplicațiile mobile

Autor: IT Sectr Publicat: 2026-02-27 Timp de citire: 8 min
Pinch-to-Zoom (ciupirea cu degetele) — un gest multi-touch de scalare prin care utilizatorul apropie sau depărtează două degete pe ecran pentru a micșora sau mări conținutul. Acest gest a devenit standardul de facto pentru vizualizarea imaginilor, hărților, paginilor web și documentelor pe dispozitivele mobile. Conform Apple HIG și Google Material Design, Pinch-to-Zoom este un gest de bază pe care utilizatorii îl așteaptă în toate aplicațiile care lucrează cu conținut vizual.

Principalele puncte

  • Pinch-to-Zoom — un gest multi-touch cu două degete pentru scalarea conținutului pe dispozitive mobile.
  • În iOS procesarea se face prin UIPinchGestureRecognizer cu urmărire continuă .began → .changed → .ended.
  • În Android se folosește ScaleGestureDetector (din Support Library) cu callback onScale pentru actualizarea scării prin Matrix.
  • UIPinchGestureRecognizer are proprietatea scale (coeficient față de starea anterioară) și velocity (viteză).
  • Pentru limitarea zoomului se aplică clamp al coeficientului scale între valorile min/max specificate (de obicei 0.5–3.0).

Ce este Pinch-to-Zoom?

Pinch-to-Zoom (ciupire sau strângere) — un gest multi-touch care folosește două degete pentru a modifica scara conținutului afișat. La apropierea degetelor (pinch close) scara scade, la depărtare (pinch open) — crește. Gestul este continuu: sistemul urmărește distanța dintre degete în timp real și generează evenimente intermediare, permițând aplicației să actualizeze lin scara.

Mecanica gestului se bazează pe calcularea distanței dintre două puncte de atingere. iOS și Android folosesc distanța euclidiană: sqrt((x2-x1)² + (y2-y1)²). Scara curentă se calculează ca raportul dintre distanța curentă și distanța inițială. Dacă distanța inițială era 100px și a devenit 150px — scale = 1.5 (creștere cu 50%). iOS transmite scale relativ la starea anterioară (delta), Android — scale acumulat prin ScaleGestureDetector.

Conform Google Material Design Guidelines, Pinch-to-Zoom este obligatoriu pentru aplicațiile care afișează imagini, hărți sau documente. În IT Sectr implementăm acest gest cu UIPinchGestureRecognizer și ScaleGestureDetector în toate proiectele care lucrează cu conținut vizual — de la galerii la editori de imagini.

Pinch-to-Zoom în iOS: UIPinchGestureRecognizer

UIPinchGestureRecognizer — o subclasă continuă a UIGestureRecognizer pentru recunoașterea gestului de ciupire. Spre deosebire de recunoscătoarele discrete (UITapGestureRecognizer), generează o secvență de evenimente: .began (degetele au atins ecranul), .changed (fiecare mișcare a degetelor), .ended (degetele s-au ridicat).

Proprietăți cheie: scale (CGFloat) — modificarea relativă a distanței dintre degete de la începutul gestului; velocity (CGFloat) — viteza de schimbare a scalei pe secundă. Scale inițială = 1.0. La depărtarea degetelor scale crește (1.0 → 1.5 → 2.0), la apropiere — scade (1.0 → 0.8 → 0.5). Proprietatea velocity este folosită pentru inerție: după .ended se poate anima continuarea scalării cu amortizare.

UIPinchGestureRecognizer suportă proprietatea delegate pentru lucrul simultan cu alte recunoscătoare (de exemplu, pan + pinch pe hartă). Metoda gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) returnează true, iar ambele gesturi funcționează în paralel. Apple recomandă adăugarea UIPinchGestureRecognizer pe un view container, nu pe elemente individuale — simplifică calculul punctului pivot de scalare.

Pinch-to-Zoom în Android: ScaleGestureDetector

ScaleGestureDetector — o clasă din Android Support Library (android.view), destinată recunoașterii scalării multi-touch. Spre deosebire de GestureDetector.onFling, ScaleGestureDetector este special conceput pentru zoom cu două degete și calculează automat focusX, focusY (centrul dintre degete) și scaleFactor (delta scării față de evenimentul anterior).

ScaleGestureDetector funcționează prin interfața OnScaleGestureListener cu trei metode: onScaleBegin(detector) (echivalentul .began), onScale(detector) (echivalentul .changed, returnează boolean — true dacă scara este acceptată), onScaleEnd(detector) (echivalentul .ended). Proprietăți: scaleFactor — multiplicatorul scării față de evenimentul anterior (~1.01 la depărtare lentă), focusX/focusY — coordonatele centrului dintre degete.

Pentru aplicarea scării la View se folosește de obicei Matrix: view.imageMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY). Matrix suportă acumularea transformărilor — fiecare apel postScale adaugă o nouă transformare la cea existentă. Important: ScaleGestureDetector nu limitează scara minimă/maximă — aceasta este sarcina dezvoltatorului. În IT Sectr aplicăm ScaleGestureDetector cu limite personalizate de scale (0.5–3.0) și animație de revenire la depășire.

Limitarea zoomului și punctul pivot

La implementarea Pinch-to-Zoom este critic să limitați scara minimă și maximă — fără aceasta, utilizatorul poate mări imaginea la infinit sau o poate micșora până la invizibilitate. Valorile standard: min = 0.5 (50% din dimensiunea originală), max = 3.0 (300%). În iOS limitarea se face prin clamp: newScale = min(max(currentScale * gesture.scale, minScale), maxScale). În Android — prin verificare după postScale.

Punctul pivot — centrul dintre degete în raport cu care se efectuează scalarea. iOS calculează automat centroidul celor două atingeri și îl transmite prin location(in:) — dezvoltatorul nu trebuie să calculeze pivotul manual. Android furnizează focusX/focusY în ScaleGestureDetector, dar la aplicarea prin Matrix pivotul este transmis ca al treilea și al patrulea argument în postScale(scaleFactor, scaleFactor, focusX, focusY). Fără punctul pivot, scalarea se va efectua în raport cu colțul stânga sus (0,0), ceea ce duce la deplasarea conținutului la zoom.

Din experiența IT Sectr, calculul corect al punctului pivot — cea mai frecventă sursă de bug-uri la implementarea Pinch-to-Zoom. În 90% din cazuri este suficient să folosiți coordonatele încorporate ale GestureRecognizer și ScaleGestureDetector, fără a calcula pivotul manual. Dacă conținutul se deplasează la scalare — verificați dacă transmiteți focusX/focusY la matrix.postScale().

Inerția după zoom

Pentru un UX lin după terminarea gestului se poate adăuga inerție: în iOS — folosiți velocity din UIPinchGestureRecognizer pentru UIView.animate cu parametri spring; în Android — OverScroller sau ValueAnimator. Inerția nu trebuie să depășească limitele min/max scale — valoarea finală este tăiată cu animație de revenire spring la depășire.

Exemple de cod în Swift și Kotlin

Exemplul 1: Pinch-to-Zoom pe iOS (Swift)

Adaugă UIPinchGestureRecognizer pe UIImageView cu limitarea scării de la 0.5 la 3.0. Arată o implementare minimă cu păstrarea scării acumulate.

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

Logica: în .began/.changed se aplică scara prin CGAffineTransform cu clamp. În .ended scale se resetează la 1.0, iar valoarea acumulată se păstrează în currentScale. Aceasta previne „deriva" scării la gesturi multiple — fiecare gest nou începe din starea curentă. Pentru imagini, în loc de CGAffineTransform se recomandă UIScrollView cu minimumZoomScale/maximumZoomScale.

Exemplul 2: Pinch-to-Zoom pe Android (Kotlin)

Implementează scalarea ImageView prin ScaleGestureDetector cu Matrix. Arată procesarea corectă a punctului pivot și limitarea scării.

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 personalizat suprascrie onTouchEvent, transmițând evenimentele către ScaleGestureDetector. OnScaleListener.onScale() este apelat la fiecare mișcare a degetelor. detector.scaleFactor — delta (1.01 la depărtare lentă). currentScale se acumulează cu coerceIn pentru clamp. focusX/focusY — centrul dintre degete, transmis la postScale pentru scalarea corectă în raport cu pivotul.

Exemplul 3: UIScrollView cu suport zoom (iOS)

UIScrollView are suport încorporat pentru Pinch-to-Zoom prin delegat. Aceasta este metoda preferată pentru imagini și documente — ScrollView gestionează scara, inerția și derularea automat.

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 creează independent UIPinchGestureRecognizer, gestionează scara prin transformarea CALayer și adaugă efect bounce la depășirea limitelor. Codul se reduce la setarea min/maxZoomScale și returnarea viewForZooming. În IT Sectr folosim UIScrollView pentru toate imaginile pe ecran complet — aceasta reduce de 5 ori volumul de cod în comparație cu implementarea manuală prin UIPinchGestureRecognizer.

Întrebări frecvente

Cu ce diferă pinch de ciupirea pe trackpad?

Pe ecranul tactil pinch folosește două degete cu urmărirea distanței dintre ele. Pe trackpad-ul MacBook scalarea este implementată prin UIPinchGestureRecognizer cu aceleași stări (.began → .changed → .ended), dar sursa atingerilor este trackpad-ul cu Force Touch, nu ecranul capacitiv. API-ul este același, mecanica identică.

Cum se limitează zoomul minim și maxim?

În iOS: după primirea scale de la UIPinchGestureRecognizer înmulțiți cu currentScale și aplicați clamp(min: 0.5, max: 3.0). În Android: prin coerceIn(minScale, maxScale) înainte de matrix.postScale(). În UIScrollView — prin proprietățile minimumZoomScale și maximumZoomScale. Fără limitare, scara poate ajunge la infinit sau la zero.

Pinch-to-Zoom funcționează cu un singur deget?

Nu, pinch necesită minim două degete. Pentru zoom cu un singur deget (double-tap + tragere în sus/jos) este necesară o implementare personalizată prin UIPanGestureRecognizer sau GestureDetector.onScroll. Acesta este un alt gest cu o altă logică — folosit de obicei pentru zoom în cameră sau hărți.

Rezumat

  • Pinch-to-Zoom — un gest multi-touch cu două degete pentru scalarea conținutului, standard pentru imagini, hărți și documente.
  • UIPinchGestureRecognizer în iOS — recunoscător continuu cu scale (delta) și velocity (viteză).
  • ScaleGestureDetector în Android — detector specializat cu onScale(), focusX/focusY pentru punctul pivot.
  • Pentru UIScrollView zoomul se încorporează prin minimumZoomScale/maximumZoomScale — mai simplu decât implementarea manuală.
  • Limitați obligatoriu scara (0.5–3.0) prin clamp — fără aceasta conținutul poate deveni invizibil.
  • Punctul pivot de scalare — centrul dintre degete; fără el conținutul se deplasează la mărire.
  • În IT Sectr folosim UIScrollView pentru imagini și ScaleGestureDetector personalizat pentru editori — alegerea depinde de complexitatea transformărilor.

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și