How to get UIButton frame X position in Swift (UIKit)?

159 views Asked by At

I want to make my blue underline start and end within the red frame. So, I'm trying to get the X position, the starting position of my UIButton. The red one (red is a bg color). How can I do that?

enter image description here

Things I've tried:

promoButton.frame.origin.x

or

promoButton.bounds.origin.x

result: 0.0

promoButton.layer.position.x

This actually move the underline, but it started at the 'P' of 'Promo' instead of the red frame.

Can anyone tell me how to get the frame X position?

Thankyou in advance.


Update:

This is how i put it in the storyboard:

First there is this Tab View

enter image description here

Next, I have the container

enter image description here

Then the Promo button it self

enter image description here

I guess 0.0 is because it's inside a view. but is it possible to calculate X position from the screen tho?

2

There are 2 answers

0
DonMag On BEST ANSWER

You can do this with auto-layout / constraints, so you don't need to worry about "frames."

We'll setup the Storyboard like this - note: we are NOT adding the "underline view" in the Storyboard:

enter image description here

I've given the "container view" and the buttons different background colors to make it easier to see the framing.

We'll make @IBOutlet connections to the three buttons:

@IBOutlet var btn1: UIButton!
@IBOutlet var btn2: UIButton!
@IBOutlet var btn3: UIButton!

and we'll connect the Touch Up Inside for all buttons to the same function.

In viewDidLoad() we'll create and add the underlineView, and give it a Height constraint of 4.0. It looks like you want it about 8-points below the buttons, so we'll also give it a Top constraint relative to the Bottom of the first button (doesn't really matter which button, as we're assuming they are the same).

Now, we'll add two class properties:

var underlineLeadingConstraint: NSLayoutConstraint!
var underlineWidthConstraint: NSLayoutConstraint!

Again in viewDidLoad() we'll create those two constraints, and set them related to btn1.

When we tap a button, we'll:

  • deactivate those two constraints

  • re-make them related to the tapped button

  • and animate the change

    @IBAction func btnTap(_ sender: UIButton) { // de-activate the underline view constraints underlineLeadingConstraint.isActive = false underlineWidthConstraint.isActive = false

      // re-create the Leading and Width constraints
      //  related to the tapped button
      underlineLeadingConstraint = underlineView.leadingAnchor.constraint(equalTo: sender.leadingAnchor)
      underlineWidthConstraint = underlineView.widthAnchor.constraint(equalTo: sender.widthAnchor)
    
      // activate those new constraints
      underlineLeadingConstraint.isActive = true
      underlineWidthConstraint.isActive = true
    
      // if you want to animate it
      UIView.animate(withDuration: 0.3, animations: {
          self.view.layoutIfNeeded()
      })
    

    }

Here's the complete controller class:

class UnderlineViewController: UIViewController {
    
    @IBOutlet var btn1: UIButton!
    @IBOutlet var btn2: UIButton!
    @IBOutlet var btn3: UIButton!

    var underlineView: UIView!
    
    var underlineLeadingConstraint: NSLayoutConstraint!
    var underlineWidthConstraint: NSLayoutConstraint!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        underlineView = UIView()
        underlineView.backgroundColor = .blue
        underlineView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(underlineView)
        
        // underlineView height is 4-points
        underlineView.heightAnchor.constraint(equalToConstant: 4.0).isActive = true
        
        // looks like you want it about 8-points below the button bottom?
        underlineView.topAnchor.constraint(equalTo: btn1.bottomAnchor, constant: 8.0).isActive = true
        
        // create Leading and Width constraints for the underlineView
        underlineLeadingConstraint = underlineView.leadingAnchor.constraint(equalTo: btn1.leadingAnchor)
        underlineWidthConstraint = underlineView.widthAnchor.constraint(equalTo: btn1.widthAnchor)
        // activate those constraints
        underlineLeadingConstraint.isActive = true
        underlineWidthConstraint.isActive = true
        
    }

    @IBAction func btnTap(_ sender: UIButton) {
        // de-activate the underline view constraints
        underlineLeadingConstraint.isActive = false
        underlineWidthConstraint.isActive = false
        
        // re-create the Leading and Width constraints
        //  related to the tapped button
        underlineLeadingConstraint = underlineView.leadingAnchor.constraint(equalTo: sender.leadingAnchor)
        underlineWidthConstraint = underlineView.widthAnchor.constraint(equalTo: sender.widthAnchor)
        
        // activate those new constraints
        underlineLeadingConstraint.isActive = true
        underlineWidthConstraint.isActive = true
        
        // if you want to animate it
        UIView.animate(withDuration: 0.3, animations: {
            self.view.layoutIfNeeded()
        })
    }
    
}

Here's how it looks when running:

enter image description here

Since we are now using constraints - instead of calculating frame coordinates - the underline width and position will automatically update when the "tab buttons" change... such as on device rotation.

0
pronebird On

Use convert(_:, to:) method available on UIView in order to convert between coordinate spaces, for example:

let button = UIButton(frame: CGRect(x: 20, y: 20, width: 50, height: 50))
let container = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
let subcontainer = UIView(frame: CGRect(x: 150, y: 150, width: 250, height: 250))

container.addSubview(subcontainer)
subcontainer.addSubview(button)

let buttonFrameWithinContainer = button.convert(button.bounds, to: container) // returns { 170, 170, 50, 50 }