Search code examples
iosswiftautolayoutcenteringuistackview

Retrieve the right position of a subView with auto-layout


I want to place programmatically a view at the center of all the the subviews created in a storyboard.

In the storyboard, I have a view, and inside a Vertical StackView, which has constraint to fill the full screen, distribution "Equal spacing". Inside of the Vertical Stack View, I have 3 horizontal stack views, with constraint height = 100, and trailing and leading space : 0 from superview. The distribution is "equal spacing" too. In each horizontal stack view, I have two views, with constraint width and height = 100, that views are red.

So far, so good, I have the interface I wanted, enter image description here

Now I want to retrieve the center of each red view, to place another view at that position (in fact, it'll be a pawn over a checkboard...)

So I wrote that:

import UIKit

class ViewController: UIViewController {

@IBOutlet weak var verticalStackView:UIStackView?

override func viewDidLoad() {
    super.viewDidLoad()
    print ("viewDidLoad")
    printPos()
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    print ("viewWillAppear")
    printPos()
}

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    print ("viewWillLayoutSubviews")
    printPos()
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    print ("viewDidLayoutSubviews")
    printPos()
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    print ("viewDidAppear")
    printPos()
    addViews()
}


func printPos() {
    guard let verticalSV = verticalStackView else { return }
    for horizontalSV in verticalSV.subviews {
        for view in horizontalSV.subviews {
            let center = view.convert(view.center, to:nil)
            print(" - \(center)")
        }
    }
}

func addViews() {
    guard let verticalSV = verticalStackView else { return }
    for horizontalSV in verticalSV.subviews {
        for redView in horizontalSV.subviews {
            let redCenter = redView.convert(redView.center, to:self.view)
            let newView = UIView(frame: CGRect(x:0, y:0, width:50, height:50))
            //newView.center = redCenter
            newView.center.x = 35 + redCenter.x / 2
            newView.center.y = redCenter.y
            newView.backgroundColor = .black
            self.view.addSubview(newView)
        }
    }
}

}

With that, I can see that in ViewDidLoad and ViewWillAppear, the metrics are those of the storyboard. The positions changed then in viewWillLayoutSubviews, in viewDidLayoutSubviews and again in viewDidAppear.

After viewDidAppear (so after all the views are in place), I have to divide x coordinate by 2 and adding something like 35 (see code) to have the new black view correctly centered in the red view. I don't understand why I can't simply use the center of the red view... And why does it works for y position ?


Solution

  • I found your issue, replace

    let redCenter = redView.convert(redView.center, to:self.view)
    

    with

    let redCenter = horizontalSV.convert(redView.center, to: self.view)
    

    When you convert, you have to convert from the view original coordinates, here it was the horizontalSv