如何在 Swift 中实现自定义 NavigationController

How to implement custom NavigationController in Swift

我有一个应用程序,它使用 NavigationController 和 TabBarController 在不同的视图中轻松导航。对于某些视图,我有一个标题和一些按钮(有时只在右侧,有时在右侧和左侧)。但是,对于某些视图,例如在观看某人的个人资料时,我不想设置标题。我想显示个人资料图片并在图片下方添加用户名。

我的问题很简单:我是否应该为此使用 NavigationController,因为我希望个人资料图片进入 "over" NavigationController,使其距离状态栏只有 16 像素,而不是 NavigationController。在仍然有左右按钮的情况下是否可能(例如,如果是您自己的,或者用于 "more options" 按钮,则可以编辑配置文件)。

我一直在考虑使用一种完全不同的方法,我不使用 NavigationController,而是在必要时使用标签来显示标题,并在左侧和右侧添加一个视图,我可以在其中添加必要时按钮。

由于这是我第一次做这样的事情,我很想听听您对如何实现这一结果以及最佳做法是什么的意见。有些东西告诉我最好使用 NavigationController,因为这是它的目的,但是我如何实现可选标题并在需要时检查它?我已经知道如何删除底部细线以及如何相应地设置背景颜色,所以这不会是最大的问题。我只关心如何在 NavigationController 上显示图像。

谢谢大家!

创建自定义 "titleLabel" 的最佳方法是 class UIViewUINavigationController。这样做的演示是 here.

主要部分是导航控制器。基本上,您想将 title 属性 留空(一个空字符串或不设置它),并且,因为 UINavigationController 只是 [=] 的子 class 21=] 作为具有 push/pop 功能的容器视图工作,以这种方式与您的 subclass 一起工作。在我的演示中,我的 subclassed 导航控制器 class 被称为 NavigationController.

在我看来,你从 subclassing UINavigationController 中获得的主要优势是 使用它的 push/pop 功能......而你可以使用 segues 他们需要一个故事板,虽然你可以使用 UIView 动画,但我觉得它不像 "smooth"。

(1) 如果您不使用 Storyboard,请记住在 AppDelegate: 中将此 subclass 设置为您的根视图控制器

var navController: NavigationController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    self.window = UIWindow(frame: UIScreen.main.bounds)
    navController = NavigationController(nibName: nil, bundle: nil)
    self.window!.rootViewController = navController
    self.window?.makeKeyAndVisible()
    return true
}

(2) 虽然还有其他方法可以做到这一点,但我的偏好是在我的子 class 中实例化视图控制器,根据需要向 push/pop 公开方法:

class NavigationController:UINavigationController {

    let redVC = RedViewController()
    let greenVC = GreenViewController()
    let titleView = TitleView()
    var titleViewHeightConstraint:NSLayoutConstraint!

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        pushViewController(redVC, animated: false)  // display the first VC
    }
    func popGreenVC() {
        popViewController(animated: true)
    }
    func pushGreenVC() {
        pushViewController(greenVC, animated: true)
    }
}

(3) 在其他 VC 中,根据需要设置 left/right 按钮,根据需要设置 push/pop。我喜欢使用 UTF-8 字符的自定义 "arrow":

class RedViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.red
        let showGreenVC = UIBarButtonItem(title: "Green \u{25B6}", style: .plain, target: self, action: #selector(pushGreenVC))
        self.navigationItem.rightBarButtonItem = showGreenVC
    }
    @objc func pushGreenVC() {
        let navController = self.navigationController as! NavigationController
        navController.pushGreenVC()
    }
}

class GreenViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.green
        let showRedVC = UIBarButtonItem(title: "\u{25C0} Red", style: .plain, target: self, action: #selector(popGreenVC))
        self.navigationItem.leftBarButtonItem = showRedVC
    }
    @objc func popGreenVC() {
        let navController = self.navigationController as! NavigationController
        navController.popGreenVC()
    }
}

请注意,我是 force-casting 视图的 naviagtionController? 属性 作为我的子classed 类型。需要转换才能访问其中的自定义方法。

(4) 接下来,包括您的自定义 "titleView"。出于演示目的,我创建了一个 50/50 yellow/blue 的。请注意,它使用自动布局锚点:

class TitleView: UIView {
    let yellowView = UIView()
    let blueView = UIView()
    convenience init() {
        self.init(frame: CGRect.zero)

        self.translatesAutoresizingMaskIntoConstraints = false
        yellowView.translatesAutoresizingMaskIntoConstraints = false
        blueView.translatesAutoresizingMaskIntoConstraints = false

        yellowView.backgroundColor = UIColor.yellow
        blueView.backgroundColor = UIColor.blue
        self.addSubview(yellowView)
        self.addSubview(blueView)

        yellowView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        yellowView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
        yellowView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.5).isActive = true
        yellowView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        blueView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        blueView.leadingAnchor.constraint(equalTo: yellowView.trailingAnchor).isActive = true
        blueView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.5).isActive = true
        blueView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
    }
}

(5) 在导航控制器的初始化中,添加代码以添加您的标题视图。请注意,我将自动布局与 两个 东西一起使用。首先,我将宽度设置为状态栏宽度的 1/3。您可以进行试验 - 对于图像,您最好设置一个恒定宽度。其次,请注意名为 titleViewHeightConstraint 的 "named" 约束的用法。我会在下一步中解释。

titleViewHeightConstraint = titleView.heightAnchor.constraint(equalToConstant: 0)
titleViewHeightConstraint.isActive = true
titleView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
titleView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
titleView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.3).isActive = true

(5) 动态调整标题标签的高度:

记住,一个UINavigationController真的是一个container view controller是所有方面的。它的 "root" 视图是全屏的,因此如果您将标题标签固定在底部,您会得到一个从上到下垂直调整大小的标签。更糟糕的是,有时会出现状态栏,尤其是纵向显示时。但其他时候(默认景观)不是。由于我使用 safeAreaLayoutGuide 导航控制器(检测是否存在状态栏) 视图其中的控制器(检测导航栏和状态栏的高度)。

(5a) 将此方法添加到您的导航控制器:

func changeTitleViewHeight(to:CGFloat) {
    titleViewHeightConstraint.constant = to - view.safeAreaInsets.top
}

(5b) 并将此覆盖添加到子classed 导航控制器内的所有视图控制器:

override func viewSafeAreaInsetsDidChange() {
    let navController = self.navigationController as! NavigationController
    navController.changeTitleViewHeight(to: view.safeAreaInsets.top)
}