web-dev-qa-db-ja.com

SwiftUIが詳細ビューのナビゲーションバーの外観をオーバーライドする

私は超シンプルなSwiftUIマスター詳細アプリを持っています:

import SwiftUI

struct ContentView: View {
    @State private var imageNames = [String]()

    var body: some View {
        NavigationView {
            MasterView(imageNames: $imageNames)
                .navigationBarTitle(Text("Master"))
                .navigationBarItems(
                    leading: EditButton(),
                    trailing: Button(
                        action: {
                            withAnimation {
                                // simplified for example
                                self.imageNames.insert("image", at: 0)
                            }
                        }
                    ) {
                        Image(systemName: "plus")
                    }
                )
        }
    }
}

struct MasterView: View {
    @Binding var imageNames: [String]

    var body: some View {
        List {
            ForEach(imageNames, id: \.self) { imageName in
                NavigationLink(
                    destination: DetailView(selectedImageName: imageName)
                ) {
                    Text(imageName)
                }
            }
        }
    }
}

struct DetailView: View {

    var selectedImageName: String

    var body: some View {
        Image(selectedImageName)
    }
}

また、ナビゲーションバーの色に対してSceneDelegateの外観プロキシを設定しています。」

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

        let navBarAppearance = UINavigationBarAppearance()
        navBarAppearance.configureWithOpaqueBackground()
        navBarAppearance.shadowColor = UIColor.systemYellow
        navBarAppearance.backgroundColor = UIColor.systemYellow
        navBarAppearance.shadowImage = UIImage()
        UINavigationBar.appearance().standardAppearance = navBarAppearance
        UINavigationBar.appearance().scrollEdgeAppearance = navBarAppearance

        // Create the SwiftUI view that provides the window contents.
        let contentView = ContentView()

        // Use a UIHostingController as window root view controller.
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }

ここで、詳細ビューが表示されたときにナビゲーションバーの背景色をクリアに変更します。私はまだそのビューに戻るボタンが欲しいので、ナビゲーションバーを非表示にすることは実際には理想的なソリューションではありません。また、変更を詳細ビューにのみ適用したいので、そのビューをポップすると、外観プロキシが引き継ぎ、別のコントローラーにプッシュすると、外観プロキシも引き継がれるはずです。

私はいろいろなことを試してきました:-didAppearの外観プロキシを変更する-UIViewControllerRepresentableで詳細ビューをラップする(限られた成功、ナビゲーションバーにアクセスしてその色を変更できる)しかし、何らかの理由で複数のナビゲーションコントローラーが存在します)

SwiftUIでこれを行う簡単な方法はありますか?

3
KerrM

最終的に、現在のUINavigationControllerに接続されていないUINavigationBarを表示するカスタムラッパーを作成しました。それはこのようなものです:

final class TransparentNavigationBarContainer<Content>: UIViewControllerRepresentable where Content: View {

    private let content: () -> Content

    init(content: @escaping () -> Content) {
        self.content = content
    }

    func makeUIViewController(context: Context) -> UIViewController {
        let controller = TransparentNavigationBarViewController()

        let rootView = self.content()
            .navigationBarTitle("", displayMode: .automatic) // needed to hide the nav bar
            .navigationBarHidden(true)
        let hostingController = UIHostingController(rootView: rootView)
        controller.addContent(hostingController)
        return controller
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) { }
}

final class TransparentNavigationBarViewController: UIViewController {

    private lazy var navigationBar: UINavigationBar = {
        let navBar = UINavigationBar(frame: .zero)
        navBar.translatesAutoresizingMaskIntoConstraints = false

        let navigationItem = UINavigationItem(title: "")

        navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "chevron.left"),
                                                           style: .done,
                                                           target: self,
                                                           action: #selector(back))

        let appearance = UINavigationBarAppearance()
        appearance.backgroundImage = UIImage()
        appearance.shadowImage = UIImage()
        appearance.backgroundColor = .clear
        appearance.configureWithTransparentBackground()
        navigationItem.largeTitleDisplayMode = .never
        navigationItem.standardAppearance = appearance
        navBar.items = [navigationItem]
        navBar.tintColor = .white
        return navBar
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.translatesAutoresizingMaskIntoConstraints = false

        self.view.addSubview(self.navigationBar)

        NSLayoutConstraint.activate([
            self.navigationBar.leftAnchor.constraint(equalTo: view.leftAnchor),
            self.navigationBar.rightAnchor.constraint(equalTo: view.rightAnchor),
            self.navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
        ])
    }

    override func didMove(toParent parent: UIViewController?) {
        super.didMove(toParent: parent)

        guard let parent = parent else {
            return
        }

        NSLayoutConstraint.activate([
            parent.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            parent.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
            parent.view.topAnchor.constraint(equalTo: self.view.topAnchor),
            parent.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
        ])
    }

    @objc func back() {
        self.navigationController?.popViewController(animated: true)
    }

    fileprivate func addContent(_ contentViewController: UIViewController) {
        contentViewController.willMove(toParent: self)
        self.addChild(contentViewController)
        contentViewController.view.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(contentViewController.view)

        NSLayoutConstraint.activate([
            self.view.topAnchor.constraint(equalTo: contentViewController.view.safeAreaLayoutGuide.topAnchor),
            self.view.bottomAnchor.constraint(equalTo: contentViewController.view.bottomAnchor),
            self.navigationBar.leadingAnchor.constraint(equalTo: contentViewController.view.leadingAnchor),
            self.navigationBar.trailingAnchor.constraint(equalTo: contentViewController.view.trailingAnchor)
        ])

        self.view.bringSubviewToFront(self.navigationBar)
    }
}

カスタムナビゲーションバーボタンの表示、「スワイプして戻る」のサポートなど、いくつかの改善点があります。

0
KerrM