6

I'm trying to add a floating action button (not a floating menu button) which will navigate me to the next view controller with a single click. I'm not getting the floating button right. I have tried the below code and it is not showing the appropriate button on the table view as it is getting scrolled along with the table. Is there any way to stick the button at the same place without getting scrolled along with the table?

func floatingButton(){
    let btn = UIButton(type: .custom)
    btn.frame = CGRect(x: 285, y: 485, width: 100, height: 100)
    btn.setTitle("All Defects", for: .normal)
    btn.backgroundColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1)
    btn.clipsToBounds = true
    btn.layer.cornerRadius = 50
    btn.layer.borderColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
    btn.layer.borderWidth = 3.0
    btn.addTarget(self,action: #selector(DestinationVC.buttonTapped), for: UIControlEvent.touchUpInside)
    view.addSubview(btn)
}

enter image description here

Saurabh
  • 745
  • 1
  • 9
  • 33
  • https://stackoverflow.com/questions/37073935/how-to-add-floating-button-on-top-of-the-uitableview ? Your button shouldn't scroll with the tableView except if in your code in `view.addSubview(btn)`, `view` is the tableView. Or is your `UIViewController` a `UITableViewController`? – Larme Jan 17 '18 at 10:40
  • i already checked that question. Thanks for sharing though. My table view is a xib file..no idea how to set the floating button – Saurabh Jan 17 '18 at 10:46
  • The link you shared tells about the floating menu button..I do not want to achieve floating menu button..instead a single floating action button as shown in the screenshot – Saurabh Jan 17 '18 at 10:58

4 Answers4

8

All subviews added to UITableView will automatically scroll with it.

What you can do is add the button to the application Window, just remember to remove it when the ViewController disappears.

var btn = UIButton(type: .custom)
func floatingButton(){
    btn.frame = CGRect(x: 285, y: 485, width: 100, height: 100)
    btn.setTitle("All Defects", for: .normal)
    btn.backgroundColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1)
    btn.clipsToBounds = true
    btn.layer.cornerRadius = 50
    btn.layer.borderColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
    btn.layer.borderWidth = 3.0
    btn.addTarget(self,action: #selector(DestinationVC.buttonTapped), for: UIControlEvent.touchUpInside)
    if let window = UIApplication.shared.keyWindow {
        window.addSubview(btn)
    }
}

and in viewWillDisappear:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    btn.removeFromSuperview()
}

For iOS 13 and above you can check for the key window using this extension:

extension UIWindow {
    static var key: UIWindow? {
        if #available(iOS 13, *) {
            return UIApplication.shared.windows.first { $0.isKeyWindow }
        } else {
            return UIApplication.shared.keyWindow
        }
    }
}
ahbou
  • 4,710
  • 23
  • 36
  • Where to add the above function – Saurabh Jan 17 '18 at 11:17
  • 1
    @Saurabh viewDidLoad or viewWillAppear – ahbou Jan 17 '18 at 12:24
  • 1
    @ahbou couple of things. On this line `UIApplication.shared.keyWindow` is showing this error `'keyWindow' was deprecated in iOS 13.0` do you know how can I replace that line? to make work your solution for iOS 13? – user2924482 Oct 24 '19 at 20:10
  • On iOS 13, an app can have multiple windows. See this answer for a solution: https://stackoverflow.com/a/58031897/3698961 – ahbou Oct 24 '19 at 21:05
5

Here's a working example on XCode 10 with Swift 4.2.

Note that the FAB button will disappear when the view disappears, and reappears when the controller is loaded again.

import UIKit

class ViewController: UITableViewController {

lazy var faButton: UIButton = {
    let button = UIButton(frame: .zero)
    button.translatesAutoresizingMaskIntoConstraints = false
    button.backgroundColor = .blue
    button.addTarget(self, action: #selector(fabTapped(_:)), for: .touchUpInside)
    return button
}()

override func viewDidLoad() {
    super.viewDidLoad()
    setupTableView()
}

override func viewDidAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    if let view = UIApplication.shared.keyWindow {
        view.addSubview(faButton)
        setupButton()
    }
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if let view = UIApplication.shared.keyWindow, faButton.isDescendant(of: view) {
        faButton.removeFromSuperview()
    }
}

func setupTableView() {
    tableView.backgroundColor = .darkGray
}

func setupButton() {
    NSLayoutConstraint.activate([
        faButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -36),
        faButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -36),
        faButton.heightAnchor.constraint(equalToConstant: 80),
        faButton.widthAnchor.constraint(equalToConstant: 80)
        ])
    faButton.layer.cornerRadius = 40
    faButton.layer.masksToBounds = true
    faButton.layer.borderColor = UIColor.lightGray.cgColor
    faButton.layer.borderWidth = 4
}

@objc func fabTapped(_ button: UIButton) {
    print("button tapped")
}

override func numberOfSections(in tableView: UITableView) -> Int {
    return 5
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 3
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    return UITableViewCell()
}

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 64
}

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 32
}

override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    return UIView()
}
}
Kelvin Fok
  • 621
  • 7
  • 9
4

If you currently using tableViewController then no , you must subclass UIViewController add UItableView and your floating button to it

Or you may override scollviewDidScroll and change button y according to tableview current offset

drag scrollview as IBOutlet and set it's delegate to the viewController

   func scrollViewDidScroll(_ scrollView: UIScrollView) {

       let  off = scrollView.contentOffset.y

       btn.frame = CGRect(x: 285, y:   off + 485, width: btn.frame.size.width, height: btn.frame.size.height)
}

code snapshot

enter image description here

in action

enter image description here see in action

Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
0

There is a way that is worked for me. In my table view controller, defined a button first, then override the function scrollViewDidScroll(), like this:

import UIKit

class MyTableViewController: UITableViewController {

    private let button = UIButton(type: UIButton.ButtonType.custom) as UIButton

    override func viewDidLoad() {
        let bottomImage = UIImage(named: "yourImage.png")
        let yPst = self.view.frame.size.height - 55 - 20
        button.frame = CGRect(x: 12, y: yPst, width: 55, height: 55)
        button.setImage(bottomImage, for: .normal)
        button.autoresizingMask = [.flexibleTopMargin, .flexibleRightMargin]
        button.addTarget(self, action: #selector(buttonClicked(_:)), for:.touchUpInside)
        button.layer.shadowRadius = 3
        button.layer.shadowColor = UIColor.lightGray.cgColor
        button.layer.shadowOpacity = 0.9
        button.layer.shadowOffset = CGSize.zero
        button.layer.zPosition = 1
        view.addSubview(button)
    }

    override func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let off = self.tableView.contentOffset.y
        let yPst = self.view.frame.size.height
        button.frame = CGRect(x: 12, y:off + yPst, width: button.frame.size.width, height: button.frame.size.height)
    }

    @objc private func buttonClicked(_ notification: NSNotification) {
        // do something when you tapped the button
    }
}
Matthew
  • 1
  • 2