uias / Tabman

™️ A powerful paging view controller with interactive indicator bars
https://uias.github.io/Tabman
MIT License
2.87k stars 238 forks source link

How to embedBar in navigationItem.titleView ? #321

Closed Jinkeycode closed 5 years ago

Jinkeycode commented 6 years ago

New Issue Checklist

Issue Description

This is my code, I used embedBar to do it, but it wasn't work!

import UIKit
import Tabman
import Pageboy

class ViewController: TabmanViewController, PageboyViewControllerDataSource {
    func numberOfViewControllers(in pageboyViewController: PageboyViewController) -> Int {
        return 4
    }

    func viewController(for pageboyViewController: PageboyViewController, at index: PageboyViewController.PageIndex) -> UIViewController? {
        return UIViewController()
    }

    func defaultPage(for pageboyViewController: PageboyViewController) -> PageboyViewController.Page? {
        return nil
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        self.dataSource = self

        // configure the bar
        self.bar.items = [Item(title: "Page 1"),
                          Item(title: "Page 2")]

        let tabbarView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width-80, height: 44))
        tabbarView.backgroundColor = UIColor.blue
        self.embedBar(in: tabbarView)

        self.navigationItem.titleView = tabbarView
    }

}

Other useful things

craigphares commented 6 years ago

It looks like if you wrap tabbarView inside one more UIView and set that view as the titleView it works.

let wrapperView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width - 80, height: 44))
let tabbarView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width - 80, height: 44))
self.embedBar(in: tabbarView)
wrapperView.addSubview(tabbarView)
self.navigationItem.titleView = wrapperView
wakaryry commented 6 years ago
// embed the tab bar into navigationbar
        guard let navigationBar = navigationController?.navigationBar else { return }
        // we margin title to the left/right item with 60
        let customV = UIView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: navigationBar.bounds.width - 60.0 * 2.0, height: navigationBar.bounds.height)))
        let titleV = UIView()
        titleV.frame = customV.bounds
        customV.addSubview(titleV)
        self.embedBar(in: titleV)
        self.navigationItem.titleView = customV

@craigphares It works. Thanks.

wakaryry commented 6 years ago

It comes problems in my app's usage in navigationItem's titleView with this library. My titleView has a left and right bar button item. And I embed it just like what I code above. It works for me to show menu. But sometimes when I scroll or switch, the titleView changes its position(frame: origin.x and size.width changes). And when I push into another controller and pop back, the menu changes position too. I added a strong frame control over viewDidAppear.

override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        adjustTitleView()
    }

    private func adjustTitleView() {
        guard let navigationBar = navigationController?.navigationBar else { return }
        // we margin title to the left/right item with 60
        self.customTitleV.frame = CGRect(origin: CGPoint(x: 60, y: 0), size: CGSize(width: navigationBar.bounds.width - 60.0 * 2.0, height: navigationBar.bounds.height))
    }

But it comes another problem. When I push and pop back, and then switch for pages, the titleView's frame changes too. I do not know what happened. I need to update the frame always in viewDidLayoutSubviews. But user could see the position changes in my app. It's not friendly.

wakaryry commented 6 years ago

It has been solved for my usage. I deleted bar button items which are from storyboard. All left/right bar button items are created with code.

wakaryry commented 6 years ago

It's suggested not to use it in titleView. I got many problems I just do not know how to solve it. Sometimes when I add a new page, or add a new item, then the layout goes wrong.

What I do now is to hide my nav bar.

override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated) 
        self.navigationController?.setNavigationBarHidden(true, animated: false)
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        self.navigationController?.setNavigationBarHidden(false, animated: animated)
    }
craigphares commented 6 years ago

@wakaryry we are successfully using Tabman in the navigationBar titleView. This seems like a real use case for this library, and it should be supported.

We're doing the same thing you mentioned previously: configuring the bar on every viewWillAppear call. We also re-declare the constraints at that moment. For the titleView, we use a custom UIView that automatically expands to fit its container frame. We have a left menuButton that we manually add to the customBarView each time. We're using SnapKit in the code below, but this could be done with any constraints technique.

// TitleView.swift
class TitleView: UIView {
    override var intrinsicContentSize: CGSize {
        return UIView.layoutFittingExpandedSize
    }
}

