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

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

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

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

Swift

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

Q&A

解決済

1回答

7300閲覧

アラートを表示するクラスを作成したい

muaik

総合スコア4

Xcode

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

Swift

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

0グッド

1クリップ

投稿2020/10/15 15:12

前提・実装したいこと

Xcodeで簡単なカードゲームアプリを作っているのですが、全ての画面にタイトルへ戻るボタンを表示しています。

ボタンを押してタイトルに戻る際には一度アラートを表示させてから画面遷移が行うようにしたいと思い、アラートを表示して画面遷移する関数を作成したのですが、タイトル画面以外全ての画面に実装するため、できれば別ファイルを作成し、一つのクラスにまとめて毎回書かなくてもいいようにしたいと思っています。

そこでTitleBackBtnというクラスを作ったのですが、エラーが発生し、うまく動作しません。

コード中盤の
self.present(vc, animated: true, completion: nil)
に「Value of type 'TitleBackBtn' has no member 'present'」というエラーが

最後の行の
present(alert, animated: true, completion: nil)
に「cannot find 'present' in scope」というエラーが出ています。

それぞれの画面のViewControllerの中に関数を書いた場合は動作するため、エラーメッセージからおそらくViewControllerの指定の仕方がselfではないのだとは思うのですが、自身では解決方法が見つかりませんでした。
Swift初心者用の書籍を1冊読み終えたばかりで、エラーが発生しているコード以前にクラス作りの基本的な部分が抜けている可能性もあり、大変申し訳ないのですが、解決方法をご存知の方がいらっしゃればご教授いただけると幸いです。

該当のソースコード

Swift

