Tôi có thể thay đổi thuộc tính số nhân cho NSLayoutConstraint không?


142

Tôi đã tạo hai chế độ xem trong một giám sát và sau đó thêm các ràng buộc giữa các chế độ xem:

_indicatorConstrainWidth = [NSLayoutConstraint constraintWithItem:self.view1 attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view2 attribute:NSLayoutAttributeWidth multiplier:1.0f constant:0.0f];
[_indicatorConstrainWidth setPriority:UILayoutPriorityDefaultLow];
_indicatorConstrainHeight = [NSLayoutConstraint constraintWithItem:self.view1 attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view2 attribute:NSLayoutAttributeHeight multiplier:1.0f constant:0.0f];
[_indicatorConstrainHeight setPriority:UILayoutPriorityDefaultLow];
[self addConstraint:_indicatorConstrainWidth];
[self addConstraint:_indicatorConstrainHeight];

Bây giờ tôi muốn thay đổi thuộc tính số nhân bằng hình động, nhưng tôi không thể tìm ra cách thay đổi thuộc tính số nhân. (Tôi đã tìm thấy _coffic trong tài sản riêng trong tệp tiêu đề NSLayoutConstraint.h, nhưng nó là riêng tư.)

Làm cách nào để thay đổi thuộc tính số nhân?

Cách giải quyết của tôi là loại bỏ ràng buộc cũ và thêm cái mới với giá trị khác multipler.


1
Cách tiếp cận hiện tại của bạn về loại bỏ ràng buộc cũ và thêm cái mới là lựa chọn đúng đắn. Tôi hiểu rằng nó không cảm thấy "đúng", nhưng đó là cách bạn phải làm.
Cướp

4
Tôi nghĩ rằng số nhân không nên là hằng số. Đó là một thiết kế xấu.
Borzh

1
@Borzh tại sao thông qua bội số tôi thực hiện các ràng buộc thích ứng. Đối với ios resizeble.
Bimawa

1
Có, tôi cũng muốn thay đổi hệ số nhân để các chế độ xem có thể giữ tỷ lệ của nó với chế độ xem chính (không phải cả chiều rộng / chiều cao mà chỉ chiều rộng hoặc chiều cao), nhưng điều kỳ lạ mà apple không cho phép. Ý tôi là đó là thiết kế tồi của Apple, không phải của bạn.
Borzh 10/2/2015

Đây là một cuộc nói chuyện tuyệt vời và bao gồm này và nhiều hơn nữa youtube.com/watch?v=N5Ert6LTruY
DogCoffee

Câu trả lời:


117

Nếu bạn chỉ có hai bộ số nhân cần được áp dụng, từ iOS8 trở đi, bạn có thể thêm cả hai bộ ràng buộc và quyết định nên kích hoạt bất cứ lúc nào:

NSLayoutConstraint *standardConstraint, *zoomedConstraint;

// ...
// switch between constraints
standardConstraint.active = NO; // this line should always be the first line. because you have to deactivate one before activating the other one. or they will conflict.
zoomedConstraint.active = YES;
[self.view layoutIfNeeded]; // or using [UIView animate ...]

Phiên bản Swift 5.0

var standardConstraint: NSLayoutConstraint!
var zoomedConstraint: NSLayoutConstraint!

// ...

// switch between constraints
standardConstraint.isActive = false // this line should always be the first line. because you have to deactivate one before activating the other one. or they will conflict.
zoomedConstraint.isActive = true
self.view.layoutIfNeeded() // or using UIView.animate

2
@micnguyen Có, bạn có thể :)
Ja͢ck

7
Nếu bạn cần nhiều hơn 2 bộ ràng buộc, bạn có thể thêm bao nhiêu tùy ý và thay đổi các thuộc tính ưu tiên của chúng. Bằng cách này, đối với 2 ràng buộc bạn không phải đặt một hoạt động và một ràng buộc khác là không hoạt động, bạn chỉ cần đặt mức độ ưu tiên cao hơn hoặc thấp hơn. Trong ví dụ trên, hãy đặt mức độ ưu tiên của StandardConstraint thành 997 và khi bạn muốn nó hoạt động, hãy đặt mức độ ưu tiên của zoomedConstraint thành 995, nếu không, hãy đảm bảo rằng XIB không có mức độ ưu tiên được đặt thành 1000, nếu không bạn sẽ không thể thay đổi chúng và bạn sẽ gặp một sự cố tuyệt vời.
Chó

