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

Retrieve the right position of a subView with auto-layout

我想以编程方式将视图放置在情节提要中创建的所有子视图的中心。

在情节提要中,我有一个视图,并且在Vertical StackView内,该视图具有约束条件,无法填充整个屏幕,分布为"等间距"。
在"垂直堆栈视图"内部,我有3个水平堆栈视图,约束高度= 100,尾随前导空间为0(超级视图中)。分布也是"等间距"。
在每个水平堆栈视图中,我有两个视图,约束宽度和高度= 100,这些视图为红色。

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

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

所以我写道:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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位置有效?


我发现了您的问题,请替换

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

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

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


所以你想要这样的东西:

demo

您应该按照Beninho85和phamot的建议进行操作,并使用约束将棋子置于其起始正方形的中心。请注意,pawn不必是正方形的子视图,以将它们约束在一起,您可以在代码中而不是在故事板中添加pawn视图及其约束:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@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约束的帮助函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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中心约束以跟踪拖动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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))
}

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

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

1
2
3
4
5
6
private func draggerDidCancel(_ dragger: UIPanGestureRecognizer) {
    UIView.animate(withDuration: 0.2) {
        self.constraintPawn(toCenterOf: self.pawnSquareView!)
        self.view.layoutIfNeeded()
    }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)})!
}

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

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

您会浪费时间在相框上。由于具有自动版式,因此可以移动框架并随框架一起添加视图,但是在ViewController生命周期中为时已晚。使用约束/锚更加容易和有效,只需注意使视图处于相同的层次结构即可:

1
2
3
4
5
6
7
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

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


enter image description here

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