61

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.)

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Did you file your bug report on Open Radar, or only privately? – jscs Jun 09 '14 at 06:41
  • @JoshCaswell Privately but contact me offline if you want radar number and wording – matt Jun 09 '14 at 17:09
  • Check this: http://stackoverflow.com/questions/24009876/how-can-use-cgfloat-in-swift/24879816#24879816 – Yatheesha Jul 22 '14 at 05:51
  • In Xcode 6 beta 5: A CGFloat can be constructed from any Integer type (including the sized integer types) and vice-versa. (17670817) – BergQuester Aug 04 '14 at 17:23
  • Great news, thanks for the heads-up @BergQuester – matt Aug 04 '14 at 18:32
  • NSTimeInterval suffers from the same cognitive dissonance. – Alex Brown Jan 16 '15 at 22:15
  • @AlexBrown NSTimeInterval is merely another word for Double so that is a tautology. – matt Jan 16 '15 at 22:34
  • Indeed, thanks for the pointer. Using NSTimeIntervals as CGFloat suffers the same problems that using Double (or Float) as CGFloat. – Alex Brown Jan 16 '15 at 23:11
  • It has become a bit easier with the implementation of https://github.com/apple/swift-evolution/blob/main/proposals/0307-allow-interchangeable-use-of-double-cgfloat-types.md. – Martin R Nov 25 '21 at 06:09

3 Answers3

24

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)

Community
  • 1
  • 1
BergQuester
  • 6,167
  • 27
  • 39
  • 1
    Personal preferences aside, that is how the typing issue is handled in swift. Apple is looking for feedback on the language, so I'm sure they would be willing to receive some feedback on this. – BergQuester Jun 08 '14 at 17:50
  • 1
    exactly. When working with `CGFloat`s, you have to type everything to `CGFloat`. Simple. – Sulthan Jun 08 '14 at 18:04
  • 5
    @Sulthan Yes, but the problem is that when you say "everything" you really mean "everything". For example, if `f` is a CGFloat and `d` is a Double (which is the default), you can't multiply them. Thus, as I said in my question, there's a lot of casting to do. But that's nuts. – matt Jun 08 '14 at 18:11
  • 3
    @Sulthan I've filed a bug. In Objective-C, once something is a CGFloat, all numeric operations just work, and the interchange of info with CGRect and CGPoint works just fine on both 32-bit and 64-bit platforms. Without that ease of use for what is, after all, one of the most common needs in iOS programming, this language is going to be really daunting to use. – matt Jun 08 '14 at 18:20
  • I agree, it would be annoying. I'm curious what their response is going to be or how they would fix this issue without breaking Swift's type safety. Keep us posted if they actually send back a response. – BergQuester Jun 08 '14 at 18:23
  • @matt It's not a bug, the documentation says "there are no implicit casts" so it's a "feature". Frankly, I consider it nuts, too, but I found other problems I condsider worse, e.g. getting a substring has linear complexity because strings are not arrays of characters but arrays of bytes. – Sulthan Jun 08 '14 at 18:30
  • 1
    Type safety is one thing (and great); float and double incompatibility like this -- in what's supposed to be a HLL -- is comparable to wearing a helmet, kneepads, and goggles while making toast for breakfast. The way to fix it is to make "floating point" one type. If we're moving away from C, we shouldn't have to care about the size of numeric types. – jscs Jun 08 '14 at 18:43
  • 4
    @Sulthan I didn't say it was a bug, I said I submitted a bug report. No one is going to want to use this language if it's going to be this picky. - Don't get me wrong, there's a lot about Swift that's _great_ in comparison to Objective-C. But this isn't that. Why Not The Best? Apple should aim higher than this. – matt Jun 08 '14 at 19:26
  • That's what I like about Swift, that it is picky. That's the intention. It's a safe language. – gnasher729 Jun 26 '15 at 16:44
15

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.

Seivan
  • 668
  • 6
  • 13
6

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.

Andrew97p
  • 565
  • 6
  • 11