3

How to move the UIToolBar to top (stick to the UINavigationBar)?

I m struggle with this thing for a long time and I've try some stuff like:

  • Custom UIToolBar that conforms to UIToolbarDelegate and (UIBarPosition)positionForBar:(id <UIBarPositioning>)bar get called and I return UIBarPositionTop but the toolbar stays at bottom.
  • Change the toolbar frame: self.navigationController.toolbar.frame = CGRectMake(0, NAV_BAR_Y, self.view.bounds.size.width, NAV_BAR_HEIGHT);
  • Custom UINaviagtionController which has this delegate function: (UIBarPosition)positionForBar:(id <UIBarPositioning>)bar { return UIBarPositionTop; }

None of the struggles goes well, same look: enter image description here

Any Help will be great.

(I would like to have navigation look as Apple App store navigation)

gran33
  • 12,421
  • 9
  • 48
  • 76

2 Answers2

2

There are 2 options that I'm aware of.

1) Related to Move UINavigationController's toolbar to the top to lie underneath navigation bar

You can subclass UINavigationController and change the Y-axis position of the toolbar when the value is set.

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) You can create your own UIToolbar and add it to view of the UIViewController. Then, you add the constraints to the leading, trailing and the top of the safe area.

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

-1

Try this solution

@interface ViewController () <UIToolbarDelegate>
{
    UIToolbar * lpToolbar;
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    lpToolbar = [[UIToolbar alloc] initWithFrame :CGRectZero];
    lpToolbar.delegate = self;
    self.navigationItem.title = @"Title";
}

-(void) viewWillAppear :(BOOL)animated
{
    [super viewWillAppear:animated];

    [self.navigationController.view addSubview :lpToolbar];
    CGRect rFrame = self.navigationController.navigationBar.frame;
    lpToolbar.frame = CGRectMake( 0.0, rFrame.origin.y + rFrame.size.height, rFrame.size.width, 50.0 );
}

-(void) viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    [lpToolbar removeFromSuperview];
}

-(UIBarPosition) positionForBar:(id <UIBarPositioning>)bar
{
    return UIBarPositionTop;
}
  • Thanks, I've tried this already before, and it doesn't a good solution, try to push another View Controller , and the Bar button items will stick to bottom. And for make them to be stick to top u need to hack the nav bar (use some private API) and I don't really want to get into there – gran33 May 08 '16 at 06:42
  • Do you want the same segment controller on the navigation bar for all View Controller's in the navigation stack? – Алексей Абдулин May 08 '16 at 07:02
  • Not necessarily, I would like the View controller to decide if so. – gran33 May 08 '16 at 07:06
  • You will create your own UIToolbar and add it to the UINavigationController view using addSubview, set frame y bellow navigation bar y + height - 1, and set delegate UIToolbarDelegate with your method of tool bar position. May be It's bad solution too, but easy. And you will add it in viewWillAppear and remove from superview in viewWillDisappear – Алексей Абдулин May 08 '16 at 07:25