繁体   English   中英

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

[英]Retrieve the right position of a subView with auto-layout

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

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

所以你想要这样的东西:

演示

你应该做Beninho85和phamot所建议的并使用约束来将棋子置于其起始方块上。 需要注意的是典当行并不一定是方形的子视图来约束他们在一起,你可以添加典当观及其代码,而不是在故事板的限制:

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

你丢失了框架的时间。 因为有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

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

在此输入图像描述

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM