Câu hỏi Làm thế nào để "tìm" ràng buộc của riêng bạn?


Giả sử tôi có UIView,

 class CleverView: UIView

Trong lớp tùy chỉnh, tôi muốn làm điều này:

func changeWidth() {

  let c = ... find my own layout constraint, for "width"
  c.constant = 70 * Gameinfo.ImportanceOfEnemyFactor
}

Tương tự như vậy tôi muốn có thể "tìm thấy" như thế, ràng buộc (hoặc tôi đoán, tất cả các ràng buộc, có thể có nhiều hơn một) gắn liền với một trong bốn cạnh.

Vì vậy, để xem xét tất cả các ràng buộc gắn liền với tôi, và tìm thấy bất kỳ chiều rộng / chiều cao những người thân, hoặc thực sự bất kỳ liên quan đến một (nói, "trái") cạnh.

Bất kỳ ý tưởng?

Nó có lẽ đáng chú ý câu hỏi này


Xin lưu ý rằng (rõ ràng) tôi đang yêu cầu làm thế nào để làm điều này tự động / lập trình.

(Có, bạn có thể nói "liên kết đến ràng buộc" hoặc "sử dụng ID" - toàn bộ điểm của QA là cách tìm chúng trên bay và hoạt động tự động.)

Nếu bạn mới bắt buộc, hãy lưu ý rằng .constraints chỉ cung cấp cho bạn các kết thúc được lưu trữ "ở đó".


15
2017-11-01 11:24


gốc


developer.apple.com/documentation/uikit/uiview/… - Desdenova
Bạn có thể vui lòng, cung cấp mã đầy đủ, những gì thực sự bạn đã cố gắng và nơi là vấn đề. Sẽ dễ dàng cho chúng tôi kiểm tra / kiểm tra. - Krunal
tại sao bạn cần tìm nó trong khi bạn có thể tạo ra @IBOutlet dễ dàng bất cứ lúc nào và có thể có tham chiếu trực tiếp đến ràng buộc. - holex


Các câu trả lời:


Thực sự có hai trường hợp:

  1. Các ràng buộc liên quan đến kích thước của một khung nhìn hoặc các quan hệ với các khung nhìn hậu duệ được lưu trong chính nó
  2. Các ràng buộc giữa hai chế độ xem được lưu trong tổ tiên chung thấp nhất của lượt xem

Lặp lại. Đối với các ràng buộc nằm giữa hai chế độ xem. iOS thực tế, luôn lưu trữ chúng trong tổ tiên chung thấp nhất. Do đó, một ràng buộc của một khung nhìn luôn có thể được tìm thấy bằng cách tìm kiếm tất cả các tổ tiên của khung nhìn.

Vì vậy, chúng ta cần phải kiểm tra xem chính nó và tất cả các giám sát của nó cho các ràng buộc. Một cách tiếp cận có thể là:

extension UIView {

    // retrieves all constraints that mention the view
    func getAllConstraints() -> [NSLayoutConstraint] {

        // array will contain self and all superviews
        var views = [self]

        // get all superviews
        var view = self
        while let superview = view.superview {
            views.append(superview)
            view = superview
        }

        // transform views to constraints and filter only those
        // constraints that include the view itself
        return views.flatMap({ $0.constraints }).filter { constraint in
            return constraint.firstItem as? UIView == self ||
                constraint.secondItem as? UIView == self
        }
    }
}

Bạn có thể áp dụng tất cả các loại bộ lọc sau khi nhận được tất cả các ràng buộc về chế độ xem và tôi đoán đó là phần khó nhất. Vài ví dụ:

extension UIView {

    // Example 1: Get all width constraints involving this view
    // We could have multiple constraints involving width, e.g.:
    // - two different width constraints with the exact same value
    // - this view's width equal to another view's width
    // - another view's height equal to this view's width (this view mentioned 2nd)
    func getWidthConstraints() -> [NSLayoutConstraint] {
        return getAllConstraints().filter( {
            ($0.firstAttribute == .width && $0.firstItem as? UIView == self) ||
            ($0.secondAttribute == .width && $0.secondItem as? UIView == self)
        } )
    }

    // Example 2: Change width constraint(s) of this view to a specific value
    // Make sure that we are looking at an equality constraint (not inequality)
    // and that the constraint is not against another view
    func changeWidth(to value: CGFloat) {

        getAllConstraints().filter( {
            $0.firstAttribute == .width &&
                $0.relation == .equal &&
                $0.secondAttribute == .notAnAttribute
        } ).forEach( {$0.constant = value })
    }

    // Example 3: Change leading constraints only where this view is
    // mentioned first. We could also filter leadingMargin, left, or leftMargin
    func changeLeading(to value: CGFloat) {
        getAllConstraints().filter( {
            $0.firstAttribute == .leading &&
                $0.firstItem as? UIView == self
        }).forEach({$0.constant = value})
    }
}

// chỉnh sửa: Ví dụ nâng cao và làm rõ các giải thích của họ trong nhận xét


12
2017-11-04 18:12



Không thực sự, câu trả lời bao gồm tất cả các trường hợp như vậy. Để hạn chế hai chế độ xem (bất kể chúng ở đâu trong cấu trúc phân cấp khung nhìn), chúng phải có tổ tiên chung, tức là ở cùng một hệ thống phân cấp khung nhìn. Đó là chính xác nơi mà ràng buộc kết thúc được lưu trong, theo định nghĩa. Vì vậy, nó được đảm bảo rằng ràng buộc của một khung nhìn sẽ là chính nó hoặc một trong những tổ tiên của nó. Nó không thể cư trú trong một cái nhìn ngẫu nhiên của hệ thống phân cấp. Vì vậy, nó đủ để tìm kiếm xem chính nó và tất cả các tổ tiên để tìm tất cả các ràng buộc. - stakri
@ Fattie Như vậy một hạn chế sẽ được lưu trữ trong một superview mặc dù. Tôi sẽ xem xét câu hỏi và câu trả lời này chỉ hợp lệ cho lý thuyết. Trong thực tế, bạn nên luôn luôn giữ tham chiếu đến một hạn chế nếu bạn đang có kế hoạch thay đổi nó. - Sulthan
Điều này cũng áp dụng cho các trình điều khiển xem con. Khi bạn thêm bộ điều khiển xem con vào bộ điều khiển chế độ xem cha, chế độ xem của trẻ sẽ trở thành một phần của cấu trúc phân cấp chế độ xem của cha mẹ dưới dạng một chế độ xem con. Sau khi bạn thêm con vào phụ huynh, bạn có thể tự do thêm ràng buộc giữa bất kỳ chế độ xem nào của chúng. Quy tắc rằng những ràng buộc mới này sẽ kết thúc được lưu trong tổ tiên chung thấp nhất của hai quan điểm liên quan (không nhất thiết là cha mẹ) cũng áp dụng ở đó. - stakri
@ Fattie Nếu bạn di chuyển một khung nhìn xung quanh (nghĩa là, bạn loại bỏ một khung nhìn từ trình giám sát của nó), tất cả các ràng buộc của nó đối với các khung nhìn khác sẽ bị loại bỏ. Toàn bộ điều khá đơn giản, thực sự. Đó là những gì tôi có nghĩa là bởi những hạn chế được lưu trữ trong superviews. Nhìn vào người xây dựng giao diện. Nó cho bạn thấy chính xác nơi mà mọi ràng buộc được lưu trữ. - Sulthan
Ngoài ra, việc loại bỏ một khung nhìn từ superview của nó không nhất thiết phải giết tất cả các ràng buộc. Nó giết chết những ràng buộc không thể tồn tại nữa. Nó sẽ không giết các quan hệ bị ràng buộc của khung nhìn đã bị loại bỏ đối với chế độ xem con của nó, ví dụ. - stakri


