0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SwiftUIとUIKit連携における画面向き制御とNavigationBar崩れの回避(高校生にもわかる対話形式)

Posted at

はじめに

SwiftUIのHostingViewAからUIKitのViewControllerB(縦画面固定)をモーダルで表示すると、横向き(landscape)で表示→閉じた際に、元のSwiftUIのナビゲーションバーの高さが異常に大きくなる問題が起きます。

今回は、先生と生徒の会話を通してわかりやすく解説していきます。


登場人物

  • 先生:アプリ開発に詳しい高校の情報科の先生。
  • まさと:iOSアプリ開発を始めたばかりの高校2年生。
  • あかね:UIまわりが気になるデザイナー志望の高校生。

ある日の放課後、情報教室にて

まさと:先生ー!SwiftUIからUIKitの画面(ViewControllerB)を呼び出すアプリ作ってるんですけど、へんなバグが出ました!

先生:どんなバグかな?

まさと:ViewControllerBを横向きで表示してから閉じると、SwiftUI画面に戻ったときナビゲーションバーがめっちゃでかくなるんです……

あかね:それってなんか見た目が壊れてるみたいでイヤだね〜!

先生:うん、それはよくある落とし穴なんだ。
UIKitとSwiftUIを組み合わせて使うと、向き(portrait/landscape)やサイズクラスがちゃんと戻らないことがある。


問題のコード

まさと:ViewControllerBでは、こうやって画面の向きを固定してます。

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return .portrait
}

先生:なるほど。これでViewControllerBは縦画面固定だね。
でも、問題はここから。

横向きの状態でViewControllerBを出して、閉じると、元のSwiftUIの画面(HostingViewA)が「まだ横向きっぽい状態のまま」になるんだ。

あかね:へぇ〜。なんでそんなことに?

先生:iOSは画面の向きに関する情報を traitCollectionSafe Area で管理してる。
だけど、途中で強制的に縦固定の画面を出すと、それが閉じられたときに元の状態にうまく戻らないことがある。


解決方法を教えて!

まさと:じゃあ、どうすればナビゲーションバーが元に戻るの?

先生:それはね、「アプリ全体の向き制御の窓口」を1箇所にまとめて、今一番前面にいる画面の向きを見るようにするんだ。

あかね:え、アプリ全体のって、どこに書けばいいの?

先生AppDelegate っていうところに書くといいよ。
iOSアプリの「司令塔」みたいな役割だね。


AppDelegate に書くコード

func application(_ application: UIApplication,
                 supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    if let topVC = UIApplication.topViewController() {
        return topVC.supportedInterfaceOrientations
    }
    return .allButUpsideDown
}

まさと:おお!シンプル!

先生:これで、今一番上に表示されてる画面の「向きの設定」が、そのままアプリ全体の向き制御として機能するようになる。


トップViewControllerを取得する拡張

あかね:でも、一番上に表示されてるViewControllerって、どうやって取るの?

先生:いい質問だね。こんな便利な拡張があるよ:

extension UIApplication {
    static func topViewController(
        base: UIViewController? = UIApplication.shared
            .connectedScenes
            .compactMap { ($0 as? UIWindowScene)?.keyWindow }
            .first?.rootViewController
    ) -> UIViewController? {
        if let nav = base as? UINavigationController {
            return topViewController(base: nav.visibleViewController)
        }
        if let tab = base as? UITabBarController,
           let sel = tab.selectedViewController {
            return topViewController(base: sel)
        }
        if let presented = base?.presentedViewController {
            return topViewController(base: presented)
        }
        return base
    }
}

最後に確認しよう!

まさと:じゃあ、ViewControllerBではもう supportedInterfaceOrientations は書かなくていいの?

先生:基本的には、NavigationControllerB でまとめて制御するのがスマートだね。

class NavigationControllerB: UINavigationController {
    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return .portrait
    }
    override var shouldAutorotate: Bool {
        return true
    }
}

あかね:これで、デザインも崩れないし安心!

先生:うん。これで横向き→縦向きの切り替えもうまくいって、ナビバーの高さも正常に戻るようになるよ。


まとめ

  • UIKit画面で向きを個別に固定すると、SwiftUI画面に戻ったときにサイズ情報が崩れることがある
  • AppDelegate で「今一番上に表示されているVCの向き」を返すようにすると、向き切り替えが安定
  • トップViewController取得には便利な拡張を使おう!

まさと・あかね:先生、ありがとう〜〜!

先生:また何かあったら、いつでも聞いてね!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?