読者です 読者をやめる 読者になる 読者になる

叉焼.log

札幌のこととか、ITの事とか

【Xcode8.3.1,swift3.1】異なるビュー間での値の受け渡し方法(Segue不使用)

どうも。叉焼です。以前からBluetoothを用いてiPhoneから操作できるラジコンを作っていたのですが何せ初めてのiPhoneアプリ開発だったもんでなかなか苦労しています。特に躓いたのがビュー間での値受け渡しでしたのでここにメモっておきます。

躓いていたところ

まず何に躓いていたか説明します。同じ症状の人がいるかもしれないので。
StoryBoardとソースコードを両方活用して開発をしていました。ソースコードだけでなんとかしたかったんですが(保守性も上がるし)毎回毎回ボタンを作って位置調整とかにうんざりしてたのでStoryBoardでボタンやスライダを配置してからソースコードと接続するというやり方を取っていました。そこで苦労したのがビュー間での値の受け渡しです。実例を書くと長すぎるので類似した例をあげると・・・

ViewController.swift
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    //ボタン押下で次のビューに移動
    @IBAction func pushButton(_ sender: Any) {
        goToNextPage()
    }
    
    func goToNextPage(){
        //次のビューのインスタンスを生成し値を渡す。
        let secondView = SecondViewController()
        secondView.setText("Hello")
        //移動先ビューコントローラーを参照
        let nextVC = self.storyboard?.instantiateViewController(withIdentifier:"secondView")
        //シーンの移動
        present(nextVC!,animated:false,completion: nil)
    }
}
SecondViewController.swift
import UIKit

class SecondViewController: UIViewController {
    var myText:String!

    override func viewDidLoad() {
        super.viewDidLoad()
        print("viewDidLoad" + myText)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    //前のビューから値を受け取る
    func setText(_ str:String){
        myText = str
        print("setText:"+myText)
    }
}
StoryBoard

f:id:cha-shu00:20170415143921p:plain

ViewControllerからSecondViewControllerへ”Hello”という文字列を渡しています。SecondViewControllerクラスはCocoa Touchで作りStoryBoardとリンクしています。こいつを実行すると以下のように出力されてprint("viewDidLoad"+myText)のところで強制終了します。

setText:Hello
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb) 

エラーメッセージを見る限り、setTextメソッドではmyTextに値がセットされているみたいです。ところがどっこいそれ以降に実行される文ではmyTextはnilになってしまいます。このコードを書いていた時はAppDelegateの役目をいまいち理解していなかったので(今も)こんなミスを犯していました。

解決法

お待たせしました。解決方法です。上述の通り、AppDelegateを利用します。このクラスはアプリ全体のライフサイクルを管理したり、ビューが持つ値を保持したりできます。

AppDelegate.swift
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var message:String?
    /*以下デフォルトのまま*/
}
ViewController.swift
import UIKit

class ViewController: UIViewController {
/*以上先程と同様*/
  func goToNextPage(){
      let appDelegate = UIApplication.shared.delegate as! AppDelegate
      appDelegate.message = "Hello"
      let nextVC = self.storyboard?.instantiateViewController(withIdentifier:"secondView")
      present(nextVC!,animated:false,completion: nil)
  }
}
SecondViewController.swift
import UIKit

class SecondViewController: UIViewController {
    var myText:String!

    override func viewDidLoad() {
        super.viewDidLoad()
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        myText = appDelegate.message
        print(myText)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

出力が正しく行われました。注意したいのは、ViewController.swiftでAppDelegateインスタンスを生成して値を渡す処理をする前に、ビューの移動処理(SecondViewControllerのインスタンス化)を書いてはならないということです。もしそうすると、SecondViewControllerのインスタンスが生成されると早い段階でそれのviewDidLoad()が呼び出されるため、AppDelegateのプロパティに値がセットされる前にviewDidLoad()からアクセスされる可能性があるからです。

次回はSegueを使った方法も書こうと思います〜。