Home > OS >  How to move a UIButton to make space for a snackbar?
How to move a UIButton to make space for a snackbar?

Time:06-10

I have a snackbar that appears at times on my view controller. But when it appears it blocks the UIButton that already exists. How do I shift the button to accommodate for the snackbar without hardcoding moving the position of the button?

Thanks!

CodePudding user response:

This can be done by declaring two different NSLayoutConstraints for your button's bottom constraint, one based on the safe area (or whatever your button is originally positioned relative to), and another based on the position of the snackbar. Then, toggle between which constraint is actually used by setting the respective .isActive properties of the constraints to false and true.

I have confirmed that this works in the example here:

import UIKit

class ViewController: UIViewController {
    // Connect an outlet to the button to allow adding the new constraint
    // to it programatically
    @IBOutlet weak var movingButton: UIButton!
    
    // This is the bottom constraint of the UIButton which will change based on whether or not
    // the snackbar is open.  It was set up in Main.storyboard with parameters as follows:
    // -- First Item:   Button.Bottom
    // -- Relation:     Equal
    // -- Second Item:  Safe Area.Bottom
    // -- Constant:     0
    // -- Priority:     1000
    // -- Multiplier:   1
    @IBOutlet var buttonBottomConstraint: NSLayoutConstraint!
    // Note:  do not use 'weak var' for this constraint, as this would cause a nil exception as the
    // constraint would get deleted as soon as its .isActive property is set to false inside toggleSnackbar
    
    // Will be determined by the snackbar at its initialization
    var newButtonBottomConstraint: NSLayoutConstraint?
    
    // Will be set inside viewDidLayoutSubviews
    var screenWidth: Double?
    var screenHeight: Double?
    
    var snackbar: UILabel?
    var snackbarIsOpen = false
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        if screenWidth == nil {
            // These will depend on the device (iPhone, iPad etc.)
            screenWidth = view.safeAreaLayoutGuide.layoutFrame.width;
            screenHeight = view.safeAreaLayoutGuide.layoutFrame.height;
        }
        // Only instantiate snackbar once, not every time
        // viewDidLayoutSubviews is called.
        if snackbar == nil {
            snackbar = UILabel(frame: CGRect(x: 0.1*screenWidth!, y: 0.95*screenHeight!, width: 0.8*screenWidth!, height: 0.1*screenHeight!))
            snackbar?.backgroundColor = .systemGray
            snackbar?.textColor = .white
            snackbar?.numberOfLines = 2
            snackbar?.textAlignment = .center
            snackbar?.font = UIFont.systemFont(ofSize: 18)
            snackbar?.text = "When this appears, the button will move upwards."
            
            // This constraint will replace movingButton's IBOutlet constraint when the snackbar is open
            newButtonBottomConstraint = NSLayoutConstraint(item: movingButton!, attribute: .bottom, relatedBy: .equal, toItem: snackbar!, attribute: .top, multiplier: 1, constant: -10)
        }
    }

    @IBAction func toggleSnackbar(_ sender: UIButton) {
        // Close the snackbar if it's already open
        if snackbarIsOpen {
            snackbar?.removeFromSuperview()
            sender.setTitle("Press For Snackbar", for: .normal)
            snackbarIsOpen = false
            
            movingButton.setTitle("Will Move", for: .normal)
            // Swap which constraint is active:  back to original storyboard value
            newButtonBottomConstraint?.isActive = false
            buttonBottomConstraint.isActive = true
        } else {
            // Open the snackbar if it isn't already open
            view.addSubview(snackbar!)
            sender.setTitle("Dismiss Snackbar", for: .normal)
            snackbarIsOpen = true
            
            movingButton.setTitle("Did Move", for: .normal)
            // Swap which constraint is active:  use new constraint based on snackbar's position
            buttonBottomConstraint.isActive = false
            newButtonBottomConstraint?.isActive = true
        }
    }
}

Additional details: The buttons were set up in Main.storyboard, as were all constraints except for the one which is relative to the snackbar. I included the Interface Builder values used for the one relevant to the example (button's bottom constraint) in the comments in the code sample.

CodePudding user response:

You could probably do this by having the button's bottom constraint depend on the snackbar container's top constraint (or vice versa, if the snackbar comes down from above). The snackbar container height is zero when nothing happens, and expands when a snackbar is displayed.

I would, however, suggest a different approach. Snackbars don't usually displace UI, and I guess it would be frustrating for users to "miss" the button because the app displays a snackbar. See also the Material Design Guidelines for how these things might work. (They aren't native to iOS, so Apple has no documentation as far as I know).

  • Related