画面遷移ができません。カウントダウンアプリを作成しています。
解決済
回答 1
投稿
- 評価
- クリップ 0
- VIEW 282
前提・実現したいこと
初心者です。
・最初の画面(ViewController)でpickerを使用して時間を選択してもらう
・開始のボタンを押して画面遷移をさせる
・遷移先(NextViewController)で「00:00:00」のようにlabelに時間を表示させ、カウントダウンを進める
上記のようなアプリを作ろうと思っています。
エラーは出ていないのですが、画面遷移ができず、開始ボタンを押すとそのまま固まってしまいます。
画面遷移のやり方を教えていただきたいです。
また、値の渡し方がこれで合っているのかもわかりません。そのことに関してもアドバイスが欲しいです。
初心者で何もわからず、根本的なところが間違っているかもしれないのですが、それがどこだかわからないです。どなたか分かる方いらっしゃいましたら、よろしくお願いします。
該当のソースコード
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource{
@IBOutlet weak var start: UIButton!
@IBOutlet weak var time: UIPickerView!
let data = [[Int](arrayLiteral: 15,30,45,60,90,120)]
var pickerdata:String!
var timeTotal:Int!
@IBAction func start(_ sender: Any) {
let storyboard: UIStoryboard = self.storyboard!
let timer = storyboard.instantiateViewController(withIdentifier: "toNextViewController") as! NextViewController
timer.getTime = timeTotal
self.present(timer, animated: true, completion: nil)
total()
self.performSegue(withIdentifier: "toNextViewController", sender: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
time.dataSource = self
time.delegate = self
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return data.count
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return data[component].count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return String(data[component][row])
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
total()
}
// pickerの数字をたすメソッド
func total() {
timeTotal = data[0][time.selectedRow(inComponent: 0)] * 60
}
}
import UIKit
class NextViewController: UIViewController {
var timer = Timer()
var count = 0
var getTime:Int!
@IBOutlet weak var timerLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
runTimer()
timer.invalidate()
count = 0
timerLabel.text = timeString(time: TimeInterval(getTime))
}
// カウントダウンをする関数
@objc func updateTimer() -> Int {
count += 1
//時間 = pickerで設定する時間 ー count
let remainCount = getTime! - count
timerLabel.text = timeString(time: TimeInterval(remainCount))
// timerLabel.text = "\(remainCount)"
// 0秒になったら止まる
if remainCount == 0 {
timer.invalidate()
timerLabel.text = "00:00:00"
}
return remainCount
}
//タイマーを動かす関数
func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateTimer), userInfo: nil, repeats: true)
}
//00:00:00に変える処理
func timeString(time: TimeInterval) -> String {
let hour = Int(time) / 3600
let minutes = Int(time) / 60 % 60
let second = Int(time) % 60
return String(format: "%02d:%02d:%02d", hour, minutes, second)
}
override func viewDidAppear(_ animated: Bool) {
timerLabel.text = timeString(time: TimeInterval(getTime))
print(getTime ?? "this is nil")
}
}
試したこと
segueに問題があるのかと思い、開始ボタンからsegueをつけたり、ViewControllerからsegueをつけたりしているのですができませんでした。現在はViewControllerから関連づけています。
補足情報(FW/ツールのバージョンなど)
Swift5でコーディングしています。
こちらのサイトを参考にして作成しました。
https://qiita.com/katsualonso14/items/bf64e2201e8fc73125e0
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+1
エラーは出ていないのですが、画面遷移ができず、開始ボタンを押すとそのまま固まってしまいます。
直接的な原因は、NextViewControllerで
override func viewDidLoad() {
super.viewDidLoad()
runTimer()
timer.invalidate() // <-ココが問題
count = 0
timerLabel.text = timeString(time: TimeInterval(getTime))
}
という具合に、タイマーをinvalidate(無効にする、という意味)しているからだと思います。
画面遷移のやり方を教えていただきたいです。
提示していただいたソースを見ると、
let storyboard: UIStoryboard = self.storyboard!
let timer = storyboard.instantiateViewController(withIdentifier: "toNextViewController") as! NextViewController
timer.getTime = timeTotal
self.present(timer, animated: true, completion: nil)
total()
self.performSegue(withIdentifier: "toNextViewController", sender: nil)
とありますが、
- StoryBoardで作成したViewControllerのインスタンス化
- Segueでの画面遷移
の2つを混同して使われているように思えます。
元記事ではSegueを使っていませんので、前者に統一するのがいいかと思います。
また、値の渡し方がこれで合っているのかもわかりません。そのことに関してもアドバイスが欲しいです。
- StoryBoardで作成したViewControllerのインスタンス化
で値渡しをするのであれば、この方法で問題ないかと思います。Segueを使う場合にはまた異なってきます。
その他、配列の初期化方法や、PickerViewに渡すデータの扱い方など、先々問題に繋がりそうな部分もありましたので、コメントを入れてみました。
もしわからない点があればコメントいただくか、内容によっては新たにご質問いただければと思います。
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource{
@IBOutlet weak var start: UIButton!
@IBOutlet weak var time: UIPickerView!
// MARK: 変更
// Int(arrayLiteral: ) はコンパイラが使う内部向け表現なので使わない
// また、今回の目的だと一次元配列で済むため、次のように書き換える
// let data = [[Int](arrayLiteral: 15,30,45,60,90,120)]
let data = [15, 30, 45, 60, 90, 120]
var pickerdata:String!
// MARK: 変更
// Int!型のように、!で宣言するオプショナル型の利用は極力避ける。
// 今回の場合は単純にゼロで初期化すれば良い。
//var timeTotal:Int!
var timeTotal = 0
@IBAction func start(_ sender: Any) {
let storyboard: UIStoryboard = self.storyboard!
let timer = storyboard.instantiateViewController(withIdentifier: "toNextViewController") as! NextViewController
// MARK:
// もし、timeTotalがオプショナル型で宣言されていたら、この時点で実行時エラーになる可能性が高い
timer.getTime = timeTotal
self.present(timer, animated: true, completion: nil)
// MARK: 削除
// present()で表示したのであれば、下記の行は不要
//total()
//self.performSegue(withIdentifier: "toNextViewController", sender: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
time.dataSource = self
time.delegate = self
// MARK: 追加
// timeTotal の初期値を900(秒)にする。
// 一番最初からピッカービューを動かさずに画面を遷移させても、初期値が代入されないことへの対応
timeTotal = 15 * 60
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
// MARK: 変更
// PickerView に使うのは1列だけなので、今回は決め打ちで値を返す
//return data.count
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
// MARK: 変更
// 今回使うのは一次元配列なので、配列の要素数を .count を使って返す
//return data[component].count
return data.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
// MARK: 変更
//return String(data[component][row])
return String(data[row])
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
total()
}
// pickerの数字をたすメソッド
func total() {
// MARK: 変更
// 一次元配列になったため計算方法を変更
//timeTotal = data[0][time.selectedRow(inComponent: 0)] * 60
timeTotal = data[time.selectedRow(inComponent: 0)] * 60
}
}
import UIKit
class NextViewController: UIViewController {
var timer = Timer()
var count = 0
var getTime:Int!
@IBOutlet weak var timerLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
runTimer()
// MARK: 削除
// invalidate()はタイマを止めるメソッドのため、記述するとタイマは動かずカウントダウンしない。
//timer.invalidate()
count = 0
timerLabel.text = timeString(time: TimeInterval(getTime))
}
// カウントダウンをする関数
@objc func updateTimer() -> Int {
count += 1
//時間 = pickerで設定する時間 ー count
// MARK: 変更
// getTimeはオプショナル型だが、!で宣言されたオプショナル型なので、アクセス時のアンラップは不要
//let remainCount = getTime! - count
let remainCount = getTime - count
timerLabel.text = timeString(time: TimeInterval(remainCount))
// timerLabel.text = "\(remainCount)"
// 0秒になったら止まる
if remainCount == 0 {
timer.invalidate()
timerLabel.text = "00:00:00"
}
return remainCount
}
//タイマーを動かす関数
func runTimer() {
// MARK: コメント
// scheduledTimerは呼び出された直後から指定された関数の呼び出しを始める。
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateTimer), userInfo: nil, repeats: true)
}
//00:00:00に変える処理
func timeString(time: TimeInterval) -> String {
let hour = Int(time) / 3600
let minutes = Int(time) / 60 % 60
let second = Int(time) % 60
return String(format: "%02d:%02d:%02d", hour, minutes, second)
}
// MARK: 削除
// viewDidLoad()で同じような処理を行なっているので、どちらか一方に記述する
// override func viewDidAppear(_ animated: Bool) {
//
// timerLabel.text = timeString(time: TimeInterval(getTime))
// print(getTime ?? "this is nil")
//
// }
}
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.32%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2020/06/17 21:59
重ねて質問失礼いたします。
最初の画面(ViewController.swift)の27行目
let timer = storyboard.instantiateViewController(withIdentifier: "toNextViewController") as! NextViewController
にて、Thread 1: signal SIGABRT というエラーが出ています。
関連付けなどの確認はしたのですが、他に考えられる原因はありますでしょうか?
2020/06/18 05:50
1. instantiateViewControllerで指定しているIdentifierが間違っている(存在しない)
2. as!による強制ダウンキャストに失敗している
関連付けに間違いはないというお話ですが、この辺りを中心として再度確認されてはいかがでしょうか。
ご質問の段階では実行時エラーはなかったはずですので、どこかを間違って修正したことが考えられます。
どうしても見つからなければ、新しくプロジェクトを立ち上げて、ソースコードだけコピーするのも一つかと思います。
のどちらかしかないと思います。
2020/06/18 23:01
丁寧な回答、感謝します。