16

How to Add floating button as Facebook's App Rooms using the storyboard?

I can't drag UIView on the tableview that's the only way I know.

enter image description here enter image description here

user3662992
  • 688
  • 1
  • 6
  • 16

6 Answers6

20

This button code below:

  • Adds Shadow
  • Makes the button round
  • Adds an animation to draw attention

Swift 4.2: COMPLETE VIEW CONTROLLER IMPLEMENTATION

import Foundation

public class FloatingButtonViewController: UIViewController {
    private var floatingButton: UIButton?
    // TODO: Replace image name with your own image:
    private let floatingButtonImageName = "NAME OF YOUR IMAGE"
    private static let buttonHeight: CGFloat = 75.0
    private static let buttonWidth: CGFloat = 75.0
    private let roundValue = FloatingButtonViewController.buttonHeight/2
    private let trailingValue: CGFloat = 15.0
    private let leadingValue: CGFloat = 15.0
    private let shadowRadius: CGFloat = 2.0
    private let shadowOpacity: Float = 0.5
    private let shadowOffset = CGSize(width: 0.0, height: 5.0)
    private let scaleKeyPath = "scale"
    private let animationKeyPath = "transform.scale"
    private let animationDuration: CFTimeInterval = 0.4
    private let animateFromValue: CGFloat = 1.00
    private let animateToValue: CGFloat = 1.05

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

    public override func viewWillDisappear(_ animated: Bool) {
        guard floatingButton?.superview != nil else {  return }
        DispatchQueue.main.async {
            self.floatingButton?.removeFromSuperview()
            self.floatingButton = nil
        }
        super.viewWillDisappear(animated)
    }

    private func createFloatingButton() {
        floatingButton = UIButton(type: .custom)
        floatingButton?.translatesAutoresizingMaskIntoConstraints = false
        floatingButton?.backgroundColor = .white
        floatingButton?.setImage(UIImage(named: floatingButtonImageName), for: .normal)
        floatingButton?.addTarget(self, action: #selector(doThisWhenButtonIsTapped(_:)), for: .touchUpInside)
        constrainFloatingButtonToWindow()
        makeFloatingButtonRound()
        addShadowToFloatingButton()
        addScaleAnimationToFloatingButton()
    }

    // TODO: Add some logic for when the button is tapped.
    @IBAction private func doThisWhenButtonIsTapped(_ sender: Any) {
        print("Button Tapped")
    }

    private func constrainFloatingButtonToWindow() {
        DispatchQueue.main.async {
            guard let keyWindow = UIApplication.shared.keyWindow,
                let floatingButton = self.floatingButton else { return }
            keyWindow.addSubview(floatingButton)
            keyWindow.trailingAnchor.constraint(equalTo: floatingButton.trailingAnchor,
                                                constant: self.trailingValue).isActive = true
            keyWindow.bottomAnchor.constraint(equalTo: floatingButton.bottomAnchor,
                                              constant: self.leadingValue).isActive = true
            floatingButton.widthAnchor.constraint(equalToConstant:
                FloatingButtonViewController.buttonWidth).isActive = true
            floatingButton.heightAnchor.constraint(equalToConstant:
                FloatingButtonViewController.buttonHeight).isActive = true
        }
    }

    private func makeFloatingButtonRound() {
        floatingButton?.layer.cornerRadius = roundValue
    }

    private func addShadowToFloatingButton() {
        floatingButton?.layer.shadowColor = UIColor.black.cgColor
        floatingButton?.layer.shadowOffset = shadowOffset
        floatingButton?.layer.masksToBounds = false
        floatingButton?.layer.shadowRadius = shadowRadius
        floatingButton?.layer.shadowOpacity = shadowOpacity
    }

    private func addScaleAnimationToFloatingButton() {
        // Add a pulsing animation to draw attention to button:
        DispatchQueue.main.async {
            let scaleAnimation: CABasicAnimation = CABasicAnimation(keyPath: self.animationKeyPath)
            scaleAnimation.duration = self.animationDuration
            scaleAnimation.repeatCount = .greatestFiniteMagnitude
            scaleAnimation.autoreverses = true
            scaleAnimation.fromValue = self.animateFromValue
            scaleAnimation.toValue = self.animateToValue
            self.floatingButton?.layer.add(scaleAnimation, forKey: self.scaleKeyPath)
        }
    }
}
Joshua Hart
  • 772
  • 1
  • 21
  • 31
  • 1
    This works well except for the removing the button in viewWillDisappear. Couldn't set button to nil and the removeFromSuperview() was not getting called. Removed the DispatchQueue and also setting button to nil. Works as expected. – Matthew Bradshaw Sep 19 '18 at 18:52
  • Hey @MatthewBradshaw could you show me your example, I have this code running in a Top 50 Paid Application and it's had zero issues. If I can be of some help to you, I don't mind helping you out :) – Joshua Hart Sep 19 '18 at 18:55
  • Sure. I can't show all of the source code but I can get you some screenshots, etc. I would definitely appreciate it. – Matthew Bradshaw Sep 19 '18 at 18:58
  • Your round button needs to be a var, not a let – Joshua Hart Sep 24 '18 at 16:16
  • `roundButton = nil` won't work unless I reference button like this: `private var roundButton: UIButton!` – bvh Apr 25 '19 at 23:17
  • @bvh I wouldn't force unwrap it. If you need to you could do: `private var roundButton: UIButton?` – Joshua Hart Apr 26 '19 at 12:15
  • 1
    Answer has been updated with complete implementation. – Joshua Hart Apr 26 '19 at 14:17
  • 1
    @JoshuaHart Thank you for the code update! I believe in `viewWillDisappear(_:)` you meant to use `super.viewWillDisappear(animated)` instead of `super.viewWillAppear(animated)`? Also, for the pulsing animation I needed to put the code in `DispatchQueue.main.async { ... } ` in order for the pulsing to work for me. Thanks again! – bvh Apr 28 '19 at 03:26
  • @bvh yes! thanks for catching my mistake, I have corrected it :) – Joshua Hart Apr 28 '19 at 03:35
  • Can someone show how can I use this code? inside viewDidLoad I did :let operationButton:FloatingButtonViewController = self.storyboard!.instantiateViewController(withIdentifier: "FloatingButtonViewController") as! FloatingButtonViewController operationButton.view.frame = self.view.bounds; operationButton.willMove(toParent: self) self.view.addSubview(operationButton.view) self.addChild(operationButton) operationButton.didMove(toParent: self) but on UITableViewController the prototype cell become invisible when filled with data.. – Marco Nov 02 '19 at 18:28
  • @Marco You don't need to do the whole view controller implementation. It's just a standard working example. To try it out, just create a new storyboard view controller on your main.storyboard, change the class type FloatingButtonViewController and attach a segue to it so that you can play with it. If you need more help, we can chat if you'd like? – Joshua Hart Nov 03 '19 at 00:19
  • @JoshuaHart thank you very much, I'm pretty new to swift and I'm a bit stuck with your code.. Your help will be very appreciated.. so how can we chat? probably is a function inside stackoverflow that i never used before.. – Marco Nov 03 '19 at 04:48
