iOSアプリ開発において、画面遷移は重要な要素の一つです。この記事では、Swiftで利用できる主な画面表示方法を、実装例とともに詳しく解説します。
目次
1. ナビゲーションスタック(Push/Pop)
階層的な画面遷移に最適な方法です。設定画面や詳細画面への遷移などでよく使われます。
特徴
- 画面を積み重ねるように遷移
- 自動的に「戻る」ボタンが表示される
- ナビゲーションバーでタイトルや操作ボタンを管理
UIKit実装例
// 次の画面へ遷移
let detailVC = DetailViewController()
navigationController?.pushViewController(detailVC, animated: true)
// 前の画面へ戻る
navigationController?.popViewController(animated: true)
// ルート画面まで一気に戻る
navigationController?.popToRootViewController(animated: true)
SwiftUI実装例
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailView()) {
Text("詳細画面へ")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
}
.navigationTitle("ホーム")
}
}
}
struct DetailView: View {
var body: some View {
Text("詳細画面")
.navigationTitle("詳細")
}
}
2. モーダル表示(Present/Dismiss)
一時的なタスクや重要な情報を表示する際に使用します。
特徴
- 現在の画面の上に新しい画面を表示
- ユーザーに明示的なアクションを求める際に有効
- iOS 13以降はカード型がデフォルト
UIKit実装例
// モーダル表示
let modalVC = ModalViewController()
// 表示スタイルの設定
modalVC.modalPresentationStyle = .pageSheet // カード型
// modalVC.modalPresentationStyle = .fullScreen // 全画面
// modalVC.modalPresentationStyle = .formSheet // フォーム型(iPad)
present(modalVC, animated: true, completion: nil)
// モーダルを閉じる(モーダル側で実行)
dismiss(animated: true, completion: nil)
SwiftUI実装例
struct ContentView: View {
@State private var showModal = false
var body: some View {
Button("モーダルを表示") {
showModal = true
}
.sheet(isPresented: $showModal) {
ModalView()
}
}
}
struct ModalView: View {
@Environment(\.dismiss) var dismiss
var body: some View {
NavigationView {
VStack {
Text("モーダル画面")
Button("閉じる") {
dismiss()
}
}
.navigationTitle("モーダル")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("キャンセル") {
dismiss()
}
}
}
}
}
}
全画面モーダル(SwiftUI)
struct ContentView: View {
@State private var showFullScreen = false
var body: some View {
Button("全画面モーダルを表示") {
showFullScreen = true
}
.fullScreenCover(isPresented: $showFullScreen) {
FullScreenView()
}
}
}
3. タブバー
複数の主要機能を切り替える際に使用します。
特徴
- 画面下部のタブで瞬時に切り替え
- 各タブは独立したナビゲーションスタックを持てる
- 最大5つまでの表示を推奨
UIKit実装例
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
// 各タブの画面を作成
let homeVC = UINavigationController(rootViewController: HomeViewController())
homeVC.tabBarItem = UITabBarItem(title: "ホーム", image: UIImage(systemName: "house"), tag: 0)
let searchVC = UINavigationController(rootViewController: SearchViewController())
searchVC.tabBarItem = UITabBarItem(title: "検索", image: UIImage(systemName: "magnifyingglass"), tag: 1)
let profileVC = UINavigationController(rootViewController: ProfileViewController())
profileVC.tabBarItem = UITabBarItem(title: "プロフィール", image: UIImage(systemName: "person"), tag: 2)
// タブバーコントローラーに設定
let tabBarController = UITabBarController()
tabBarController.viewControllers = [homeVC, searchVC, profileVC]
window.rootViewController = tabBarController
window.makeKeyAndVisible()
self.window = window
}
}
SwiftUI実装例
struct ContentView: View {
var body: some View {
TabView {
HomeView()
.tabItem {
Label("ホーム", systemImage: "house")
}
SearchView()
.tabItem {
Label("検索", systemImage: "magnifyingglass")
}
ProfileView()
.tabItem {
Label("プロフィール", systemImage: "person")
}
}
}
}
4. アラート・アクションシート
ユーザーに重要な情報を伝えたり、選択肢を提示する際に使用します。
UIKit実装例
// アラート
let alert = UIAlertController(
title: "確認",
message: "この操作を実行しますか?",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "キャンセル", style: .cancel))
alert.addAction(UIAlertAction(title: "実行", style: .destructive) { _ in
// 実行時の処理
print("実行されました")
})
present(alert, animated: true)
// アクションシート
let actionSheet = UIAlertController(
title: "写真を選択",
message: nil,
preferredStyle: .actionSheet
)
actionSheet.addAction(UIAlertAction(title: "カメラ", style: .default) { _ in
print("カメラを起動")
})
actionSheet.addAction(UIAlertAction(title: "フォトライブラリ", style: .default) { _ in
print("フォトライブラリを開く")
})
actionSheet.addAction(UIAlertAction(title: "キャンセル", style: .cancel))
present(actionSheet, animated: true)
SwiftUI実装例
struct ContentView: View {
@State private var showAlert = false
@State private var showActionSheet = false
var body: some View {
VStack(spacing: 20) {
Button("アラートを表示") {
showAlert = true
}
.alert("確認", isPresented: $showAlert) {
Button("キャンセル", role: .cancel) { }
Button("実行", role: .destructive) {
print("実行されました")
}
} message: {
Text("この操作を実行しますか?")
}
Button("アクションシートを表示") {
showActionSheet = true
}
.confirmationDialog("写真を選択", isPresented: $showActionSheet) {
Button("カメラ") {
print("カメラを起動")
}
Button("フォトライブラリ") {
print("フォトライブラリを開く")
}
Button("キャンセル", role: .cancel) { }
}
}
}
}
5. ポップオーバー
iPadアプリで特定の要素から吹き出しのように情報を表示する際に使用します。
UIKit実装例
let popoverVC = PopoverViewController()
popoverVC.modalPresentationStyle = .popover
popoverVC.preferredContentSize = CGSize(width: 300, height: 400)
if let popover = popoverVC.popoverPresentationController {
popover.sourceView = sender as? UIView
popover.sourceRect = (sender as? UIView)?.bounds ?? .zero
popover.permittedArrowDirections = .any
popover.delegate = self
}
present(popoverVC, animated: true)
// iPhoneでもポップオーバーとして表示する場合
extension YourViewController: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
SwiftUI実装例
struct ContentView: View {
@State private var showPopover = false
var body: some View {
Button("ポップオーバーを表示") {
showPopover = true
}
.popover(isPresented: $showPopover) {
VStack {
Text("ポップオーバーの内容")
.padding()
Button("閉じる") {
showPopover = false
}
}
.frame(width: 300, height: 200)
}
}
}
6. カスタムトランジション
独自のアニメーションで画面遷移を実装したい場合に使用します。
UIKit実装例
// カスタムトランジションデリゲート
class CustomTransitionDelegate: NSObject, UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return CustomPresentAnimator()
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return CustomDismissAnimator()
}
}
// アニメーター
class CustomPresentAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toView = transitionContext.view(forKey: .to) else { return }
let containerView = transitionContext.containerView
toView.frame = containerView.bounds
toView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
toView.alpha = 0
containerView.addSubview(toView)
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
toView.transform = .identity
toView.alpha = 1
}) { finished in
transitionContext.completeTransition(finished)
}
}
}
// 使用例
let customVC = CustomViewController()
let transitionDelegate = CustomTransitionDelegate()
customVC.transitioningDelegate = transitionDelegate
customVC.modalPresentationStyle = .custom
present(customVC, animated: true)
SwiftUI実装例
struct ContentView: View {
@State private var showView = false
var body: some View {
Button("カスタム遷移") {
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
showView.toggle()
}
}
.sheet(isPresented: $showView) {
CustomView()
.transition(.asymmetric(
insertion: .scale.combined(with: .opacity),
removal: .scale.combined(with: .opacity)
))
}
}
}
7. コンテナビューコントローラ
複数の子ViewControllerを管理する親ViewControllerを作成します。
UIKit実装例
class ContainerViewController: UIViewController {
private let childVC1 = FirstViewController()
private let childVC2 = SecondViewController()
override func viewDidLoad() {
super.viewDidLoad()
// 子ViewControllerを追加
addChild(childVC1)
view.addSubview(childVC1.view)
childVC1.view.frame = view.bounds
childVC1.didMove(toParent: self)
}
func switchToSecondViewController() {
// 子ViewControllerを切り替え
childVC1.willMove(toParent: nil)
childVC1.view.removeFromSuperview()
childVC1.removeFromParent()
addChild(childVC2)
view.addSubview(childVC2.view)
childVC2.view.frame = view.bounds
childVC2.didMove(toParent: self)
}
}
まとめ
画面遷移方法の選び方:
- 階層的な情報の閲覧: ナビゲーションスタック
- 一時的なタスクや入力: モーダル表示
- 主要機能の切り替え: タブバー
- 確認や選択肢の提示: アラート・アクションシート
- 補足情報の表示(iPad): ポップオーバー
- 独自のUX: カスタムトランジション
適切な画面遷移を選ぶことで、ユーザーにとって直感的で使いやすいアプリを作ることができます。SwiftUIではより宣言的に、UIKitではより細かい制御が可能です。プロジェクトの要件に応じて最適な方法を選択しましょう。