1

导航栏下方带有 UISegmentedControl 的屏幕截图

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

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

在 的文档中UIBarPositioningDelegate,它说,

UINavigationBarDelegate、UISearchBarDelegate 和 UIToolbarDelegate 协议扩展了此协议以允许在屏幕上定位这些栏。

UIBarPosition的文档中:

案例顶部

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

UIToolbar.delegate的文档中:

当工具栏由导航控制器管理时,您可能不会设置委托。默认值为无。

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

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:)发挥作用?由于我们总是可以定位自己(手动或使用自动布局),所以 a 的用例是UIToolbarDelegate什么?

@Leo Natan 在上面第一个问题链接中的回答提到了UIToolbarDelegate,但似乎工具栏放置在 Interface Builder 中。

此外,如果我们不使用UIToolbarDelegatehere,为什么不使用 plainUIView而不是 aUIToolbar呢?

4

3 回答 3

1

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

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

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

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

我知道有两个选项。

1) 与将 UINavigationController 的工具栏移到顶部以位于导航栏下方有关

当您必须在其他ViewControllers由您的NavigationController.

设置值后,您可以子类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
    }
}

于 2019-06-12T20:20:06.917 回答
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;
    }
}
于 2018-02-28T04:50:06.030 回答
1

通过设置工具栏的委托并让委托方法 return .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
    }
}
于 2019-05-24T20:11:12.317 回答