Should conditional compilation be used to cope with difference in CGFloat on different architectures?
Matt,
Building on your solution, and if you use it in several places, then a little extension might make it more palatable:
extension CGFloat {
var ceil: CGFloat {
#if arch(x86_64) || arch(arm64)
return ceil(x)
#else
return ceilf(x)
#endif
}
}
The rest of the code will be cleaner:
var x = CGFloat(0.5)
x.ceil
How to determine if CGFloat is Float or Double
Documentation on Swift Floating-Point Numbers:
Floating-point types can represent a much wider range of values than
integer types, and can store numbers that are much larger or smaller
than can be stored in an Int. Swift provides two signed floating-point
number types:
- Double represents a 64-bit floating-point number.
- Float represents a 32-bit floating-point number.
You can test using the sizeof
function:
if sizeof(CGFloat) == sizeof(Double) {
// CGFloat is a Double
} else {
// CGFloat is a Float
}
Probably the easiest way to deal with this is to use conditional compilation to define a wrapper which will call the proper version:
import Accelerate
func getrf_(__m: UnsafeMutablePointer<__CLPK_integer>,
__n: UnsafeMutablePointer<__CLPK_integer>,
__a: UnsafeMutablePointer<CGFloat>,
__lda: UnsafeMutablePointer<__CLPK_integer>,
__ipiv: UnsafeMutablePointer<__CLPK_integer>,
__info: UnsafeMutablePointer<__CLPK_integer>) -> Int32 {
#if __LP64__ // CGFloat is Double on 64 bit archetecture
return dgetrf_(__m, __n, UnsafeMutablePointer<__CLPK_doublereal>(__a), __lda, __ipiv, __info)
#else
return sgetrf_(__m, __n, UnsafeMutablePointer<__CLPK_real>(__a), __lda, __ipiv, __info)
#endif
}
Convert CGFloat to CFloat in Swift
For some reason I needed to make an explicit typecast to CDouble
to make it work.
let roundedWidth = round(CDouble(CGRectGetWidth(self.bounds)))
I find this pretty strange since a CGFloat
is a CDouble
by definition. Seems like the compilator got a bit confused here for some reason.
Conversion between CGFloat and NSNumber without unnecessary promotion to Double
Update: One can cast a CGFloat
value to NSNumber
and back:
let c1 = CGFloat(12.3)
let num = c1 as NSNumber
let c2 = num as CGFloat
This preserves the precision of CGFloat
and works with Swift 2
and Swift 3.
(Previous answer – far too complicated): There are two solutions that I found. The first uses the toll-free bridging
between NSNumber
and CFNumber
(as in What is most common and correct practice to get a CGFloat from an NSNumber?
for Objective-C). It uses the fact that CFNumber
has a dedicated
conversion mode for CGFloat
values:
extension NSNumber {
// CGFloat -> NSNumber
class func numberWithCGFloat(var value: CGFloat) -> NSNumber {
return CFNumberCreate(nil , .CGFloatType, &value)
}
// NSNumber -> CGFloat
var cgFloatValue : CGFloat {
var value : CGFloat = 0
CFNumberGetValue(self, .CGFloatType, &value)
return value
}
}
That is simple and nice. The only drawback: I could not figure out
how to make the constructor an init
method instead of a class method
.
The second possible solution is a bit longer:
extension NSNumber {
// CGFloat -> NSNumber
private convenience init(doubleOrFloat d : Double) {
self.init(double : d)
}
private convenience init(doubleOrFloat f : Float) {
self.init(float : f)
}
convenience init(cgFloat : CGFloat) {
self.init(doubleOrFloat: cgFloat.native)
}
// NSNumber -> CGFloat
private func doubleOrFloatValue() -> Double {
return self.doubleValue
}
private func doubleOrFloatValue() -> Float {
return self.floatValue
}
var cgFloatValue : CGFloat {
return CGFloat(floatLiteral: doubleOrFloatValue())
}
}
There are two private "helper" init methods with the same external
parameter name doubleOrFloat
but different parameter types. From the actual
type of cgFloat.native
the compiler determines which one to call
in
convenience init(cgFloat : CGFloat) {
self.init(doubleOrFloat: cgFloat.native)
}
Same idea in the accessor method. From the type of self.native
the compiler determines which of the two doubleOrFloatValue()
methods to call in
var cgFloatValue : CGFloat {
return CGFloat(floatLiteral: doubleOrFloatValue())
}
Cannot find overload for '/', '*', Failure when running an app on anything but iPhone 5s simulator
Your problem is a difference between 32- and 64-bit architecture. Note that the target architecture you're compiling your target for is determined by the selected device—if you've got the iPhone 4S simulator selected as your target in Xcode, for example, you'll be building for 32 bit; if you've got the iPhone 5S simulator selected, you'll be building for 64-bit.
You haven't included enough code to help us figure out what exactly is going on (we'd need to know the types of the variable you're assigning to) but here's my theory. In your first error, sprite.speed is probably a CGFloat. CGFloat is 32-bit ("float") on 32-bit targets, 64-bit ("double") on 64-bit targets. So this, for example:
var x:CGFloat = Double(arc4random()) / 0x100000000
...will compile fine on a 64-bit target, because you're putting a double into a double. But when compiling for a 32-bit target, it'll give you the error that you're getting, because you're losing precision by trying to stuff a double into a float.
This will work on both:
var x:CGFloat = CGFloat(arc4random()) / 0x100000000
Your other errors are caused by the same issue (though again, I can't reproduce them accurately without knowing what type you've declared width
and height
as.) For example, this will fail to compile for a 32-bit architecture:
let lengthDiceroll = Double(arc4random()) / 0x100000000
let width:CGFloat = 5
var y:CGPoint = CGPointMake(width * lengthDiceroll, 0)
...because lengthDiceroll is a Double, so width * lengthDiceroll
is a Double. CGPointMake takes CGFloat arguments, so you're trying to stuff a Double (64-bit) into a float (32-bit.)
This will compile on both architectures:
let lengthDiceroll = Double(arc4random()) / 0x100000000
let width:CGFloat = 5
var y:CGPoint = CGPointMake(width * CGFloat(lengthDiceroll), 0)
...or possibly better, declare lengthDiceroll as CGFloat in the first place. It won't be as accurate on 32-bit architectures, but that's sort of the point of CGFloat:
let lengthDiceroll = CGFloat(arc4random()) / 0x100000000
let width:CGFloat = 5
var y:CGPoint = CGPointMake(width * lengthDiceroll, 0)
Round up a CGFloat in Swift
Update: Apple have now defined some CGFloat-specific versions of common functions like ceil
:
func ceil(x: CGFloat) -> CGFloat
...specifically to cope with the 32/64-bit difference. If you simply use ceil
with a CGFloat argument it should now work on all architectures.
My original answer:
This is pretty horrible, I think, but can anyone think of a better way? #if
doesn't seem to work for CGFLOAT_IS_DOUBLE
; I think you're limited to build configurations, from what I can see in the documentation for conditional compilation.
var x = CGFloat(0.5)
#if arch(x86_64) || arch(arm64)
var test = ceil(x)
#else
var test = ceilf(x)
#endif
iphone and iPad difference in Designing
I am assuming that you are opting for a universal executable, so conditional compilation is not an option for you.
When you make a universal executable, you should check the features that you are relying upon before making calls dependent on the device type. In this particular case you are relying upon the screen having a particular size. Instead of hard-coding the "magic numbers" (42, 15, 440, and 60) you should calculate them from the current size of the available screen:
CGFloat w = [UIScreen mainScreen].bounds.size.width;
CGFloat h = [UIScreen mainScreen].bounds.size.height;
// Do something like this if you can
UITextView *textview = [[UITextView alloc]
initWithFrame:CGRectMake(w*0.025, h*0.25, w*0.5, h*0.125)
];
There is a chance that calculating actual sizes from the screen size is not possible, because you do not want your view to scale proportionally to the screen. In cases like that you can check the values of h
and w
, detect the device size, and use the corresponding set of pre-defined sizes to init your view.
Related Topics
Default Value For Optional Generic Parameter in Swift Function
How to Get User Home Directory Path (Users/"User Name") Without Knowing the Username in Swift3
How to Create Custom Notifications in Swift 3
How to Check If Time Is Within a Specific Range in Swift
Swift: 'Hashable.Hashvalue' Is Deprecated as a Protocol Requirement;
Can't Programmatically Change Color Set in Storyboard as Color from Xcassets Catalog
Firebase: How to Update Multiple Nodes Transactionally? Swift 3
How to Save a Struct to Realm in Swift
Linker Command Failed with Exit Code 1 After Installing Cocoapods and Firebase Pod
Command Compileswiftsources Failed With a Nonzero Exit Code Xcode 10
Compile Error in Swift 4 on Parameter Passing
How to Create Swift Class for Category
Upvote/Downvote System Within Swift via Firebase
How to Use a @Fetchrequest with the New Searchable Modifier in Swiftui
Change Color Searchbar Result Icon Swift
Distinction in Swift Between Uppercase "Self" and Lowercase "Self"