Pinch-to-Zoom — comprendere il gesto di pizzicamento nelle applicazioni mobili

Autore: IT Sectr Pubblicato: 2026-02-27 Tempo di lettura: 8 min
Pinch-to-Zoom (pizzicamento delle dita) è un gesto multitouch di zoom in cui l'utente avvicina o allontana due dita sullo schermo per ridurre o ingrandire il contenuto. Questo gesto è diventato lo standard de facto per visualizzare immagini, mappe, pagine web e documenti sui dispositivi mobili. Secondo Apple HIG e Google Material Design, Pinch-to-Zoom è un gesto di base che gli utenti si aspettano in tutte le applicazioni che lavorano con contenuti visivi.

Punti chiave

  • Pinch-to-Zoom è un gesto multitouch a due dita per scalare i contenuti sui dispositivi mobili.
  • In iOS, la gestione avviene tramite UIPinchGestureRecognizer con tracciamento continuo di .began → .changed → .ended.
  • Android utilizza ScaleGestureDetector (dalla Support Library) con il callback onScale per aggiornare lo zoom tramite Matrix.
  • UIPinchGestureRecognizer ha le proprietà scale (rapporto rispetto allo stato precedente) e velocity (velocità).
  • Per limitare lo zoom, viene applicato un clamp del fattore di scala tra i valori min/max impostati (di solito 0.5–3.0).

Cos'è Pinch-to-Zoom?

Pinch-to-Zoom (pizzicamento) è un gesto multitouch che utilizza due dita per modificare la scala del contenuto visualizzato. Avvicinando le dita (pinch close), la scala diminuisce; allontanandole (pinch open), aumenta. Il gesto è continuo: il sistema traccia la distanza tra le dita in tempo reale e genera eventi intermedi, consentendo all'applicazione di aggiornare la scala in modo fluido.

La meccanica del gesto si basa sul calcolo della distanza tra due punti di tocco. iOS e Android utilizzano la distanza euclidea: sqrt((x2−x1)² + (y2−y1)²). La scala attuale viene calcolata come rapporto tra la distanza attuale e la distanza iniziale. Se la distanza iniziale era 100px e diventa 150px — scala = 1.5 (aumento del 50%). iOS passa la scala relativa allo stato precedente (delta), Android passa la scala accumulata tramite ScaleGestureDetector.

Secondo le Google Material Design Guidelines, Pinch-to-Zoom è obbligatorio per le applicazioni che mostrano immagini, mappe o documenti. In IT Sectr, implementiamo questo gesto con UIPinchGestureRecognizer e ScaleGestureDetector in tutti i progetti che lavorano con contenuti visivi — dalle gallerie agli editor di immagini.

Pinch-to-Zoom in iOS: UIPinchGestureRecognizer

UIPinchGestureRecognizer è una sottoclasse continua di UIGestureRecognizer per riconoscere il gesto di pizzicamento. A differenza dei riconoscitori discreti (UITapGestureRecognizer), genera una sequenza di eventi: .began (le dita toccano lo schermo), .changed (ogni movimento delle dita), .ended (le dita vengono sollevate).

Proprietà chiave: scale (CGFloat) — cambiamento relativo della distanza tra le dita dall'inizio del gesto; velocity (CGFloat) — tasso di cambiamento della scala al secondo. Scala iniziale = 1.0. Allontanando le dita, la scala aumenta (1.0 → 1.5 → 2.0); avvicinandole, diminuisce (1.0 → 0.8 → 0.5). La proprietà velocity viene utilizzata per l'inerzia: dopo .ended, puoi animare la continuazione della scala con decelerazione.

UIPinchGestureRecognizer supporta la proprietà delegate per il riconoscimento simultaneo con altri riconoscitori (ad esempio, pan + pinch su una mappa). Il metodo gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) restituisce true ed entrambi i gesti funzionano in parallelo. Apple raccomanda di aggiungere UIPinchGestureRecognizer alla vista contenitore piuttosto che a singoli elementi — questo semplifica il calcolo del punto pivot di scala.

Pinch-to-Zoom in Android: ScaleGestureDetector

ScaleGestureDetector è una classe della Android Support Library (android.view) progettata per riconoscere la scala multitouch. A differenza di GestureDetector.onFling, ScaleGestureDetector è specificamente costruito per lo zoom a due dita e calcola automaticamente focusX, focusY (il punto centrale tra le dita) e scaleFactor (delta di scala dall'evento precedente).

ScaleGestureDetector funziona tramite l'interfaccia OnScaleGestureListener con tre metodi: onScaleBegin(detector) (analogo a .began), onScale(detector) (analogo a .changed, restituisce boolean — true se la scala viene accettata), onScaleEnd(detector) (analogo a .ended). Proprietà: scaleFactor — moltiplicatore di scala relativo all'evento precedente (~1.01 per allontanamento lento), focusX/focusY — coordinate del punto centrale tra le dita.

Per applicare la scala a una View, di solito si utilizza Matrix: view.imageMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY). Matrix supporta l'accumulo di trasformazioni — ogni chiamata postScale aggiunge una nuova trasformazione a quella esistente. È importante ricordare: ScaleGestureDetector non limita la scala minima/massima — questa è responsabilità dello sviluppatore. In IT Sectr, utilizziamo ScaleGestureDetector con limiti di scala personalizzati (0.5–3.0) e un'animazione di ritorno in caso di superamento.

Limitazione dello zoom e punto pivot

Nell'implementare Pinch-to-Zoom, è di fondamentale importanza limitare la scala minima e massima — senza questo, l'utente potrebbe ingrandire all'infinito o ridurre fino all'invisibilità. Valori standard: min = 0.5 (50% della dimensione originale), max = 3.0 (300%). In iOS, la limitazione avviene tramite clamp: newScale = min(max(currentScale * gesture.scale, minScale), maxScale). In Android — tramite un controllo dopo postScale.

Il punto pivot è il centro tra le dita, rispetto al quale viene eseguita la scala. iOS calcola automaticamente il centroide dei due tocchi e lo passa tramite location(in:) — lo sviluppatore non deve calcolare il pivot manualmente. Android fornisce focusX/focusY in ScaleGestureDetector, ma quando si applica tramite Matrix, il pivot viene passato come terzo e quarto argomento in postScale(scaleFactor, scaleFactor, focusX, focusY). Senza un punto pivot, la scala verrà eseguita rispetto all'angolo superiore sinistro (0,0), causando lo spostamento del contenuto durante lo zoom.

Secondo l'esperienza di IT Sectr, il calcolo corretto del punto pivot è la fonte più comune di bug nell'implementazione di Pinch-to-Zoom. Nel 90% dei casi, utilizzare le coordinate integrate di GestureRecognizer e ScaleGestureDetector è sufficiente — non è necessario calcolare il pivot manualmente. Se il contenuto si sposta durante la scala, verifica se stai passando focusX/focusY a matrix.postScale().

Inerzia dopo lo zoom

Per un'esperienza utente fluida dopo la fine del gesto, puoi aggiungere inerzia: in iOS — utilizza velocity da UIPinchGestureRecognizer con UIView.animate e parametri spring; in Android — OverScroller o ValueAnimator. L'inerzia non deve superare i limiti di scala min/max — il valore finale viene limitato con un'animazione di ritorno spring in caso di superamento.

Esempi di codice in Swift e Kotlin

Esempio 1: Pinch-to-Zoom in iOS (Swift)

Aggiunge UIPinchGestureRecognizer a UIImageView con limitazione della scala da 0.5 a 3.0. Mostra un'implementazione minima con conservazione della scala accumulata.

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: in .began/.changed, la scala viene applicata tramite CGAffineTransform con clamp. In .ended, la scala viene reimpostata a 1.0 e il valore accumulato viene memorizzato in currentScale. Ciò impedisce la deriva della scala durante gesti ripetuti — ogni nuovo gesto parte dallo stato corrente. Per le immagini, si raccomanda UIScrollView con minimumZoomScale/maximumZoomScale invece di CGAffineTransform.

Esempio 2: Pinch-to-Zoom in Android (Kotlin)

Implementa la scala di ImageView tramite ScaleGestureDetector con Matrix. Mostra la corretta gestione del punto pivot e la limitazione della scala.

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

La ImageView personalizzata sovrascrive onTouchEvent, passando gli eventi a ScaleGestureDetector. OnScaleListener.onScale() viene chiamato ad ogni movimento delle dita. detector.scaleFactor è il delta (1.01 per allontanamento lento). currentScale si accumula con coerceIn per il clamp. focusX/focusY — il punto centrale tra le dita, passato a postScale per una corretta scala del pivot.

Esempio 3: UIScrollView con supporto zoom (iOS)

UIScrollView ha il supporto integrato di Pinch-to-Zoom tramite il suo delegato. Questo è l'approccio preferito per immagini e documenti — ScrollView gestisce automaticamente scala, inerzia e scorrimento.

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 crea automaticamente un UIPinchGestureRecognizer, gestisce la scala tramite la trasformazione CALayer e aggiunge un effetto di rimbalzo quando si superano i limiti. Il codice si riduce all'impostazione di min/maxZoomScale e alla restituzione di viewForZooming. In IT Sectr, utilizziamo UIScrollView per tutte le immagini a schermo intero — questo riduce il volume di codice di 5 volte rispetto all'implementazione manuale tramite UIPinchGestureRecognizer.

Domande frequenti

In cosa il pinch differisce dal pizzicamento su un trackpad?

Su uno schermo tattile, pinch utilizza due dita con tracciamento della distanza tra di esse. Su un trackpad MacBook, lo zoom è implementato tramite UIPinchGestureRecognizer con gli stessi stati (.began → .changed → .ended), ma la fonte del tocco è un trackpad con Force Touch, non uno schermo capacitivo. L'API è la stessa, la meccanica è identica.

Come limitare lo zoom minimo e massimo?

In iOS: dopo aver ottenuto la scala da UIPinchGestureRecognizer, moltiplica per currentScale e applica clamp(min: 0.5, max: 3.0). In Android: usa coerceIn(minScale, maxScale) prima di matrix.postScale(). In UIScrollView — tramite le proprietà minimumZoomScale e maximumZoomScale. Senza limitazione, la scala può andare all'infinito o a zero.

Pinch-to-Zoom funziona con un dito?

No, il pinch richiede almeno due dita. Per lo zoom a un dito (doppio tocco + trascinamento verso l'alto/basso), è necessaria un'implementazione personalizzata tramite UIPanGestureRecognizer o GestureDetector.onScroll. Questo è un gesto diverso con logica diversa — di solito utilizzato per lo zoom nella fotocamera o nelle mappe.

Riepilogo

  • Pinch-to-Zoom è un gesto multitouch a due dita per scalare i contenuti, lo standard per immagini, mappe e documenti.
  • UIPinchGestureRecognizer in iOS è un riconoscitore continuo con scale (delta) e velocity (velocità).
  • ScaleGestureDetector in Android è un rilevatore specializzato con onScale(), focusX/focusY per il punto pivot.
  • Per UIScrollView, lo zoom è integrato tramite minimumZoomScale/maximumZoomScale — più semplice dell'implementazione manuale.
  • Limita sempre la scala (0.5–3.0) tramite clamp — senza questo, il contenuto può diventare invisibile.
  • Il punto pivot della scala è il centro tra le dita; senza di esso, il contenuto si sposta durante lo zoom.
  • In IT Sectr, utilizziamo UIScrollView per le immagini e ScaleGestureDetector personalizzato per gli editor — la scelta dipende dalla complessità delle trasformazioni.

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche