LGPlusButtonsView icon indicating copy to clipboard operation
LGPlusButtonsView copied to clipboard

How to add LGPlusButton view as subview to tab bar

Open punithbm opened this issue 9 years ago • 3 comments

Hi There,

Can you please help me how to add LGPlusButton view to TabBar. If i add them as subview to TabbarController or the NavigationController there is a issues in touch detection.

screen shot 2016-06-27 at 3 45 22 pm

punithbm avatar Jun 27 '16 10:06 punithbm

Hi,

I just did something similar. In my case, I have a Tab Bar that is 20pt taller than the standard iOS Tab Bar. So, when I tried .BottomRight, the + button would be obstructed by my tab bar.

So, since I already was subclassing UITabBarController for other reasons, I was able to place the + button inside another "view" that I could control. The idea is that I can then place this container view wherever I want, in effect, I can place the + button wherever I want if I place it inside this container view.

Below are my steps:

  1. Create a subclass of UITabBarController and make sure your Tab Bar is using that. (Either in IB or in code). For simplicity, let's call your subclass "SectionTabBarController".
  2. In your SectionTabBarController, viewDidLoad, AFTER you call super/base viewDidLoad, create a container view (this is where you'll drop in the LG button later on).
  3. In my case, I used a view called actionButtonContainer and I added that to self.view in my subclass. I used AutoLayout in my case to place the container view exactly where I wanted it.
  4. Now, finally, create your LGPlusButtonView and add it to your actionButtonContainer view.
  5. I also had to add a little magic to "expand" the container view when the buttons are showing - otherwise, the buttons would not get taps. You can see that below in my code.

Below is my code - inside CustomTabBarController viewDidLoad. Note that I use a library for AutoLayout called "PureLayout", so that's why you see all my constraints are in the form "autoPin...". Remember, I'm placing the container view "bottom right".

//
//  SectionTabBarController.swift
//  SomeApp
//
//  Created by Eric A. Soto on 7/9/16.
//

import UIKit
import LGPlusButtonsView
import Intercom

/**
 A custom class to style our TabBar + add our Floating Action Button
 */
class SectionTabBarController: UITabBarController, LGPlusButtonsViewDelegate {

    var actionButton: LGPlusButtonsView!
    var actionButtonContainerView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Create a placeholder view for our Action Button
        actionButtonContainerView = UIView()
        self.view.addSubview(actionButtonContainerView)
        // Constrain it (we start with the action buttons not showing = closed)
        actionButtonContainerView.translatesAutoresizingMaskIntoConstraints = false
        constrainActionButtonContainer(.Closed)

        // Create our button
        actionButton = LGPlusButtonsView(numberOfButtons: 3, firstButtonIsPlusButton: true, showAfterInit: true, delegate: self)

        actionButton.setButtonsTitles(["?", "Get Help", "View Conversations"], forState: .Normal)
        actionButton.setButtonAtIndex(0, title: "X", forState: .Selected)
        actionButton.setButtonsAdjustsImageWhenHighlighted(false)
        actionButton.setButtonsBackgroundColor(AppFormatting.TabBar.backgroundColorWhenSelected, forState: .Normal)

        // Set our default buttons (all of them), then we'll customize the PLUS
        actionButton.setButtonsSize(CGSize(width: 170.0, height: 38.0), forOrientation: .All)
        actionButton.setButtonsLayerCornerRadius(6.0, forOrientation: .All)
        actionButton.setButtonsTitleFont(AppFormatting.Fonts.defaultFont(18.0), forOrientation: .All)

        // Format the plus icon
        actionButton.setButtonAtIndex(0, size: CGSize(width: 44.0, height: 44.0), forOrientation: .All)
        actionButton.setButtonAtIndex(0, layerCornerRadius: 22.00, forOrientation: .All)
        actionButton.setButtonAtIndex(0, titleFont: AppFormatting.Fonts.defaultFontBold(24.0), forOrientation: .All)

        // Now add to our container
        actionButtonContainerView.addSubview(actionButton)
        // Constrain it
        actionButton.translatesAutoresizingMaskIntoConstraints = false
        actionButton.autoCenterInSuperview()

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: - LGPlusButtonsView Delegate

    /**
     When the buttons show, we want to constrain our container view for the Action Button to include the 
     buttons. Otherwise, we won't be able to tap on them!
     */
    func plusButtonsViewWillShowButtons(plusButtonsView: LGPlusButtonsView!) {
        constrainActionButtonContainer(.Open)
    }

    /**
     When the buttons hide, we want to constrain our container view for the Action Button to just the
     plus button. Otherwise, it will receive taps instead of the view underneath!
     */
    func plusButtonsViewWillHideButtons(plusButtonsView: LGPlusButtonsView!) {
        constrainActionButtonContainer(.Closed)
    }

    /**
     Handle a button Tap on our Floating Action Button
     */
    func plusButtonsView(plusButtonsView: LGPlusButtonsView!, buttonPressedWithTitle title: String!, description: String!, index: UInt) {

        // On tap of button zero, we let the library do its thing, so we just exit here!
        guard index > 0 else {
            return
        }

        // ASSERT COMMENT: We have an index 1 or above

        // On tap of a button, we hide the buttons
        plusButtonsView.hideButtonsAnimated(true, completionHandler: nil)

        // Now, decide what to do based on which button was tapped.
        switch index {
        case 1:
            Intercom.presentMessageComposer()
        case 2:
            Intercom.presentConversationList()
        default:
            Intercom.presentConversationList()
        }
    }

    // MARK: - Helpers

    enum actionButtonState {
        case Open
        case Closed
    }

    func constrainActionButtonContainer(state: actionButtonState) {

        // if the view has constraints, clear them prior to resetting
        if actionButtonContainerView.constraints.count > 0 {
            actionButtonContainerView.removeConstraints(actionButtonContainerView.constraints)
        }

        // Position the bottom/right edge
        actionButtonContainerView.autoPinEdge(.Right, toEdge: .Right, ofView: self.view, withOffset: -3.0)
        actionButtonContainerView.autoPinEdge(.Bottom, toEdge: .Bottom, ofView: self.view, withOffset: -65.0)

        // Now, constrain for the state we want
        switch state {
        case .Closed:
            // When the action buttons are hidden, we only accommodate the + button, otherwise, our view will take over taps from other items!
            actionButtonContainerView.autoSetDimensionsToSize(CGSize(width: 60, height: 60))
        case .Open:
            // When the action buttons are shown, we make the view larger to accomodate the buttons
            actionButtonContainerView.autoSetDimensionsToSize(CGSize(width: 170, height: 150))
        }

    }

}

I know this is not "exactly" what you're trying to do, but I hope it might give you ideas.

Good luck!

Eric.

ericwastaken avatar Jul 10 '16 23:07 ericwastaken

@ericwastaken Hi Eric, Thank you, It helped me a lot..:)