Tôi đoán bạn có thể làm việc với ràng buộc tài sản của UIView. constraints về cơ bản trả về một mảng ràng buộc trực tiếp được gán cho UIView. Nó sẽ không thể giúp bạn có được các ràng buộc do superview nắm giữ như các ràng buộc hàng đầu, trailing, top hoặc bottom nhưng chiều rộng và chiều cao được giữ bởi chính View. Đối với các ràng buộc của superview, bạn có thể lặp qua các ràng buộc của superview. Giả sử chế độ xem thông minh có những ràng buộc sau:

enter image description here

class CleverView: UIView {

    func printSuperViewConstriantsCount() {
        var c = 0
        self.superview?.constraints.forEach({ (constraint) in
            guard constraint.secondItem is CleverView || constraint.firstItem is CleverView else {
                return
            }
            c += 1
            print(constraint.firstAttribute.toString())
        })
        print("superview constraints:\(c)")
    }

    func printSelfConstriantsCount() {
        self.constraints.forEach { (constraint) in
            return print(constraint.firstAttribute.toString())
        }
        print("self constraints:\(self.constraints.count)")
    }
}

Đầu ra:

hàng đầu
dẫn đầu
đường mòn
ràng buộc superview: 3
Chiều cao
tự ràng buộc: 1

Về cơ bản, bạn có thể xem NSLayoutConstraint để có được thông tin về một ràng buộc cụ thể.

Để in tên của các ràng buộc, chúng ta có thể sử dụng phần mở rộng này

extension NSLayoutAttribute {
    func toString() -> String {
        switch self {
        case .left:
            return "left"
        case .right:
            return "right"
        case .top:
            return "top"
        case .bottom:
            return "bottom"
        case .leading:
            return "leading"
        case .trailing:
            return "trailing"
        case .width:
            return "width"
        case .height:
            return "height"
        case .centerX:
            return "centerX"
        case .centerY:
            return "centerY"
        case .lastBaseline:
            return "lastBaseline"
        case .firstBaseline:
            return "firstBaseline"
        case .leftMargin:
            return "leftMargin"
        case .rightMargin:
            return "rightMargin"
        case .topMargin:
            return "topMargin"
        case .bottomMargin:
            return "bottomMargin"
        case .leadingMargin:
            return "leadingMargin"
        case .trailingMargin:
            return "trailingMargin"
        case .centerXWithinMargins:
            return "centerXWithinMargins"
        case .centerYWithinMargins:
            return "centerYWithinMargins"
        case .notAnAttribute:
            return "notAnAttribute"
        }
    }
}

7
2017-11-01 11:38



đó là tuyệt vời, nhưng nếu nó là một trong những "khoảng cách đến một lần xem" phong cách ?? - Fattie
@ Fattie: chỉnh sửa câu trả lời để bao gồm vòng lặp thông qua các ràng buộc superview. - Puneet Sharma


Có thể cứu ai đó đang gõ .......

Dựa trên câu trả lời bounty chiến thắng của stakri, đây là chính xác làm thế nào để có được

tất cả các ràng buộc của loại "chiều rộng phân đoạn của chế độ xem khác"

tất cả các ràng buộc của loại "chiều rộng điểm cố định"

Vì thế ..

 fileprivate extension UIView {
    func widthAsPointsConstraints()->[NSLayoutConstraint] {}
    func widthAsFractionOfAnotherViewConstraints()->[NSLayoutConstraint] {}
}

Kode đầy đủ bên dưới. Tất nhiên, bạn có thể làm "chiều cao" theo cùng một cách.

Vì vậy, sử dụng chúng như thế này ...

let cc = someView.widthAsFractionOfAnotherViewConstraints()
for c in cc {
   c.changeToNewConstraintWith(multiplier: 0.25)
}

hoặc là

let cc = someView.widthAsPointsConstraints()
for c in cc {
    c.constant = 150.0
}

