2

I was watching some of the videos at WWDC2014 and trying to code I liked, but one of the weird things I noticed is that Swift keeps getting mad at me and wanting me to cast to different number types. This is easy enough but in the videos at WWDC they did NOT need to do this. Here is an example from "What's New With Interface Builder":

-M_PI/2 keeps giving me the error: "Could not find an overload for '/' that accepts the supplied arguments'

Does anyone have a solution to this problem, that does NOT simply involve casting because there is clearly another way of doing this? I have many many more examples for similar problems to this.

if !ringLayer {
            ringLayer = CAShapeLayer()

            let innerRect = CGRectInset(bounds, lineWidth / 2.0, lineWidth / 2.0)
            let innerPath = UIBezierPath(ovalInRect: innerRect)

            ringLayer.path = innerPath.CGPath
            ringLayer.fillColor = nil
            ringLayer.lineWidth = lineWidth
            ringLayer.strokeColor = UIColor.blueColor().CGColor
            ringLayer.anchorPoint = CGPointMake(0.5, 0.5)
            ringLayer.transform = CATransform3DRotate
                 (ringLayer.transform, -M_PI/2, 0, 0, 1)

            layer.addSublayer(ringLayer)
        }
        ringLayer.frame = layer.bounds
Maria Zverina
  • 10,863
  • 3
  • 44
  • 61
DBoyer
  • 3,062
  • 4
  • 22
  • 32
  • where do you have M_PI defined? – Jesse Naugher Jun 08 '14 at 18:48
  • is M_PI not a constant in QuartzCore? The intelliSense picks it up when you type it – DBoyer Jun 08 '14 at 18:51
  • I do not get any errors when I try to use -M_PI/2. Perhaps you can create a playground that shows your problem? – drewag Jun 08 '14 at 18:52
  • I have the same problem for trying to put a CGFloat into a Double, which should work fine as well – DBoyer Jun 08 '14 at 18:54
  • It works perfectly fine in a playground, just not in my actual source file – DBoyer Jun 08 '14 at 19:10
  • Try M_PI/2.0. Remember that there's no implicit casting and no operations between mismatched types. So you're trying to divide a double by an int. – David Berry Jun 08 '14 at 19:17
  • No luck there either :(. The funny part is the code renders fine in IB (I am using IBDesignable) but the error is still there so I can't run it. – DBoyer Jun 08 '14 at 19:18
  • `CGFloat(-M_PI/2)` or `CGFloat(-M_PI_2)` – David Berry Jun 08 '14 at 19:32
  • The swift operator type matching code takes both operands AND the destination into account when known, so it's trying to find a divide operator that takes two doubles and returns a float. – David Berry Jun 08 '14 at 19:34
  • I am just curious why the code from WWDC didn't require you to case like that, there must be another way – DBoyer Jun 08 '14 at 19:42
  • The following line works fine in the playground. What happens if you put it in your code? `let transform = CATransform3DRotate(CATransform3DIdentity, -M_PI/2, 1.0, 1.0, 1.0)` – Steve Waddicor Jun 08 '14 at 20:07
  • Where exactly is this example in the video? I can't see it in the slides, but I'm still downloading the video itself. (Personally, I can see logic to the error: you're losing precision when taking a double down to a float, so making you add an explicit cast like `Float(-M_PI/2)` seems reasonable, but I know that's not the actual question you're asking...) – Matt Gibson Jun 09 '14 at 09:11
  • Aha! I think [this answer](http://stackoverflow.com/a/24118250/300836) explains why sometimes you need a cast and sometimes you don't, if it's right—there's a difference between the definition of CGFloat on 32-bit and 64-bit architectures, so sometimes it won't need a cast, and sometimes it will. – Matt Gibson Jun 09 '14 at 10:27
  • possible duplicate of [swift : Confusion due to no implicit conversion of CGFloat](http://stackoverflow.com/questions/24118134/swift-confusion-due-to-no-implicit-conversion-of-cgfloat) – Sulthan Jun 09 '14 at 13:04

3 Answers3

6

Edit: NB: CGFloat has changed in beta 4, specifically to make handling this 32/64-bit difference easier. Read the release notes and don't take the below as gospel now: it was written for beta 2.

After a clue from this answer I've worked it out: it depends on the selected project architecture. If I leave the Project architecture at the default of (armv7, arm64), then I get the same error as you with this code:

 // Error with arm7 target:
 ringLayer.transform = CATransform3DRotate(ringLayer.transform, -M_PI/2, 0, 0, 1)

...and need to cast to a Float (well, CGFloat underneath, I'm sure) to make it work:

 // Works with explicit cast on arm7 target
 ringLayer.transform = CATransform3DRotate(ringLayer.transform, Float(-M_PI/2), 0, 0, 1)

However, if I change the target architecture to arm64 only, then the code works as written in the Apple example from the video:

 // Works fine with arm64 target:
 ringLayer.transform = CATransform3DRotate(ringLayer.transform, -M_PI/2, 0, 0, 1)

So to answer your question, I believe this is because CGFloat is defined as double on 64-bit architecture, so it's okay to use M_PI (which is also a double)-derived values as a CGFloat parameter. However, when arm7 is the target, CGFloat is a float, not a double, so you'd be losing precision when passing M_PI (still a double)-derived expressions directly as a CGFloat parameter.

Note that Xcode by default will only build for the "active" architecture for Debug builds—I found it was possible to toggle this error by switching between iPhone 4S and iPhone 5S schemes in the standard drop-down in the menu bar of Xcode, as they have different architectures. I'd guess that in the demo video, there's a 64-bit architecture target selected, but in your project you've got a 32-bit architecture selected?

Given that a CGFloat is double-precision on 64-bit architectures, the simplest way of dealing with this specific problem would be to always cast to CGFloat.

But as a demonstration of dealing with this type of issue when you need to do different things on different architectures, Swift does support conditional compilation:

    #if arch(x86_64) || arch(arm64)
        ringLayer.transform = CATransform3DRotate (ringLayer.transform, -M_PI / 2, 0, 0, 1)
    #else
        ringLayer.transform = CATransform3DRotate (ringLayer.transform, CGFloat(-M_PI / 2), 0, 0, 1)
    #endif

However, that's just an example. You really don't want to be doing this sort of thing all over the place, so I'd certainly stick to simply using CGFloat(<whatever POSIX double value you need>) to get either a 32- or 64-bit value depending on the target architecture.

Apple have added much more help for dealing with different floats in later compiler releases—for example, in early betas you couldn't even take floor() of a single-precision float easily, whereas now (currently Xcode 6.1) there are overrides for floor(), ceil(), etc. for both float and double, so you don't need to be fiddling with conditional compilation.

Community
  • 1
  • 1
Matt Gibson
  • 37,886
  • 9
  • 99
  • 128
0

There seems to be issues currently with automatic conversions between Objective C numeric types and Swift types. For this I was able to get it to work by marking the lineWidth to the Float type. I don't know why they didn't have that issue in the video, I assume that is a different build they were using. Either there is an Objective C interop setting I'm missing, or it's just a beta issue.

To verify some of the basic issues (even happening in Playground) I used:

var x:NSNumber = 1
var y:Integer = 2
var z:Int = 3
x += 5 //error
y += 6 //error
z = z + y //error
  • You [can't add NSNumbers](http://stackoverflow.com/questions/494002/how-to-add-two-nsnumber-objects) anyway, and they're immutable so even if you could, `+=` wouldn't work. `Integer` is a protocol but doesn't define + or +=, which is why adding anything to `y` isn't going to work. `Int` is an actual type, and has operators defined for it (e.g. `func +=(inout lhs: Int, rhs: Int)`), so you can actually do arithmetic with it. – Matt Gibson Jun 09 '14 at 17:53
  • Ah cool, thanks. I was thinking they would take the Objective C wrapped types and do all the auto converting behind the scenes like with C# boxing, but it makes sense that they don't do that if objc doesn't. Also meant to use NSInteger instead of Integer there, and that actually works fine as expected, so false alarm there : ). – pixateRobin Jun 09 '14 at 23:04
0

For Swift 1.2 you have to cast second parameter to CGFloat

This code works:

ringLayer.transform = CATransform3DRotate(ringLayer.transform, CGFloat(-M_PI/2), 0, 0, 1)
skywinder
  • 21,291
  • 15
  • 93
  • 123