37301


ViewControllerでbuttonのtagの中に得点ポイントを仕込んであり  
そのbuttonをタップすると、ViewControllerのLabelにポイント表示する、  
という簡単なものを作っています。  
buttonのあるセルはXIBファイルで作成し、使いまわせるようにしています。

現在、UITableViewCellのbuttonアクション中からViewControllerの@IBOutletを利用しようとすると
(今はdelegateで実行しています)  
なぜかすべての@IBOutletがnilになってしまっているため落ちてしまいます。  
どのようにすれば回避できるのでしょうか。よろしくお願いいたします。

■UITableViewCell

[code]

    import UIKit
    
    protocol Calk {
        func setPoint()
    }
    
    class BonusPointTableViewCell: UITableViewCell {
    
        @IBOutlet weak var pointImg: UIImageView!
        @IBOutlet weak var pointName: UILabel!
        @IBOutlet weak var addBtn: UIButton!
    
        var delegate: Calk!
    
        var ttvc = TopTableViewController()
    
        @IBAction func AddBtn(_ btn: UIButton) {
    
            let getPoint = Int(btn.tag)
            UserDefaults.standard.set(getPoint, forKey: "getPoint")
    
            self.delegate = ttvc
    
            delegate.setPoint()
        }
    
        override func awakeFromNib() {
            super.awakeFromNib()
            // Initialization code
        }
    
        override func setSelected(_ selected: Bool, animated: Bool) {
            super.setSelected(selected, animated: animated)
    
            // Configure the view for the selected state
        }
    
    }
    
[/code]

■ViewController ※一部省略しています

[code]

    import UIKit
    
    class TopTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, Calk {
    
        @IBOutlet var tableview: UITableView!
    
        @IBOutlet var dispPoint: UILabel!
    
        var myPoint: Int = 0
    
        var getPoint: Int = 0
    
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            tableview.dataSource = self
            tableview.delegate = self
    
    
            self.tableview?.register(UINib(nibName: "BonusPointTableViewCell", bundle: nil), forCellReuseIdentifier: "bonusPointCell")
    
        }
    
    
        /**
         * テーブル設定
         */
    
        func numberOfSections(in tableView: UITableView) -> Int {
            return 1
        }
    
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return bonusName.count
        }
    
        func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
            return 50
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
            let cell = tableView.dequeueReusableCell(withIdentifier: "bonusPointCell", for: indexPath) as! BonusPointTableViewCell
    
            for i in 0...bonusName.count {
                if (i == indexPath.row) {
                    // セルにポイント名を表示
                    cell.pointName?.text = bonusName[i][0]
                    // セルにポイント画像を表示
                    getImg(i: Int(bonusName[i][1])!, c: cell)
                    // セル内のボタンのタグに点数を埋め込んでおく
                    cell.addBtn.tag = Int(bonusName[i][1])!
                }
            }
    
            return cell
        }
    
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    
            let message = setMessage(mCode: bonusName[indexPath.row][2])
    
            let alert = UIAlertController(
                title: bonusName[indexPath.row][0],
                message: message,
                preferredStyle: .alert)
    
            alert.addAction(UIAlertAction(title: "OK", style: .cancel))
    
            self.present(alert, animated: true, completion: nil)
        }
    
    
        func setPoint() {
            getPoint = UserDefaults.standard.object(forKey:"getPoint") as! Int
            // ※ここでdispPointなどの@IBOutletがnilになってしまうため落ちます
            dispPoint.text = String(getPoint)    
    
        }
    
    }
    
[/code]

