Khi nào tôi có thể kích hoạt / hủy kích hoạt các ràng buộc bố cục?


104

Tôi đã thiết lập nhiều bộ ràng buộc trong IB và tôi muốn chuyển đổi theo chương trình giữa chúng tùy thuộc vào một số trạng thái. Có một constraintsAbộ sưu tập cửa hàng tất cả đều được đánh dấu là đã cài đặt từ IB và một constraintsBbộ sưu tập cửa hàng tất cả đều được gỡ cài đặt trong IB.

Tôi có thể lập trình chuyển đổi giữa hai tập hợp như vậy:

NSLayoutConstraint.deactivateConstraints(constraintsA)
NSLayoutConstraint.activateConstraints(constraintsB)

Nhưng ... tôi không thể biết khi nào nên làm điều đó. Có vẻ như tôi sẽ có thể làm điều đó ngay lập tức viewDidLoad, nhưng tôi không thể làm điều đó thành công. Tôi đã thử gọi view.updateConstraints()view.layoutSubviews()sau khi thiết lập các ràng buộc, nhưng vô ích.

Tôi đã nhận thấy rằng nếu tôi đặt các ràng buộc trong viewDidLayoutSubviewsmọi thứ sẽ hoạt động như mong đợi. Tôi đoán tôi muốn biết hai điều ...

  1. Tại sao tôi nhận được hành vi này?
  2. Có thể kích hoạt / hủy kích hoạt các ràng buộc từ viewDidLoad không?

2
Bạn có nghĩa là hủy kích hoạtConstraints và kích hoạtConstraints hoạt động trong viewWillLayoutSubviews? Tôi đã thử điều đó và nó không hoạt động ở đó hoặc trong viewDidLoad. Nó đã hoạt động trong viewDidAppear; chế độ xem xuất hiện tại nơi các ràng buộc mới nên đặt nó, nhưng nếu tôi xoay sang ngang, chế độ xem sẽ di chuyển trở lại vị trí được xác định bởi các ràng buộc được đặt trong IB (và vẫn ở đó khi tôi xoay trở lại dọc). Ghi nhật ký các ràng buộc, hiển thị những cái chính xác (những cái mới được kích hoạt). Đây dường như là một lỗi đối với tôi.
rdelmar

1
Đúng, chúng hợp lệ (chúng hoạt động trong viewDidAppear) và không cần gọi super vì không có triển khai mặc định của viewWillLayoutSubviews (dù sao thì tôi cũng đã thử nó với cách gọi super, nhưng điều đó không có gì khác biệt).
rdelmar

1
@rdelmar Vừa có cơ hội để kiểm tra thêm ... Tôi có thể xác minh rằng tôi thực sự có hành vi giống như bạn đã mô tả ... lúc đầu hoạt động trong viewDidAppear, nhưng sau đó hoàn nguyên khi xoay.
tybro0103

3
Rõ ràng bạn không thể đánh dấu các ràng buộc là không được cài đặt trong IB cho mục đích này. Tìm thấy thông tin đó ở đây: stackoverflow.com/questions/27663249/… và nó đã giải quyết được vấn đề cho tôi.
Stefan

1
Tôi đã thực hiện các ràng buộc của mình theo cách tương tự như được mô tả trong câu hỏi, ngoại trừ việc tôi đã kích hoạt / hủy kích hoạt một số trong số chúng trong viewDidAppear. Điều này đã hiệu quả, nhưng bạn có thể thấy các phần tử nhanh chóng thay đổi vị trí (một vấn đề nhỏ nhưng không mong muốn). Thực hiện thay đổi trong viewWillAppear hoặc viewDidLoad không hoạt động. Nhưng sau khi đọc câu hỏi này, tôi đã thử thực hiện thay đổi đó trong viewDidLayoutSubviews. Nó hoạt động và thay đổi vị trí không còn hiển thị cho người dùng. (Nó cũng hoạt động trong viewWillLayoutSubviews). Vì vậy, cảm ơn cho mẹo đó!
Peacetype

Câu trả lời:


185