1
@WonderDog có bất kỳ lợi thế đặc biệt nào không? thành ngữ của việc kích hoạt và vô hiệu hóa đối với tôi dường như dễ tiêu hóa hơn là phải thực hiện các ưu tiên tương đối.
Ja͢ck 6/07/2015

2
@WonderDog / Jack Tôi cố gắng kết hợp các cách tiếp cận của bạn vì ràng buộc của tôi đã được xác định trong bảng phân cảnh. Và nếu tôi tạo ra hai sự đối lập, cả hai đều có cùng mức độ ưu tiên nhưng số nhân khác nhau, chúng đã xung đột và cho tôi rất nhiều màu đỏ. Và từ những gì tôi có thể nói với bạn, bạn không thể đặt một chế độ không hoạt động thông qua IB. Vì vậy, tôi đã tạo ra ràng buộc chính của mình với mức độ ưu tiên 1000 và ràng buộc phụ mà tôi muốn tạo hiệu ứng với mức độ ưu tiên 999 (chơi tốt trong IB). Sau đó, bên trong một khối hoạt hình, tôi đặt thuộc tính hoạt động chính của một thành sai và nó hoạt hình độc đáo thành thứ cấp. Cảm ơn cả hai vì sự giúp đỡ!
Clay Garrett

2
Hãy nhớ rằng bạn có thể muốn tham chiếu mạnh đến các ràng buộc của mình nếu kích hoạt và hủy kích hoạt chúng, vì "Kích hoạt hoặc hủy kích hoạt các cuộc gọi ràng buộc addConstraint(_:)removeConstraint(_:)trên chế độ xem là tổ tiên chung gần nhất của các mục được quản lý bởi ràng buộc này".
qix

166

Đây là một tiện ích mở rộng NSLayoutConstraint trong Swift giúp việc thiết lập một số nhân mới khá dễ dàng:

Trong Swift 3.0+

import UIKit
extension NSLayoutConstraint {
    /**
     Change multiplier constraint

     - parameter multiplier: CGFloat
     - returns: NSLayoutConstraint
    */
    func setMultiplier(multiplier:CGFloat) -> NSLayoutConstraint {

        NSLayoutConstraint.deactivate([self])

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

        newConstraint.priority = priority
        newConstraint.shouldBeArchived = self.shouldBeArchived
        newConstraint.identifier = self.identifier

        NSLayoutConstraint.activate([newConstraint])
        return newConstraint
    }
}

Sử dụng demo:

@IBOutlet weak var myDemoConstraint:NSLayoutConstraint!

override func viewDidLoad() {
    let newMultiplier:CGFloat = 0.80
    myDemoConstraint = myDemoConstraint.setMultiplier(newMultiplier)

    //If later in view lifecycle, you may need to call view.layoutIfNeeded() 
}

11
NSLayoutConstraint.deactivateConstraints([self])nên được thực hiện trước khi tạo newConstraint, nếu không nó có thể dẫn đến cảnh báo: Không thể đồng thời thỏa mãn các ràng buộc . Cũng newConstraint.active = truekhông cần thiết vì NSLayoutConstraint.activateConstraints([newConstraint])làm chính xác điều tương tự.
tzaloga

1
kích hoạt và hủy kích hoạt không hoạt động trước ios8, có cách nào khác không ??
narahari_arjun

2
Điều này không hoạt động khi thay đổi hệ số nhân một lần nữa, nó nhận được giá trị không
J. Doe

4
Cảm ơn vì đã tiết kiệm thời gian cho chúng tôi! Tôi sẽ đổi tên hàm thành một cái gì đó cloneWithMultiplierđể làm cho nó rõ ràng hơn rằng nó không chỉ áp dụng các tác dụng phụ mà thay vào đó trả về một ràng buộc mới
Alexandre G

5
Lưu ý rằng nếu bạn đặt hệ số nhân cho 0.0bạn, bạn sẽ không thể cập nhật lại.
Daniel Storm

