32560


swift初心者です。今開発しているアプリケーションはQRコードから現在地と目的地を取得し、ダイクストラで最短経路を求め案内をするというものなのですが、その途中でつまずいています。QRコードから読み取った文字列をNavigationクラスのNavigation関数に渡してその関数内でラベルに表示させたいのですがunexpectedly
found nil while unwrapping an Optional valueが出てしまいます。

ViewController.swiftで現在地と目的地をstartとgoalに格納しその値をstep1関数に渡して最短経路を求めています。step1関数が含まれているコードは割愛しています。それと同時にstとglに読み取った現在地と目的地をString型で格納しています。これをNavigationクラスのNavigation()関数に渡し、storyboard上のラベルに表示したいのですがエラーが出てしまい、迷宮入りしてます。どなたかご指摘、アドバイスお願いします

コードは以下の通りです

ViewController.swift

[code]

    import UIKit
    import AVFoundation
    class ViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
    
        @IBOutlet weak var CaptureView: UIView!
        @IBOutlet weak var Label: UILabel!
        @IBOutlet weak var Label2: UILabel!
        @IBOutlet weak var Caution: UILabel!
    
    
        var myVideoInput:AVCaptureDeviceInput!
        var myMetadataOutput:AVCaptureMetadataOutput!
        var myVideoLayer: AVCaptureVideoPreviewLayer!
        var start:Int = 0
        var goal:Int = 0
        var st:String = ""
        var gl:String = ""
        let mySession: AVCaptureSession! = AVCaptureSession()
        var myDevice: AVCaptureDevice!
        var a:Int = 0
        var route:[Int] = []
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            Caution.text = "スタート地を選択してください"
    
            myDevice = AVCaptureDevice.defaultDevice(withDeviceType: .builtInWideAngleCamera, mediaType: AVMediaTypeVideo, position: .back)
    
            do {
                myVideoInput = try AVCaptureDeviceInput(device:myDevice)
            } catch  {
                print(error)
            }
    
            if mySession.canAddInput(myVideoInput) {
                mySession.addInput(myVideoInput)
            }
    
            myMetadataOutput = AVCaptureMetadataOutput()
            mySession.addOutput(myMetadataOutput)
            myMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
            myMetadataOutput.metadataObjectTypes = [AVMetadataObjectTypeQRCode]
    
    
            self.myVideoLayer = AVCaptureVideoPreviewLayer(session: mySession) as AVCaptureVideoPreviewLayer
            self.myVideoLayer.frame = self.CaptureView.bounds
            self.myVideoLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
            self.CaptureView.layer.addSublayer(self.myVideoLayer)
    
            func viewDidLayoutSubviews() {
                super.viewDidLayoutSubviews()
    
                self.myVideoLayer.frame = self.CaptureView.bounds
            }
            // Viewに追加.
            self.view.layer.addSublayer(myVideoLayer)
    
            mySession.startRunning()
        }
    
        @IBAction func Start(_ sender: Any) {
            a = 1
            Caution.text = "QRコードをかざしてください"
        }
    
    
        @IBAction func Goal(_ sender: Any) {
            a = 2
            Caution.text = "QRコードをかざしてください"
        }
    
        func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
            if metadataObjects.count > 0 {
                let qrData: AVMetadataMachineReadableCodeObject  = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
                print(metadataObjects.flatMap { ($0 as AnyObject).stringValue })
                if a == 1{
                    Label.text = qrData.stringValue
                    start = Int(qrData.stringValue)!
                    st = qrData.stringValue
                    if start != 0 && goal == 0{
                        Caution.text = "目的地を選択してください"
                    }
                    else if start != 0 && goal != 0{
                        Caution.text = "案内開始を押してください"
                    }
                }
                else if a == 2{
                    Label2.text = qrData.stringValue
                    goal = Int(qrData.stringValue)!
                    gl = qrData.stringValue
                    if start == 0 && goal != 0{
                        Caution.text = "スタート地を選択してください"
                    }
                    else if start != 0 && goal != 0{
                        Caution.text = "案内開始を押してください"
                    }
                }
            }
    
        }
    
        @IBAction func GuideStart(_ sender: Any) {
            if start != 0 && goal != 0 && start != goal{
                performSegue(withIdentifier: "toVC2", sender: nil)
                let dijkstra = Step1()
                route = dijkstra.step1(start: start, goal: goal)
                print(route)
                print(start,goal)
                let NV = Navigation()
                NV.Navigation(cr: st, gl: gl)
            }
    
            else if start == goal{
                Caution.text = "同じ地点は選択できません"
            }
            else if start == 0 && goal != 0{
                Caution.text = "スタート地を選択してください"
            }
            else if start != 0 && goal == 0{
                Caution.text = "目的地を選択してください"
            }
        }
    Navigation.swift
    
    import UIKit
    import AVFoundation
    
    class Navigation : UIViewController {
        @IBOutlet weak var current: UILabel!
        @IBOutlet weak var goal: UILabel!
            func Navigation(cr:String, gl:String){
            current.text = cr
            goal.text = gl
            }
    }
    
[/code]

ストーリーボードの画面です。見えませんが、ちゃんとラベルとViewは配置しています  
[![ストーリボード](https://i.stack.imgur.com/xrC70.png)](https://i.stack.imgur.com/xrC70.png)  
エラー直前のスレッドです  
[![エラー直前のスレッド](https://i.stack.imgur.com/gmtjt.jpg)](https://i.stack.imgur.com/gmtjt.jpg)


CC BY-SA 3.0
2017-02-10T06:09:01.303
32553
2017-02-10T07:58:34.853
2017-02-10T07:10:05.773
76
19087
question
swift
QRコードで読み取った文字列を関数に渡すと unexpectedly found nil while unwrapping an Optional value エラー


問題なのは

[code]

    let NV = Navigation()
    NV.Navigation(cr: st, gl: gl)
    
[/code]

の部分です．

[code]

    class Navigation : UIViewController {
        @IBOutlet weak var current: UILabel!
        @IBOutlet weak var goal: UILabel!
    
        var cr: String
        var gl: String
    
        overrride func viewDidLoad() {
            current.text = cr
            goal.text = gl
        }
    }
    
[/code]

としていおいて，

`performSegue(withIdentifier: "toVC2", sender: nil)` の遷移であれば，  
Segue遷移時に呼ばれる下記のメソッドで処理すべきです．

[code]

    @IBAction func GuideStart(_ sender: Any) {
        if start != 0 && goal != 0 && start != goal{
            performSegue(withIdentifier: "toVC2", sender: nil)
            let dijkstra = Step1()
            route = dijkstra.step1(start: start, goal: goal)
            print(route)
            print(start,goal)
        }
        ~~ 省略 ~~
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let vc = segue.destination as? Navigation else { return }
        vc.cr = st
        vc.gl = gl
    }
    
[/code]

または，

[code]

    class ViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
        ~~ 省略 ~~
        @IBAction func GuideStart(_ sender: Any) {
            if start != 0 && goal != 0 && start != goal{
                let dijkstra = Step1()
                route = dijkstra.step1(start: start, goal: goal)
                print(route)
                print(start,goal)
                guard let vc = storyboard?.instantiateViewController(withIdentifier: "NavicationのStoryboardID") as? Navigation else { return }
                vc.cr = st
                vc.gl = gl
                navigationController?.pushViewController(self, animated: true)
            }
            ~~ 省略 ~~
        }
    }
    
[/code]

みたいにできます．

それぞれのメソッドとかは調べたらどういう動きをするのかわかると思います．


CC BY-SA 3.0
2017-02-10T07:34:44.947
32557
2017-02-10T07:34:44.947
20603
32553
answer


問題点は2点あります。

### 対象のインスタンスを間違えないようにする。

[code]

    @IBAction func GuideStart(_ sender: Any) {
        if start != 0 && goal != 0 && start != goal{
            performSegue(withIdentifier: "toVC2", sender: nil)
            let dijkstra = Step1()
            route = dijkstra.step1(start: start, goal: goal)
            print(route)
            print(start,goal)
            let NV = Navigation() // 新しくView Controllerのインスタンスを作っている。
            NV.Navigation(cr: st, gl: gl) // 遷移先のView Controllerに、メソッドを送っていない。
        }
        // 後略
    }
    
[/code]

たとえ話をします。質問者さんは、某企業の営業部長です。営業社員の田中くんに、顧客のB物産に出向くよう命令します。そして、営業社員の渡辺くんに、B物産に渡す資料を預けます。渡辺くんは、なんの資料かわからないが、重要書類なので、ゴミ箱に捨てるわけにもいかず、シュレッダーにかけます。そして田中くんは、頼んでいた資料を持たずに、手ぶらで来たと、顧客に怒鳴られて、帰ってきました。  
こんな、プログラムになっています。  
修正すると、このようなコードになります。

[code]

    @IBAction func GuideStart(_ sender: Any) {
        if start != 0 && goal != 0 && start != goal{
            performSegue(withIdentifier: "toVC2", sender: nil)
            let dijkstra = Step1()
            route = dijkstra.step1(start: start, goal: goal)
            print(route)
            print(start,goal)
        }
        // 後略
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "toVC2" {
            if let controler = segue.destination as? Navigation {
                controler.Navigation(cr: st, gl: gl)
            }
        }
    }
    
[/code]

メソッド`prepare(for segue:〜)`に遷移時の処理を記述するのは、定番の手法なので、覚えてください。

* * *

### View Controllerの生成時のステップを把握する。

View
Controllerは、複数のステップを踏んで、生成されます。イニシアライザによる生成や、Storyboardのセグエによる遷移の段階では、ボタンやラベルなどのビューがまだ生成されておらず、ビューに対して処理を加えようとすると、オブジェクトが`nil`であるというエラーになります。「unexpectedly
found nil while unwrapping an Optional value」は、ここで発生しています。  
ビューが確実に生成されたタイミングは、メソッド`viewDidLoad()`が呼ばれた時です。`Navigation`クラスをこのように書き換えます。

[code]

    class Navigation : UIViewController {
        @IBOutlet weak var current: UILabel!
        @IBOutlet weak var goal: UILabel!
    
        var currentText = ""
        var goalText = ""
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            current.text = currentText
            goal.text = goalText
        }
    
        func Navigation(cr:String, gl:String){
            currentText = cr
            goalText = gl
        }
    }
    
[/code]


CC BY-SA 3.0
2017-02-10T07:58:34.853
32560
2017-02-10T07:58:34.853
18540
32553
answer