Ngoài ra, ở phía dưới tôi dán trong một mã demo đơn giản, ví dụ đầu ra ...

enter image description here

Đây là kode. V2 ...

fileprivate extension UIView { // experimental

    func allConstraints()->[NSLayoutConstraint] {

        var views = [self]
        var view = self
        while let superview = view.superview {

            views.append(superview)
            view = superview
        }

        return views.flatMap({ $0.constraints }).filter { constraint in
            return constraint.firstItem as? UIView == self ||
                constraint.secondItem as? UIView == self
        }
    }

     func widthAsPointsConstraints()->[NSLayoutConstraint] {

        return self.allConstraints()
         .filter({
            ( $0.firstItem as? UIView == self && $0.secondItem == nil )
         })
         .filter({
            $0.firstAttribute == .width && $0.secondAttribute == .notAnAttribute
         })
    }

    func widthAsFractionOfAnotherViewConstraints()->[NSLayoutConstraint] {

        func _bothviews(_ c: NSLayoutConstraint)->Bool {
            if c.firstItem == nil { return false }
            if c.secondItem == nil { return false }
            if !c.firstItem!.isKind(of: UIView.self) { return false }
            if !c.secondItem!.isKind(of: UIView.self) { return false }
            return true
        }

        func _ab(_ c: NSLayoutConstraint)->Bool {
            return _bothviews(c)
                && c.firstItem as? UIView == self
                && c.secondItem as? UIView != self
                && c.firstAttribute == .width
        }

        func _ba(_ c: NSLayoutConstraint)->Bool {
            return _bothviews(c)
                && c.firstItem as? UIView != self
                && c.secondItem as? UIView == self
                && c.secondAttribute == .width
        }

        // note that .relation could be anything: and we don't mind that

        return self.allConstraints()
            .filter({ _ab($0) || _ba($0) })
    }
}

extension NSLayoutConstraint {

    // typical routine to "change" multiplier fraction...

    @discardableResult
    func changeToNewConstraintWith(multiplier:CGFloat) -> NSLayoutConstraint {

        //NSLayoutConstraint.deactivate([self])
        self.isActive = false

        let nc = NSLayoutConstraint(
            item: firstItem as Any,
            attribute: firstAttribute,
            relatedBy: relation,
            toItem: secondItem,
            attribute: secondAttribute,
            multiplier: multiplier,
            constant: constant)

        nc.priority = priority
        nc.shouldBeArchived = self.shouldBeArchived
        nc.identifier = self.identifier

        //NSLayoutConstraint.activate([nc])
        nc.isActive = true
        return nc
    }
}

Chỉ là một bản demo mẫu ...

override func viewDidAppear(_ animated: Bool) {

    super.viewDidAppear(animated)

    _teste()

    delay(5) {
        print("changing any 'fraction fo another view' style widths ...\n\n")
        let cc = self.animeHolder.widthAsFractionOfAnotherViewConstraints()
        for c in cc {
            c.changeToNewConstraintWith(multiplier: 0.25)
        }
        self._teste()
    }

    delay(10) {
        print("changing any 'points' style widths ...\n\n")
        let cc = self.animeHolder.widthAsPointsConstraints()
        for c in cc {
            c.constant = 150.0
        }
        self._teste()
    }
}

func _teste() {

    print("\n---- allConstraints")
    for c in animeHolder.allConstraints() {
        print("\n \(c)")
    }
    print("\n---- widthAsPointsConstraints")
    for c in animeHolder.widthAsPointsConstraints() {
        print("\n \(c)\n \(c.multiplier) \(c.constant)")
    }
    print("\n---- widthAsFractionOfAnotherViewConstraints")
    for c in animeHolder.widthAsFractionOfAnotherViewConstraints() {
        print("\n \(c)\n \(c.multiplier) \(c.constant)")
    }
    print("\n----\n")
}

0
2017-11-11 17:38