Tôi kích hoạt và hủy kích hoạt NSLayoutConstraints trong viewDidLoad, và tôi không gặp bất kỳ vấn đề nào với nó. Vì vậy, nó hoạt động. Phải có sự khác biệt trong thiết lập giữa ứng dụng của bạn và của tôi :-)

Tôi sẽ chỉ mô tả thiết lập của mình - có thể nó có thể giúp bạn dẫn đầu:

  1. Tôi thiết lập @IBOutlets tất cả các ràng buộc mà tôi cần để kích hoạt / hủy kích hoạt.
  2. bên trong ViewController , tôi lưu các ràng buộc thành các thuộc tính lớp không yếu. Lý do cho điều này là tôi thấy rằng sau khi hủy kích hoạt một ràng buộc, tôi không thể kích hoạt lại nó - nó là con số không. Vì vậy, nó dường như bị xóa khi ngừng kích hoạt.
  3. Tôi không sử dụng NSLayoutConstraint.deactivate/activatenhư bạn làm, tôi sử dụngconstraint.active = YES / NOthay thế.
  4. Sau khi thiết lập các ràng buộc, tôi gọi view.layoutIfNeeded().

131
"lưu các ràng buộc vào thuộc tính lớp không yếu" Bạn đã tiết kiệm cho tôi rất nhiều thời gian, cảm ơn!
OpenUserX03

10
"Tôi lưu các ràng buộc thành các thuộc tính lớp không yếu": Điều này đã giúp tôi rất đau lòng. Tôi không biết rằng tôi đang gọi một bộ chọn trên một đối tượng nil. Cảm ơn!!
static0886

4
Điều quan trọng cần lưu ý là các ràng buộc "không hoạt động" không bị bố cục tự động bỏ qua, chúng sẽ bị loại bỏ. Kích hoạt / hủy kích hoạt các ràng buộc thực sự thêm chúng và loại bỏ chúng. Đã dành một chút thời gian để gỡ lỗi một bố cục tự động xung đột sau khi tôi thêm các ràng buộc mà tôi đã đặt trước đó với .active = falsehy vọng chúng sẽ bị bỏ qua cho đến khi tôi đặt chúng thành hoạt động.
lbarbosa

1
lưu các ràng buộc vào thuộc tính lớp không yếu, ok, điều này tiết kiệm rất nhiều thời gian, tôi đã nhận được một số kết quả hỗn hợp mà không có điều này. Cảm ơn anh bạn!
MegaManX

3
Tài liệu của Táo khuyết cho biết: Kích hoạt hoặc hủy kích hoạt ràng buộc gọi addConstraint ( :) và 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. Sử dụng thuộc tính này thay vì gọi trực tiếp addConstraint ( :) hoặc removeConstraint ( :). Vì vậy, có vẻ như khi một ràng buộc bị hủy kích hoạt, nó sẽ bị loại bỏ và sau đó không có tham chiếu mạnh nào đến ràng buộc còn lại, trừ khi IBOutlet mạnh. Do đó, ràng buộc bị xóa. IMHO đây gần như là một lỗi hoặc ít nhất là hành vi rất bất ngờ.
Olle Raab

52

Có lẽ bạn có thể kiểm tra của bạn @properties, thay thế weakbằngstrong .

Đôi khi nó do active = NOthiết lập self.yourConstraint = nilnên bạn không thể sử dụng self.yourConstraintlại.


5
Như đã nêu trong Hướng dẫn ngôn ngữ Swift , các thuộc tính mạnh theo mặc định nên bạn cũng có thể chỉ cần xóa weakvà điều đó sẽ thực hiện được.
Jonathan Cabrera

30
override func viewDidLayoutSubviews() {
// do it here, after constraints have been materialized
}

1
Bởi vì bộ điều khiển chế độ xem của tôi là bộ điều khiển chế độ xem con - làm điều đó trong "didLayoutSubviews" dường như là cách duy nhất hoạt động !! FYI.
TalL

Đây là câu trả lời hợp lệ duy nhất
Yunus Eren Güzel

@TalL Ý của bạn là các ràng buộc đối với chính bộ điều khiển chế độ xem con hay đó là các chế độ xem phụ?
Stefan

đây là một trong những tốt nhất
ACAkgul

14

