7

我有一个超级简单的 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()
        }
    }

现在,我想做的是让导航栏的背景颜色在详细视图出现时更改为清除。我仍然想要那个视图中的后退按钮,所以隐藏导航栏并不是一个理想的解决方案。我还希望更改仅适用于 Detail 视图,因此当我弹出该视图时,外观代理应该接管,如果我推送到另一个控制器,那么外观代理也应该接管。

我一直在尝试各种事情: - 更改外观代理didAppear - 将详细视图包装在一个UIViewControllerRepresentable(有限的成功,我可以进入导航栏并更改其颜色,但由于某种原因,导航控制器不止一个)

在 SwiftUI 中是否有一种简单的方法可以做到这一点?

4

3 回答 3

8

我更喜欢为此使用 ViewModifer。下面是我的自定义 ViewModifier

struct NavigationBarModifier: ViewModifier {

var backgroundColor: UIColor?

init(backgroundColor: UIColor?) {
    self.backgroundColor = backgroundColor

    let coloredAppearance = UINavigationBarAppearance()
    coloredAppearance.configureWithTransparentBackground()
    coloredAppearance.backgroundColor = backgroundColor
    coloredAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
    coloredAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]

    UINavigationBar.appearance().standardAppearance = coloredAppearance
    UINavigationBar.appearance().compactAppearance = coloredAppearance
    UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
    UINavigationBar.appearance().tintColor = .white
}

func body(content: Content) -> some View {
    ZStack{
        content
        VStack {
            GeometryReader { geometry in
                Color(self.backgroundColor ?? .clear)
                    .frame(height: geometry.safeAreaInsets.top)
                    .edgesIgnoringSafeArea(.top)
                Spacer()
            }
        }
    }
}}

您还可以为您的栏使用不同的文本颜色和色调颜色对其进行初始化,我现在添加了静态颜色。

你可以从任何地方调用这个修饰符。在你的情况下

    NavigationLink(
destination: DetailView(selectedImageName: imageName)
    .modifier(NavigationBarModifier(backgroundColor: .green))

)

下面是截图。 带有绿色导航栏的详细视图

于 2020-05-26T21:34:06.127 回答
2

在我看来,这是 SwiftUI 中最直接的解决方案。

问题:框架在DetailView中添加返回按钮解决方案:自定义返回按钮和导航栏被渲染

struct DetailView: View {
var selectedImageName: String
@Environment(\.presentationMode) var presentationMode

var body: some View {
    CustomizedNavigationController(imageName: selectedImageName) { backButtonDidTapped in
        if backButtonDidTapped {
            presentationMode.wrappedValue.dismiss()
        }
    } // creating customized navigation bar
    .navigationBarTitle("Detail")
    .navigationBarHidden(true) // Hide framework driven navigation bar
 }
}

如果框架驱动的导航栏没有隐藏在细节视图中,我们会得到两个这样的导航栏:双导航栏

使用 UINavigationBar.appearance() 在某些场景中是非常不安全的,比如当我们想要在弹出窗口中同时显示这些 Master 和 Detail 视图时。我们的应用程序中的所有其他导航栏都有可能获得与详细信息视图相同的导航栏配置。

struct CustomizedNavigationController: UIViewControllerRepresentable {
let imageName: String
var backButtonTapped: (Bool) -> Void

class Coordinator: NSObject {
    var parent: CustomizedNavigationController
    var navigationViewController: UINavigationController
    
    init(_ parent: CustomizedNavigationController) {
        self.parent = parent
        let navVC = UINavigationController(rootViewController: UIHostingController(rootView: Image(systemName: parent.imageName).resizable()
                                                                                    .aspectRatio(contentMode: .fit)
                                                                                    .foregroundColor(.blue)))
        navVC.navigationBar.isTranslucent = true
        navVC.navigationBar.tintColor = UIColor(red: 41/255, green: 159/244, blue: 253/255, alpha: 1)
        navVC.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.red]
        navVC.navigationBar.barTintColor = .yellow
        navVC.navigationBar.topItem?.title = parent.imageName
        self.navigationViewController = navVC
    }
    
    @objc func backButtonPressed(sender: UIBarButtonItem) {
        self.parent.backButtonTapped(sender.isEnabled)
    }
}

func makeCoordinator() -> Coordinator {
    Coordinator(self)
}

func makeUIViewController(context: Context) -> UINavigationController {
    // creates custom back button 
    let navController = context.coordinator.navigationViewController
    let backImage = UIImage(systemName: "chevron.left")
    let backButtonItem = UIBarButtonItem(image: backImage, style: .plain, target: context.coordinator, action: #selector(Coordinator.backButtonPressed))
    navController.navigationBar.topItem?.leftBarButtonItem = backButtonItem
    return navController
}

func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {
    //Not required
}
}

这是查看完整代码的链接。

于 2021-08-09T05:17:21.543 回答
1

我最终创建了一个自定义包装器,它显示了一个未附加到当前 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)
    }
}

有一些改进需要做,比如显示自定义导航栏按钮,支持“滑动返回”和其他一些事情。

于 2020-06-08T15:43:26.893 回答