【Swift】エラーメッセージと対策まとめ①

プログラミングスクールテックキャンプでのレッスンは5のままあまり進んでいませんが、自作アプリも同時で開発を進めようと思いいろいろ取り組み始めました。

教材通りに進めるのではなくて自分で0からプロジェクトを作っていくことで早速様々なエラーに直面してうまく進んでいません。(汗

てことで現時点で直面したエラーとその意味、自分が見つけた解決策を備忘録として残しておこうと思いました。

swift Expected declaration

直訳すると「swiftは宣言が期待される」みたいな感じになると思うが意味が不明。

以下のような記述をすると最後のimageView.image = img のところでエラーが出た。

class ViewController: UIViewController {

    let img = UIImage(named: "panda2.png")
    let imageView = UIImageView()
    imageView.image = img

たぶんこの部分はクラスのプロパティやメソッドを書く部分だから宣言じゃない記述はしないでね、ということだと思う。

class ViewController: UIViewController {

    let img = UIImage(named: "panda2.png")
    let imageView = UIImageView()

    override func viewDidLoad() {
        super.viewDidLoad()
        imageView.image = img		//こっちに移動
   }
}

このようにメソッド内で代入の処理をしてあげればエラーが消えた。

もしくは以下のようにimageView2を宣言するやり方ならエラーが出なかった。

class ViewController: UIViewController {
    let imageView2 = UIImageView(image: UIImage(named: "panda2.png"))

Class 'ViewController’ has no initializers

訳は「ViewControllerクラスは初期化子がありません」だが、これも意味不明。

class ViewController: UIViewController {
	let a: Int
	var b: Int

こうやってaやbを宣言するとこのエラーが出る。これは変数が初期化されていませんよ、という意味なので値を入れてあげるか、Optional型として宣言すればよい。

aのほうはletで定数宣言なので値が不変ということを意味しているから以下のように初期値を与えないといけない。

let a: Int = 10

定数のaを以下のようにOptional型を宣言してはnilが入ってしまうのでエラーが消えない

let a: Int!
let a: Int?

bのほうはvarで変数として宣言している(値があとから変わっても良い)から、宣言の時点で初期値を与えてもいいし、Optional型で宣言してもよい。

var b: Int = 5
var b: Int!
var b: Int?

としておいてもこのエラーが消えてくれる。

UI部品を宣言した場合も「Class 'ViewController’ has no initializers」のエラーが出る。

class ViewController: UIViewController {
	let myButton1: UIButton
	let myButton2: UIButton!
	let myButton3: UIButton?

このいずれもエラー。

letの場合は初期化してインスタンスを生成しないといけない様子。以下のような形で初期化してインスタンスを生成してあげたらエラーがでなくなった。

let 変数名: クラス名 = クラス名の初期化メソッド

let 変数名 = クラス名の初期化メソッド

let myButton1: UIButton = UIButton(type: UIButtonType.system)			//これはエラー出ない
let myButton2 = UIButton(frame: CGRect(x: 0, y: 0, width: 150, height: 150))	//これはエラー出ない
let myButton3 = UIButton()							//これはエラー出ない

varの場合は!や?をつけてOptional宣言すれば一応はエラーは出ない。以下のような感じになる。

var myButton1: UIButton!	//これはエラー出ない
var myButton2: UIButton?	//これはエラー出ない
var myButton3: UIButton		//これはエラー

もちろんこのままでは使えないので、どこかでクラスのインスタンスを生成する必要があるから、あとのメソッド内で以下のような形でボタンを生成してあげることになる。

myButton1 = UIButton()
myButton2 = UIButton(type: UIButtonType.system)

あとInterface Builderを使って、オブジェクトを配置してそれをソースコードと紐づけをするOutlet接続をした場合は以下のように宣言が自動的に生成され、これは当然エラーが出ない。

    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var btn: UIButton!

Cannot subscript a value of type '[String]’ with an index of type 'UInt32’

これの訳は「タイプ '[String]’の値に 'UInt32’タイプのインデックスを添字することはできません」となって、これは意味がつうじる!

文字列型の配列の添字としてUInt32を使えませんよという意味です。例えば以下のようなコードを書くと起こります。

class ViewController: UIViewController {
    let animalNames: [String] = ["dog", "panda", "cat", "rat", "human"] //文字列型の配列を定義

    override func viewDidLoad() {
        super.viewDidLoad()
        let idx = arc4random() % UInt32(animalNames.count)	//ランダム関数をもちいて番号を決定
        let animalName = animalNames[idx]			//UInt32型のidxを添字に使うとエラー
    }
}

Stringの配列で要素の数字を入れようとすると、Intと型があわなくてエラーになってた。
それでUInt32からIntに型変換したらうまくいきました。

let animalName = animalNames[idx]	//UInt32型のidxを添字に使うとエラー
//↓
let animalName = animalNames[Int(idx)]	//UInt32型のidxをIntにキャストすればOK

Switch must be exhaustive

これを訳すと「switchは網羅的でなければならない」となる。このエラーに直面したのはswitch文のときだからすぐエラー内容に勘付きました。

最初に記述していたのはこのような感じ。

switch(a) {
case 0:
   print("aは0です")
case 1:
   print("aは1です")
}

switch文にはdefaultを設定しないといけない、ということだから。

switch(a) {
case 0:
   print("aは0です")
case 1:
   print("aは1です")
default:
   print("breakします")
   break
}

このようにdefaultの条件を入れれば「Switch must be exhaustive」のエラーは出なくなった。

Cannot assign to property: 'a’ is a 'let’ constant

Cannot assign to value: 'a’ is a 'let’ constant

訳は「プロパティに値を割り当てることはできません: 'a’は 'let’定数です」となる。let付きで宣言した変数は「定数」となるので値が変わることを許されない。

例えば以下のようにaを10と決めたのに、あとから3を代入するようなことはできないと理解している。

let a: Int = 10
a = 3

けど、クラスのインスタンスをletで宣言する場合は値を変更してもエラーにならないのは不思議。

let myButton3 = UIButton()
myButton3.setTitle("ボタン1", for: UIControlState.highlighted)
myButton3.setTitle("ボタン2", for: UIControlState.focused)

このようにしても問題ない。myButton3というインスタンス自体はメモリで確保されてるから、その中身が変わってもいいよ、ということなのかもしれない。

以下のように配列を定数で定義した場合、その値を変えようとすると以下のエラーが出た。

   let animalNames: [String] = ["dog", "panda", "cat", "rat", "human"]
   animalNames[1] = "bird"

Cannot assign through subscript: 'animalNames’ is a 'let’ constant

訳は「animalNamesは定数なので、割当はできないよ。」という意味になるかと。

以上のことからクラスのインスタンスはletで定義して後からプロパティ値を変えてもよく、普通のIntやStringや配列などはletで定義したら後から値を変えてはいけない、ということがわかった。

Extraneous argument label 'title:’ in call

直訳すると「呼び出し中の余分な引数ラベル 'title:’」となるがなんとなく意味は通じる。

これは以下のようにUIButtonのタイトルを設定したときに起きたエラー。直訳すると引数のtitle:というラベルは要らないよ、ということです。

let myButton3 = UIButton()
myButton3.setTitle(title: "ボタン1", for: UIControlState.highlighted)

なので以下のようにするとエラーが消えました。

myButton3.setTitle("ボタン1", for: UIControlState.highlighted)

しかしswiftの引数の渡し方はめちゃくちゃクセがあって意味不明です。第1引数はラベル不要とか、第2引数からはラベルが必要とか、いちいちリファレンスを見たりしないといけないこともある。

でもXCodeの補完機能にまかせてナビしてもらえば入力直前でヒントが表示されるので、これは活用していきたい。


ということでエラーメッセージいくつかと解決案を書いてみました。これからアプリを作っていく上で他のエラーにも遭遇すると思います。その都度エラーのメモをここに残していく予定です。もしこの記事がお役に立てたら、今後もエラーメッセージを増やしていくのでブクマしておいてもらえると嬉しいです。