Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to add LGPlusButton view as subview to tab bar #31

Open
punithbm opened this issue Jun 27, 2016 · 3 comments
Open

How to add LGPlusButton view as subview to tab bar #31

punithbm opened this issue Jun 27, 2016 · 3 comments

Comments

@punithbm
Copy link

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

@ericwastaken
Copy link

ericwastaken commented Jul 10, 2016

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.

@punithbm
Copy link
Author

punithbm commented Jul 11, 2016

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

@ericwastaken
Copy link

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()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants