Minitour / AZTabBarController

A custom tab bar controller for iOS written in Swift 4.2
MIT License
348 stars 65 forks source link

is posible to add my own buttons with irregular shapes? #11

Closed Seb-AS closed 7 years ago

Seb-AS commented 7 years ago

I want to make something like the image.

thanks topbar

Minitour commented 7 years ago

@Seb-AS Hi, This isn't possible out of the box, But i'll see if I can find a hack for you.

Seb-AS commented 7 years ago

@Minitour Thanks

Minitour commented 7 years ago

@Seb-AS Are you planning to have a controller in the middle? or you are using it for a custom action?

Seb-AS commented 7 years ago

@Minitour yes, the idea it's to go home when the user taps on the round tabBar item.

Another question: its possible to not tint that tabbar item?, because it'll have a logo with a few colors and when its loaded to the tabbar are overrided by tabController.defaultColor.

thanks topbarmiddlenotselected topbarmiddleselected

Minitour commented 7 years ago

To use icons with multi color patterns use:

tabBar.ignoreIconColors = true
Seb-AS commented 7 years ago

@Minitour cool!

Minitour commented 7 years ago

Hi again @Seb-AS, Sorry for the late response, I was quite busy the last few days. Anyways Here is a solution for your problem:

simulator screen shot 6 may 2017 12 38 50

Create Extension to Access the Tab Bar View

First you need to modify the AZTabBarController.swift:

Let's start by adding this extension:

public extension AZTabBarController{
    public var buttonsContainerView: UIView? {
        return buttonsContainer
    }
}

Add Delegate Method

Now let's add this new Delegate method to the AZTabBarDelegate protocol:

public protocol AZTabBarDelegate: class {
...
func tabBar(_ tabBar: AZTabBarController, didSetupButtonAtIndex index: Int, button: UIButton)
}

Add it to the AZTabBarDelegate extension to make it optional:

public extension AZTabBarDelegate{
...
func tabBar(_ tabBar: AZTabBarController, didSetupButtonAtIndex index: Int, button: UIButton){}
}

we'll need this delegate method to disable the button.

Now let's add in the controller flow

In the setupButtons function, add the line:

self.delegate?.tabBar(self, didSetupButtonAtIndex: i, button: button)

It should look something like this:

private func setupButtons(){
        if self.buttons == nil {
            self.buttons = NSMutableArray(capacity: self.tabIcons.count)
            for i in 0 ..< self.tabIcons.count {

                let button:UIButton = self.createButton(forIndex: i)

                self.buttonsContainer.addSubview(button)

                self.buttons[i] = button

--------> self.delegate?.tabBar(self, didSetupButtonAtIndex: i, button: button)
            }
            self.setupButtonsConstraints()
        }
        self.customizeButtons()

        self.buttonsContainer.backgroundColor = self.buttonsBackgroundColor != nil ? self.buttonsBackgroundColor : UIColor.lightGray
    }

Setting Up the Button

After you have initialized your controller and setup all the tabs it's time to setup the button:

First let's implement the delegate method we added:

extension ViewController: AZTabBarDelegate{
...
    func tabBar(_ tabBar: AZTabBarController, didSetupButtonAtIndex index: Int, button: UIButton) {
        if index == 2 {
            button.isHidden = true
            button.isEnabled = false
        }
    }
}

Add a Target Function

func didSelectMiddleMenu(sender: UIButton){
        tabController.setIndex(2, animated: true)
}

Now in the viewDidLoad where you setup your controller, add the following code:

Create the Button

