Swift: Floating Plus button over Tableview using The StoryBoard
Asked Answered
M

6

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

Midgett answered 30/7, 2015 at 13:46 Comment(1)
Old question , but here's a great Swift Library - github.com/yoavlt/LiquidFloatingActionButtonWarfield
P
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)
        }
    }
}
Phyllotaxis answered 10/5, 2018 at 16:2 Comment(12)
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.Sanitarium
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 :)Phyllotaxis
Sure. I can't show all of the source code but I can get you some screenshots, etc. I would definitely appreciate it.Sanitarium
Your round button needs to be a var, not a letPhyllotaxis
roundButton = nil won't work unless I reference button like this: private var roundButton: UIButton!Unemployment
@Unemployment I wouldn't force unwrap it. If you need to you could do: private var roundButton: UIButton?Phyllotaxis
Answer has been updated with complete implementation.Phyllotaxis
@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!Unemployment
@Unemployment yes! thanks for catching my mistake, I have corrected it :)Phyllotaxis
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..Fala
@Fala 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?Phyllotaxis
@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..Fala
H
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**/

}
Henandchickens answered 5/1, 2017 at 6:36 Comment(0)
R
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

Reimpression answered 30/7, 2015 at 14:14 Comment(7)
it is on the back of the table how to make on frontMidgett
otherwise it will be inside the table rowMidgett
it I drag a button automatically it will be in the row !Midgett
Drag it to the scene and make sure it's ordered so that the button is under (but not inside) the table viewReimpression
@Midgett can you please try aboveReimpression
notice it's not possible to do for Static tablesOgdon
@Reimpression I am not sure if you are still alive, I hope yes :D However, this should be marked as accepted answer since it is the only one that uses Storyboard as asked in question. The key is to put the button in the scene, below the table, not inside it, as @Reimpression said. Check the order in his ViewController scene on the image above.Malikamalin
O
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)])
}
Overhappy answered 9/9, 2017 at 0:18 Comment(0)
A
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
Athematic answered 20/8, 2019 at 15:36 Comment(0)
M
0

Swift 5, improving @litso's answer:

enter image description here

Malikamalin answered 10/7, 2023 at 18:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.