Tôi tin rằng vấn đề bạn đang gặp phải là do các ràng buộc không được thêm vào chế độ xem của họ cho đến khi AFTER viewDidLoad()được gọi. Bạn có một số tùy chọn:

A) Bạn có thể kết nối các ràng buộc bố cục của mình với IBOutlet và truy cập chúng trong mã của bạn bằng các tham chiếu này. Vì các cửa hàng được kết nối trước khi viewDidLoad()bắt đầu, các ràng buộc sẽ có thể truy cập được và bạn có thể tiếp tục kích hoạt và hủy kích hoạt chúng ở đó.

B) Nếu bạn muốn sử dụng constraints()chức năng của UIView để truy cập các ràng buộc khác nhau, bạn phải đợi viewDidLayoutSubviews()bắt đầu và thực hiện nó ở đó, vì đó là điểm đầu tiên sau khi tạo bộ điều khiển chế độ xem từ nib rằng nó sẽ có bất kỳ ràng buộc nào được cài đặt. Đừng quên gọi layoutIfNeeded()khi bạn đã hoàn tất. Điều này có nhược điểm là quá trình chuyển bố cục sẽ được thực hiện hai lần nếu có bất kỳ thay đổi nào cần áp dụng và bạn phải đảm bảo rằng không có khả năng một vòng lặp vô hạn sẽ được kích hoạt.

Một lời cảnh báo nhanh chóng: các ràng buộc đã bị vô hiệu hóa KHÔNG được phương thức trả về constraints() ! Điều này có nghĩa là nếu bạn NÊN vô hiệu hóa một ràng buộc với ý định bật lại nó sau này, bạn sẽ cần phải giữ một tham chiếu đến nó.

C) Bạn có thể quên cách tiếp cận bảng phân cảnh và thay vào đó thêm các ràng buộc của bạn theo cách thủ công. Vì bạn đang làm điều này nên viewDidLoad()tôi giả định rằng mục đích là chỉ thực hiện một lần trong suốt thời gian tồn tại của đối tượng hơn là thay đổi bố cục một cách nhanh chóng, vì vậy đây phải là một phương pháp có thể chấp nhận được.


10

Bạn cũng có thể điều chỉnh thuộc prioritytính để "bật" và "tắt" chúng (ví dụ: giá trị 750 để bật và 250 để tắt). Vì lý do nào đó, việc thay đổi activeBOOL không ảnh hưởng đến giao diện người dùng của tôi. Không cần layoutIfNeededvà có thể được thiết lập và thay đổi tại viewDidLoad hoặc bất kỳ lúc nào sau đó.


Một gợi ý rất tốt. Thay đổi mức độ ưu tiên ràng buộc hoạt động trong viewWillTransition(to:, with:)hoặc viewWillLayoutSubviews()và bạn có thể giữ tất cả các ràng buộc thay thế của mình dưới dạng "cài đặt" trong bảng phân cảnh. Mức độ ưu tiên ràng buộc có thể không thay đổi từ không bắt buộc thành bắt buộc, vì vậy hãy sử dụng các giá trị bên dưới 1000. Mặt khác, việc kích hoạt (thêm) và hủy kích hoạt (loại bỏ) các ràng buộc chỉ hoạt động trong viewDidLayoutSubviews()và yêu cầu giữ các strong @IBOutlettham chiếu đến NSLayoutConstraint-s.
Gary

"Vì lý do nào đó, việc thay đổi BOOL đang hoạt động không ảnh hưởng gì đến giao diện người dùng của tôi". Dựa vào đây . Tôi nghĩ rằng bạn không thể thay đổi một ràng buộc có mức ưu tiên 1000 trong thời gian chạy. Nếu bạn muốn Deactive nó, sau đó bạn nên thiết lập các ưu tiên ban đầu đến 999 hoặc thấp hơn ....
Mật ong

Tôi không đồng ý với tuyên bố này vì nó có thể dẫn đến các vấn đề khó gỡ lỗi và không trả lời được câu hỏi. Đặt mức độ ưu tiên thành 250 sẽ không "hủy kích hoạt" ràng buộc, nó vẫn có hiệu lực và ảnh hưởng đến bố cục. Có vẻ như nó "vô hiệu hóa" ràng buộc trong hầu hết các trường hợp nhưng chắc chắn không phải trong mọi trường hợp. (đặc biệt, không phải trường hợp khiến tôi phải tìm câu trả lời cho câu hỏi này)
Tumata