var button = RoundedButton()
button.isEnabled = true
button.backgroundColor = .white
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(#imageLiteral(resourceName: "ic_phone"), for: [])
button.tintColor = #colorLiteral(red: 0.9529411793, green: 0.6862745285, blue: 0.1333333403, alpha: 1)

Add Target to the Button

button.addTarget(self, action: #selector(didSelectMiddleMenu(sender:)), for: .touchUpInside)

Add The button:

tabController.view.addSubview(button)

Get the current container

let container = tabController.buttonsContainerView!

Add Constraints:

button.topAnchor.constraint(equalTo: container.topAnchor, constant: -20).isActive = true
button.heightAnchor.constraint(equalTo: container.heightAnchor, multiplier: 1.1).isActive = true
button.widthAnchor.constraint(equalTo: button.heightAnchor, multiplier: 1.0).isActive = true
button.centerXAnchor.constraint(equalTo: tabController.view.centerXAnchor).isActive = true

Design the container

container.layer.shadowOffset = CGSize(width: 0, height: -2)
container.layer.shadowRadius = 10
container.layer.shadowOpacity = 0.1
container.layer.shadowColor = UIColor.black.cgColor

Add the little details

tabController.animateTabChange = true
tabController.tabBarHeight = 60
tabController.selectionIndicatorHeight = 3
tabController.selectionIndicatorColor = #colorLiteral(red: 0.9529411793, green: 0.6862745285, blue: 0.1333333403, alpha: 1)

Rounded Button

Here is the Custom UIButton Class I created:

class RoundedButton: UIButton{

    override var backgroundColor: UIColor?{
        get{
            return UIColor(cgColor: layer.backgroundColor ?? UIColor.clear.cgColor)
        }set{
            layer.backgroundColor = newValue?.cgColor
        }
    }

    open var transformScale: CGFloat = 0.9

    private var didSetupDesign = false

    override func layoutSubviews() {
        super.layoutSubviews()

        if !didSetupDesign{
            didSetupDesign = true
            design()
        }
    }

    func design(){
        layer.cornerRadius = frame.size.height/2
        layer.shadowOffset = CGSize(width: 0, height: 0)
        layer.shadowRadius = 7
        layer.shadowOpacity = 0.1
        layer.shadowColor = UIColor.black.cgColor
    }

    override var isHighlighted: Bool{
        set{
            UIView.animate(withDuration: 0.1) { [weak self] in
                let scale = self?.transformScale ?? 0.9
                self?.transform = newValue ? CGAffineTransform(scaleX: scale, y: scale) : .identity
            }
            super.isHighlighted = newValue
        }get{
            return super.isHighlighted
        }
    }
}

If you have any further questions feel free to ask

Seb-AS commented 7 years ago

That's great @Minitour, I'll take a look. Thanks for your hard work!

Minitour commented 7 years ago

@Seb-AS Did you manage to achieve what you were looking for? If you want I can make a branch.

Seb-AS commented 7 years ago

@Minitour yes, thanks, everything works excellent I've another question because I don't see a way to control the indicator line width and y position.

Minitour commented 7 years ago

@Seb-AS I actually did apply the effect you were looking for however I forgot to post it. anyways here it is:

To the extension we have created previously add this function:

public extension AZTabBarController{
    public var buttonsContainerView: UIView? {
        return buttonsContainer
    }

    public var selectionIndicatorView: UIView?{
        return selectionIndicator
    }
}

Then in the viewDidLoad add these lines of code:

tabController.selectionIndicatorHeight = 10.5
tabController.selectionIndicatorColor = .clear

let indicatorView = tabController.selectionIndicatorView
let subIndicatorView = SubIndicatorView()
subIndicatorView.backgroundColor = #colorLiteral(red: 0.9529411793, green: 0.6862745285, blue: 0.1333333403, alpha: 1)

subIndicatorView.translatesAutoresizingMaskIntoConstraints = false
indicatorView?.addSubview(subIndicatorView)
subIndicatorView.widthAnchor.constraint(equalTo: subIndicatorView.heightAnchor, multiplier: 5.0).isActive = true
subIndicatorView.heightAnchor.constraint(equalTo: indicatorView!.heightAnchor, multiplier: 0.33).isActive = true
subIndicatorView.centerXAnchor.constraint(equalTo: indicatorView!.centerXAnchor).isActive = true
subIndicatorView.topAnchor.constraint(equalTo: indicatorView!.topAnchor).isActive = true

Here is the class for the SubIndicatorView:

class SubIndicatorView: UIView{
    override var backgroundColor: UIColor?{
        get{
            return UIColor(cgColor: layer.backgroundColor ?? UIColor.clear.cgColor)
        }set{
            layer.backgroundColor = newValue?.cgColor
        }
    }

    private var didSetupDesign = false

    override func layoutSubviews() {
        super.layoutSubviews()

        if !didSetupDesign{
            didSetupDesign = true
            design()
        }
    }

    func design(){
        layer.cornerRadius = frame.size.height/2
        layer.shadowOffset = CGSize(width: 0, height: 0)
        layer.shadowRadius = 3
        layer.shadowOpacity = 0.7
        layer.shadowColor = #colorLiteral(red: 0.9529411793, green: 0.6862745285, blue: 0.1333333403, alpha: 1).cgColor
        layer.masksToBounds = false
    }
}

The final result should look something like this: simulator screen shot 10 may 2017 17 38 36

Minitour commented 7 years ago

@Seb-AS Did you get it to work?

Seb-AS commented 7 years ago

@Minitour thanks for the code, I tested but I got a crash in

let indicatorView = tabController.selectionIndicatorView

subIndicatorView.heightAnchor.constraint(equalTo: indicatorView!.heightAnchor, multiplier: 0.33).isActive = true

Looks like indicatorView is nil

Minitour commented 7 years ago

Yeah I forgot I modified some other stuff in the class. Here are the extra changes you need to apply in order for this to work.

Make selectionIndicator lazy:

internal lazy var selectionIndicator: UIView = UIView()

Update the setupSelectionIndicator function:

private func setupSelectionIndicator() {
        self.selectionIndicator.translatesAutoresizingMaskIntoConstraints = false
        self.buttonsContainer.addSubview(self.selectionIndicator)
        self.setupSelectionIndicatorConstraints()
        self.selectionIndicatorHeightConstraint.constant = self.selectionIndicatorHeight
        self.selectionIndicator.backgroundColor = self.selectionIndicatorColor ?? UIColor.black
    }
Seb-AS commented 7 years ago

@Minitour works!!! Thank you so much for all your help.

Minitour commented 7 years ago

@Seb-AS You're welcome 😄

Abhishekg219 commented 6 years ago

Is there a separate branch for this ?

Minitour commented 6 years ago

@Abhishekg219 No, but these changes are made on release 1.1.3, so what you can do is create a branch there and follow the guide.

alanmaynard commented 6 years ago

simulator screen shot - iphone x - 2018-05-28 at 20 26 16

After following the process above, this is the result on iPhone X on release 1.3.2. Is there any optimization for this?