57

Các multipliertài sản chỉ được đọc. Bạn phải xóa NSLayoutConstraint cũ và thay thế nó bằng một cái mới để sửa đổi nó.

Tuy nhiên, vì bạn biết bạn muốn thay đổi hệ số nhân, bạn chỉ có thể thay đổi hằng số bằng cách tự nhân nó khi cần thay đổi thường là ít mã hơn.


64
Có vẻ kỳ quặc mà số nhân chỉ được đọc. Tôi đã không mong đợi điều đó!
jowie

135
@jowie thật trớ trêu khi giá trị "không đổi" có thể được thay đổi trong khi số nhân không thể.
Tomas Andrle

7
Cái này sai. NSLayoutConstraint chỉ định ràng buộc đẳng thức affine (in). Ví dụ: "View1.bottomY = A * View2.bottomY + B" 'A' là cấp số nhân, 'B' là hằng số. Cho dù bạn điều chỉnh B như thế nào, nó sẽ không tạo ra phương trình giống như thay đổi giá trị của A.
Tamás Zahola

8
@FruityGeek okay, vì vậy bạn đang sử dụng View2.bottomYcủa hiện tại giá trị để tính toán của chế mới liên tục. Điều đó thật thiếu sót, vì View2.bottomYgiá trị cũ sẽ được nung trong hằng số, vì vậy bạn phải từ bỏ kiểu khai báo và cập nhật thủ công các ràng buộc bất cứ khi nào View2.bottomYthay đổi. Trong trường hợp này, bạn có thể làm bố trí thủ công.
Tamás Zahola

2
Điều này sẽ không hoạt động trong trường hợp, khi tôi muốn NSLayoutConstraint phản hồi về thay đổi giới hạn hoặc xoay thiết bị mà không cần thêm mã.
tzaloga

49

Hàm trợ giúp tôi sử dụng để thay đổi hệ số nhân của một ràng buộc bố cục hiện có. Nó tạo và kích hoạt một ràng buộc mới và hủy kích hoạt cái cũ.

struct MyConstraint {
  static func changeMultiplier(_ constraint: NSLayoutConstraint, multiplier: CGFloat) -> NSLayoutConstraint {
    let newConstraint = NSLayoutConstraint(
      item: constraint.firstItem,
      attribute: constraint.firstAttribute,
      relatedBy: constraint.relation,
      toItem: constraint.secondItem,
      attribute: constraint.secondAttribute,
      multiplier: multiplier,
      constant: constraint.constant)

    newConstraint.priority = constraint.priority

    NSLayoutConstraint.deactivate([constraint])
    NSLayoutConstraint.activate([newConstraint])

    return newConstraint
  }
}

Cách sử dụng, thay đổi hệ số nhân thành 1,2:

constraint = MyConstraint.changeMultiplier(constraint, multiplier: 1.2)

1
Có áp dụng được từ iOS 8 không? hoặc nó có thể được sử dụng trong các phiên bản trước
Durai Amuthan.H

1
NSLayoutConstraint.deactivateChức năng chỉ dành cho iOS 8.0 trở lên.
Evgenii

Tại sao bạn trả lại nó?
mskw

@mskw, hàm trả về đối tượng ràng buộc mới mà nó tạo. Cái trước có thể được loại bỏ.
Evgenii

42

Phiên bản Objective-C cho câu trả lời của Andrew Schreiber

Tạo thể loại cho NSLayoutConstraint Class và thêm phương thức trong tệp .h như thế này

    #import <UIKit/UIKit.h>

@interface NSLayoutConstraint (Multiplier)
-(instancetype)updateMultiplier:(CGFloat)multiplier;
@end

Trong tệp .m

#import "NSLayoutConstraint+Multiplier.h"

