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

Posted

技术标签:

【中文标题】使用自动布局检索子视图的正确位置【英文标题】:Retrieve the right position of a subView with auto-layout 【发布时间】:2017-11-03 00:23:28 【问题描述】:

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

在情节提要中,我有一个视图,在一个垂直 StackView 内部,它具有填充全屏的约束,分布“等间距”。 在垂直堆栈视图内部,我有 3 个水平堆栈视图,约束高度 = 100,尾随和前导空间:来自超级视图的 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 位置?

【问题讨论】:

【参考方案1】:

我找到了你的问题,替换

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

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

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

【讨论】:

这正是我想要的!我假设原始视图坐标始终是超级视图的坐标?于是我改成了更通用的'let redCenter = redView.superview!.convert(redView.center, to:self.view)' @FredericP centerframe 在superview 的坐标系中,而bounds 在它自己的坐标系中。所以另一种方式是redView.convert(CGPoint(x: redView.bounds.midX, y: redView.bounds.midY), to:self.view)【参考方案2】:

所以你想要这样的东西:

您应该按照 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

【讨论】:

【参考方案3】:

您会在框架上浪费时间。因为有 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

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

【讨论】:

我不能将黑色视图添加为红色视图的子视图,因为创建后它们会从一个正方形拖到另一个正方形。我的理解是‘redView.convert(redView.center, to:self.view)’给出了self.view坐标系中的redView中心? 在您的情况下,redCenter 的值是多少,是正确的吗?是的,它通常应该,但我从不知道从/到什么是正确的 我不知道,事实上!实际上,在 iPhone 7 模拟器上,它给了我 viewDidAppear - (66.0, 78.0) - (552.0, 78.0) - (66.0, 337.5) - (552.0, 337.5) - (66.0, 597.0) - (552.0, 597.0) 。我所知道的是,如果我在那个位置放置一个视图,它在 x 方向上的距离大约是 2 倍(这与 iPhone SE、iPhone 7 和 iPhone 7 + 的结果相同) 你试过我的解决方案了吗?如果您想用框架解决它,请尝试打印所有内容以查看所有框架是否具有良好的值。如果你还是卡住了,请给我你的看法,我会努力解决的 不,我无法限制我在红色视图中的视图,因为我稍后必须移动它......中心的值不正确,恕我直言:552-597的位置似乎超过了iPhone 7 屏幕的 375x667 尺寸。为了重现我的测试,我认为从我的问题中创建一个项目很容易。我描述了制作故事板的步骤,并复制了完整的 ViewController 代码。【参考方案4】:

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

【讨论】:

谢谢,但我不想在IB中放置新的黑色视图,我必须通过程序来完成(实际上,创建后,黑色视图会从一个正方形移动到另一个正方形) 我认为不是移动视图。您可以通过炫酷的动画隐藏和显示不同位置的视图。这大大提高了性能。 对不起,它对我不起作用。最后,黑色视图在用户拖动时会被移动,因此它们必须是“全屏”视图的子视图,并且我必须知道之后它们将被移动到哪里。

以上是关于使用自动布局检索子视图的正确位置的主要内容,如果未能解决你的问题,请参考以下文章

使用自动布局更改按钮位置

自动布局:ViewDidAppear 中的帧大小不正确

UIView 没有正确定位启用自动布局的故事板

如何在firebase中检索子(自动增量)名称

表格视图单元格中的 ImageView 切断标签(自动布局)[关闭]

如何正确设置 4s 的自动布局约束?