表題の現象ですが、Swiftを始めたばかりの頃は結構遭遇するのではないかと思います。
私は以前は良くハマっていました。
今回はなぜ数字を変数にしただけでエラーになるかを書いていきます。
エラーになる状況
下のコードは普通に動きます。
10はマジックナンバーなのでmarginと言った変数にしたいとします。
let x = view.frame.size.width + 10
しかし変数にした途端にエラーになってしまいます。
let margin = 10
// 変数化しただけでエラー、辛い(˘ω˘)
let x = view.frame.size.width + margin // `Binary operator '+' cannot be applied to operands of type 'CGFloat' and 'Int'` エラー
なぜ変数にするとエラーになるのか
これはview.frame.size.widthとmarginの型が違うからです。
view.frame.size.widthはCGFloat型でmarginはInt型です。
SwiftではCGFloatとIntの演算はサポートされていないのでエラーとなります。
CGFloat(10) + Int(10) // エラーになる
view.frame.size.width + 10はなぜエラーにならないか
CGFloatとIntの演算はエラーになるという事なので、view.frame.size.width + 10も型が違ってエラーになりそうです。
しかし実際はエラーは発生しません。
これは10
は演算時にCGFloatに変換されるからです。
10
がCGFloatに変換される仕組み
10
がCGFloatに変換される仕組みですが、CGFloatが実装しているIntegerLiteralConvertibleというプロトコルが関係しています。
IntegerLiteralConvertibleを実装していると10
などのIntリテラルが自動でそのクラスに変換されるようになります。
CGFloatはIntegerLiteralConvertibleを実装しているので、10はCGFloatの10.0に変換されます。
Intリテラルがそのクラスに変換されるとは?
Intリテラルがそのクラスに変換されると言う所についてもう少し深掘りします。
下の実装は型が違うためにエラーが起きます。
class MyClass {}
let myClass: MyClass = 1 // MyClass型に1を入れようとするとエラー
しかしMyClassにIntegerLiteralConvertibleを付けるとエラーが起きなくなります。
class MyClass: IntegerLiteralConvertible {
typealias IntegerLiteralType = Int
required init(integerLiteral value: Int) {}
}
let myClass: MyClass = 1
これはIntegerLiteralConvertibleを実装した為、1(Intリテラル)が自動でMyClass(integerLiteral: 1)に変換された為です。
let myClass: MyClass = 1
// ↑ は ↓ のように変換される
// let myClass: MyClass = MyClass(integerLiteral: 1)
CGFloatも下のように変換されています。
let value: CGFloat = 1
// ↑ は ↓ のように変換される
// let value: CGFloat = CGFloat(integerLiteral: 1)
まとめ(frame.size.width + 10がエラーにならない理由)
つまりview.frame.size.width + 10という計算の時、10はCGFloat(integerLiteral: 10)になるので問題なく動いているという事です。
let x = view.frame.size.width + 10
// ↓ のように変換されているのでエラーにならない
// let x = view.frame.size.width + CGFloat(integerLiteral: 10)
逆に変数にしてしまうと、IntリテラルではなくInt型になるのでCGFloatへの自動変換は行われなくなります。
let margin = 10 // → marginはInt型
その為変数にする際はキャスト or 型宣言を付ける必要があります。
let margin: CGFloat = 10
参考URL