@implementation NSLayoutConstraint (Multiplier)
-(instancetype)updateMultiplier:(CGFloat)multiplier {

    [NSLayoutConstraint deactivateConstraints:[NSArray arrayWithObjects:self, nil]];

   NSLayoutConstraint *newConstraint = [NSLayoutConstraint constraintWithItem:self.firstItem attribute:self.firstAttribute relatedBy:self.relation toItem:self.secondItem attribute:self.secondAttribute multiplier:multiplier constant:self.constant];
    [newConstraint setPriority:self.priority];
    newConstraint.shouldBeArchived = self.shouldBeArchived;
    newConstraint.identifier = self.identifier;
    newConstraint.active = true;

    [NSLayoutConstraint activateConstraints:[NSArray arrayWithObjects:newConstraint, nil]];
    //NSLayoutConstraint.activateConstraints([newConstraint])
    return newConstraint;
}
@end

Sau đó, trong ViewContoder tạo ổ cắm cho ràng buộc bạn muốn cập nhật.

@property (strong, nonatomic) IBOutlet NSLayoutConstraint *topConstraint;

và cập nhật hệ số nhân bất cứ nơi nào bạn muốn dưới đây ..

self.topConstraint = [self.topConstraint updateMultiplier:0.9099];

Tôi đã di chuyển hủy kích hoạt trong mã mẫu active = truenày giống như kích hoạt và do đó gây ra một ràng buộc trùng lặp được thêm vào trước khi hủy kích hoạt được gọi, điều này gây ra lỗi ràng buộc trong một số trường hợp.
Jules

13

Thay vào đó, bạn có thể thay đổi thuộc tính "không đổi" để đạt được cùng một mục tiêu với một chút toán học. Giả sử hệ số nhân mặc định của bạn trên ràng buộc là 1.0f. Đây là mã Xamarin C # có thể dễ dàng dịch sang object-c

private void SetMultiplier(nfloat multiplier)
{
    FirstItemWidthConstraint.Constant = -secondItem.Frame.Width * (1.0f - multiplier);
}

đây là một giải pháp tốt
J. Doe

3
Điều này sẽ phá vỡ nếu khung của secondItem thay đổi độ rộng sau khi đoạn mã trên được thực thi. Sẽ ổn nếu secondItem sẽ không bao giờ thay đổi kích thước, nhưng nếu secondItem không thay đổi kích thước thì ràng buộc sẽ không còn tính toán giá trị chính xác cho bố cục.
John Stephen

Như John Stephen đã chỉ ra, điều này sẽ không hoạt động đối với các chế độ xem, thiết bị động: nó không tự động cập nhật cùng với bố cục.
Womble

1
Giải pháp tuyệt vời cho trường hợp của tôi, trong đó chiều rộng của mục thứ hai thực sự là [UIScreen mainScreen] .bound.size. Thong (không bao giờ thay đổi)
Arik Segal

7

Như trong các câu trả lời khác được giải thích: Bạn cần loại bỏ ràng buộc và tạo một ràng buộc mới.


Bạn có thể tránh trả về ràng buộc mới bằng cách tạo phương thức tĩnh cho NSLayoutConstraintvới inouttham số, cho phép bạn gán lại ràng buộc đã truyền

import UIKit

extension NSLayoutConstraint {

    static func setMultiplier(_ multiplier: CGFloat, of constraint: inout NSLayoutConstraint) {
        NSLayoutConstraint.deactivate([constraint])

        let newConstraint = NSLayoutConstraint(item: constraint.firstItem, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: constraint.secondItem, attribute: constraint.secondAttribute, multiplier: multiplier, constant: constraint.constant)

        newConstraint.priority = constraint.priority
        newConstraint.shouldBeArchived = constraint.shouldBeArchived
        newConstraint.identifier = constraint.identifier

        NSLayoutConstraint.activate([newConstraint])
        constraint = newConstraint
    }

}

Ví dụ sử dụng:

@IBOutlet weak var constraint: NSLayoutConstraint!

override func viewDidLoad() {
    NSLayoutConstraint.setMultiplier(0.8, of: &constraint)
    // view.layoutIfNeeded() 
}

2

KHÔNG CÓ MÃ SỐ TRÊN NÀO LÀM VIỆC CHO TÔI SAU KHI THAM GIA ĐỂ SỬA ĐỔI MÃ SỐ CỦA TÔI MÃ SỐ NÀY Mã này đang hoạt động trong Xcode 10 và swift 4.2

