10
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Xcode 13 × iOS 16 でUIHostingControllerのナビゲーションが非表示にならない問題の対処

Last updated at Posted at 2022-12-06

はじめに

UIHostingControllerでナビゲーションを非表示になるように実装していたのに、iOS 16のみナビゲーションが非表示にならず困ったため、その解決方法について記載します。

環境

  • Xcode 13.4.1
    • Simulator: iPhone 13 mini (iOS 15.5)
  • Xcode 14.1
    • Simulator: iPhone 14 Pro (iOS 16.1)
  • 実機: iPhone 14 Pro (iOS 16.1.1)

現象

UIKitとSwiftUIでナビゲーションを非表示にしたViewControllerを作り、それぞれUITabBarControllerに配置します。
(後述のためにSwiftUI製のViewには中央にボタンを配置しておきます)

ナビゲーションを非表示にするために、以下の2つの処理を実行しています。

  • SwiftUI側で .navigationBarHidden(true) をセット
  • UIKit側の viewWillAppearnavigationController?.setNavigationBarHidden(true, animated: true) を実行
Code
SwiftUIViewController.swift
final class SwiftUIViewController: UIHostingController<SwiftUIView> {
    private let viewModel = SwiftUIViewModel()
    private var cancellable: Set<AnyCancellable> = []

    init() {
        super.init(rootView: SwiftUIView(viewModel: viewModel))
        // viewModelのハンドリング
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()        
        title = "SwiftUIView w/o NavigationBar"
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.setNavigationBarHidden(true, animated: true)
    }
}

struct SwiftUIView: View {
    @ObservedObject var viewModel: SwiftUIViewModel
    
    var body: some View {
        ZStack {
            Color(.orange)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            Button(
                "Go next (with NavigationBar)",
                action: { viewModel.onTapButton.send() }
            )
        }
        .ignoresSafeArea(edges: .top)
        .navigationBarHidden(true)
    }
}

Xcode 13.4.1でiOS 15.5のSimulatorにビルドして確認すると、以下のような画面が表示されます。

ところが、Xcode 13.4.1のDeviceSupportにiOS 16.1を追加した状態でiOS 16.1の実機にビルドすると、SwiftUIで実装した方の画面にナビゲーションが表示されてしまい、思ったような動作になっていませんでした。
Buttonを配置してナビゲーションを表示する画面と表示しない画面にpushするような実装をしてみても、同様にiOS 16.1.1ではナビゲーションが非表示になりません。ただし、一度別の画面に遷移したあとpopしてきたときにはナビゲーションが非表示になっていました。

iOS 15.5 (Simulator) iOS 16.1.1 (実機)

解決方法

1. Xcode 14.x でビルドする

同じコードのまま、今度はXcode 14.1でビルドしてみます。
すると、Simulator、実機ともにiOS 16以上でもナビゲーションが表示されないようになりました。

iOS 16.1 (Simulator) iOS 16.1.1 (実機)

2. UIHostingControllerのviewをUIViewController上に配置する

別の事情でXcodeのバージョンを上げることができない場合、UIHostingControllerのviewを別のUIViewControllerに配置することで解決できます。

本来表示したいUIHostingController SwiftUIViewController の他にUIViewController SwiftUIParentViewController を用意し、

  • SwiftUIViewController のviewを SwiftUIParentViewController のviewにaddSubviewする
  • ViewModelのハンドリングを SwiftUIParentViewController に移植する
  • SwiftUIParentViewControllerviewWillAppear でナビゲーションを非表示にする
  • SwiftUIViewController を呼び出していた箇所を SwiftUIParentViewController を呼び出すように変更する

の4つの対応で簡単に対処することができます。

final class SwiftUIParentViewController: UIViewController {
    private let viewModel = SwiftUIViewModel()

    init() {
        super.init(nibName: nil, bundle: nil)
        // viewModelのハンドリング
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func loadView() {
        super.loadView()
        title = "SwiftUIView w/o NavigationBar"

        // SwiftUIViewControllerの `view` を view に addSubview する
        let vc = SwiftUIViewController(viewModel: viewModel)
        guard let childView = vc.view else { return }
        view.addSubview(childView)
        childView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            childView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            childView.topAnchor.constraint(equalTo: view.topAnchor),
            childView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            childView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
        ])
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // SwiftUIParentViewController 側でナビゲーションを非表示にする
        navigationController?.setNavigationBarHidden(true, animated: true)
    }
}

final class SwiftUIViewController: UIHostingController<SwiftUIView> {
    init(viewModel: SwiftUIViewModel) {
        super.init(rootView: SwiftUIView(viewModel: viewModel))
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
iOS 15.5 (Simulator) iOS 16.1.1 (実機)

3. UINavigationBar上の戻るボタンだけ非表示にする

上記の例では背景を全面的にオレンジ色にしていてナビゲーションが目立ってしまうため、何がなんでもナビゲーションを非表示にしたUIを実装しようとしていました。
ただ、左上の戻るボタンを非表示にしたいけどナビゲーションは表示でも非表示でもどちらでも構わない、というケースもあるかと思います。
そのような時は、ナビゲーションを非表示にする処理を削除し、SwiftUI側で .navigationBarBackButtonHidden(true) をセットして戻るボタンだけ非表示にするというのも解決策の1つです。

final class SwiftUIViewController: UIHostingController<SwiftUIView> {
    private let viewModel = SwiftUIViewModel()

    init() {
        super.init(rootView: SwiftUIView(viewModel: viewModel))
        // viewModelのハンドリング
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        title = "SwiftUIView w/o NavigationBar"
    }
}

struct SwiftUIView: View {
    @ObservedObject var viewModel: SwiftUIViewModel
    
    var body: some View {
        ZStack {
            Color(.white)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            Button(
                "Go next (with NavigationBar)",
                action: { viewModel.onTapButton.send() }
            )
        }
        .ignoresSafeArea(edges: .top)
        .navigationBarBackButtonHidden(true) // 戻るボタンだけ非表示にする
    }
}
iOS 15.5 (Simulator) iOS 16.1.1 (実機)

iOS 15ではナビゲーションにタイトルと戻るボタンが一瞬表示されて非表示に、iOS 16ではタイトルは非表示にならず戻るボタンのみ一瞬表示されて非表示になる、という差分がありました。
この解決方法を選択する場合はタイトルをセットしないようにした方が良さそうです。

おわりに

新しいOSが公開されるのをいつもとても楽しみにしていますが、その反面アプリ開発者としては謎の不具合との出会いに悪戦苦闘したりします。
よりよい解決方法を模索しながら楽しく開発していきたいですね :muscle:

10
2
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
10
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?