Recently, when using snapkit layout, I found that updating constraints would cause crashes. Why?
checkButton.snp.makeConstraints { (make) in make.left.top.equalToSuperview() make.height.equalTo(radioListSubviewButtonHeight) make.width.equalTo(self).multipliedBy(0.5) } checkButton.snp.updateConstraints { (make) in make.width.equalTo(self).multipliedBy(0.7) }
It seems that there is no problem at all, but it crashes as soon as it runs. The location of the crash is in the activeIfNeeded method:
internal func activateIfNeeded(updatingExisting: Bool = false) { guard let item = self.from.layoutConstraintItem else { print("WARNING: SnapKit failed to get from item from constraint. Activate will be a no-op.") return } let layoutConstraints = self.layoutConstraints if updatingExisting { var existingLayoutConstraints: [LayoutConstraint] = [] for constraint in item.constraints { existingLayoutConstraints += constraint.layoutConstraints } for layoutConstraint in layoutConstraints { let existingLayoutConstraint = existingLayoutConstraints.first { $0 == layoutConstraint } guard let updateLayoutConstraint = existingLayoutConstraint else { fatalError("Updated constraint could not find existing matching constraint to update: \(layoutConstraint)") } let updateLayoutAttribute = (updateLayoutConstraint.secondAttribute == .notAnAttribute) ? updateLayoutConstraint.firstAttribute : updateLayoutConstraint.secondAttribute updateLayoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: updateLayoutAttribute) } } else { NSLayoutConstraint.activate(layoutConstraints) item.add(constraints: [self]) } }
The fatalerror message indicates that the existing constraints cannot be found to update. The output existingLayoutConstraints is not nil, but the output existingLayoutConstraints is nil, which is strange.
Click to enter the first method. It is found that the first value satisfying the predicate condition is taken instead of the first value simply:
/// Returns the first element of the sequence that satisfies the given /// predicate. /// /// The following example uses the `first(where:)` method to find the first /// negative number in an array of integers: /// /// let numbers = [3, 7, 4, -2, 9, -6, 10, 1] /// if let firstNegative = numbers.first(where: { $0 < 0 }) { /// print("The first negative number is \(firstNegative).") /// } /// // Prints "The first negative number is -2." /// /// - Parameter predicate: A closure that takes an element of the sequence as /// its argument and returns a Boolean value indicating whether the /// element is a match. /// - Returns: The first element of the sequence that satisfies `predicate`, /// or `nil` if there is no element that satisfies `predicate`. /// /// - Complexity: O(*n*), where *n* is the length of the sequence. public func first(where predicate: (Element) throws -> Bool) rethrows -> Element?
The predicate condition here is $0 == layoutConstraint. Entering LayoutConstraint class, we find that = = operator has been overloaded:
internal func ==(lhs: LayoutConstraint, rhs: LayoutConstraint) -> Bool { guard lhs.firstItem === rhs.firstItem && lhs.secondItem === rhs.secondItem && lhs.firstAttribute == rhs.firstAttribute && lhs.secondAttribute == rhs.secondAttribute && lhs.relation == rhs.relation && lhs.priority == rhs.priority && lhs.multiplier == rhs.multiplier else { return false } return true }
When we judge the equality, we even judge the multiplier. In this case, the multiplier before and after updating the constraint is not equal, so we can't find the corresponding constraint.
resolvent:
To modify the multiplier, you can't use update, you can use remake