import UIKit
extension NSLayoutConstraint {
/**
 Change multiplier constraint

 - parameter multiplier: CGFloat
 - returns: NSLayoutConstraintfor 
*/i
func setMultiplier(multiplier:CGFloat) -> NSLayoutConstraint {

    NSLayoutConstraint.deactivate([self])

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

    newConstraint.priority = priority
    newConstraint.shouldBeArchived = self.shouldBeArchived
    newConstraint.identifier = self.identifier

    NSLayoutConstraint.activate([newConstraint])
    return newConstraint
}
}



 @IBOutlet weak var myDemoConstraint:NSLayoutConstraint!

override func viewDidLoad() {
let newMultiplier:CGFloat = 0.80
myDemoConstraint = myDemoConstraint.setMultiplier(newMultiplier)

//If later in view lifecycle, you may need to call view.layoutIfNeeded() 
 }

cảm ơn, dường như hoạt động tốt
Radu Ursache

chào mừng radu-ursache
Ullas Pujary

2

Có, w có thể thay đổi giá trị số nhân chỉ bằng cách tạo phần mở rộng của NSLayoutConstraint và sử dụng nó như ->

   func setMultiplier(_ multiplier:CGFloat) -> NSLayoutConstraint {

        NSLayoutConstraint.deactivate([self])

        let newConstraint = NSLayoutConstraint(
            item: firstItem!,
            attribute: firstAttribute,
            relatedBy: relation,
            toItem: secondItem,
            attribute: secondAttribute,
            multiplier: multiplier,
            constant: constant)

        newConstraint.priority = priority
        newConstraint.shouldBeArchived = shouldBeArchived
        newConstraint.identifier = identifier

        NSLayoutConstraint.activate([newConstraint])
        return newConstraint
    }

            self.mainImageViewHeightMultiplier = self.mainImageViewHeightMultiplier.setMultiplier(375.0/812.0)

1

Chuyển đổi bằng cách thay đổi ràng buộc hoạt động trong mã theo đề xuất của nhiều câu trả lời khác không phù hợp với tôi. Vì vậy, tôi đã tạo ra 2 ràng buộc, một ràng buộc và không ràng buộc, liên kết cả hai với mã, và sau đó chuyển đổi bằng cách loại bỏ một ràng buộc và thêm cái khác.

Để hoàn thiện, để ràng buộc ràng buộc, kéo liên kết vào mã bằng nút chuột phải, giống như bất kỳ yếu tố đồ họa nào khác:

nhập mô tả hình ảnh ở đây

Tôi đặt tên cho một tỷ lệ và các tỷ lệ khác.

Sau đó, thêm mã sau đây, tại viewDidLoad

override open func viewDidLoad() {
   super.viewDidLoad()
   if ... {

       view.removeConstraint(proportionIphone)
       view.addConstraint(proportionIpad) 
   }     
}

Tôi đang sử dụng xCode 10 và swift 5.0


0

Đây là câu trả lời dựa trên câu trả lời của @ Tianfu trong C #. Các câu trả lời khác yêu cầu kích hoạt và hủy kích hoạt các ràng buộc không hoạt động đối với tôi.

    var isMapZoomed = false
@IBAction func didTapMapZoom(_ sender: UIButton) {
    let offset = -1.0*graphHeightConstraint.secondItem!.frame.height*(1.0 - graphHeightConstraint.multiplier)
    graphHeightConstraint.constant = (isMapZoomed) ? offset : 0.0

    isMapZoomed = !isMapZoomed
    self.view.layoutIfNeeded()
}

0

Để thay đổi giá trị của hệ số nhân, hãy làm theo các bước dưới đây: 1. tạo các cửa hàng cho các ràng buộc cần thiết, ví dụ: someConstraint. 2. thay đổi giá trị số nhân như someConstraint.constant * = 0.8 hoặc bất kỳ giá trị số nhân nào.

đó là nó, mã hóa hạnh phúc.


-1

Người ta có thể đọc:

var multiplier: CGFloat
The multiplier applied to the second attribute participating in the constraint.

trên trang tài liệu này . Điều đó không có nghĩa là người ta có thể sửa đổi hệ số nhân (vì nó là var)?


var multiplier: CGFloat { get }là định nghĩa có nghĩa là nó chỉ có một getter.
Jonny
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.