

セクションに日付、セルにチェック項目を表示させるテーブルビューを作成中、tableDataの定義のところに「Cannot use instance
member 'checkListItem1' within property initializer; property initializers run
before 'self' is
available」というエラーが出てしまいました。これを解決するには、どのようにすれば良いのでしょうか。ご回答に心から感謝いたします。

[code]

        　import UIKit
    
    　　　　class ViewController : UIViewController, UITableViewDelegate, UITableViewDataSource {
    
      ////
        func numberOfSections(in tableView: UITableView) -> Int // Default is 1 if not implemented
        { return sectionTitle.count
        }
     ////
        func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? // fixed font style. use custom view (UILabel) if you want something different
        {
            return sectionTitle[section]
        }
    
    
        // ステータスバーの高さ、上にカレンダーかデイトピッカーを置く。
        let statusBarHeight = UIApplication.shared.statusBarFrame.height + 200
    
        //セクションの項目
        var sectionTitle = ["H28,01,23","H27,12,31","H28,01,12","H28,02,21","H28,11,10"]
    
    
        // チェックリストの項目とチェック状態
        var checkListItem1: [String : Bool] = [
            "アイテム1" : true,
            "アイテム2" : false,
            "アイテム3" : true,
            "アイテム4" : true,
            "アイテム5" : false
        ]
        var checkListItem2: [String : Bool] = [
            "アイテム2-1" : false,
            "アイテム2-2" : true,
            "アイテム2-3" : true,
            "アイテム2-4" : true,
            "アイテム2-5" : false
        ]
        var checkListItem3: [String : Bool] = [
            "アイテム3-1" : true,
            "アイテム3-2" : true,
            "アイテム3-3" : true,
            "アイテム3-4" : true,
            "アイテム3-5" : false
        ]
        var checkListItem4: [String : Bool] = [
            "アイテム4-1" : true,
            "アイテム4-2" : false,
            "アイテム4-3" : true,
            "アイテム4-4" : false,
            "アイテム4-5" : false
        ]
        var checkListItem5: [String : Bool] = [
            "アイテム5-1" : true,
            "アイテム5-2" : false,
            "アイテム5-3" : true,
            "アイテム5-4" : true,
            "アイテム5-5" : true
        ]
        var tableData = [checkListItem1, checkListItem2, checkListItem3, checkListItem4, checkListItem5]
    
        let tableView = UITableView()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // UITableView の作成
            tableView.frame = CGRect(
                x: 0,
                y: statusBarHeight,
                width: self.view.frame.width,
                height: self.view.frame.height - statusBarHeight
            )
            tableView.delegate = self
            tableView.dataSource = self
            self.view.addSubview(tableView)
        }
    
        // セルの作成
        //
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
            // Dictonary のキーの配列を取得
            var keys = [String](tableData.keys)
    
            // キーで並び替え
            keys.sort()
    
            // キーの文字列を取得
            let cellText = keys[indexPath.row]
    
            // セルの作成とテキストの設定
            let cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
            cell.textLabel?.text = cellText
    
            ///
            let sectionData = tableData[(indexPath as NSIndexPath).section]
            let cellData = sectionData[(indexPath as NSIndexPath).row]
    
    
    
            // チェック状態が true なら、初めからチェック状態にする
            if self.tableData[cellText]! {
                cell.imageView?.image = UIImage(named: "checked")
            } else {
                cell.imageView?.image = UIImage(named: "unchecked")
            }
    
            return cell
        }
    
        // セルがタップされた時の処理
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    
            if let cell = tableView.cellForRow(at: indexPath) {
    
                // タップしたセルのテキストを取得
                let cellText = cell.textLabel?.text
    
                // 画像を切り替えと Dictonary の値を変更
                if cell.imageView?.image == UIImage(named: "checked") {
    
                    self.tableData.updateValue(false, forKey: cellText!)
                    cell.imageView?.image = UIImage(named: "unchecked")
                } else {
    
                    self.tableData.updateValue(true, forKey: cellText!)
                    cell.imageView?.image = UIImage(named: "checked")
                }
    
                // 選択状態を解除
                cell.isSelected = false
            }
        }
    
    　　　　//    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    　　　　//        return 56
    　　　　//    }
    　　　 ///
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            let sectionData = tableData[section]
            return self.tableData.count
        }    
    }
    
[/code]


CC BY-SA 3.0
2016-12-22T13:55:08.873
31318
2017-01-26T09:42:50.343
2016-12-22T21:52:29.610
13657
13657
question
swift swift3
テーブルビューを作成中の「Cannot use instance member....」エラー


簡単に言うと、Swiftではインスタンスプロパティの初期化式に他のインスタンス変数(全て`self`の参照、とみなされます)を使用することはできません。

* * *

### 原因となるSwift言語独特の事情

