執筆者: 霽月
最終更新: 2023/03/29
霽月です。Swift初心者向けの記事です。
今回は少し難しめの画面遷移に挑戦してみました。
iOSにはPopover(ポップオーバー)と呼ばれるUIがあり、例えばボタンを押した際に、そのボタンから出る吹き出しのような形でビューを表示することができます。画面の大きいiPadのアプリではよく使われるUIですが、基本的にiPhoneではモーダルで表示されるので、普段iPhoneしか使わない人は見たことがないかもしれません。
今回はそのPopoverの吹き出しの中でプッシュ遷移をやってみようと思います。普通のプッシュ・モーダル遷移などに比べれば割とマイナーな遷移ですが、Apple純正の「時計」「Pages」「Numbers」など、プリインストールのアプリで結構使われてたりします。完成形を先にお見せします。
まずはStoryboardを開いて、ViewControllerを選択した状態で「Editor」→「Embed In」→「Navigation Controller」を押す、というお決まりの流れでナビゲーションバーを出します。Storyboardはもうこれ以上使わないので、ViewController.swiftに戻ります。さようなら、Storyboard君。
とりあえず、ポップオーバーを表示するボタンを作ってみます。
先ほどナビゲーションバーを用意したのは、UIBarButtonItemを置くためです。ポップオーバーはUIButtonから出るよりUIBarButtonItemから出た方が見慣れてる感じがしたので。まあどっちでも良いですけど。
ということで、できたのがこちらです。
import UIKit
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .add,
target: self,
action: #selector(didTapBarButton)
)
}
@objc func didTapBarButton(_ sender: UIBarButtonItem) {
let vc = PopoverViewController()
vc.preferredContentSize = CGSize(width: 300, height: 240)
vc.modalPresentationStyle = .popover
let presentationController = vc.popoverPresentationController
if let sourceView = sender.value(forKey: "view") as? UIView {
presentationController?.sourceView = sourceView
presentationController?.sourceRect = sourceView.bounds
}
presentationController?.permittedArrowDirections = .up
presentationController?.delegate = self
present(vc, animated: true)
}
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .none
}
}
class PopoverViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
「+」ボタンを押したらdidTapButtonが呼び出されて、PopoverViewControllerをポップアップで表示します。
ポップアップを出すコードは長いですが、やってることはシンプルです。ポップオーバーのサイズを設定して、ビューのプレゼンテーションスタイルをポップオーバーに設定して、ポップオーバーの吹き出し口を向ける場所と、向きを設定して、最後にpresentで表示します。
記事の冒頭で話したように、本来ポップオーバーはiPhoneではモーダルとして表示されるのですが、presentationControllerのdelegateをselfにしてadaptivePresentationStyle関数で.noneを返すことで、iPhoneでもポップオーバーとして表示されます。
さて、個人的にここが一番の難関でした。
まず、プッシュ遷移やモーダル遷移を行うにはナビゲーションバーが必須です。
プッシュ遷移を行うコードはこんな感じになっていて、
navigationController?.pushViewController(vc, animated: true)
navigationControllerがnilだったら、プッシュ遷移は不可能なわけです。
だから、PopoverViewControllerにナビゲーションバーを追加する方法を考えねばなりません。
ナビゲーションバーといえばStoryboardで出すものという認識だったので、Storyboardを弄ってPopoverViewControllerにナビゲーションバーをEmbed Inしてみるも、反映されず……。
PopoverViewControllerのviewDidLoadの中で、navigationControllerに新しく生成したUINavigationControllerを代入できないかと考えるも、navigationControllerはゲッターしか定義されていないので代入不可……。
ダメ元でChatGPT君に助けを求めるも、UIViewControllerの、実在しないプロパティを教えられ……w
いや、どうすんのよこれ。
となったところで、一旦考えるのをやめてナビゲーションバーについて情報収集することにしました。
すると、モーダルの画面にナビゲーションバーを追加している動画(リンク)が見つかりました。
動画ではこのようにしてモーダル遷移することで、それを可能にしていました。
let rootVC = SecondViewController()
let navVC = UINavigationController(rootViewController: rootVC)
navVC.modalPresentationStyle = .fullScreen
present(navVC, animated: true)
なるほど、rootVCではなくnavVCの方をpresentするのか。興味深い。
ということで、こちらも同じやり方でやってみます。
まずはdidTapButton関数の中身を変えていきます。
let vc = PopoverViewController()
vc.preferredContentSize = CGSize(width: 300, height: 240)
let navVC = UINavigationController(rootViewController: vc)
navVC.modalPresentationStyle = .popover
let presentationController = navVC.popoverPresentationController
if let sourceView = sender.value(forKey: "view") as? UIView {
presentationController?.sourceView = sourceView
presentationController?.sourceRect = sourceView.bounds
}
presentationController?.permittedArrowDirections = .up
presentationController?.delegate = self
present(navVC, animated: true)
navVCを宣言して、vcのところをいくつかnavVCに変えただけです。
PopoverViewControllerのviewDidLoadに以下を追加して、ナビゲーションバーに「画面1」と表示します。
title = "画面1"
実行してみます。
ちゃんとタイトルが表示されました!これで問題は解決です。
それでは、PopoverViewControllerにボタンを追加しましょう。
ボタンを宣言して、
private let button: UIButton = {
let button = UIButton()
button.setTitle("遷移する", for: .normal)
button.backgroundColor = .systemRed
button.setTitleColor(.white, for: .normal)
return button
}()
ボタンを配置するコードをViewDidLoadに書きます。
button.frame = CGRect(x: 100, y: 100, width: preferredContentSize.width - 200, height: 40)
view.addSubview(button)
ボタンのイベントを登録して、
button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
何もないただのUIViewControllerに遷移するコードを書きます。
@objc func didTapButton() {
let vc = UIViewController()
vc.view.backgroundColor = .white
vc.title = "画面2"
navigationController?.pushViewController(vc, animated: true)
}
ビューの背景色を設定しないと遷移の時にアニメーションがバグったので、白に設定しています。
これでようやく完成です。対戦ありがとうございました。
import UIKit
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .add,
target: self,
action: #selector(didTapBarButton)
)
}
@objc func didTapBarButton(_ sender: UIBarButtonItem) {
let vc = PopoverViewController()
vc.preferredContentSize = CGSize(width: 300, height: 240)
let navVC = UINavigationController(rootViewController: vc)
navVC.modalPresentationStyle = .popover
let presentationController = navVC.popoverPresentationController
if let sourceView = sender.value(forKey: "view") as? UIView {
presentationController?.sourceView = sourceView
presentationController?.sourceRect = sourceView.bounds
}
presentationController?.permittedArrowDirections = .up
presentationController?.delegate = self
present(navVC, animated: true)
}
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .none
}
}
class PopoverViewController: UIViewController {
private let button: UIButton = {
let button = UIButton()
button.setTitle("遷移する", for: .normal)
button.backgroundColor = .systemRed
button.setTitleColor(.white, for: .normal)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
title = "画面1"
button.frame = CGRect(x: 100, y: 100, width: preferredContentSize.width - 200, height: 40)
view.addSubview(button)
button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
}
@objc func didTapButton() {
let vc = UIViewController()
vc.view.backgroundColor = .white
vc.title = "画面2"
navigationController?.pushViewController(vc, animated: true)
}
}
この人が書いた記事