Swift数值计算和CGFloat(CGPoint、CGRect等)

61

当我需要与Cocoa Touch通信时,尤其是涉及到CGRect和CGPoint时(例如,因为我们正在谈论某个物体的frame或bounds),我发现Swift数值计算特别笨拙。

CGFloat vs. Double

考虑以下来自UIViewController子类的看似无害的代码:

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

这段代码无法编译,最后一行出现了通常的神秘错误:

找不到接受提供的参数的“*”的重载项

正如你现在已经知道的那样,这个错误表明类型之间存在某种阻抗不匹配。 r.size.width 是 CGFloat 类型,它可以自动地与 Swift Float 交换,但无法与 Swift Double 变量互操作(默认情况下,这就是 scale 的类型)。

这个示例人为地简化了问题,因此有一个人为简单的解决方案,即从一开始将 scale 转换为 Float 类型。但当涉及计算建议的 CGRect 的元素时,需要从各个位置提取许多变量,就需要进行大量的强制转换。

详细初始化器

另一个问题是创建新的 CGRect 时会发生什么。尽管有文档,但没有值但没有标签的初始化器。由于我们使用的是 Doubles,这无法编译:

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

但是,即使我们将d转换为浮点数,编译器仍然无法编译:

在调用中缺少参数标签“x:y:width:height:”

因此,我们最终退回到使用CGRectMake,这并没有改善Objective-C的情况。有时,CGRectMakeCGSizeMake也无法改善情况。考虑一下我某个应用程序中的实际代码:

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

在我的项目中,有一个是正常的。但在另一个项目中,完全相同的代码却神秘地失败了,并显示以下错误:

'NSNumber'不是'CGFloat'的子类型。

好像有时候Swift会尝试通过将Float转换为NSNumber来"跨越桥梁",而当桥梁另一侧期望的是CGFloat时,这显然是错误的。我还没有弄清楚两个项目之间造成错误出现和消失的区别(也许其他人已经知道了)。

注意:我可能已经找到了问题所在:它似乎取决于Build Active Architecture Only构建设置,这反过来又表明这是一个64位的问题。这很有道理,因为在64位设备上,Float不会匹配CGFloat。这意味着阻抗不匹配问题比我想象的要严重得多。

结论

我正在寻找有关此主题的实用智慧。我认为有人可能已经设计出了一些CGRect和CGPoint扩展,这将使生活变得更加轻松。(或者可能有人编写了大量额外的算术运算符函数重载,以便将CGFloat与Int或Double结合使用,如果可能的话,就可以"自动"进行转换。)


在 Xcode 6 beta 5 中:CGFloat 可以从任何整数类型(包括大小整数类型)构造,反之亦然。 (17670817) - BergQuester
NSTimeInterval 也存在同样的认知失调。 - Alex Brown
@AlexBrown NSTimeInterval仅仅是Double的另一个说法,因此这是一种重言。 - matt
确实,感谢指针。使用NSTimeIntervals作为CGFloat会遇到与使用Double(或Float)作为CGFloat相同的问题。 - Alex Brown
随着https://github.com/apple/swift-evolution/blob/main/proposals/0307-allow-interchangeable-use-of-double-cgfloat-types.md的实现,这变得更容易了一些。 - Martin R
显示剩余4条评论
3个回答

24

明确将 scale 类型指定为 CGFloat,正如您所发现的那样,这确实是在 Swift 中处理类型问题的方法。供他人参考:

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

对于您的第二个问题,我不确定如何回答,建议您使用不同的标题单独发布。

更新:

2014年7月4日,Swift创始人和主要开发者Chris Lattner在Apple Developer Forum上针对此问题发表了以下观点:

CGFloat是一个类型别名,根据您构建32位或64位来表示float或double。这正是Objective-C的工作方式,但在Swift中存在问题,因为Swift不允许隐式转换。

我们意识到这个问题并认为它很严重:我们正在评估几种不同的解决方案,并将在以后的Beta版中推出其中一种。正如您所注意到的,您可以通过将其强制转换为Double来处理此问题。这虽然不太优雅但是有效的 :-)

Xcode 6 Beta 5中的更新:

CGFloat可以从任何整数类型(包括大小整数类型)构造,反之亦然。(17670817)


1
个人偏好不提,这就是 Swift 中处理打字问题的方式。苹果正在寻求有关该语言的反馈,因此我相信他们会愿意收到一些关于此问题的反馈。 - BergQuester
1
当使用 CGFloat 时,您必须将所有内容输入为 CGFloat。很简单。 - Sulthan
5
@Sulthan 是的,但问题在于当你说“everything”时,你真正意思是“所有的东西”。例如,如果 f 是一个 CGFloat,而 d 是一个 Double(这是默认值),你不能将它们相乘。因此,正如我在问题中所说的那样,需要进行许多强制类型转换。但这很疯狂。 - matt
3
@Sulthan 我已经提交了一个bug。在Objective-C中,一旦某个东西是CGFloat类型,所有数字操作都可以正常工作,并且与CGRect和CGPoint的信息交换在32位和64位平台上也可以正常运行。如果没有这种易用性,对于iOS编程中最常见的需求之一来说,这种语言将会非常令人望而生畏。 - matt
4
@Sulthan 我并没有说这是个漏洞,我说我提交了一个漏洞报告。如果这门语言一直这么挑剔的话,没人会想使用它的。不要误会,与Objective-C相比,Swift有很多优点。但这不是那样的情况。为什么不争取更好呢?苹果应该有更高的追求。 - matt
显示剩余4条评论

15

3
苹果公司应将此内容纳入语言中。 - matt
@SSS,你误解了Chris所说的话。他们正在评估解决方案,我不认为他们会添加隐式转换。 - Sulthan
@Sulthan 谢谢,现在更有意义了。 - Seivan
在 Xcode 6 beta 5 中:可以从任何整数类型(包括大小整数类型)构造 CGFloat,反之亦然。 (17670817) - BergQuester
截至2015年4月6日,该项目似乎已被放弃。最后一次提交是8个月前,而且它不能在Xcode 6.2中编译。 - Orlin Georgiev
@OrlinGeorgiev 现在已经修复了。等待语言设计师获得更多反馈。 - Seivan

6
我创建了一个Double和Int的扩展,为它们添加了一个计算的CGFloatValue属性。
extension Double {
    var CGFloatValue: CGFloat {
        get {
            return CGFloat(self)
        }
    }
}
extension Int {
    var CGFloatValue: CGFloat {
        get {
            return CGFloat(self)
        }
    }
}

您可以使用 let someCGFloat = someDoubleOrInt.CGFloatValue 访问它。

此外,关于您的CGRect初始化器,您会收到缺少参数标签错误,因为您已省略了标签,您需要使用 CGRect(x: d, y: d, width: d, height: d),除非只有一个参数,否则不能省略标签。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接