質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

2009閲覧

画面遷移を繰り返すとエラーが発生する

bacchi

総合スコア13

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2016/12/09 03:08

###前提・実現したいこと
UITableViewでセルを選択して画面遷移すると選択したセルに該当する星座の地平座標(現在時刻・現在位置から見たときの方位角と高度)を算出し、スマホを空にかざすと画面に表示された矢印がその方向を示してくれるというようなアプリを作っています。

遷移後の画面から星座選択の画面に戻り、また星座を選択して画面遷移する、ということを1、2回繰り返しているとアプリが落ちて以下のようなエラーが出ていました。

何度も画面を行ったり来たりしてきちんと矢印の図を表示するところまで出来るようにしたいです。

UITableViewで選択した星座の地平座標を算出するために日時と位置情報を取得し、さらに加速度センサーと方位センサー(電子コンパス)を使用しています。

文字数制限のためソースコードを大幅に省略していてわかりにくいと思いますがアドバイス頂けると助かります。

###発生している問題・エラーメッセージ

unexpectedly found nil while unwrapping an Optional value

見てみると、スマホの傾き(a)がnilになっていました。

###SecondViewController.swift(星座選択画面)

Swift

1import UIKit 2 3class SecondViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 4 5 @IBOutlet weak var Label2: UILabel! 6 7 @IBOutlet weak var tableView: UITableView! 8 9 // Tableで使用する配列を設定する 10 private let myItems: NSArray = ["いて座", "うお座", "おうし座", "おおいぬ座", "おおぐま座", "おとめ座", "おひつじ座", "オリオン座", "カシオペア座", "かに座", "こいぬ座", "こぐま座", "こと座", "さそり座", "しし座", "てんびん座", "はくちょう座", "ふたご座", "みずがめ座", "やぎ座", "わし座"] 11 12 13 override func viewDidLoad() { 14 super.viewDidLoad() 15 16 Label2.text = "探したい星座を選ぼう!" 17 } 18 19 override func didReceiveMemoryWarning() { 20 super.didReceiveMemoryWarning() 21 // Dispose of any resources that can be recreated. 22 } 23 24 25 override func viewWillAppear(_ animated: Bool) { 26 super.viewWillAppear(animated) 27 28 if let indexPathForSelectedRow = tableView.indexPathForSelectedRow { 29 tableView.deselectRow(at: indexPathForSelectedRow, animated: true) 30 } 31 } 32 33 override func viewDidAppear(_ animated: Bool) { 34 super.viewDidAppear(animated) 35 if let indexPathForSelectedRow = tableView.indexPathForSelectedRow { 36 tableView.deselectRow(at: indexPathForSelectedRow, animated: true) 37 } 38 } 39 40 41 // Cellの総数を返す. 42 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 43 return myItems.count 44 } 45 46 // Cellに値を設定する 47 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 48 // 再利用するCellを取得する. 49 let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath as IndexPath) 50 51 // Cellに値を設定する. 52 cell.textLabel!.text = "\(myItems[indexPath.row])" 53 54 return cell 55 } 56 57 // Cellが選択された際に呼び出される 58 func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 59 print("Num: \(indexPath.row)") 60 print("Value: \(myItems[indexPath.row])") 61 62 let storyboard: UIStoryboard = self.storyboard! 63 let nextView = storyboard.instantiateViewController(withIdentifier: "third") as! ThirdViewController 64 65 let seizaNum = indexPath.row 66 67 let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate 68 appDelegate.seizaNum = seizaNum 69 70 self.present(nextView, animated: true, completion: nil) 71 } 72 73 @IBAction func backToSecond(segue: UIStoryboardSegue) {} 74 75 /* 76 // MARK: - Navigation 77 78 // In a storyboard-based application, you will often want to do a little preparation before navigation 79 override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 80 // Get the new view controller using segue.destinationViewController. 81 // Pass the selected object to the new view controller. 82 } 83 */ 84} 85

###ThirdViewController.swift(画面遷移後)

Swift

