Ios 如何将所有子视图移动到新的父视图,同时保持所有约束不变?

Ios 如何将所有子视图移动到新的父视图,同时保持所有约束不变?,ios,swift,uiview,nslayoutconstraint,Ios,Swift,Uiview,Nslayoutconstraint,我希望以编程方式将UIView的所有子视图移动到另一个UIView中,同时保持子视图之间以及(新)父视图之间的所有约束不变 “正确”的方法是什么 我当前的解决方案只是在当前父视图的子视图和所有约束之间循环,并检查它是在父视图和子视图之间还是在两个子视图之间。匹配约束被存储并应用于新父项: var constraints = [NSLayoutConstraint]() for subview in oldParent.subviews { for constraint in oldPar

我希望以编程方式将
UIView
的所有子视图移动到另一个
UIView
中,同时保持子视图之间以及(新)父视图之间的所有约束不变

“正确”的方法是什么

我当前的解决方案只是在当前父视图的子视图和所有约束之间循环,并检查它是在父视图和子视图之间还是在两个子视图之间。匹配约束被存储并应用于新父项:

var constraints = [NSLayoutConstraint]()
for subview in oldParent.subviews {
    for constraint in oldParent.constraints {
        if let newConstraint = moveConstraint(constraint, ofView: subview, fromView: oldParent, toView: newParent) {
            let active = oldConstraint.isActive
            oldParent.removeConstraint(constraint)

            newConstraint.isActive = active
            constraints.append(newConstraint)
        }
    }

    newParent.addSubview(subview)
}

newParent.addConstraints(constraints)


func moveConstraint(_ constraint: NSLayoutConstraint, ofView view: UIView, fromView prevSuperview: UIView, toView newSuperview: UIView) -> NSLayoutConstraint? {
    // Check prevSuperview + Layout Guides
    var prevSuperviewIsFirstItem: Bool = constraint.firstItem === prevSuperview
    if !prevSuperviewIsFirstItem {
        for layoutGuid in prevSuperview.layoutGuides {
            if constraint.firstItem === layoutGuid {
                prevSuperviewIsFirstItem = true
                break
            }
        }
    }
    
    var prevSuperviewIsSecondItem: Bool = constraint.secondItem === prevSuperview
    if !prevSuperviewIsSecondItem {
        for layoutGuid in prevSuperview.layoutGuides {
            if constraint.secondItem === layoutGuid {
                prevSuperviewIsSecondItem = true
                break
            }
        }
    }
    
    // Move constraints between prevSuperview + View
    var newConstraint: NSLayoutConstraint? = nil 
    if prevSuperviewIsFirstItem && constraint.secondItem === view {
        newConstraint = NSLayoutConstraint(item: newSuperview, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: view, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant)
    } else if prevSuperviewIsSecondItem && constraint.firstItem === view {
        newConstraint = NSLayoutConstraint(item: view, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: newSuperview, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant)
    }

    if let newConstraint = newConstraint {
        // handle newConstraint.isActive only after oldConstraint has
        // been removed to avoid conflicts

        newConstraint.identifier = constraint.identifier
        newConstraint.priority = constraint.priority
        newConstraint.shouldBeArchived = constraint.shouldBeArchived
        return newConstraint
    }
    

    // Move constraints between view and other subview
    if constraint.firstItem === view || constraint.secondItem === view {
        return constraint
    }
    
    return nil
}
虽然根据我的测试,这似乎工作得很好,但我想确保这确实是正确的。处理约束始终是一个困难的问题,在将来的某个时候很容易导致错误

那么:复制过程是否足以正确传递子视图及其约束?或者还有别的什么要考虑的吗?还是有更好的办法

编辑:上下文

一个用例是具有卡片式布局的
UICollectionViewCell
。为了实现这一点,我在另一个回答中给出了一个提示:向
contentView
添加两个子视图,它们充当包装器,并通过其图层属性应用阴影和圆角。目标是创建一个
UICollectionViewCell
子类,该子类自动将这些包装子视图添加到
awakeFromNib()
中,并将
contentView
的所有子视图移动到此包装视图


当然,可以很容易地在InterfaceBuilder中添加这些包装器,但是当在不同的项目中处理许多不同的单元格时,一种自动处理这些内容的通用方法将是一个很好的解决方案。

@dfd是正确的,当涉及到约束时,视图之间有着紧密的联系。如果从其超级视图中删除视图,则这些约束将被打破,但如果视图中有一个视图,并且仅更改父视图,则子视图将保留其约束。哇,听起来比实际情况要混乱得多。但是,总的来说,你做这件事的方式很好,我想不出一个更好的方法,也许可以在UIView上做一个扩展?类似于view.moveTo(newView)?

免责声明:这绝不是一个完整的答案

以下是我注意到的一些事情-


#1不建议访问这些属性。改用
firstAnchor
secondAnchor
属性。

     /* accessors
     firstItem.firstAttribute {==,<=,>=} secondItem.secondAttribute * multiplier + constant
     Access to these properties is not recommended. Use the `firstAnchor` and `secondAnchor` properties instead.
     */
    unowned(unsafe) open var firstItem: AnyObject? { get }

    unowned(unsafe) open var secondItem: AnyObject? { get }

#2当前代码不考虑
优先级

open var priority: UILayoutPriority
open var shouldBeArchived: Bool
open var isActive: Bool
open var identifier: String?

#3当前代码不考虑
应保留的值

open var priority: UILayoutPriority
open var shouldBeArchived: Bool
open var isActive: Bool
open var identifier: String?
而且直到今天我才知道这件事,我也不确定什么是正确的处理方法


#4当前代码不考虑
firstAnchor
secondAnchor

     /* accessors
     firstAnchor{==,<=,>=} secondAnchor * multiplier + constant
     */
    @available(iOS 10.0, *)
    @NSCopying open var firstAnchor: NSLayoutAnchor<AnyObject> { get }

    @available(iOS 10.0, *)
    @NSCopying open var secondAnchor: NSLayoutAnchor<AnyObject>? { get }

#6当前代码不考虑
标识符

open var priority: UILayoutPriority
open var shouldBeArchived: Bool
open var isActive: Bool
open var identifier: String?

如果您正在为自己的应用程序解决此问题,并且可以确保您没有在最初创建的约束上使用这些值中的某些值,那么应该可以

如果您计划将其放入一个库中,该库将在其他应用程序中使用,该库将为所有当前问题+未来更改/问题打开


我强烈建议,如果可能的话,你应该尽量避免这样做,并重新考虑应该做些什么来实现同样的目标。也许您希望有一个包含所有这些子视图的包装器
UIView
实例,并且该包装器
UIView
实例被传输到另一个视图。这将最大限度地减少此代码必须接触/重新创建的约束数量


在任何情况下-您必须执行以下操作(如果您计划继续执行此操作)

  • firstAnchor
    secondAnchor
    值从旧约束复制到新约束。-不可能,这些是GET only,请参见评论。
  • 优先级
    值从旧约束复制到新约束
  • 标识符
    值从旧约束复制到新约束
  • 值从旧约束复制到新约束
  • 最初不要使用
    isActive=false
    激活任何约束


  • 我还不是专家,但我知道约束与视图层次结构的关系有多紧密。(只要你想一想,这是有道理的。)这里有一个想法——为什么不创建一个带有子视图的单亲视图并将其子类化呢?约束将继续存在,维护代码更容易,您甚至可以根据需要在每个子类父视图中命名视图和约束进行编辑。非常感谢您提供的详细答案!我已更新代码以复制您提到的属性:
    priority
    identifier
    shouldBeArchived
    isActive
    。但是我不确定锚是什么?我认为
    nslayoutAcho
    只是一个更容易使用
    NSLayoutConstraint
    s的API,不是吗?锚属性对应于
    NSLayoutConstraint.Attribute
    s,是只读属性。因此,当使用与
    oldConstraint
    相同的
    项目和
    属性创建
    newConstraint
    时,锚定应该是相同的,不是吗?是的,应该是。我从中粘贴的文档通过{get}部分清楚地说明了这一点。我现在将更新答案。