52173


プラグロム初心者です。以下のようなプログラムでボタンをタップしてUILabelのテキストを変更しようとしています。class
ViewController内に変更用の関数を作成して変更は出来たのですが、クラス外の関数、ファイルから制御しようとすると、シミュレーターの起動まではするのですが、ボタンをタップしたところでエラーになってしまいます。エラーの原因もわからず

[code]

    import UIKit
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
            animalView.text = ""
        }
    
        @IBOutlet weak var animalView: UILabel!
    
        @IBAction func catButton(_ sender: Any) {
            let animal = Animal.cat
            animalChange(animal)
        }
        @IBAction func dogButton(_ sender: Any) {
            let animal = Animal.dog
            animalChange(animal)    }
        @IBAction func cowButton(_ sender: Any) {
            let animal = Animal.cow
            animalChange(animal)
        }
    
    }
    
    enum Animal{
        case cat
        case dog
        case cow
    }
    
    func animalChange(_ insAnimal: Animal){
        switch insAnimal{
        case.cat:
            print("ねこ")
            let catView = ViewController()
            catView.animalView.text = ""      ←ここでエラーになります。
        case.dog:
            print("いぬ")
    //        let dogView = ViewController()
    //        dogView.animalView.text = ""
        case.cow:
            print("うし")
    //        let cowView = ViewController()
    //        cowView.animalView.text = ""
        }
    
    }
    
[/code]


CC BY-SA 4.0
2019-01-21T06:53:16.263
52168
2019-01-21T11:35:53.950
2019-01-21T11:35:53.950
3060
31859
question
swift
class ViewControllerのUILabelをクラス外の関数や別ファイルから制御したい


一番問題なのは、この行です。

[code]

            let catView = ViewController()
    
[/code]

この宣言は`ViewController`の **新しい**
インスタンスを作成し、そのインスタンスで`catView`という変数を初期化します。(ちょっとしたことですが、iOSではviewとview
controllerは別物なので、view controllerを保持する変数を`〜View`という名前にするのはやめた方が良いです。)

`catView`に保持されている`ViewController`インスタンスは、クラスが同じというだけで実際にボタンを表示し、画面表示を担当していたインスタンスとは全くの別物です。また、iOSがstoryboardからインスタンス化した訳ではないので、IBOutletもIBActionもすべて接続されていません。

従って、`catView.animalView`は`nil`ですから、`catView.animalView.text = ""`の行で
**Unexpectedly Found Nil**
のエラーになるはずです。(ご質問を書かれる際に、エラーが出るのであればエラーメッセージをデバッグコンソールから拾ってきて、ご質問本文に含めるようにしてください。)

無理やり自分でIBOutletを設定しても、新しく作られた`ViewController`は画面表示を担当していないので、エラーにこそならないようにはできますが、画面には全く何も反映されない、なんてことが起こります。

* * *

このような場合、「実際に画面表示を担当している`ViewController`のインスタンスを渡してやる」ということが必要になります。

1つの方法として、 **`animalChange`関数に引数を追加してやる** と言う方法があります。

[code]

    func animalChange(_ vc: ViewController, _ insAnimal: Animal){
        switch insAnimal{
        case.cat:
            print("ねこ")
    //        let catView = ViewController() //←百害あって一利なし、すぐに削除してください
            vc.animalView.text = ""
        case.dog:
            print("いぬ")
            // let dogView = ViewController()
            vc.animalView.text = ""
        case.cow:
            print("うし")
            // let cowView = ViewController()
            vc.animalView.text = ""
        }
    }
    
[/code]

呼び出し側はこんな感じ。

[code]

        @IBAction func catButton(_ sender: Any) {
            let animal = Animal.cat
            animalChange(self, animal)
        }
        @IBAction func dogButton(_ sender: Any) {
            let animal = Animal.dog
            animalChange(self, animal)
        }
        @IBAction func cowButton(_ sender: Any) {
            let animal = Animal.cow
            animalChange(self, animal)
        }
    
[/code]

* * *

今のコードを今後どのように発展させるおつもりかわからないんですが、`UILabel`のようなUI要素をこのように`ViewController`の「外側」に書くというのは極めて異例です。わざわざ時間をとって変なやり方を覚える必要はないように思います。

コードを管理しやすい塊に分割する、程度の意味でクラス宣言の外側にメソッドを追加する場合、Swiftでは普通extensionと言うものを使います。

[code]

    extension ViewController {
        func animalChange(_ insAnimal: Animal){
            switch insAnimal{
            case.cat:
                print("ねこ")
                self.animalView.text = ""
            case.dog:
                print("いぬ")
                self.animalView.text = ""
            case.cow:
                print("うし")
                self.animalView.text = ""
            }
        }
    }
    
[/code]

extension内のインスタンスメソッド内では`self`が同じインスタンスを表しますから、「新しいインスタンスを作ってしまう」なんてことは起こらず、呼び出し側で`self`を渡すなんてことをしなくてもUIが確実に動作するようになります。

呼び出しは、あなたの元のコード通りでOKです。

[code]

        @IBAction func catButton(_ sender: Any) {
            let animal = Animal.cat
            animalChange(animal)
        }
        @IBAction func dogButton(_ sender: Any) {
            let animal = Animal.dog
            animalChange(animal)
        }
        @IBAction func cowButton(_ sender: Any) {
            let animal = Animal.cow
            animalChange(animal)
        }
    
[/code]

* * *

extensionなんて百も承知の上で、何かの練習をされていたのかもしれませんが、`let catView =
ViewController()`なんて書き方は、「画面表示には無関係でIBOutletもIBActionも接続されていない役立たずの別インスタンスを作ってしまう」
**やってはいけない** 書き方だと言うのは覚えておいて下さい。


CC BY-SA 4.0
2019-01-21T11:23:07.670
52173
2019-01-21T11:23:07.670
13972
52168
answer