punithbm avatar Jul 11 '16 12:07 punithbm

Glad I was able to help.

Note that after I did the above, I had a situation where I was still not getting TAPS for the views underneath. I ended up having to place the action button on a view that covered everything.

Well, turns out that a view can implement "pointInside" to let the OS know if a tap should be handled by the view OR passed on to the rest of the responder chain.

So, I moved some things around and below is what I ended up with.

I now have ActionButtonView.swift as a standalone subclass of UIView:

//
//  ActionButtonView.swift
//  SomeApp
//
//  Created by Eric A. Soto on 7/10/16.
//

import UIKit
import LGPlusButtonsView
import Intercom

class ActionButtonView: UIView, LGPlusButtonsViewDelegate {

    // Public Properties
    var actionButton: LGPlusButtonsView!
    var actionButtonContainerView: UIView!

    // MARK: - Constructors & Events

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupActionButton()
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
        fatalError("init(coder:) has not been implemented. This view requires properties to be set, so implementing it via IB is not supported.");
    }

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        // We only want to match the point if it's inside our container view's frame
        return CGRectContainsPoint(actionButtonContainerView.frame, point)
    }

    // MARK: - Exposed Methods

    func hideButtonsIfShowing() {
        actionButton.hideButtonsAnimated(true, completionHandler: nil)
    }

    // MARK: - LGPlusButtonsView Delegate

    /**
     When the buttons show, we want to constrain our container view for the Action Button to include the
     buttons. Otherwise, we won't be able to tap on them!
     */
    func plusButtonsViewWillShowButtons(plusButtonsView: LGPlusButtonsView!) {
        constrainActionButtonContainer(.Open)
    }

    /**
     When the buttons hide, we want to constrain our container view for the Action Button to just the
     plus button. Otherwise, it will receive taps instead of the view underneath!
     */
    func plusButtonsViewWillHideButtons(plusButtonsView: LGPlusButtonsView!) {
        constrainActionButtonContainer(.Closed)
    }

    /**
     Handle a button Tap on our Floating Action Button
     */
    func plusButtonsView(plusButtonsView: LGPlusButtonsView!, buttonPressedWithTitle title: String!, description: String!, index: UInt) {

        // On tap of button zero, we let the library do its thing
        guard index > 0 else {
            return
        }

        // ASSERT COMMENT: We have an index 1 or above

        // On tap of a button, we hide the buttons
        plusButtonsView.hideButtonsAnimated(true, completionHandler: nil)

        // Now, decide what to do based on which button was tapped.
        switch index {
        case 1:
            Intercom.presentMessageComposer()
        case 2:
            Intercom.presentConversationList()
        case 3:
            // Nada - this is the app version button
            break
        default:
            Intercom.presentConversationList()
        }
    }

}

private extension ActionButtonView {

    // MARK: - Implementation

