Core Graphics transforms provide control over coordinate systems, drawing contexts, and paths by enabling you to apply rotation, scaling, and translations. As I’ve been writing about Swift Style, I hope I’m now better able to articulate why I hate the way they’ve been automatically named into Swift.
The current Swift constructors and transformers are:
init(rotationAngle: CGFloat)
init(scaleX: CGFloat, y: CGFloat)
init(translationX: CGFloat, y: CGFloat)
rotated(by: CGFloat)
- scaledBy(x: CGFloat, y: CGFloat)
translatedBy(x: CGFloat, y: CGFloat)
Here are my issues:
Confusing naming. “Scale” can be a noun or verb. You can only figure out which one was intended by looking at the translation API. If you have to look at another API to figure out some meaning, the API was written wrong.
In this example, if the two arguments were swapped around and balanced (xScale, yScale), the API would make a lot more sense. I’m not arguing for those terms. I just want to make the point that you should avoid using ambiguous words. This is a classic example of that error.
Missing Types. Why scale and translate by identical meaningless “x” and “y” values? Or rotate by “rotationAngle”? There are better, more exact terms of art and a couple of missing types to support those terms.
Although Core Graphics added CGVector
, it is still missing two key types: CGAngle
(stores radians or degrees) and CGScale
(stores sx, sy scaling factor pairs).
If I were in charge, you’d be able to initialize a CGAngle using radians, degrees, and π count (for example, 360 degrees is 2π and 45 degrees is 0.25 π). And you’d be able to pull out properties for each of those items as well from a unified structure. A CGScale would provide a natural way to store scaling factors.
Redundancy. If you can come up with a more redundant term than rotationAngle
, I’d like to hear it. rotationAngle
uses two nearly identical words to do the work of one: “radians”. It presumes only one confusing initialization style while ignoring other common use cases like “degrees” and “multiples of pi”.
Incorrect Type Use. Although it’s convenient to break out factors into component elements, you’re really translating by a vector or scaling by an (sx, sy) pair. You should be allowed to use these types (for example, scale: CGScale
) as well as the broken down member calls (for example, sx:sy:
). This is most notable in the use of CGVector
. Although the CGVector type was introduced long after Affine Transforms, transforms were never updated to take advantage of a semantically richer approach.
Mix and Match Prepositions. Core Graphics includes rotated(by:)
, scaledBy()
, translatedBy().
So where does that “by” belong? In a coherent API, each “by” should either be in the parentheses or outside.
I vote “out”. Doing so enables you to create call families like these, where the specifics of the labels preserve share abstractions and the by
isn’t glommed onto the first argument label, throwing off the API’s balance.
public func translatedBy(tx: CGFloat, ty: CGFloat) -> CGAffineTransform public func translatedBy(vector: CGVector) -> CGAffineTransform
Speaking of unbalanced arguments, scaleX
is way longer than y
, as is translationX
vs y
, yet both arguments have equal weight and priority in the calls. Unbalanced labels offer another good indicator of badly designed APIs.
6 Comments
I wrote an angle struct for myself, and boy has it made everything so much easier to deal with. It can deal with radians, degrees, and revolutions (1 is a full turn, 0.25 is a quarter turn). It has static vars for common angles, and has functions to normalize, get the nearest 1°, 5°, 45°.
I would love to see something like this in the standard library.
I also agree that every CG method which takes a rotation, should have it’s argument renamed “radians” (or take an angle type instead).
Hey,
I just dropped by while searching for Airplay programs, and I came by yours. Just want to say thanks for your work and good job on the license the poem.
Godspeed!
Also not to mention, your eloquent criticism on the badly designed API. Perhaps it is the result of the big scale company’s top-down pressure on programers and engineers…
Really interesting! A CGAngle would be really useful and it would not be complicated to add it to the standard lib.
https://github.com/erica/SwiftGeometry/blob/master/Sources/CGAngle.swift