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

59
demandé sur matt 2014-06-08 21:29:37

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.

15
répondu Seivan 2014-08-08 09:37:25

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)

24
répondu BergQuester 2014-08-04 17:22:53

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.

6
répondu Andrew97p 2014-06-21 15:03:03