1import Foundation 2import UIKit 3 4 5 6class TitleBackBtn{ 7 8 //アラートボタン 9 func titleAlert(){ 10 //アラートボタンのタイトルと本文の表示 11 let alert: UIAlertController = UIAlertController(title: "タイトルへ戻る", message: "タイトル画面へ戻ってもよろしいですか?", preferredStyle: UIAlertController.Style.alert) 12 13 //OKボタンの表示と押された時の処理 14 let defaultAction: UIAlertAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler:{ 15 //OKボタンが押された時の処理 16 (action: UIAlertAction!) -> Void in 17 print("OK") 18 let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil) 19 let vc = storyboard.instantiateViewController(withIdentifier: "titleView") as! ViewController 20 self.present(vc, animated: true, completion: nil) 21 }) 22 //キャンセルボタンの表示と押された時の処理 23 let cancelAction: UIAlertAction = UIAlertAction(title: "キャンセル", style: UIAlertAction.Style.cancel, handler:{ 24 //キャンセルボタンが押された時の処理 25 (action: UIAlertAction!) -> Void in 26 print("Cancel") 27 }) 28 29 //UIAlertControllerにActionを追加 30 alert.addAction(cancelAction) 31 alert.addAction(defaultAction) 32 33 //Alertを表示 34 present(alert, animated: true, completion: nil) 35 } 36}

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

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

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

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

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

TsukubaDepot

2020/10/16 07:35

このコードだと、タイトルに戻るたびに新しい View Controller を作ってしまいますが、それについては認識されていますでしょうか。 つまり、見た目は「元の画面」に戻ったように見えても、それは戻ったのではなく「元の画面と同じ画面」を「新しく」表示しているコードになっています。このコードのまま、次々に元の画面を新規作成してしまうと、そのうちメモリ不足で落ちてしまいます。 一般的な書き方であれば、遷移元に戻るのであれば dismiss(animated:completion:) を使って現在の画面を破棄するのですが、参考にされた本にはそのことについて言及されていないでしょうか。
muaik

2020/10/16 10:21

構造上の問題を教えていただきありがとうございます。 お恥ずかしながら、ご指摘いただくまで画面が破棄されずに重なり続けてしまう状態であることは認識していませんでした。今回の画面遷移については詳しい記載が本になかったため、ネットにあったコードを参考にしたものです。 dismissの使い方を調べコードを修正し、現在の画面を破棄することはできました。 1つ前の画面には戻るのですが、2つ前、3つ前の画面に戻れず新たな問題が発生しています。 これも初歩的な問題だと思うので恐縮ですが、2つ3つと重なることを防ぐためタイトル画面以外は、画面遷移する際に遷移元の画面を破棄しつつ遷移したいのですが、そのような方法はあるのでしょうか?
TsukubaDepot

2020/10/16 10:33

全体的な構造や実現されたい内容を拝見しないことには何とも言えない部分はありますが、たとえば Navigation Controllerを使っていれば、任意の画面に戻ることは割合簡単にできますし、present/dismiss を使った方法でも、やれないことはないかと思います。 遷移前の画面を破棄する方法は基本的にないと思います。というのは、遷移先となる View Controller のインスタンス(子)は遷移元が親となっているためです。依存関係があるため、子よりも親を先に破棄することはできず、別の方法を考える必要があるかと思います。 もしかしたら、画面遷移の関係を整理すると、もっと簡単に解決できるかもしれません。
TsukubaDepot

2020/10/16 10:34

もし差し支えなければ、参考にされている書籍名を教えていただけないでしょうか。 同じ本が手元にあれば、参考になる部分をご提示できるかもしれません。
muaik

2020/10/16 11:13

ご丁寧に回答いただきありがとうございます。 書籍については「たった2日でマスターできるiPhoneアプリ開発集中講座」を読み、参考しています。 また「絶対に挫折しないiPhoneアプリ開発「超」入門 第8版」も持っています。こちらはSwiftUIの使い方を解説していたためあまり読み進めていない状態です。 現在、人狼ゲームのようなカードゲームアプリを作っており画面は、 タイトル画面→プレイヤー名入力画面→プレイヤーが配られた役職カードを確認する画面→話し合いのタイマー画面・・・ といった遷移をしていくような構造になっているのですが、最初のご指摘を受けて、このまま作り続けると延々と画面が積み重なっていってしまうような気がしています。 「NavigationControllerを使っていれば・・・」とアドバイスをいただき少し調べて見て、そちらの方が良さそうだとは思ったのですが、使った場合に全体がどのような構造になるのかがイメージできず、またそのやり方もぼんやりとしかまだわかっていません。 最初の質問から本題が離れてしまい申し訳ないのですが、NavigationControllerを使うと、画面は重ならず、遷移したい画面に移って行けるということでしょうか?
TsukubaDepot

2020/10/16 12:28

「たった2日で〜」は第3版しかもっていませんが、 P.344に「ナビゲーションコントローラとナビゲーションバー」という形で、NavigationController の説明が書いてありました。 iOS 標準の「設定」アプリのように、一つ項目を選ぶごとに次々に関連した View Controller を表示させる方式です(ドリルダウン)。 https://qiita.com/usagimaru/items/9b55daa4d88b0bb98f38#%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%E9%9A%8E%E5%B1%A4%E5%9E%8B この方法も、これまでに表示した view Controller を表示していることには変わりませんが、積み重ねた View Controller のうち、任意の View Controller に戻ることが容易なため、いきなり数階層もどるような場合には便利かと思います。また、一つ前の画面に戻る操作は自動で組み込まれるため(非表示にもできる)、画面遷移の管理が楽という利点もあります。 一方、presetで View Controller を表示する方法は、上に示したリンクのうち、モーダル(分岐型)と呼ばれている方法です。こちらも階層を重ねていくことには違いありませんが、Navigation Controller と違い、複数の View Controller を集約して管理しているわけではないので、全て自分の責任で管理する必要があります。 実は、画面遷移は UI 的に考えても難しい側面があるのですが、一番良いのは iOS 標準アプリや、よく使われているアプリを使い込み、それらの画面遷移がどのようになっているかよく観察することかと思います。実際には Navigation Controller だけ、独自に View Controller を present するだけ、ということは少なく、両方を組み合わせて使っている例が多いかと思います。 あまりまとまりがなくて申し訳ないのですが、どちらにしても画面遷移の関係をよく考え、新しく作る View Controller を最小限にする(使い回す)ような構造にするのが良いのではないかと思います。
muaik

2020/10/16 14:42

画面遷移について詳しく解説していただき、イメージしづらかった部分が理解できてきました。 質問のこと以前に書籍を復習し(自分の持っている版にも記載がありました!)もう少し全体の設計や画面遷移について勉強し改めて取り組んでみようと思います。 脱線してしまった質問に丁寧なご回答をいただき本当にありがとうございました。
TsukubaDepot

2020/10/16 14:46

追記は脇道にそれてしまったので、ご質問への回答は回答の方に記載しました。 ただ、これでどれだけ記述量が減るのか難しいところなのですが(汎用性を持たせようとすればするほどむしろ煩雑になる)、参考になればと思います。
guest

回答1

0

ベストアンサー

たとえば、ですが、Alert を表示するためのクラスメソッドを下記のような感じで作り、

Swift

1import UIKit 2 3class TitleBackBtn { 4 // MARK: - class method にする 5 class func titleAlert(completionHandler: (() -> ())?, cancellationHandler: (() -> ())?) -> UIViewController { 6 //アラートボタンのタイトルと本文の表示 7 let alert = UIAlertController(title: "タイトルへ戻る", message: "タイトル画面へ戻ってもよろしいですか?", preferredStyle: .alert) 8 9 //OKボタンの表示と押された時の処理 10 let defaultAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: { _ in 11 DispatchQueue.main.async { 12 completionHandler?() 13 } 14 }) 15 16 //キャンセルボタンの表示と押された時の処理 17 let cancelAction: UIAlertAction = UIAlertAction(title: "キャンセル", style: UIAlertAction.Style.cancel, handler: { _ in 18 DispatchQueue.main.async { 19 cancellationHandler?() 20 } 21 }) 22 23 //UIAlertControllerにActionを追加 24 alert.addAction(cancelAction) 25 alert.addAction(defaultAction) 26 27 return alert 28 } 29}

呼び出す方ではこんな感じで呼び出せば、ある程度は共通化できるかと思います。

Swift

1 let alertVC = TitleBackBtn.titleAlert(completionHandler: { 2 // Ok ボタンを押した時の処理をクロージャとして記述する。 3 print("Ok が押されました。") 4 5 // 必要な処理 6 7 // nextViewController を破棄する 8 self.dismiss(animated: true, completion: nil) 9 }, cancellationHandler: nil) 10 // キャンセルボタンを押した時の処理が必要なければ nil を渡す 11 12 // しかし、alertVC を表示させるには、いずれにしても present で表示する必要がある。 13 present(alertVC, animated: true, completion: nil)

上記の例では、Ok や Cancel ボタンを押した後の処理をクロージャとして渡すような形を想像していますが、ある程度処理が共通なのであればその処理もまとめて titleAlert で処理させることは可能です。

ただし、dismiss など、titleAlert を表示させた View Controller のメソッドを使うとなると直接アクセスすることはできませんので、やはりクロージャを使って処理させるか、titleAlert の引数に View Controller のインスタンスを渡して、それで処理させることになるかと思います。

また、titleAlert だけで表示まで行わせることも不可能ではないと思いますが、その他の処理を考えるといったんインスタンスを作り、別途 present で表示させるのが現実的ではないか、と思っています。

投稿2020/10/16 14:36

TsukubaDepot

総合スコア5086

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問