SwiftUI から UINavigationItem にアクセスして UINavigationBar をカスタマイズする
背景
SwiftUI から NavigationBar の background color や shadow color 等をカスタマイズする方法として、よく紹介されているのは以下のような方法。
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .red
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
だが、この方法はすべての画面に影響を及ぼすため、全画面共通の Navigation Bar の見た目では無い限りは使い勝手が悪い。
UIKit では UINavigationItem にアクセスしてプロパティを変更することで、その画面 (UINavigationController) に影響を閉じ込めることができた。
なので SwiftUI でも UINavigationItem にアクセスできるような UIHostingController wrapper を作成する。
使い方
@Environment(\.navigationItem)
で取ってくる
struct InnerView: View {
@Environment(\.navigationItem) var navigationItem // : UINavigationItem?
var body: some View {
Text("Hello, world!")
.onAppear {
navigationItem.title = "Title"
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .red
navigationItem?.standardAppearance = appearance
navigationItem?.scrollEdgeAppearance = appearance
navigationItem?.compactAppearance = appearance
}
}
}
実装
import SwiftUI
import TinyConstraints
import UIKit
/// SwiftUI において ``UINavigationItem`` にアクセスするための Wrapper
/// ``UIHostingController`` の代わりに使うことで ``EnvironmentValues.navigationItem`` を経由して SwiftUI View で操作が可能になる
public final class NavigationItemHost<Content: View>: UIViewController {
private let rootView: Content
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public init(rootView: Content) {
self.rootView = rootView
super.init(nibName: nil, bundle: nil)
}
override public func viewDidLoad() {
super.viewDidLoad()
let rootViewWithItem = rootView
.environment(\.navigationItem, navigationItem)
let host = UIHostingController(rootView: rootViewWithItem)
addChild(host)
view.addSubview(host.view)
host.didMove(toParent: self)
host.view.edgesToSuperview()
}
}
public struct UINavigationItemKey: EnvironmentKey {
public static var defaultValue: UINavigationItem?
}
public extension EnvironmentValues {
var navigationItem: UINavigationItem? {
get { self[UINavigationItemKey.self] }
set { self[UINavigationItemKey.self] = newValue }
}
}