【问题标题】:How to implement custom NavigationController in Swift如何在 Swift 中实现自定义 NavigationController
【发布时间】:2018-10-13 12:29:22
【问题描述】:

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

我的问题很简单:我是否应该为此使用 NavigationController,因为我希望个人资料图片“越过”NavigationController,使其距状态栏仅 16 个像素,而不是距 NavigationController。如果仍然有左右按钮(例如,如果它是您自己的,或者用于“更多选项”按钮,则可以编辑配置文件)。

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

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

谢谢大家!

【问题讨论】:

  • 您想如何在视图之间“切换”?使用 (a) Storyboard segues 或 (b) push/pop 转换,您将通过导航控制器获得很多好处。可以扩展UIView 来模拟“返回”或“弹出”过渡,但是您需要一个容器视图。当然,如果您只是展示视图控制器,则不需要导航控制器。
  • 我不使用 Storyboard 并且在代码中做所有事情,所以目前我确实在推动它们。很遗憾失去它以及进行转换、显示正确按钮等的大量额外工作。但是,我可以使用 NagigationController 实现我想要的吗?
  • 不能说。我在我的最新项目中将UINavigationController 子类化,但没有尝试像你想要的那样“替换”标题。不过,我已将其设置为空字符串。我确定您可以创建自己的“titleLabel”(导航栏中没有这样的公共属性),但我不确定是否使用自动布局进行放置。在我对事物进行子类化之前,我实际上走了另一条路——使用普通的UIViews 制作了我自己的导航栏,并过渡到非常接近你通过 push/pop 获得的导航栏。但是,是的,“非常接近”是关于你会得到什么。
  • 最后的 cmets。我需要带有最多 4 个表视图的工具箱来导航 - but - 作为子视图控制器。因为它是UIViewController 的子类,所以我发现UINavigationController 工作得很好。它有自己的视图,我只需要为其添加其他控制器。我添加了一个委托协议,根据需要将方法浮动到实际的父级,就是这样!我刚刚检查过,您可以像对待任何其他视图一样对待导航控制器的视图,关闭自动调整大小蒙版。我会从那开始,看看你是否可以添加一个子视图和约束。祝你好运。
  • 我会调查的。我也不知道我可以这样对待它。可能会给我所需的灵活性。感谢您的洞察力!

标签: ios swift uinavigationcontroller user-profile


【解决方案1】:

创建自定义“titleLabel”的最佳方法是继承UIViewUINavigationController。这样做的演示是here

主要部分是导航控制器。基本上,您希望将title 属性留空(一个空字符串或不设置它),并且由于UINavigationController 只是UIViewController 的一个子类,作为具有推送/弹出功能的容器视图,工作以这种方式与您的子类。在我的演示中,我的子类导航控制器类称为NavigationController

在我看来,子类化UINavigationController 的主要优势使用它的推送/弹出功能...虽然您可以使用 segues,但它们需要故事板,而您可以使用UIView 动画,对我来说只是感觉不那么“流畅”。

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

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 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 中,根据需要设置左/右按钮并根据需要按下/弹出。我喜欢使用 UTF-8 字符的自定义“箭头”:

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()
    }
}

请注意,我将视图的 naviagtionController? 属性强制转换为我的子类类型。需要强制转换才能访问其中的自定义方法。

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

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 的“命名”约束的使用。我将在下一步中解释这一点。

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 确实是一个容器视图控制器,是所有方面的。它的“根”视图是全屏的,所以如果你将标题标签锚定到底部,你会得到一个从上到下垂直大小的标签。更糟糕的是,有时会出现状态栏,尤其是纵向显示时。但其他时候(默认情况)不是。因为我使用 bothsafeAreaLayoutGuide 导航控制器(检测是否存在状态栏)其中的视图控制器(检测高度导航栏加上状态栏)。

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-29
    • 1970-01-01
    • 1970-01-01
    • 2016-06-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多