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.

  • UIStackView with UILabels in Xcode 7.3
  • how to change the background color of UIStackView?
  • Dynamically add subViews to UITableViewCell
  • Can you give UIStackView borders?
  • height and vertical position are ambiguous for UIStackView
  • Drag and Drop Image from UICollectionView to UIView?
  • 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() {
        print ("viewDidLoad")
    override func viewWillAppear(_ animated: Bool) {
        print ("viewWillAppear")
    override func viewWillLayoutSubviews() {
        print ("viewWillLayoutSubviews")
    override func viewDidLayoutSubviews() {
        print ("viewDidLayoutSubviews")
    override func viewDidAppear(_ animated: Bool) {
        print ("viewDidAppear")
    func printPos() {
        guard let verticalSV = verticalStackView else { return }
        for horizontalSV in verticalSV.subviews {
            for view in horizontalSV.subviews {
                let center = view.convert(, 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(, to:self.view)
                let newView = UIView(frame: CGRect(x:0, y:0, width:50, height:50))
                // = redCenter
       = 35 + redCenter.x / 2
       = redCenter.y
                newView.backgroundColor = .black

    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 ?

    4 Solutions Collect From Internet About “Retrieve the right position of a subView with auto-layout”

    I found your issue, replace

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


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

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

    You lose your time with frames. Because there is AutoLayout, frames are moved and with frames you can add your views but it’s too late in the ViewController lifecycle. Much easier and effective to use constraints/anchors, just take care to have views in the same hierarchy:

    let newView = UIView()
    newView.translatesAutoresizingMaskIntoConstraints = false
    newView.centerXAnchor.constraint(equalTo: self.redView.centerXAnchor).isActive = true
    newView.centerYAnchor.constraint(equalTo: self.redView.centerYAnchor).isActive = true
    newView.widthAnchor.constraint(equalToConstant: 50.0).isActive = true
    newView.heightAnchor.constraint(equalToConstant: 50.0).isActive = true

    But to answer the initial problem maybe it was due to the fact that there is the stackview between so maybe coordinates are relative to the stackview instead of the container or something like that.

    So you want something like this:


    You should do what Beninho85 and phamot suggest and use constraints to center the pawn over its starting square. Note that the pawn does not have to be a subview of the square to constrain them together, and you can add the pawn view and its constraints in code instead of in the storyboard:

    @IBOutlet private var squareViews: [UIView]!
    private let pawnView = UIView()
    private var pawnSquareView: UIView?
    private var pawnXConstraint: NSLayoutConstraint?
    private var pawnYConstraint: NSLayoutConstraint?
    override func viewDidLoad() {
        let imageView = UIImageView(image: #imageLiteral(resourceName: "Pin"))
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.contentMode = .scaleAspectFit
            imageView.centerXAnchor.constraint(equalTo: pawnView.centerXAnchor),
            imageView.centerYAnchor.constraint(equalTo: pawnView.centerYAnchor),
            imageView.widthAnchor.constraint(equalTo: pawnView.widthAnchor, multiplier: 0.8),
            imageView.heightAnchor.constraint(equalTo: pawnView.heightAnchor, multiplier: 0.8)])
        pawnView.translatesAutoresizingMaskIntoConstraints = false
        let squareView = squareViews[0]
            pawnView.widthAnchor.constraint(equalTo: squareView.widthAnchor),
            pawnView.heightAnchor.constraint(equalTo: squareView.heightAnchor)])
        constraintPawn(toCenterOf: squareView)
        let dragger = UIPanGestureRecognizer(target: self, action: #selector(draggerDidFire(_:)))
        dragger.maximumNumberOfTouches = 1

    Here are the helper functions for setting up the x and y constraints:

    private func constraintPawn(toCenterOf squareView: UIView) {
        pawnSquareView = squareView
            xConstraint: pawnView.centerXAnchor.constraint(equalTo: squareView.centerXAnchor),
            yConstraint: pawnView.centerYAnchor.constraint(equalTo: squareView.centerYAnchor))
    private func replaceConstraintsWith(xConstraint: NSLayoutConstraint, yConstraint: NSLayoutConstraint) {
        pawnXConstraint?.isActive = false
        pawnYConstraint?.isActive = false
        pawnXConstraint = xConstraint
        pawnYConstraint = yConstraint
        NSLayoutConstraint.activate([pawnXConstraint!, pawnYConstraint!])

    When the pan gesture starts, deactivate the existing x and y center constraints and create new x and y center constraints to track the drag:

    @objc private func draggerDidFire(_ dragger: UIPanGestureRecognizer) {
        switch dragger.state {
        case .began: draggerDidBegin(dragger)
        case .cancelled: draggerDidCancel(dragger)
        case .ended: draggerDidEnd(dragger)
        case .changed: draggerDidChange(dragger)
        default: break
    private func draggerDidBegin(_ dragger: UIPanGestureRecognizer) {
        let point =
        dragger.setTranslation(point, in: pawnView.superview)
            xConstraint: pawnView.centerXAnchor.constraint(equalTo: pawnView.superview!.leftAnchor, constant: point.x),
            yConstraint: pawnView.centerYAnchor.constraint(equalTo: pawnView.superview!.topAnchor, constant: point.y))

    Note that I set the translation of the dragger so that it is exactly equal to the desired center of the pawn view.

    If the drag is cancelled, restore the constraints to the starting square’s center:

    private func draggerDidCancel(_ dragger: UIPanGestureRecognizer) {
        UIView.animate(withDuration: 0.2) {
            self.constraintPawn(toCenterOf: self.pawnSquareView!)

    If the drag ends normally, set constraints to the nearest square’s center:

    private func draggerDidEnd(_ dragger: UIPanGestureRecognizer) {
        let squareView = nearestSquareView(toRootViewPoint: dragger.translation(in: view))
        UIView.animate(withDuration: 0.2) {
            self.constraintPawn(toCenterOf: squareView)
    private func nearestSquareView(toRootViewPoint point: CGPoint) -> UIView {
        func distance(of candidate: UIView) -> CGFloat {
            let center = candidate.superview!.convert(, to: self.view)
            return hypot(point.x - center.x, point.y - center.y)
        return squareViews.min(by: { distance(of: $0) < distance(of: $1)})!

    When the drag changes (meaning the touch moved), update the constants of the existing constraint to match the translation of the gesture:

    private func draggerDidChange(_ dragger: UIPanGestureRecognizer) {
        let point = dragger.translation(in: pawnView.superview!)
        pawnXConstraint?.constant = point.x
        pawnYConstraint?.constant = point.y

    enter image description here

    You need not divide the height and width of the coordinates to get the center of the view. You can use align option by selecting multiple views and add horizontal centers and vertical centers constraints.