Nó có thể dẫn đến sự cố cũng như "Thay đổi mức độ ưu tiên từ bắt buộc thành không theo ràng buộc đã cài đặt (hoặc ngược lại) không được hỗ trợ. Bạn đã vượt qua mức độ ưu tiên 250 và mức độ ưu tiên hiện tại là 1000".
Karthick Ramesh

8

Thời điểm thích hợp để hủy kích hoạt các ràng buộc không sử dụng:

-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    self.myLittleConstraint.active = NO;
}

Hãy nhớ rằng viewWillLayoutSubviewscó thể được gọi nhiều lần, vì vậy không có tính toán nặng ở đây, được không?

Lưu ý: nếu bạn muốn phản hồi một số ràng buộc sau này, thì hãy luôn lưu trữ strongtham chiếu đến chúng.


2
Đối với tôi, cách duy nhất đáng tin cậy là điều chỉnh các ràng buộc trong viewDidLayoutSubviews(). Điều chỉnh các ràng buộc trong viewWillLayoutSubviews()không hoạt động trong trường hợp của tôi.
petrsyn

6

Khi một dạng xem đang được tạo, các phương thức vòng đời sau được gọi theo thứ tự:

  1. loadView
  2. viewDidLoad
  3. viewWillAppear
  4. viewWillLayoutSubviews
  5. viewDidLayoutSubviews
  6. viewDidAppear

Bây giờ đến câu hỏi của bạn.

  1. Tại sao tôi nhận được hành vi này?

Trả lời: Bởi vì khi bạn cố gắng thiết lập các ràng buộc trên các khung nhìn trong viewDidLoadkhung nhìn không có giới hạn của nó, do đó không thể thiết lập các ràng buộc. Nó chỉ sauviewDidLayoutSubviews giới hạn của chế độ xem được hoàn thiện.

  1. Có thể kích hoạt / hủy kích hoạt các ràng buộc từ viewDidLoad không?

Trả lời: Không. Lý do đã giải thích ở trên.


Trong mô tả của bạn về vòng đời viewController, bạn đã nói về cách chế độ xem được tải lần đầu tiên và sau đó viewDidLoad được gọi. Tuy nhiên, bạn cũng nói chế độ xem không được tạo ra bởi thời gian viewDidLoad được gọi, đây rõ ràng là một mâu thuẫn. Ngoài ra, bạn có thể tự kiểm tra và thấy rằng chế độ xem đã được tạo bởi thời gian viewDidLoad được gọi vì bạn có thể thêm các lượt xem phụ vào chế độ xem.
ABakerSmith

viewDidLoad sẽ ổn vì các khung nhìn được tạo và tải ... Trong thực tế, khi bạn kích hoạt các ràng buộc chủ yếu phụ thuộc vào hiệu suất. Tôi đoán vấn đề ban đầu không liên quan đến nơi các ràng buộc được kích hoạt. stackoverflow.com/questions/19387998/…
Gabe

@ABakerSmith Tôi đã chỉnh sửa câu trả lời của mình để rõ ràng hơn.
Sumeet

1

Tôi đã tìm thấy miễn là bạn thiết lập các ràng buộc cho mỗi bình thường trong ghi đè của - (void)updateConstraints(mục tiêu c), với strongtham chiếu cho ban đầu được sử dụng các ràng buộc hoạt động và không hoạt động. Và ở những nơi khác trong chu kỳ xem, hãy tắt và / hoặc kích hoạt những gì bạn cần, sau đó gọilayoutIfNeeded , bạn sẽ không gặp vấn đề gì.

Điều chính là không liên tục sử dụng lại việc ghi đè updateConstraintsvà tách biệt các kích hoạt của các ràng buộc, miễn là bạn gọi updateConstraints sau lần khởi tạo và bố trí đầu tiên. Có vẻ như vấn đề sau đó là ở đâu trong chu kỳ xem.

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.