1import UIKit 2import CoreMotion 3import CoreLocation 4 5 6class ThirdViewController: UIViewController, CLLocationManagerDelegate { 7 8 // 座標(0,0)の位置にUIImageViewを幅300,高さ300で作成する 9 let myImageView = UIImageView(frame: CGRect(x:0, y:0, width:300, height:300)) 10 11 var myMotionManager: CMMotionManager! 12 var locationMgr: CLLocationManager! 13 14 var a: Double! 15 var MJD2: Double! 16 var δ: Double! 17 var α: Double! 18 var deg_A2: Double! 19 var deg_h: Double! 20 var difY: Double! 21 22 override func viewDidLoad() { 23 super.viewDidLoad() 24 25 let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate 26 let seizaNum = appDelegate.seizaNum 27 28 // 探したい星の赤緯(δ[度])、赤経(α[時]) 29 // いて座 30 if seizaNum == 0 { 31 self.δ = -25.0 32 self.α = 19.0 33 34 Label3.text = "いて座を探しています..." 35 } 36 // うお座 37 else if seizaNum == 1 { 38 self.δ = 15.0 39 self.α = 1.0 40 41 Label3.text = "うお座を探しています..." 42 } 43 /* 44 省略 45 */ 46 47 48 // 現在時刻を取得 49 let myDate: Date = Date() 50 51 // カレンダーを取得 52 let myCalendar: NSCalendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian)! 53 54 /* 55 省略 56 */ 57 58 // 準ユリウス日(MJD2)を求める 59 let Y = Double(myComponetns.year!) 60 let M = Double(myComponetns.month!) 61 let D = Double(myComponetns.day!) 62 let hour = Double(myComponetns.hour!) 63 /* 64 省略 65 */ 66 67 locationMgr = CLLocationManager() 68 69 locationMgr.delegate = self 70 71 // 位置情報の精度を設定 72 locationMgr.desiredAccuracy = kCLLocationAccuracyBest 73 locationMgr.distanceFilter = 100 74 75 locationMgr.startUpdatingLocation() 76 77 78 // 何度動いたら角度の変更が通知されるかを指定する(この場合動くたび) 79 locationMgr.headingFilter = kCLHeadingFilterNone 80 81 // デバイスのどの方向を北にするかを設定する(この場合デバイスの上部) 82 locationMgr.headingOrientation = CLDeviceOrientation.portrait 83 84 // センサーからの通知を受け取れるようにする 85 locationMgr.startUpdatingHeading() 86 87 myMotionManager = CMMotionManager() 88 89 // 更新周期を設定 90 myMotionManager.accelerometerUpdateInterval = 0.1 91 92 // 加速度の取得を開始 93 myMotionManager.startAccelerometerUpdates(to: OperationQueue.main, withHandler: {(accelerometerData, error) in 94 if let e = error { 95 print(e.localizedDescription) 96 return 97 } 98 guard let data = accelerometerData else { 99 return 100 } 101 102 // 取得した加速度を傾き(ラジアン値)に変換 103 let a_radian = -atan(data.acceleration.y/sqrt(pow(data.acceleration.x,2) + pow(data.acceleration.z,2))) 104 105 // ラジアンを度に変換 106 self.a = fabs(a_radian*(180 / M_PI) - 90.0) // スマホの傾きをaとする。立てた状態のスマホを直通する軸を0度にする(-90°)。絶対値をとる。 107 }) 108 } 109 110 111 // 位置情報が更新された場合の処理 112 func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { 113 114 // 観測地の緯度(Ψ[度])、経度(λ[時]) 115 let Ψ = manager.location!.coordinate.latitude 116 let λ = manager.location!.coordinate.longitude 117 let λ2 = -λ / 15 // 東経は-、西経は+。 度を時に変換する 118 119 // グリニッジ恒星時(θG[時])を求める 120 // 地方恒星時(θ[時])を求める 121 // 時角(H[度])を求める 122 // 度をラジアンに変換する 123 /* 124 省略 125 */ 126 127 // 方位角(deg_A2)と高度(h)を求める 128 let tanA = coshsinA / coshcosA 129 let A = atan(tanA) 130 let deg_A = A * (180.0 / M_PI) // ラジアンを度に変換する 131 132 // 象限ごとの場合分け 133 if coshsinA > 0 && coshcosA > 0 { // sinAは+、cosAも+ ⇨第1象限 134 self.deg_A2 = deg_A 135 136 // 高度(h) 137 let A2 = self.deg_A2 * (M_PI / 180.0) // 度をラジアンに変換する 138 let cosA = cos(A2) 139 let tanh = sinh / (coshcosA / cosA) 140 let h = atan(tanh) 141 self.deg_h = h * (180.0 / M_PI) // ラジアンを度に変換する 142 } 143 /* 144 省略 145 */ 146 } 147 148 149 // コンパスの角度に変化があった場合はlocationManagerメソッドが起動する 150 func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) { 151 152 // デバイスの現在の向きを変数(myHoui)で定義する 153 let myHoui: Double = newHeading.magneticHeading 154 155 // ある一点の高度(h)とデバイスの傾き(a)を比較する 156 self.difY = self.deg_h - self.a 157 158 switch self.a { 159 // デバイスの傾き(a)が0~45度のとき 160 case 0...45: 161 // 探したい星の方位角(deg_A2)が180°より大きいとき 162 if self.deg_A2 > 180.0 { 163 if myHoui > (self.deg_A2 - 180.0) && myHoui <= (self.deg_A2 - 5.0) { 164 if self.difY > 5.0 { 165 let myImage = UIImage(named: "myarrow45.png") 166 myImageView.image = myImage 167 myImageView.layer.position = CGPoint(x: self.view.bounds.width/2, y: 250.0) 168 self.view.addSubview(myImageView) 169 } 170 /* 171 省略 172 */ 173 default: 174 break 175 } 176 } 177 178 override func didReceiveMemoryWarning() { 179 super.didReceiveMemoryWarning() 180 // Dispose of any resources that can be recreated. 181 } 182 183 /* 184 // MARK: - Navigation 185 186 // In a storyboard-based application, you will often want to do a little preparation before navigation 187 override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 188 // Get the new view controller using segue.destinationViewController. 189 // Pass the selected object to the new view controller. 190 } 191 */ 192} 193

###補足情報(言語/FW/ツール等のバージョンなど)
Swift3.0
Xcode8.0

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

MotionManagerのハンドラの中でaに値が代入される前に、locationManager(_:didUpdateHeading:)が呼び出されているのではないでしょうか?printを追加するなどして確認してみて下さい。

もしそうであれば、locationManager(_:didUpdateHeading:)の最初にnilチェックを行えば良いと思います。

swift

1func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) { 2 guard let a = self.a else { 3 return //a=nilなので何もせずに戻る 4 } 5 //以下、傾きは a でアクセス出来る 6 : 7 // ある一点の高度(h)とデバイスの傾き(a)を比較する 8 self.difY = self.deg_h - a 9 :

こんな感じ?(動作確認はしていません)

投稿2016/12/09 04:19

編集2016/12/09 04:20
fuzzball

総合スコア16731

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問