以编程方式定位 UIToolbar 的最佳方式(有或没有 UIToolbarDelegate)?

Posted

技术标签:

【中文标题】以编程方式定位 UIToolbar 的最佳方式(有或没有 UIToolbarDelegate)?【英文标题】:Best way to position UIToolbar programmatically (with or without UIToolbarDelegate)? 【发布时间】:2018-02-28 04:11:17 【问题描述】:

我正在 Playgound 中实现导航栏下方的分段控件。

这似乎是个经典问题,有人问过:

UISegmentedControl below UINavigationbar in ios 7 Add segmented control to navigation bar and keep title with buttons

UIBarPositioningDelegate 的文档中,它说,

UINavigationBarDelegate、UISearchBarDelegate 和 UIToolbarDelegate 协议扩展了这个协议以允许 这些条在屏幕上的位置。

在UIBarPosition的文档中:

案例顶部

指定栏位于其包含视图的顶部。

在UIToolbar.delegate的文档中:

当工具栏由 导航控制器。默认值为 nil。

我目前的解决方案如下(保留注释掉的代码以供参考和方便):

import UIKit
import PlaygroundSupport

class ViewController : UIViewController, UIToolbarDelegate

    let toolbar : UIToolbar = 
        let ret = UIToolbar()

        let segmented = UISegmentedControl(items: ["Good", "Bad"])

        let barItem = UIBarButtonItem(customView: segmented)

        ret.setItems([barItem], animated: false)
        return ret
    ()

    override func viewDidLoad() 
        super.viewDidLoad()

        view.addSubview(toolbar)
        // toolbar.delegate = self
    

    override func viewDidLayoutSubviews() 
        toolbar.frame = CGRect(
            x: 0,
            y: navigationController?.navigationBar.frame.height ?? 0,
            width: navigationController?.navigationBar.frame.width ?? 0,
            height: 44
        )
    

    func position(for bar: UIBarPositioning) -> UIBarPosition 
        return .topAttached
    



//class Toolbar : UIToolbar 
//    override var barPosition: UIBarPosition 
//        return .topAttached
//    
//

let vc = ViewController()
vc.title = "Try"
vc.view.backgroundColor = .red

// Another way to add toolbar...
// let segmented = UISegmentedControl(items: ["Good", "Bad"])
// let barItem = UIBarButtonItem(customView: segmented)
// vc.toolbarItems = [barItem]

// Navigation Controller
let navVC = UINavigationController(navigationBarClass: UINavigationBar.self, toolbarClass: UIToolbar.self)

navVC.pushViewController(vc, animated: true)
navVC.preferredContentSize = CGSize(width: 375, height: 640)
// navVC.isToolbarHidden = false


// Page setup
PlaygroundPage.current.liveView = navVC
PlaygroundPage.current.needsIndefiniteExecution = true

如您所见,这不使用UIToolbarDelegate

UIToolbarDelegate(提供position(for:))如何在这种情况下发挥作用?由于我们总是可以定位自己(手动或使用自动布局),UIToolbarDelegate 的用例是什么?

@Leo Natan 在the first question link above 中的回答提到了UIToolbarDelegate,但似乎工具栏放置在Interface Builder 中。

此外,如果我们不在这里使用UIToolbarDelegate,为什么不直接使用UIView 而不是UIToolbar

【问题讨论】:

【参考方案1】:

试试这个

UIView *containerVw = [[UIView alloc] initWithFrame:CGRectMake(0, 64, 320, 60)];
containerVw.backgroundColor = UIColorFromRGB(0xffffff);
[self.view addSubview:containerVw];

UIView *bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, 124, 320, 1)];
bottomView.backgroundColor = [UIColor grayColor];
[self.view addSubview:bottomView];

UISegmentedControl *sg = [[UISegmentedControl alloc] initWithItems:@[@"Good", @"Bad"]];
sg.frame = CGRectMake(10, 10, 300, 40);
[view addSubview:sg];

for (UIView *view in self.navigationController.navigationBar.subviews) 
    for (UIView *subView in view.subviews) 
        [subView isKindOfClass:[UIImageView class]];
        subView.hidden = YES;
    

【讨论】:

谢谢!但是如果我们确实使用 UIToolbar 和 UIToolbarDelegate 呢?请参阅我的问题的最后几段。【参考方案2】:

通过设置工具栏的委托并让委托方法返回.top,您可以在工具栏底部获得正常的阴影。如果您还将工具栏框架调整为高一点,它将覆盖导航栏的阴影,最终结果将是看起来更高的导航栏并添加了分段控件。

