Wichtige Punkte
Pinch-to-Zoom (Zwickgestus) ist ein Multi-Touch-Gestus, der zwei Finger verwendet, um den Maßstab des angezeigten Inhalts zu ändern. Beim Zusammenführen der Finger (Pinch Close) verringert sich der Maßstab; beim Spreizen (Pinch Open) erhöht er sich. Der Gestus ist kontinuierlich: Das System verfolgt den Abstand zwischen den Fingern in Echtzeit und erzeugt Zwischenereignisse, sodass die Anwendung den Maßstab fließend aktualisieren kann.
Die Mechanik des Gestus basiert auf der Berechnung des Abstands zwischen zwei Berührungspunkten. iOS und Android verwenden den euklidischen Abstand: sqrt((x2−x1)² + (y2−y1)²). Der aktuelle Maßstab wird als Verhältnis des aktuellen Abstands zum ursprünglichen Abstand berechnet. Wenn der ursprüngliche Abstand 100px betrug und 150px wird — Skalierung = 1,5 (50% Erhöhung). iOS übergibt die Skalierung relativ zum vorherigen Zustand (Delta), Android übergibt die akkumulierte Skalierung über ScaleGestureDetector.
Laut den Google Material Design Guidelines ist Pinch-to-Zoom für Anwendungen, die Bilder, Karten oder Dokumente anzeigen, obligatorisch. Bei IT Sectr implementieren wir diesen Gestus mit UIPinchGestureRecognizer und ScaleGestureDetector in allen Projekten, die mit visuellen Inhalten arbeiten — von Galerien bis hin zu Bildeditoren.
UIPinchGestureRecognizer ist eine kontinuierliche Unterklasse von UIGestureRecognizer zum Erkennen des Zwickgestus. Anders als diskrete Erkennungselemente (UITapGestureRecognizer) erzeugt er eine Ereignissequenz: .began (Finger berühren den Bildschirm), .changed (jede Fingerbewegung), .ended (Finger werden angehoben).
Wichtige Eigenschaften: scale (CGFloat) — relative Änderung des Abstands zwischen den Fingern seit Beginn des Gestus; velocity (CGFloat) — Änderungsrate der Skalierung pro Sekunde. Anfängliche Skalierung = 1,0. Beim Spreizen der Finger steigt die Skalierung (1,0 → 1,5 → 2,0); beim Zusammenführen sinkt sie (1,0 → 0,8 → 0,5). Die Eigenschaft velocity wird für die Trägheit verwendet: Nach .ended kann das fortgesetzte Skalieren mit Verlangsamung animiert werden.
UIPinchGestureRecognizer unterstützt die delegate-Eigenschaft für die gleichzeitige Erkennung mit anderen Erkennungselementen (z. B. Pan + Pinch auf einer Karte). Die Methode gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) gibt true zurück, und beide Gesten arbeiten parallel. Apple empfiehlt, UIPinchGestureRecognizer zur Container-View hinzuzufügen, nicht zu einzelnen Elementen — dies vereinfacht die Berechnung des Pivot-Punkts der Skalierung.
ScaleGestureDetector ist eine Klasse aus der Android Support Library (android.view), die für die Erkennung von Multi-Touch-Skalierung entwickelt wurde. Anders als GestureDetector.onFling ist ScaleGestureDetector speziell für den Zwei-Finger-Zoom konzipiert und berechnet automatisch focusX, focusY (den Mittelpunkt zwischen den Fingern) und scaleFactor (Skalierungs-Delta vom vorherigen Ereignis).
ScaleGestureDetector arbeitet über das OnScaleGestureListener-Interface mit drei Methoden: onScaleBegin(detector) (analog zu .began), onScale(detector) (analog zu .changed, gibt boolean zurück — true, wenn die Skalierung akzeptiert wird), onScaleEnd(detector) (analog zu .ended). Eigenschaften: scaleFactor — Skalierungsmultiplikator relativ zum vorherigen Ereignis (~1,01 bei langsamem Spreizen), focusX/focusY — Koordinaten des Mittelpunkts zwischen den Fingern.
Zum Anwenden der Skalierung auf eine View wird normalerweise Matrix verwendet: view.imageMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY). Matrix unterstützt die Akkumulation von Transformationen — jeder postScale-Aufruf fügt eine neue Transformation zur bestehenden hinzu. Wichtig zu beachten: ScaleGestureDetector begrenzt die minimale/maximale Skalierung nicht — das ist die Aufgabe des Entwicklers. Bei IT Sectr verwenden wir ScaleGestureDetector mit benutzerdefinierten Skalierungsgrenzen (0,5–3,0) und einer Rücksetzungsanimation bei Überschreitung.
Bei der Implementierung von Pinch-to-Zoom ist es entscheidend wichtig, die minimale und maximale Skalierung zu begrenzen — ohne dies kann der Benutzer bis ins Unendliche hineinzoomen oder bis zur Unsichtbarkeit herauszoomen. Standardwerte: min = 0,5 (50% der ursprünglichen Größe), max = 3,0 (300%). Unter iOS erfolgt die Begrenzung über Clamp: newScale = min(max(currentScale * gesture.scale, minScale), maxScale). Unter Android — über eine Prüfung nach postScale.
Der Pivot-Punkt ist der Mittelpunkt zwischen den Fingern, relativ zu dem die Skalierung durchgeführt wird. iOS berechnet automatisch den Schwerpunkt der zwei Berührungen und übergibt ihn über location(in:) — der Entwickler muss den Pivot nicht manuell berechnen. Android stellt focusX/focusY im ScaleGestureDetector bereit, aber bei der Anwendung über Matrix wird der Pivot als drittes und viertes Argument in postScale(scaleFactor, scaleFactor, focusX, focusY) übergeben. Ohne einen Pivot-Punkt erfolgt die Skalierung relativ zur oberen linken Ecke (0,0), was während des Zooms zu einer Verschiebung des Inhalts führt.
Nach Erfahrung von IT Sectr ist die korrekte Berechnung des Pivot-Punkts die häufigste Fehlerquelle bei der Implementierung von Pinch-to-Zoom. In 90% der Fälle reicht es aus, die integrierten Koordinaten von GestureRecognizer und ScaleGestureDetector zu verwenden — kein manuelles Berechnen des Pivots erforderlich. Wenn sich der Inhalt beim Skalieren verschiebt, überprüfen Sie, ob Sie focusX/focusY an matrix.postScale() übergeben.
Für eine fließende Benutzererfahrung nach Beendigung des Gestus können Sie Trägheit hinzufügen: unter iOS — verwenden Sie velocity von UIPinchGestureRecognizer mit UIView.animate und Spring-Parametern; unter Android — OverScroller oder ValueAnimator. Die Trägheit sollte die Min-/Max-Skalierungsgrenzen nicht überschreiten — der Endwert wird mit einer Spring-Rücksetzungsanimation bei Überschreitung begrenzt.
Fügt UIPinchGestureRecognizer zu UIImageView mit Skalierungsbegrenzung von 0,5 bis 3,0 hinzu. Zeigt eine minimale Implementierung mit Erhaltung der akkumulierten Skalierung.
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
}
}
}
Logik: in .began/.changed wird die Skalierung über CGAffineTransform mit Clamp angewendet. In .ended wird die Skalierung auf 1,0 zurückgesetzt und der akkumulierte Wert in currentScale gespeichert. Dies verhindert ein Abdriften der Skalierung bei wiederholten Gesten — jeder neue Gestus beginnt vom aktuellen Zustand. Für Bilder wird UIScrollView mit minimumZoomScale/maximumZoomScale anstelle von CGAffineTransform empfohlen.
Implementiert die ImageView-Skalierung über ScaleGestureDetector mit Matrix. Zeigt die korrekte Handhabung des Pivot-Punkts und die Skalierungsbegrenzung.
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
}
}
Die benutzerdefinierte ImageView überschreibt onTouchEvent und übergibt Ereignisse an ScaleGestureDetector. OnScaleListener.onScale() wird bei jeder Fingerbewegung aufgerufen. detector.scaleFactor ist das Delta (1,01 bei langsamem Spreizen). currentScale akkumuliert mit coerceIn zum Clampen. focusX/focusY — der Mittelpunkt zwischen den Fingern, der an postScale für korrekte Pivot-Skalierung übergeben wird.
UIScrollView verfügt über integrierte Pinch-to-Zoom-Unterstützung über seinen Delegaten. Dies ist der bevorzugte Ansatz für Bilder und Dokumente — ScrollView verwaltet Skalierung, Trägheit und Scrollen automatisch.
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 erstellt automatisch einen UIPinchGestureRecognizer, verwaltet die Skalierung über die CALayer-Transformation und fügt einen Bounce-Effekt bei Überschreitung der Grenzen hinzu. Der Code reduziert sich auf das Setzen von min/maxZoomScale und die Rückgabe von viewForZooming. Bei IT Sectr verwenden wir UIScrollView für alle Vollbildbilder — dies reduziert das Codevolumen um das Fünffache im Vergleich zur manuellen Implementierung über UIPinchGestureRecognizer.
Häufig gestellte Fragen
Auf einem Touchscreen verwendet Pinch zwei Finger mit Abstandsverfolgung zwischen ihnen. Auf einem MacBook-Trackpad wird der Zoom über UIPinchGestureRecognizer mit denselben Zuständen (.began → .changed → .ended) implementiert, aber die Berührungsquelle ist ein Trackpad mit Force Touch, kein kapazitiver Bildschirm. Die API ist dieselbe, die Mechanik ist identisch.
In iOS: Nach Erhalt der Skalierung von UIPinchGestureRecognizer mit currentScale multiplizieren und clamp(min: 0.5, max: 3.0) anwenden. In Android: coerceIn(minScale, maxScale) vor matrix.postScale() verwenden. In UIScrollView — über die Eigenschaften minimumZoomScale und maximumZoomScale. Ohne Begrenzung kann der Maßstab ins Unendliche oder auf Null gehen.
Nein, der Pinch erfordert mindestens zwei Finger. Für den Ein-Finger-Zoom (Doppeltippen + nach oben/unten ziehen) ist eine benutzerdefinierte Implementierung über UIPanGestureRecognizer oder GestureDetector.onScroll erforderlich. Dies ist ein anderer Gestus mit einer anderen Logik — normalerweise für den Zoom in der Kamera oder auf Karten verwendet.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch