iOS - 查找视图的顶部约束?

22

我正在尝试在代码中查找视图的顶部约束。顶部约束已在Storyboard中添加,我不想使用IBOutlet。

记录以下代码中firstAttribute的值似乎总是返回NSLayoutAttributeHeight类型的约束。 有什么办法可以可靠地找到视图的顶部约束吗?

NSLayoutConstraint *topConstraint;

for (NSLayoutConstraint *constraint in self.constraints) {
    if (constraint.firstAttribute == NSLayoutAttributeTop) {
        topConstraint = constraint;
        break;
    }
}
8个回答

32

你应该遍历self.superview.constraints而不是self.constraints

self.constraints仅包含与视图相关的约束(例如高度和宽度约束)。

以下是此操作的示例代码:

- (void)awakeFromNib
{
  [super awakeFromNib];

  if (!self.topConstraint) {
    [self findTopConstraint];
  }
}

- (void)findTopConstraint
{
  for (NSLayoutConstraint *constraint in self.superview.constraints) {
    if ([self isTopConstraint:constraint]) {
      self.topConstraint = constraint;
      break;
    }
  }
}

- (BOOL)isTopConstraint:(NSLayoutConstraint *)constraint
{
  return  [self firstItemMatchesTopConstraint:constraint] ||
          [self secondItemMatchesTopConstraint:constraint];
}

- (BOOL)firstItemMatchesTopConstraint:(NSLayoutConstraint *)constraint
{
  return constraint.firstItem == self && constraint.firstAttribute == NSLayoutAttributeTop;
}

- (BOOL)secondItemMatchesTopConstraint:(NSLayoutConstraint *)constraint
{
  return constraint.secondItem == self && constraint.secondAttribute == NSLayoutAttributeTop;
}

感谢枚举父级约束条件解决了问题。 - aryaxt
通过更改findTopConstraint以使用UIView方法constraintsAffectingLayoutForAxis:返回的数组,可以使其更准确和更短。这将消除正交轴上的约束。 - uchuugaka
1
@uchuugaka,苹果文档中的这个声明让我对使用constraintsAffectingLayoutForAxis:产生了疑虑。该方法仅应用于调试基于约束的布局。任何应用程序都不应将调用此方法作为其操作的一部分发布。 - JRG-Developer
据我所知,在枚举“constraints”数组方面没有任何问题。 - JRG-Developer
1
没注意到。文档注释发现得不错。 - uchuugaka

26

我通常在 IB 中设置所需约束的 标识符,然后在代码中像这样(Swift)查找:

if let index = constraints.index(where: { $0.identifier == "checkmarkLeftMargin" }) {
    checkmarkImageViewLeftMargin = constraints[index]
}

OR 由 @Tim Vermeulen 提供

checkmarkImageViewLeftMargin = constraints.first { $0.identifier == "checkmarkLeftMargin" }

3
如果有一个约束条件满足筛选器中标识符为“checkmarkLeftMargin”,则将其分配给constraint变量。 - aryaxt
1
@SeanDev 当必要的约束条件找到后,循环会中断。在第一个示例中,所有约束条件都使用指定标识符进行搜索。当然,在实际情况下,这不会花费太多时间,但仍需注意。 - SoftDesigner
3
first(where:)可以解决这个问题! - Tim Vermeulen
2
如果您在界面构建器中完成所有操作,还可以设置一个 Outlet。 - paxos

6

根据@Igor 的答案,我稍微改变了itemMatch方法,以考虑第一个或第二个项目不是UIView的情况。例如,当将UIView上边缘约束到安全区域上边缘时。

extension UIView {
    func findConstraint(layoutAttribute: NSLayoutConstraint.Attribute) -> NSLayoutConstraint? {
        if let constraints = superview?.constraints {
            for constraint in constraints where itemMatch(constraint: constraint, layoutAttribute: layoutAttribute) {
                return constraint
            }
        }
        return nil
    }

    func itemMatch(constraint: NSLayoutConstraint, layoutAttribute: NSLayoutConstraint.Attribute) -> Bool {
        let firstItemMatch = constraint.firstItem as? UIView == self && constraint.firstAttribute == layoutAttribute
        let secondItemMatch = constraint.secondItem as? UIView == self && constraint.secondAttribute == layoutAttribute
        return firstItemMatch || secondItemMatch
    }
}

5
使用Swift和UIView扩展
extension UIView {
    func findConstraint(layoutAttribute: NSLayoutAttribute) -> NSLayoutConstraint? {
        if let constraints = superview?.constraints {
            for constraint in constraints where itemMatch(constraint: constraint, layoutAttribute: layoutAttribute) {
                return constraint
            }
        }
        return nil
    }

    func itemMatch(constraint: NSLayoutConstraint, layoutAttribute: NSLayoutAttribute) -> Bool {
        if let firstItem = constraint.firstItem as? UIView, let secondItem = constraint.secondItem as? UIView {
            let firstItemMatch = firstItem == self && constraint.firstAttribute == layoutAttribute
            let secondItemMatch = secondItem == self && constraint.secondAttribute == layoutAttribute
            return firstItemMatch || secondItemMatch
        }
        return false
    }
}

2

在Xcode的检查器中设置标识符。这就是它的用途。您可以自行命名。如果这不足够,您可以创建IBOutlet。


这并不适用于每种情况。例如,如果您正在开发一个旨在被其他开发人员使用/子类化等的库,您可能*不希望他们必须显式设置IBOutlet到顶部约束。 - JRG-Developer
为什么不呢?如果你不去做,那么他们就必须去做。否则,你可以使用标识符来命名它。但是显式属性更清晰、更易于理解。 - uchuugaka
动态的意思是创建易于识别的事物。这就是为什么现在UIKit和AppKit到处都有标识属性的原因。如果没有这个或者一个outlet,那么你只能通过查看影响给定视图特定方向布局的每个约束的属性来猜测。 - uchuugaka
使用标识符或插座不是动态的。使用代码并通过约束进行操作不是猜测,可以看一下上面提供的示例非常可靠。当您需要记住标识符名称时,使用标识符是猜测。您在为特定情况编写代码时使用标识符(非动态)和特定标识符。 - aryaxt
一个视图到其父视图总会有一个顶部约束。 - aryaxt
显示剩余3条评论

0

我用Swift编写了一个小扩展:

extension UIButton {
    var topConstraints: [NSLayoutConstraint]? {
        return self.constraints.filter( { ($0.firstItem as? UIButton == self && $0.firstAttribute == .top) || ($0.secondItem as? UIButton == self && $0.secondAttribute == .top) })
    }
}

0
这是一个基于@Igor方法的一行扩展方法:
extension UIView{

    func constraint(for layoutAttribute: NSLayoutConstraint.Attribute) -> NSLayoutConstraint? {
        return superview?.constraints.first { itemMatch(constraint: $0, layoutAttribute: layoutAttribute) }
    }

    private func itemMatch(constraint: NSLayoutConstraint, layoutAttribute: NSLayoutConstraint.Attribute) -> Bool {
        if let firstItem = constraint.firstItem as? UIView, let secondItem = constraint.secondItem as? UIView {
            let firstItemMatch = firstItem == self && constraint.firstAttribute == layoutAttribute
            let secondItemMatch = secondItem == self && constraint.secondAttribute == layoutAttribute
            return firstItemMatch || secondItemMatch
        }
        return false
    }
}

我还将方法签名风格改为与Swift 3...5的风格相匹配,例如:

constraint(for:)而不是findConstraint(layoutAttribute:)


0
 func topConstraint() -> NSLayoutConstraint? {
    return superview?.constraints.first {$0.firstAttribute == .top}
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接