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

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

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

到目前为止,这么好,我有我想要的接口, 在这里输入图像说明

现在我想检索每个红色视图的中心,在这个位置放置另一个视图(实际上,它将成为棋盘上的棋子…)

所以我写到:

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中,度量指标是故事板的指标。 然后在viewWillLayoutSubviews中,在viewDidLayoutSubviews中再次在viewDidAppear中改变位置。

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

我发现你的问题,取而代之

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

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

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

你失去了时间与帧。 由于存在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是相对于堆栈视图,而不是容器或类似的东西。

所以你想要这样的东西:

演示

你应该做Beninho85和phamotbuild议,并使用限制中心的典当在其起始广场。 请注意,pawn不一定是正方形的子视图来将它们约束在一起,并且可以在代码中添加pawn视图及其约束,而不是在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() { 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中心约束,并创build新的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)) } 

请注意,我设置拖动器的翻译,以便它正好等于典当视图的所需的中心。

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

 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 } 

在这里输入图像说明

您不必划分坐标的高度和宽度以获取视图的中心。 您可以通过select多个视图并添加水平中心和垂直中心约束来使用alignment选项。