Swift: Komfortabler mit CGFloat, CGPoint und CGRect arbeiten

In den letzten Wochen habe ich recht intensiv unter Swift 2.n mit dem SpriteKit gearbeitet. Ein großes Ärgernis ist dabei das umständliche Hantieren mit CGPoint, CGSize, CGRect– und CGVector-Strukturen. Lästig sind auch die ständig erforderlichen Typumwandlungen zwischen den im SpriteKit üblichen CGFloat-Zahlenformat und »gewöhnlichen« Integer- und Fließkommazahlen. Das ist umso absurder, als CGFloat auf 64-Bit-Plattformen ohnedies eine Double-Zahl ist. Einzig auf 32-Bit-Architekturen ist CGFloat tatsächlich ein Float.

Wie auch immer: Swift wäre nicht Swift, könnten wir uns das Leben nicht mit ein paar neuen Operatoren, nachträglichen Erweiterungen vorhandener Strukturen sowie mit globalen Funktionen leichter machen.

Eigene Operatoren

In Swift können Sie mühelos eigene Operatoren definieren. Der Code solte auf Anhieb verständlich sein:

// Operatoren für CGPoint-Daten
// CGPoints addieren/subtrahieren
public func + (left: CGPoint, right: CGPoint) -> CGPoint {
  return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
public func += (inout left: CGPoint, right: CGPoint) {
  left = left + right
}
public func - (left: CGPoint, right: CGPoint) -> CGPoint {
  return CGPoint(x: left.x - right.x, y: left.y - right.y)
}
public func -= (inout left: CGPoint, right: CGPoint) {
  left = left - right
}

// CGPoint skalieren
public func * (point: CGPoint, scalar: CGFloat) -> CGPoint {
  return CGPoint(x: point.x * scalar, y: point.y * scalar)
}
public func *= (inout point: CGPoint, scalar: CGFloat) {
  point = point * scalar
}

Analoge Operatoren können Sie natürlich auch für CGSize, CGRect und CGVector definieren.

CGRect-Struktur um eigene Methoden erweitern

Mit extension können Sie vorhandene Strukturen um eigene Methoden erweitern. Die folgenden drei Methoden liefern den linken unteren und den rechten oberen Eckpunkt sowie den Mittelpunkt eines Rechtecks:

public extension CGRect {
  // Mittelpunkt
  func middlePoint() -> CGPoint {
    return CGPoint(x: CGRectGetMidX(self), y: CGRectGetMidY(self))
  }
  // links unten im iOS-Koordinatensystem
  func minPoint() -> CGPoint {
    return CGPoint(x: CGRectGetMinX(self), y: CGRectGetMinY(self))
  }
  // rechts oben im iOS-Koordinatensystem
  func maxPoint() -> CGPoint {
    return CGPoint(x: CGRectGetMaxX(self), y: CGRectGetMaxY(self))
  }
}

Die folgenden Zeilen erweitern CGPoint um nützliche Methoden:

public extension CGPoint {
  // passt die Koordinaten des Punkts so an, dass dieser sich
  // innerhalb des Rechtecks befindet
  func withinRect(rect: CGRect) -> CGPoint {
    return CGPoint(x: min(CGRectGetMaxX(rect), max(CGRectGetMinX(rect), self.x)),
                   y: min(CGRectGetMaxY(rect), max(CGRectGetMinY(rect), self.y)))
  }

  // Länge
  func length() -> CGFloat {
    return pythagoras(self.x,  self.y)
  }

  // Abstand
  func distanceTo(other: CGPoint) -> CGFloat {
    return pythagoras(self.x-other.x, self.y-other.y)
  }
}

Pythagoras

Auch die gerade verwendete Funktion pythargoras ist selbst definiert. Sie berechnet die Hypotenuse eines rechtwinkligen Dreiecks. Die Funktion erwartet CGFloat-Parameter und liefert natürlich auch ein CGFloat-Ergebnis.

// Pythagoras: a^2 + b^2 = c^2
// c = pythagoras(a, b)
public func pythagoras(a:CGFloat, _ b:CGFloat) -> CGFloat {
  return sqrt(a*a + b*b)
}

Update 12.9.2016: Die pythagoras-Funktion ist überflüssig, hypot(a, b) erfüllt dieselbe Aufgabe.

Dies und das

Beim Berechnen von Winkeln werden Sie häufig Pi al CGFloat-Zahl brauchen. Die Konstante pi minimiert die Tipparbeit. (Wenn Sie möchten, können Sie auch das Symbol π anstelle von pi verwenden.)

cgrand() liefert eine Zufallszahl zwischen 0 und 1 als CGFloat. Achten Sie darauf, dass Sie in der Initialisierung Ihres Programms (z.B. in viewDidLoad) den Zufallszahlengenerator initialisieren, beispielsweise so: srand48(Int(arc4random_uniform(100000000))). Vergessen Sie darauf, erhalten Sie bei jedem Programmablauf (bei jedem Spiel) immer wieder dieselben Zufallszahlen.

Die generische Funktion minMax stellt sicher, dass ein Zahlenwert innerhalb der Schranken lower und upper bleibt.

// Pi
let pi = CGFloat(M_PI)

// CGFloat-Zufallszahlen
public func cgrand() -> CGFloat {
  return CGFloat(drand48())
}

// ergebnis = minMax(x, lower, upper) 
// stellt sicher, dass lower <= ergebnis <= upper gilt 
public func minMax<T : Comparable>(value: T, minimum: T, maximum: T) -> T {
  return max(min(value, maximum), minimum)
}

Update 12.9.2016: In Swift 3 ist die Definition von pi überflüssig, es gibt .pi vordefiniert für die Datentypen Double, Float und CGFloat.

Links

Hier ist der komplette Code (es sind nur rund 100 Zeilen): CGOperators.swift.zip

Natürlich bin ich nicht der Erste, der auf die Idee gekommen ist, den Umgang mit CG-Strukturen eleganter zu gestalten. Sie finden im Internet diverse Bibliotheken, die allesamt deutlich umfangreicher sind als der oben präsentierte Code. Besonders empfehlenswert sind Vector- und ScalarArithmetic:

Egal, ob eigener Code oder fertige Bibliotheken aus dem Internet: spätestens mit dem Update auf Swift 3 wird wieder alles durcheinander geraten, sind Updates an eigenem oder fremden CG-Code erforderlich. Schön wäre es, wenn sich Apple die Mühe machen würde, diesen Code selbst zu schreiben und standardmäßig zur Verfügung zu stellen und so nicht alle SpriteKit-Programmierer zu zwingen, sich entweder in Abhängigkeit von fremdem Code zu begeben oder Code wie hier beschrieben selbst zu verfassen, was ja eigentlich auch schade um die Zeit ist.

Ein Gedanke zu „Swift: Komfortabler mit CGFloat, CGPoint und CGRect arbeiten“

  1. Kleiner Klugscheißerhinweis: floating point = Gleitkomma
    (fließen = flow, float = schwimmen, schweben, gleiten)

Kommentare sind geschlossen.