Pinch-to-Zoom — essentie, verwerking van knijpbeweging in mobiele apps

Auteur: IT Sectr Gepubliceerd: 2026-02-27 Leestijd: 8 min
Pinch-to-Zoom (knijpen met vingers) — een multi-touch schaalgebaar waarbij de gebruiker twee vingers op het scherm samenbrengt of spreidt om de inhoud te verkleinen of te vergroten. Dit gebaar is de de facto standaard geworden voor het bekijken van afbeeldingen, kaarten, webpagina's en documenten op mobiele apparaten. Volgens Apple HIG en Google Material Design is Pinch-to-Zoom het basisgebaar dat gebruikers verwachten in alle apps die met visuele inhoud werken.

Belangrijkste punten

  • Pinch-to-Zoom — een multi-touch gebaar met twee vingers voor het schalen van inhoud op mobiele apparaten.
  • In iOS wordt de verwerking gedaan via UIPinchGestureRecognizer met continue tracking .began → .changed → .ended.
  • In Android wordt ScaleGestureDetector (uit Support Library) gebruikt met callback onScale om de schaal via Matrix bij te werken.
  • UIPinchGestureRecognizer heeft de eigenschap scale (coëfficiënt ten opzichte van vorige toestand) en velocity (snelheid).
  • Om de zoom te beperken wordt clamp van de scale-coëfficiënt toegepast tussen opgegeven min/max waarden (meestal 0.5–3.0).

Wat is Pinch-to-Zoom?

Pinch-to-Zoom (knijpen of samenknijpen) — een multi-touch gebaar dat twee vingers gebruikt om de schaal van weergegeven inhoud te wijzigen. Bij het samenbrengen van vingers (pinch close) wordt de schaal kleiner, bij het spreiden (pinch open) — groter. Het gebaar is continu: het systeem volgt de afstand tussen de vingers in realtime en genereert tussentijdse gebeurtenissen, zodat de app de schaal soepel kan bijwerken.

De mechanica van het gebaar is gebaseerd op het berekenen van de afstand tussen twee aanraakpunten. iOS en Android gebruiken de Euclidische afstand: sqrt((x2-x1)² + (y2-y1)²). De huidige scale wordt berekend als de verhouding van de huidige afstand tot de beginwaarde. Als de beginwaarde 100px was en 150px werd — scale = 1.5 (50% toename). iOS geeft scale door ten opzichte van de vorige toestand (delta), Android — geaccumuleerde scale via ScaleGestureDetector.

Volgens Google Material Design Guidelines is Pinch-to-Zoom verplicht voor apps die afbeeldingen, kaarten of documenten weergeven. Bij IT Sectr implementeren we dit gebaar met UIPinchGestureRecognizer en ScaleGestureDetector in alle projecten die met visuele inhoud werken — van galerijen tot beeldbewerkers.

Pinch-to-Zoom in iOS: UIPinchGestureRecognizer

UIPinchGestureRecognizer — een continue subklasse van UIGestureRecognizer voor het herkennen van het knijpgebaar. In tegenstelling tot discrete herkenners (UITapGestureRecognizer) genereert het een reeks gebeurtenissen: .began (vingers raakten het scherm), .changed (elke beweging van vingers), .ended (vingers opgeheven).

Belangrijkste eigenschappen: scale (CGFloat) — relatieve verandering van de afstand tussen vingers sinds het begin van het gebaar; velocity (CGFloat) — snelheid van scale-verandering per seconde. Begin-scale = 1.0. Bij het spreiden van vingers neemt scale toe (1.0 → 1.5 → 2.0), bij samenbrengen — af (1.0 → 0.8 → 0.5). De eigenschap velocity wordt gebruikt voor traagheid: na .ended kan men het schalen met demping animeren.

UIPinchGestureRecognizer ondersteunt de eigenschap delegate voor gelijktijdig werken met andere herkenners (bijv. pan + pinch op een kaart). De methode gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) geeft true terug en beide gebaren werken parallel. Apple raadt aan om UIPinchGestureRecognizer toe te voegen aan een container view, niet aan afzonderlijke elementen — dit vereenvoudigt de berekening van het pivotpunt van schalen.

Pinch-to-Zoom in Android: ScaleGestureDetector

ScaleGestureDetector — een klasse uit Android Support Library (android.view), bedoeld voor het herkennen van multi-touch schalen. In tegenstelling tot GestureDetector.onFling is ScaleGestureDetector speciaal ontworpen voor twee-vinger zoom en berekent automatisch focusX, focusY (midden tussen vingers) en scaleFactor (delta van schaal ten opzichte van vorige gebeurtenis).

ScaleGestureDetector werkt via de interface OnScaleGestureListener met drie methoden: onScaleBegin(detector) (equivalent van .began), onScale(detector) (equivalent van .changed, geeft boolean terug — true als schaal is geaccepteerd), onScaleEnd(detector) (equivalent van .ended). Eigenschappen: scaleFactor — vermenigvuldiger van schaal ten opzichte van vorige gebeurtenis (~1.01 bij langzaam spreiden), focusX/focusY — coördinaten van het midden tussen vingers.

Voor het toepassen van schaal op View wordt meestal Matrix gebruikt: view.imageMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY). Matrix ondersteunt accumulatie van transformaties — elke postScale-aanroep voegt een nieuwe transformatie toe aan de bestaande. Belangrijk: ScaleGestureDetector beperkt de minimale/maximale schaal niet — dit is de taak van de ontwikkelaar. Bij IT Sectr passen we ScaleGestureDetector toe met aangepaste scale-grenzen (0.5–3.0) en terugkeeranimatie bij overschrijding.

Zoombeperking en pivotpunt

Bij de implementatie van Pinch-to-Zoom is het beperken van de minimale en maximale schaal van cruciaal belang — zonder dit kan de gebruiker de afbeelding oneindig vergroten of tot onzichtbaarheid verkleinen. Standaardwaarden: min = 0.5 (50% van originele grootte), max = 3.0 (300%). In iOS gebeurt de beperking via clamp: newScale = min(max(currentScale * gesture.scale, minScale), maxScale). In Android — via controle na postScale.

Het pivotpunt — het midden tussen de vingers ten opzichte waarvan het schalen wordt uitgevoerd. iOS berekent automatisch het middelpunt van twee aanrakingen en geeft het door via location(in:) — de ontwikkelaar hoeft pivot niet handmatig te berekenen. Android biedt focusX/focusY in ScaleGestureDetector, maar bij toepassing via Matrix wordt pivot doorgegeven als derde en vierde argument in postScale(scaleFactor, scaleFactor, focusX, focusY). Zonder pivotpunt wordt het schalen uitgevoerd ten opzichte van de linkerbovenhoek (0,0), wat leidt tot verschuiving van inhoud bij zoom.

Volgens ervaring van IT Sectr is de juiste berekening van het pivotpunt de meest voorkomende bron van bugs bij de implementatie van Pinch-to-Zoom. In 90% van de gevallen is het voldoende om de ingebouwde coördinaten van GestureRecognizer en ScaleGestureDetector te gebruiken, zonder pivot handmatig te berekenen. Als de inhoud verschuift bij schalen — controleer of u focusX/focusY doorgeeft aan matrix.postScale().

Traagheid na zoom

Voor een soepele UX na het einde van het gebaar kan traagheid worden toegevoegd: in iOS — gebruik velocity van UIPinchGestureRecognizer voor UIView.animate met spring-parameters; in Android — OverScroller of ValueAnimator. Traagheid mag niet buiten de min/max scale-grenzen komen — de uiteindelijke waarde wordt afgekapt met spring-terugkeeranimatie bij overschrijding.

Codevoorbeelden in Swift en Kotlin

Voorbeeld 1: Pinch-to-Zoom op iOS (Swift)

Voegt UIPinchGestureRecognizer toe aan UIImageView met schaalbeperking van 0.5 tot 3.0. Toont een minimale implementatie met behoud van geaccumuleerde schaal.

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 wordt schaal toegepast via CGAffineTransform met clamp. In .ended wordt scale gereset naar 1.0 en de geaccumuleerde waarde opgeslagen in currentScale. Dit voorkomt „drift“ van schaal bij meerdere gebaren — elk nieuw gebaar begint vanuit de huidige toestand. Voor afbeeldingen wordt in plaats van CGAffineTransform UIScrollView met minimumZoomScale/maximumZoomScale aanbevolen.

Voorbeeld 2: Pinch-to-Zoom op Android (Kotlin)

Implementeert het schalen van ImageView via ScaleGestureDetector met Matrix. Toont correcte verwerking van het pivotpunt en schaalbeperking.

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

Aangepaste ImageView overschrijft onTouchEvent en geeft gebeurtenissen door aan ScaleGestureDetector. OnScaleListener.onScale() wordt aangeroepen bij elke vingerbeweging. detector.scaleFactor — delta (1.01 bij langzaam spreiden). currentScale accumuleert met coerceIn voor clamp. focusX/focusY — midden tussen vingers, doorgegeven aan postScale voor correct pivot-schalen.

Voorbeeld 3: UIScrollView met zoomondersteuning (iOS)

UIScrollView heeft ingebouwde ondersteuning voor Pinch-to-Zoom via een delegatie. Dit is de geprefereerde methode voor afbeeldingen en documenten — ScrollView beheert schaal, traagheid en scrollen automatisch.

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 maakt zelfstandig UIPinchGestureRecognizer aan, beheert schaal via CALayer-transformatie en voegt een bounce-effect toe bij overschrijding van grenzen. De code beperkt zich tot het instellen van min/maxZoomScale en het retourneren van viewForZooming. Bij IT Sectr gebruiken we UIScrollView voor alle volledige-scherm afbeeldingen — dit vermindert de codehoeveelheid met 5 keer in vergelijking met handmatige implementatie via UIPinchGestureRecognizer.

Veelgestelde vragen

Wat is het verschil tussen pinch en knijpen op een trackpad?

Op een aanraakscherm gebruikt pinch twee vingers met het volgen van de afstand ertussen. Op een MacBook-trackpad wordt schalen geïmplementeerd via UIPinchGestureRecognizer met dezelfde toestanden (.began → .changed → .ended), maar de bron van aanrakingen is het trackpad met Force Touch, niet een capacitief scherm. De API is hetzelfde, de mechanica identiek.

Hoe beperk ik de minimale en maximale zoom?

In iOS: vermenigvuldig scale van UIPinchGestureRecognizer met currentScale en pas clamp(min: 0.5, max: 3.0) toe. In Android: via coerceIn(minScale, maxScale) vóór matrix.postScale(). In UIScrollView — via de eigenschappen minimumZoomScale en maximumZoomScale. Zonder beperking kan de schaal naar oneindig of nul gaan.

Werkt Pinch-to-Zoom met één vinger?

Nee, pinch vereist minimaal twee vingers. Voor zoom met één vinger (double-tap + slepen omhoog/omlaag) is een aangepaste implementatie nodig via UIPanGestureRecognizer of GestureDetector.onScroll. Dit is een ander gebaar met een andere logica — meestal gebruikt voor zoom in camera of kaarten.

Samenvatting

  • Pinch-to-Zoom — een multi-touch gebaar met twee vingers voor het schalen van inhoud, standaard voor afbeeldingen, kaarten en documenten.
  • UIPinchGestureRecognizer in iOS — continue herkenner met scale (delta) en velocity (snelheid).
  • ScaleGestureDetector in Android — gespecialiseerde detector met onScale(), focusX/focusY voor pivotpunt.
  • Voor UIScrollView wordt zoom ingebouwd via minimumZoomScale/maximumZoomScale — eenvoudiger dan handmatige implementatie.
  • Beperk altijd de schaal (0.5–3.0) via clamp — zonder dit kan inhoud onzichtbaar worden.
  • Het pivotpunt van schalen — het midden tussen vingers; zonder dit verschuift inhoud bij vergroten.
  • Bij IT Sectr gebruiken we UIScrollView voor afbeeldingen en aangepaste ScaleGestureDetector voor editors — de keuze hangt af van de complexiteit van transformaties.

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook