使用自动布局检索子视图的正确位置

时间:2017-06-01 18:21:55

标签: ios swift autolayout centering uistackview

我想以编程方式将视图放在故事板中创建的所有子视图的中心。

在故事板中,我有一个视图,并且在一个垂直StackView内部,它具有填充整个屏幕的约束,分布“等间距”。 在垂直堆栈视图的内部,我有3个水平堆栈视图,约束高度= 100,尾随和前导空间:来自superview的0。分布也是“等间距”。 在每个水平堆栈视图中,我有两个视图,约束宽度和高度= 100,视图是红色。

到目前为止,很好,我有我想要的界面, enter image description here

现在我想要检索每个红色视图的中心,在该位置放置另一个视图(事实上,它将是一个棋盘上的棋子......)

所以我写了:

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)
        }
    }
}

}

有了这个,我可以看到在ViewDidLoad和ViewWillAppear中,指标是故事板的指标。然后,在viewDillLayoutSubviews,viewDidLayoutSubviews和viewDidAppear中更改了位置。

在viewDidAppear之后(所有视图到位后),我必须将x坐标除以2并添加类似35(参见代码)的内容,以使新的黑色视图在红色视图中正确居中。我不明白为什么我不能简单地使用红色视图的中心...为什么它适用于y位置?

4 个答案:

答案 0 :(得分:3)

我找到了你的问题,替换

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

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

转换时,您必须从视图原始坐标转换,这里是horizo​​ntalSv

答案 1 :(得分:3)

所以你想要这样的东西:

demo

你应该做Beninho85和phamot所建议的并使用约束来将棋子置于其起始方块上。请注意,pawn 必须是方形的子视图才能将它们约束在一起,您可以在代码中而不是在故事板中添加pawn视图及其约束:

@IBOutlet private var squareViews: [UIView]!

private let pawnView = UIView()
private var pawnSquareView: UIView?
private var pawnXConstraint: NSLayoutConstraint?
private var pawnYConstraint: NSLayoutConstraint?

override func viewDidLoad() {
    super.viewDidLoad()

    let imageView = UIImageView(image: #imageLiteral(resourceName: "Pin"))
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.contentMode = .scaleAspectFit
    pawnView.addSubview(imageView)
    NSLayoutConstraint.activate([
        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
    view.addSubview(pawnView)
    let squareView = squareViews[0]
    NSLayoutConstraint.activate([
        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
    pawnView.addGestureRecognizer(dragger)
}

以下是用于设置x和y约束的辅助函数:

private func constraintPawn(toCenterOf squareView: UIView) {
    pawnSquareView = squareView
    self.replaceConstraintsWith(
        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!])
}

当平移手势开始时,停用现有的x和y中心约束并创建新的x和y中心约束以跟踪拖动:

@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 = pawnView.center
    dragger.setTranslation(point, in: pawnView.superview)
    replaceConstraintsWith(
        xConstraint: pawnView.centerXAnchor.constraint(equalTo: pawnView.superview!.leftAnchor, constant: point.x),
        yConstraint: pawnView.centerYAnchor.constraint(equalTo: pawnView.superview!.topAnchor, constant: point.y))
}

请注意,我设置了拖动器的平移,使其完全等于所需的pawn视图中心。

如果拖动被取消,请将约束恢复到起始方块的中心:

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

如果拖动正常结束,请将约束设置为最近的方块的中心:

private func draggerDidEnd(_ dragger: UIPanGestureRecognizer) {
    let squareView = nearestSquareView(toRootViewPoint: dragger.translation(in: view))
    UIView.animate(withDuration: 0.2) {
        self.constraintPawn(toCenterOf: squareView)
        self.view.layoutIfNeeded()
    }
}

private func nearestSquareView(toRootViewPoint point: CGPoint) -> UIView {
    func distance(of candidate: UIView) -> CGFloat {
        let center = candidate.superview!.convert(candidate.center, to: self.view)
        return hypot(point.x - center.x, point.y - center.y)
    }
    return squareViews.min(by: { distance(of: $0) < distance(of: $1)})!
}

当拖动更改(意味着触摸移动)时,更新现有约束的常量以匹配手势的转换:

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

答案 2 :(得分:1)

你丢帧了。因为有AutoLayout,所以移动框架并使用框架可以添加视图,但在ViewController生命周期中为时已晚。使用约束/锚点更加容易和有效,只需注意在同一层次结构中拥有视图:

let newView = UIView()
self.view.addSubview(newView)
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

但回答最初的问题可能是因为堆栈视图之间存在堆栈视图,所以坐标相对于stackview而不是容器或类似的东西。

答案 3 :(得分:0)

here

您无需划分坐标的高度和宽度即可获得视图的中心。您可以通过选择多个视图并添加水平中心和垂直中心约束来使用对齐选项。