[![ViewControllerのメソッド実行したときの値](https://i.stack.imgur.com/e1k3P.png)](https://i.stack.imgur.com/e1k3P.png)


CC BY-SA 3.0
2017-08-18T03:43:58.357
37298
2017-08-18T05:48:57.050
2017-08-18T05:45:34.580
13972
23206
question
swift swift3 uitableview delegate xib
XIBで作られたセルのbuttonアクションからViewControllerの@IBOutletにアクセスするとnilになっている


すでに、kishikawa katsumi さんのコメントにもありますが、あなたのコードの中で今回のエラーの元凶と言えるのは、この行です。

[code]

    var ttvc = TopTableViewController()
    
[/code]

この行は、
**`TopTableViewController`のインスタンスを新たに作成し**、それを保持するプロパティ`ttvc`を宣言しています。そのような新しく作成されたインスタンスを`delegate`に設定しても期待の動作は行われません。`delegate`には
**現在表示を担当しているViewControllerを設定しないといけません** 。(※)

`TopTableViewController()`と言うイニシャライザはnib(.xibや.storyboard)の中身は無視して適当(ドキュメントにもきちんと説明されていない)にインスタンスを作成しますので、当然その中身の`@IBOutlet`とかの設定も行われない、従って`nil`になるのだと思ってください。

ただし、「Storyboard‌​からインスタンスを作」ればそれでいいのかと言うと、そんなことはなく、現在画面表示を担当しているインスタンスとは別に`TopTableViewController`のインスタンスを作成しても、`nil`でクラッシュしなくなるだけで現在の画面は更新されません。新しく作った`TopTableViewController`のインスタンスは現在画面表示を担当していないので、それが当然の動作ということになります。

上記のようなプロパティ宣言は、 **そこでインスタンスを作成するのが必要かつ適切である**
ことに絶対の自信がある場合にだけご使用ください。今回の場合は、不必要かつ不適切です。

* * *

まずは、`BonusPointTableViewCell`の方はこんな感じに定義を修正してください。

(削除すべき箇所がわかりやすくなるようコメントアウトして残してありますが、もちろん削除してしまって構いません。)

[code]

    import UIKit
    
    protocol Calk: class { //<-`delegate`をOptionalで宣言したいので、`: class`または`@objc`が必要
        func setPoint()
    }
    
    class BonusPointTableViewCell: UITableViewCell {
    
        @IBOutlet weak var pointImg: UIImageView!
        @IBOutlet weak var pointName: UILabel!
        @IBOutlet weak var addBtn: UIButton!
    
        weak var delegate: Calk? //<-`delegate`は必ずweak, nilになる可能性は常にあるので明示的にOptionalにする
    
        //### var ttvc = TopTableViewController() //<-こんなプロパティ宣言はNG
    
        @IBAction func AddBtn(_ btn: UIButton) {
    
            let getPoint = Int(btn.tag)
            UserDefaults.standard.set(getPoint, forKey: "getPoint")
    
            //### self.delegate = ttvc //<-delegateのメソッドを呼ぶときに自前でインスタンスを作ってはdelegateの意味無し
    
            delegate?.setPoint() //<-明示的Optionalなので、Optional Chainingを使う
        }
    
        override func awakeFromNib() {
            super.awakeFromNib()
            // Initialization code
        }
    
        override func setSelected(_ selected: Bool, animated: Bool) {
            super.setSelected(selected, animated: animated)
    
            // Configure the view for the selected state
        }
    
    }
    
[/code]

`TopTableViewController`の方は、メソッドを一つだけ修正すれば(とりあえずは)動くようになります。

[code]

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
        let cell = tableView.dequeueReusableCell(withIdentifier: "bonusPointCell", for: indexPath) as! BonusPointTableViewCell
    
        cell.delegate = self //### 現在表示を担当しているインスタンスを`delegate`に設定する
    
        //### `i == indexPath.row`の時にしか行われない処理に`for`を使う意味はない
        let bonus = bonusName[indexPath.row]
        // セルにポイント名を表示
        cell.pointName?.text = bonus[0]
        // セルにポイント画像を表示
        getImg(i: Int(bonus[1])!, c: cell) //<-パラメータラベルにはもっとわかりやすい名前をつけましょう
        // セル内のボタンのタグに点数を埋め込んでおく
        cell.addBtn.tag = Int(bonus[1])!
    
        return cell
    }
    
[/code]

* * *

他にも例えば、`bonusName`が`[[String]]`型として宣言してあるようですが、自前の`Bonus`型を宣言して、`bonusName`を`[Bonus]`型にしてしまえば、意味のわかりにくい`[0]`
`[1]`
`[2]`など使わなくて済むのに、なんてことがあるのですが、一度にあれこれ言うと消化不良になりそうなので、ここでは「コードの改善の余地がある」ことを指摘するだけにしておきます。

と言うわけで、とりあえず上記の修正、お試しください。

※今回の場合は、delegateメソッドを呼ぶ目的が、現在の画面の更新であるためです。他の目的のdelegateの場合は、その目的によりどのような条件のインスタンスを設定すべきかは異なります。


CC BY-SA 3.0
2017-08-18T05:37:46.007
37301
2017-08-18T05:48:57.050
2017-08-18T05:48:57.050
13972
13972
37298
answer