61
votes

I'm finding Swift numerics particularly clumsy when, as so often happens in real life, I have to communicate with Cocoa Touch with regard to CGRect and CGPoint (e.g., because we're talking about something's frame or bounds).

CGFloat vs. Double

Consider the following innocent-looking code from a UIViewController subclass:

let scale = 2.0
let r = self.view.bounds
var r2 = CGRect()
r2.size.width = r.size.width * scale

This code fails to compile, with the usual mysterious error on the last line:

Could not find an overload for '*' that accepts the supplied arguments

This error, as I'm sure you know by now, indicates some kind of impedance mismatch between types. r.size.width arrives as a CGFloat, which will interchange automatically with a Swift Float but cannot interoperate with a Swift Double variable (which, by default, is what scale is).

The example is artificially brief, so there's an artificially simple solution, which is to cast scale to a Float from the get-go. But when many variables drawn from all over the place are involved in the calculation of a proposed CGRect's elements, there's a lot of casting to do.

Verbose Initializer

Another irritation is what happens when the time comes to create a new CGRect. Despite the documentation, there's no initializer with values but without labels. This fails to compile because we've got Doubles:

let d = 2.0
var r3 = CGRect(d, d, d, d)

But even if we cast d to a Float, we don't compile:

Missing argument labels 'x:y:width:height:' in call

So we end up falling back on CGRectMake, which is no improvement on Objective-C. And sometimes CGRectMake and CGSizeMake are no improvement. Consider this actual code from one of my apps:

let kSEP : Float = 2.0
let intercellSpacing = CGSizeMake(kSEP, kSEP);

In one of my projects, that works. In another, it mysteriously fails — the exact same code! — with this error:

'NSNumber' is not a subtype of 'CGFloat'

It's as if, sometimes, Swift tries to "cross the bridge" by casting a Float to an NSNumber, which of course is the wrong thing to do when what's on the other side of the bridge expects a CGFloat. I have not yet figured out what the difference is between the two projects that causes the error to appear in one but not the other (perhaps someone else has).

NOTE: I may have figured out that problem: it seems to depend on the Build Active Architecture Only build setting, which in turn suggests that it's a 64-bit issue. Which makes sense, since Float would not be a match for CGFloat on a 64-bit device. That means that the impedance mismatch problem is even worse than I thought.

Conclusion

I'm looking for practical words of wisdom on this topic. I'm thinking someone may have devised some CGRect and CGPoint extension that will make life a lot easier. (Or possibly someone has written a boatload of additional arithmetic operator function overloads, such that combining CGFloat with Int or Double "just works" — if that's possible.)

3
Did you file your bug report on Open Radar, or only privately?jscs
@JoshCaswell Privately but contact me offline if you want radar number and wordingmatt
In Xcode 6 beta 5: A CGFloat can be constructed from any Integer type (including the sized integer types) and vice-versa. (17670817)BergQuester
Great news, thanks for the heads-up @BergQuestermatt

3 Answers

15
votes

I wrote a library that handles operator overloading to allow interaction between Int, CGFloat and Double.

https://github.com/seivan/ScalarArithmetic

As of Beta 5, here's a list of things that you currently can't do with vanilla Swift. https://github.com/seivan/ScalarArithmetic#sample

I suggest running the test suite with and without ScalarArithmetic just to see what's going on.

24
votes

Explicitly typing scale to CGFloat, as you have discovered, is indeed the way handle the typing issue in swift. For reference for others:

let scale: CGFloat = 2.0
let r = self.view.bounds
var r2 = CGRect()
r2.size.width = r.width * scale

Not sure how to answer your second question, you may want to post it separately with a different title.

Update:

Swift creator and lead developer Chris Lattner had this to say on this issue on the Apple Developer Forum on July 4th, 2014:

What is happening here is that CGFloat is a typealias for either Float or Double depending on whether you're building for 32 or 64-bits. This is exactly how Objective-C works, but is problematic in Swift because Swift doesn't allow implicit conversions.

We're aware of this problem and consider it to be serious: we are evaluating several different solutions right now and will roll one out in a later beta. As you notice, you can cope with this today by casting to Double. This is inelegant but effective :-)

Update In Xcode 6 Beta 5:

A CGFloat can be constructed from any Integer type (including the sized integer types) and vice-versa. (17670817)

6
votes

I created an extension for Double and Int that adds a computed CGFloatValue property to them.

extension Double {
    var CGFloatValue: CGFloat {
        get {
            return CGFloat(self)
        }
    }
}
extension Int {
    var CGFloatValue: CGFloat {
        get {
            return CGFloat(self)
        }
    }
}

You would access it by using let someCGFloat = someDoubleOrInt.CGFloatValue

Also, as for your CGRect Initializer, you get the missing argument labels error because you have left off the labels, you need CGRect(x: d, y: d, width: d, height: d) you can't leave the labels out unless there is only one argument.