Swift numerics et CGFloat (CGPoint, CGRect, etc.)
Je trouve Swift numerics particulièrement maladroit quand, comme cela arrive si souvent dans la vraie vie, je dois communiquer avec Cocoa Touch en ce qui concerne CGRect et CGPoint (par exemple, parce que nous parlons de quelque chose frame
ou bounds
).
CGFloat vs. Double
Considérez le code innocent suivant d'une sous-classe UIViewController:
let scale = 2.0
let r = self.view.bounds
var r2 = CGRect()
r2.size.width = r.size.width * scale
Ce code échoue à compiler, avec l'erreur mystérieuse habituelle sur la dernière ligne:
Impossible de trouver un surcharge pour ' * ' qui accepte les arguments fournis
Cette erreur, comme je suis sûr que vous le savez maintenant, indique une sorte de discordance d'impédance entre les types. r.size.width
arrive en tant que CGFloat, qui échangera automatiquement avec un flotteur Swift mais ne peut pas interopérer avec une variable double Swift (qui, par défaut, est ce que scale
est).
L'exemple est artificiellement bref, donc il y a une solution artificiellement simple, qui est de lancer scale
dans un flotteur dès le départ. Mais quand de nombreuses variables tirées de partout sont impliquées dans le calcul des éléments D'un CGRect proposé, il y a beaucoup de casting à faire.
Initialiseur Verbeux
Une autre irritation est ce qui se passe quand vient le temps de créer un nouveau CGRect. Malgré la documentation, il n'y a pas d'initialiseur avec des valeurs mais sans étiquettes. Cela ne parvient pas à compiler parce que nous avons des Doubles:
let d = 2.0
var r3 = CGRect(d, d, d, d)
Mais même si nous lançons d
dans un Float, nous ne compilons pas:
Étiquettes D'argument manquantes 'x :y: width: height:' dans l'appel
Nous finissons donc par retomber sur CGRectMake
, ce qui n'est pas une amélioration sur Objective-C. et parfois CGRectMake et CGSizeMake ne sont pas une amélioration. Considérez ce code réel d'une de mes applications:
let kSEP : Float = 2.0
let intercellSpacing = CGSizeMake(kSEP, kSEP);
Dans un de mes projets, ça marche. Dans un autre, il échoue mystérieusement-exactement le même code! - avec cette erreur:
'NSNumber' n'est pas un sous-type de 'CGFloat'
C'est comme si, parfois, Swift essaie de "traverser le pont" en lançant un flotteur sur un NSNumber, ce qui est bien sûr la mauvaise chose à faire quand ce qui est de l'autre côté du pont attend un CGFloat. Je n'ai pas encore compris ce qu'est la différence entre les deux projets qui provoque l'erreur à apparaître dans l'un mais pas l'autre (peut-être quelqu'un d'autre).
NOTE: j'ai peut-être compris ce problème: il semble dépendre du paramètre Build Active Architecture Only build, qui à son tour suggère que c'est un problème 64 bits. Ce qui est logique, puisque Float ne correspondrait pas à CGFloat sur un périphérique 64 bits. Cela signifie que le problème d'incompatibilité d'impédance est encore pire que ce que je pensais.
Conclusion
Je cherche des mots pratiques de sagesse sur ce sujet. Je pense que quelqu'un a peut-être conçu une extension CGRect et CGPoint qui rendra la vie beaucoup plus facile. (Ou peut-être quelqu'un a écrit un chargement de surcharges de fonctions arithmétiques supplémentaires, telles que combiner CGFloat avec Int ou Double "fonctionne" - si c'est possible.)
3 réponses
J'ai écrit une bibliothèque qui gère la surcharge de l'opérateur pour permettre l'interaction entre Int, CGFloat et Double.
Https://github.com/seivan/ScalarArithmetic
À partir de la version bêta 5, Voici une liste de choses que vous ne pouvez actuellement pas faire avec Vanilla Swift. https://github.com/seivan/ScalarArithmetic#sample
Je suggère d'exécuter la suite de tests avec et sans Scalarithmetic juste pour voir ce qui se passe.
Tapant scale
à CGFloat
, comme vous l'avez découvert, est en effet la façon de gérer le problème de frappe rapide. Pour référence pour d'autres:
let scale: CGFloat = 2.0
let r = self.view.bounds
var r2 = CGRect()
r2.size.width = r.width * scale
Ne savez Pas comment répondre à votre deuxième question, vous pouvez poster séparément avec un titre différent.
Mise à Jour:
Le créateur et développeur principal de Swift Chris Lattner avait ceci à dire sur cette question sur le Apple Developer Forum {[13] } le 4 juillet 2014:
Ce qui se passe ici est que CGFloat est un typealias pour les flotteurs ou Double selon que vous construisez pour 32 ou 64 bits. C'est exactement ainsi que fonctionne Objective-C, mais cela pose problème dans Swift parce que Swift n'autorise pas les conversions implicites.
Nous sommes au courant de ce problème et le considérer comme sérieux: nous évaluons plusieurs différentes solutions en ce moment et en déploieront une dans une version bêta ultérieure. Comme vous le remarquez, vous pouvez faire face à cela aujourd'hui en lançant pour doubler. C'est inélégant, mais efficace :-)
Mise À Jour De Xcode 6 Bêta 5:
Un CGFloat peut être construit à partir de n'importe quel type entier (y compris taille types d'entiers) et vice-versa. (17670817)
J'ai créé une extension pour Double et Int qui leur ajoute une propriété CGFloatValue calculée.
extension Double {
var CGFloatValue: CGFloat {
get {
return CGFloat(self)
}
}
}
extension Int {
var CGFloatValue: CGFloat {
get {
return CGFloat(self)
}
}
}
Vous y accéderiez en utilisant let someCGFloat = someDoubleOrInt.CGFloatValue
En outre, comme pour votre initialiseur CGRect, vous obtenez l'erreur d'étiquettes d'argument manquante parce que vous avez laissé les étiquettes, vous avez besoin de CGRect(x: d, y: d, width: d, height: d)
vous ne pouvez pas laisser les étiquettes sauf s'il n'y a qu'un seul argument.