// ViewController.swift
class ViewController: TabmanViewController, PageboyViewControllerDataSource {
...
    lazy private var menuButton: UIButton = {
        let button = UIButton()
        button.setImage(UIImage(named: "menu"), for: .normal)
        return button
    }()
    let titleView = TitleView()
    let customBarView = UIView()

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        configureBar()
    }

    private func configureBar() {
        self.embedBar(in: customBarView)
        titleView.addSubview(customBarView)
        customBarView.addSubview(menuButton)
        navigationItem.titleView = titleView

        customBarView.snp.makeConstraints { (make) in
            make.left.equalToSuperview()
            make.right.equalToSuperview().offset(16)
            make.top.equalToSuperview()
            make.bottom.equalToSuperview()
        }

        menuButton.snp.makeConstraints { (make) in
            make.left.equalToSuperview().offset(8)
            make.width.equalTo(self.menuInset)
            make.top.equalToSuperview()
            make.bottom.equalToSuperview()
        }
    }
...
}

Hope this helps.

wakaryry commented 6 years ago

@craigphares Thanks for your nice work. It really helps much.

class TitleView: UIView {
    override var intrinsicContentSize: CGSize {
        return UIView.layoutFittingExpandedSize
    }
}

Controller:

override func viewDidLoad() {
        super.viewDidLoad()

        buildPages()
    }

private func buildPages() {
        self.automaticallyAdjustsChildViewInsets = true
        self.dataSource = self
        self.bar.items = [
            Item(title: "Mine"),
            Item(title: "Discovery"),
            Item(title: "Service")
        ]
        self.bar.style = .buttonBar
        self.bar.appearance = TabmanBar.Appearance({ (appearance) in
            appearance.style.background = TabmanBar.BackgroundView.Style.clear
            // center the indicator
            //appearance.layout.interItemSpacing = 0.0
            //appearance.layout.edgeInset = 0.0
        })

        self.configureBar()
    }

private func configureBar() {
        self.embedBar(in: self.tabsView)

        self.titleView.addSubview(self.tabsView)

        // left/rightItem: UIButton
        self.titleView.addSubview(self.leftItem)
        self.titleView.addSubview(self.rightItem)

        self.navigationItem.titleView = self.titleView

        self.tabsView.snp.makeConstraints { (make) in
            make.left.equalToSuperview().offset(60)
            make.right.equalToSuperview().offset(-60)
            make.top.bottom.equalToSuperview()
        }

        self.leftItem.snp.makeConstraints { (make) in
            make.left.equalToSuperview().offset(8)
            make.width.equalTo(30)
            make.top.bottom.equalToSuperview()
        }

        self.rightItem.snp.makeConstraints { (make) in
            make.right.equalToSuperview().offset(-8)
            make.width.equalTo(30)
            make.top.bottom.equalToSuperview()
        }
    }

I config all in viewDidLoad.

But with a little problem: The indicator does not show since I scroll the pages.

wakaryry commented 5 years ago

Does anyone get a titleView usage for the latest? Thanks.

BeIllegalBeagle commented 5 years ago

I've tried to use your implementation @craigphares and @wakaryry but I get an error for self.embedBar is there another way you can ember the TabmanBar to the navigation controller so that it is hidden when scrolling down?

wakaryry commented 5 years ago

@BeIllegalBeagle https://github.com/uias/Tabman/issues/370

BeIllegalBeagle commented 5 years ago

@wakaryry where should I look for the unwanted constraints that maybe added?

wakaryry commented 5 years ago

@BeIllegalBeagle It could be solved in 2.3.0

Jinkeycode commented 5 years ago

image After setting .selectedFont with a large size, such as UIFont.boldSystemFont(ofSize: 21.0), the text cannot be display fully. How can I solve it?

msaps commented 5 years ago

@Jinkeycode is this just using the current release of Tabman or are you using the new unreleased APIs in the 2.4.0 branch?

Jinkeycode commented 5 years ago

@msaps I use 2.3.0 release version

msaps commented 5 years ago

This is now natively supported in Tabman with 2.4.0 🎉

Available via BarLocation.navigationItem(item:) - and should be called in viewWillAppear.

addBar(bar, dataSource: self, at: .navigationItem(item: navigationItem))

Checkout the Adding a Bar docs for more info 😄.

cc @Jinkeycode @wakaryry @BeIllegalBeagle