class ViewController : UIViewController, UIToolbarDelegate

    lazy var toolbar: UIToolbar = 
        let ret = UIToolbar()
        ret.delegate = self

        let segmented = UISegmentedControl(items: ["Good", "Bad"])

        let barItem = UIBarButtonItem(customView: segmented)

        ret.setItems([barItem], animated: false)

        return ret
    ()

    override func viewDidLoad() 
        super.viewDidLoad()

        view.addSubview(toolbar)
        toolbar.delegate = self
    

    override func viewDidLayoutSubviews() 
        toolbar.frame = CGRect(
            x: 0,
            y: navigationController?.navigationBar.frame.height - 1 ?? 0,
            width: navigationController?.navigationBar.frame.width ?? 0,
            height: toolbar.frame.height
        )
    

    func position(for bar: UIBarPositioning) -> UIBarPosition 
        return .top
    

【讨论】:

【参考方案3】:

UIToolbarDelegate(提供位置(for:))如何在这种情况下发挥作用?既然我们总是可以定位自己(手动或使用自动布局),那么 UIToolbarDelegate 的用例是什么?

我真的不知道UIToolbarDelegate是如何发挥作用的,如果你改变UINavigationController.toolbar它会崩溃,“你不能手动设置UINavigationController管理的UIToolbar委托”,而且如果您尝试更改工具栏的约束或其translatesAutoresizingMaskIntoConstraints 属性,也会发生同样的情况。

此外,如果我们这里不使用 UIToolbarDelegate,为什么不直接使用普通的 UIView 而不是 UIToolbar?

这似乎是一个合理的问题。我想这个问题的答案是你有一个UIView 子类,它已经具有UIToolbar 的行为,那么我们为什么要创建另一个类似UIToolbar 的类,除非你只想要导航栏下方的一些视图。

我知道有 2 个选项。

1) 与Move UINavigationController's toolbar to the top to lie underneath navigation bar相关

当您必须在由您的NavigationController 管理的其他ViewControllers 中显示工具栏时,第一种方法可能会有所帮助。

您可以继承UINavigationController,并在设置值时更改工具栏的Y轴位置。

import UIKit

private var context = 0

class NavigationController: UINavigationController 
    private var inToolbarFrameChange = false
    var observerBag: [NSKeyValueObservation] = []

    override func awakeFromNib() 
        super.awakeFromNib()
        self.inToolbarFrameChange = false
    

    override func viewDidLoad() 
        super.viewDidLoad()

        observerBag.append(
            toolbar.observe(\.center, options: .new)  toolbar, _ in
                if !self.inToolbarFrameChange 
                    self.inToolbarFrameChange = true
                    toolbar.frame = CGRect(
                        x: 0,
                        y: self.navigationBar.frame.height + UIApplication.shared.statusBarFrame.height,
                        width: toolbar.frame.width,
                        height: toolbar.frame.height
                    )
                    self.inToolbarFrameChange = false
                
            
        )
    

    override func setToolbarHidden(_ hidden: Bool, animated: Bool) 
        super.setToolbarHidden(hidden, animated: false)

        var rectTB = self.toolbar.frame
        rectTB = .zero
    

2) 您可以创建自己的UIToolbar 并将其添加到UIViewController 的视图中。然后,将约束添加到安全区域的前导、尾随和顶部。

import UIKit

final class ViewController: UIViewController 
    private let toolbar = UIToolbar()
    private let segmentedControl: UISegmentedControl = 
        let control = UISegmentedControl(items: ["Op 1", "Op 2"])
        control.isEnabled = false
        return control
    ()

   override func loadView() 
        super.loadView()
        setupToolbar()
   

    override func viewWillAppear(_ animated: Bool) 
        super.viewWillAppear(animated)
        navigationController?.navigationBar.hideBorderLine()
    

    private func setupToolbar() 
        let barItem = UIBarButtonItem(customView: segmentedControl)
        toolbar.setItems([barItem], animated: false)
        toolbar.isTranslucent = false
        toolbar.isOpaque = false

        view.addSubview(toolbar)

        toolbar.translatesAutoresizingMaskIntoConstraints = false
        toolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        toolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        toolbar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
    


private extension UINavigationBar 

    func showBorderLine() 
        findBorderLine().isHidden = false
    

    func hideBorderLine() 
        findBorderLine().isHidden = true
    

    private func findBorderLine() -> UIImageView! 
        return self.subviews
            .flatMap  $0.subviews 
            .compactMap  $0 as? UIImageView 
            .filter  $0.bounds.size.width == self.bounds.size.width 
            .filter  $0.bounds.size.height <= 2 
            .first
    


【讨论】:

以上是关于以编程方式定位 UIToolbar 的最佳方式(有或没有 UIToolbarDelegate)?的主要内容,如果未能解决你的问题,请参考以下文章

以编程方式修改 UIToolBar 项

如何以编程方式将 UIButton 添加到 UIToolBar?

以编程方式位于底部的 UIToolbar

UIToolbar外观用于以编程方式添加工具栏

以编程方式创建 UIToolbar

移动 UIToolbar 的最佳方式是啥?