    func setupActionButton() {

        // Create a placeholder view for our Action Button
        actionButtonContainerView = UIView()
        self.addSubview(actionButtonContainerView)
        // Constrain it (we start with the action buttons not showing = closed)
        actionButtonContainerView.translatesAutoresizingMaskIntoConstraints = false
        constrainActionButtonContainer(.Closed)

        // Create our button
        actionButton = LGPlusButtonsView(numberOfButtons: 4, firstButtonIsPlusButton: true, showAfterInit: true, delegate: self)
        actionButton.coverColor = UIColor.blackColor().colorWithAlphaComponent(0.4)
        actionButton.setButtonsTitles(["?", "Get Help", "View Conversations", AppUtility.appVersionString], forState: .Normal)
        actionButton.setButtonsAdjustsImageWhenHighlighted(false)
        actionButton.setButtonsBackgroundColor(AppFormatting.TabBar.backgroundColorWhenSelected, forState: .Normal)

        // Set our default buttons (all of them), then we'll customize the PLUS
        actionButton.setButtonsSize(CGSize(width: 170.0, height: 38.0), forOrientation: .All)
        actionButton.setButtonsLayerCornerRadius(6.0, forOrientation: .All)
        actionButton.setButtonsTitleFont(AppFormatting.Fonts.defaultFont(18.0), forOrientation: .All)

        // Format the plus icon
        actionButton.setButtonAtIndex(0, title: "X", forState: .Selected)
        actionButton.setButtonAtIndex(0, size: CGSize(width: 44.0, height: 44.0), forOrientation: .All)
        actionButton.setButtonAtIndex(0, layerCornerRadius: 22.00, forOrientation: .All)
        actionButton.setButtonAtIndex(0, titleFont: AppFormatting.Fonts.defaultFontBold(24.0), forOrientation: .All)
        actionButton.setButtonAtIndex(0, backgroundColor: AppFormatting.TabBar.backgroundColorWhenSelected.colorWithAlphaComponent(0.6), forState: .Normal)
        actionButton.setButtonAtIndex(0, backgroundColor: AppFormatting.TabBar.backgroundColorWhenSelected, forState: .Selected)

        // Format the App Version button
        actionButton.setButtonAtIndex(3, layerCornerRadius: 0.0, forOrientation: .All)
        actionButton.setButtonAtIndex(3, backgroundColor: UIColor.whiteColor(), forState: .Normal)
        actionButton.setButtonAtIndex(3, titleColor: AppFormatting.Fonts.defaultFontColor, forState: .Normal)
        actionButton.setButtonAtIndex(3, titleFont: AppFormatting.Fonts.defaultFont(14.0), forOrientation: .All)
        actionButton.setButtonAtIndex(3, size: CGSize(width: 170.0, height: 24.0), forOrientation: .All)
        actionButton.setButtonAtIndex(3, layerBorderColor: AppFormatting.Fonts.defaultFontColor)
        actionButton.setButtonAtIndex(3, layerBorderWidth: 1.0)

        // Now add to our container
        actionButtonContainerView.addSubview(actionButton)
        // Constrain it
        actionButton.translatesAutoresizingMaskIntoConstraints = false
        actionButton.autoCenterInSuperview()

    }

    // MARK: - Helpers

    enum actionButtonState {
        case Open
        case Closed
    }

    func constrainActionButtonContainer(state: actionButtonState) {

        let viewWidth = self.frame.width
        let viewHeight = self.frame.height

        let bottomOffset:CGFloat = -65.0
        let rightOffset:CGFloat = 0 // Offsetting from the right causes misalignment of the COVER!
        let closedSize = CGSize(width: 60.0, height: 60.0)
        let openSize = CGSize(width: viewWidth+rightOffset, height: viewHeight-bottomOffset)

        // if the view has constraints, clear them prior to resetting
        if actionButtonContainerView.constraints.count > 0 {
            actionButtonContainerView.removeConstraints(actionButtonContainerView.constraints)
        }

        // Position the bottom/right edge
        actionButtonContainerView.autoPinEdge(.Right, toEdge: .Right, ofView: self, withOffset: rightOffset)
        actionButtonContainerView.autoPinEdge(.Bottom, toEdge: .Bottom, ofView: self, withOffset: bottomOffset)

        // Now, constrain for the state we want
        switch state {
        case .Closed:
            // When the action buttons are hidden, we only accommodate the + button, otherwise, our view will take over taps from other items!
            actionButtonContainerView.autoSetDimensionsToSize(closedSize)
        case .Open:
            // When the action buttons are shown, we make the view larger to accomodate the buttons
            actionButtonContainerView.autoSetDimensionsToSize(openSize)
        }

    }

}

Then, in my UITabBarController subclass:

// Inside viewDidLoad

        // Add our ActionButton (it will constrain itself to where it needs to)
        actionButtonView = ActionButtonView(frame:self.view.frame)
        self.view.addSubview(actionButtonView)
        actionButtonView.autoPinEdgesToSuperviewEdges()

ericwastaken avatar Jul 11 '16 17:07 ericwastaken