15

Its a bit late, but may be it can help someone. You can do it very easily via programatically. Just create an instance of the button, set the desired property of button It is preferable to use constraints over frame cause it'll render the button at same position on every screen size. I am posting the code which will make the button round. Play with the value of constraints to change the position and size of button.

Swift 3

var roundButton = UIButton()
override func viewDidLoad() {
    super.viewDidLoad()
    self.roundButton = UIButton(type: .custom)
    self.roundButton.setTitleColor(UIColor.orange, for: .normal)
    self.roundButton.addTarget(self, action: #selector(ButtonClick(_:)), for: UIControlEvents.touchUpInside)
    self.view.addSubview(roundButton)
}

override func viewWillLayoutSubviews() {

    roundButton.layer.cornerRadius = roundButton.layer.frame.size.width/2
    roundButton.backgroundColor = UIColor.lightGray
    roundButton.clipsToBounds = true
    roundButton.setImage(UIImage(named:"your-image"), for: .normal)
    roundButton.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        roundButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -3),  
    roundButton.bottomAnchor.constraint  
    (equalTo: self.view.bottomAnchor, constant: -53), 
    roundButton.widthAnchor.constraint(equalToConstant: 50),
    roundButton.heightAnchor.constraint(equalToConstant: 50)])
}

/** Action Handler for button **/

@IBAction func ButtonClick(_ sender: UIButton){

 /** Do whatever you wanna do on button click**/

}
Kunal Kumar
  • 1,722
  • 1
  • 17
  • 32
7

Put a table view inside a view controller. You should then be able to add the the button on top of the table view.

Storyboard

litso
  • 322
  • 1
  • 6
4

Following the answer of @Kunal Kumar, it works on the UIScrollView, and UIView, but it doesn't works on the UITableView. I found it is easy to make it works with UITableView, only need to add one line code. Thank you so much @Kunal Kumar

in the viewDidLoad, change self.view.addSubview(roundButton) to self.navigationController?.view.addSubview(roundButton) and it will work!!

by the way, I think we need to add super.viewWillLayoutSubviews() in the viewWillLayoutSubviews() method.

below is the all code with above mentioned,

Swift 3

// MARK: Floating Button

var roundButton = UIButton()
func createFloatingButton() {
    self.roundButton = UIButton(type: .custom)
    self.roundButton.setTitleColor(UIColor.orange, for: .normal)
    self.roundButton.addTarget(self, action: #selector(ButtonClick(_:)), for: UIControlEvents.touchUpInside)
    //change view to navigationController?.view, if you have a navigationController in this tableview
    self.navigationController?.view.addSubview(roundButton)
}

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()

    roundButton.layer.cornerRadius = roundButton.layer.frame.size.width/2
    roundButton.backgroundColor = UIColor.lightGray
    roundButton.clipsToBounds = true
    roundButton.setImage(UIImage(named:"ic_wb_sunny_48pt"), for: .normal)
    roundButton.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        roundButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -3),
        roundButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -53),
        roundButton.widthAnchor.constraint(equalToConstant: 50),
        roundButton.heightAnchor.constraint(equalToConstant: 50)])
}
Yuan Fu
  • 310
  • 2
  • 14
2

It's simply because your UIButton is under the UITableView, move it in the storyboard's tree so it appears like this :

| View
|-- Table View
|-- Button
minimoz
  • 33
  • 6
0

Swift 5, improving @litso's answer:

enter image description here

stackich
  • 3,607
  • 3
  • 17
  • 41