詳しくはSwift本([The Swift Programming Language (Swift
3.0.1)](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/index.html#//apple_ref/doc/uid/TP40014097))の[こちら](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID216)を熟読してもらわないといけないのですが、長文の英語なので要点をまとめると、Swiftの(クラス)イニシャライザでは、次のことを指定の順序で行わないといけません。

  1. 自クラスで導入した全てのインスタンスプロパティに初期値を与える

(プロパティに初期化式を与えてもいいし、自前の`init`の中で代入しても良い)

  2. イニシャライザの中でスーパークラスの指定イニシャライザを呼ぶ

(イニシャライザを継承すると自動的に呼んでくれるし、自前のイニシャライザの中で明示的に`super.init(...)`で呼んでも良い)

とここまでは、他のオブジェクト指向言語にも似たような規則があるわけですが、Swiftではここにさらに厳しい制約があって、

  * **上記の1.2.が済むまでは`self`にアクセスしてはいけない**

と言うことになっています。従ってイニシャライザよりも前に評価される初期化式の中では、(`self`経由でアクセスされる)他のインスタンスプロパティを使うこともできなければ、インスタンスメソッドを呼ぶこともできない、と言うことになります。

(ちなみに上の1.2.がSwift用語で言う「2フェーズイニシャライズ」のフェーズ1と言うことになっています。)

* * *

### 対処方法

従って他のインスタンスプロパティの値を使用するような(あるいはインスタンスメソッドの結果を使用するような)値で初期化したい場合は、以下のような対応方法があります。

  1. 自前のイニシャライザの中で`super.init(...)`を読んだ後に値を与える

上記の事情により初期値を与えないと`super.init(...)`は呼べませんから、初期化式等で仮の値を与え(場合によってはそのために変数のデータ型をImplicitly
Unwrapped Optionalにしないといけないかもしれません)、それを`super.init(...)`の後で書き換える、と言う形になります。

あなたのコードのように普通は自前のイニシャライザを定義したりしないViewControllerの場合には、イニシャライザを定義するだけで面倒なので、このパターンは使いにくいかもしれません。開き直って、`viewDidLoad()`の中で初期化すると言うのは大変よく見られるパターンです。

  2. `lazy`変数を使用する

上記の制約からは例外に当たるのですが、`lazy`宣言されたインスタンスプロパティについては、初期化式の中で、他のインスタンスプロパティを含むことができます。`lazy`プロパティの初期化は、「初めてそのプロパティが参照される直前(従ってイニシャライザの実行より後)に実行される」「`super.init(...)`の前には実際には初期化されていないのだが、参照する際には必ず初期化されるので、未初期化のままの値が使われる可能性はない」ことから例外扱いされているのでしょう。

  3. 参照されているプロパティをクラス定数かトップレベル定数にしてしまう

クラスプロパティやトップレベルの変数・定数はクラスのイニシャライザが呼ばれるより前に初期化が完了しているので、クラス定義の中でインスタンスプロパティの初期値として問題なく使えます。

* * *

### あなたのコードの場合は

* * *

1.の「`viewDidLoad()`の中で初期化する」を採用するのであれば、`tableData`のプロパティ宣言は

[code]

    var tableData: [[String: Bool]] = []
    
[/code]

のようにしておいて、

[code]

    override func viewDidLoad() {
        super.viewDidLoad()
        tableData = [checkListItem1, checkListItem2, checkListItem3, checkListItem4, checkListItem5]
    
        // UITableView の作成
        tableView.frame = CGRect(
            x: 0,
            y: statusBarHeight,
            width: self.view.frame.width,
            height: self.view.frame.height - statusBarHeight
        )
        tableView.delegate = self
        tableView.dataSource = self
        self.view.addSubview(tableView)
    }
    
[/code]

のようになるでしょうか。

* * *

2.の`lazy`を使用するなら、書き換えるのはこの1行になります。

[code]

    lazy var tableData: [[String: Bool]] = [self.checkListItem1, self.checkListItem2, self.checkListItem3, self.checkListItem4, self.checkListItem5]
    
[/code]

各プロパティの参照に`self`が必要なのは、`lazy`プロパティの初期化式が実はクロージャーの一種ということを考えれば納得がいくのですが、`self.`を付け忘れた時のエラーメッセージがいまいちわかりにくいです。ご自身で確かめてください。

* * *

3.のパターンでも書き換えができますね。`checkListItem1`〜`checkListItem5`をクラスの外に追い出してトップレベル定数としてしまいます。

[code]

    // チェックリストの項目とチェック状態
    private let checkListItem1: [String : Bool] = [
        "アイテム1" : true,
        "アイテム2" : false,
        "アイテム3" : true,
        "アイテム4" : true,
        "アイテム5" : false
    ]
    private let checkListItem2: [String : Bool] = [
        //...
    ]
    private let checkListItem3: [String : Bool] = [
        //...
    ]
    private let checkListItem4: [String : Bool] = [
        //...
    ]
    private let checkListItem5: [String : Bool] = [
        //...
    ]
    class ViewController: UIViewController {
    
        //...
    
        var tableData = [checkListItem1, checkListItem2, checkListItem3, checkListItem4, checkListItem5]
    
        //...
    }
    
[/code]

チェック状態を表す`Bool`値は書き換えられるので`let`定数にはできないと思われるかもしれませんが、Swiftでは`Array`も`Dictionary`も値型なので、`tableData`には`checkListItem1`〜`checkListItem5`の内容が初期値としてコピーされるだけで、`tableData`さえ`var`で宣言されていれば、`checkListItem1`〜`checkListItem5`の方は`let`でも問題ありません。別クラスから`ViewController`のそれらインスタンスプロパティをいじる、なんてことはしないというのが前提ですが。

* * *

### さらに

確実で簡単なのは最初の方法でしょうか。お試しください。

で、表題のエラーを解消すると、あなたのコードの中に潜むエラーが芋づる式に顔を出すようですが、そちらはまた別件ということで、ご自身で解決できない部分が残ればまた別スレでご質問いただいた方がいいと思います。


CC BY-SA 3.0
2016-12-22T18:34:00.057
31320
2016-12-22T18:34:00.057
13972
31318
answer