Debug conflict autolayout constraints

Là lập trình viên iOS, chắc hẳn ai cũng biết về autolayout. Autolayout là tính năng giúp lập trình viên layout các thành phần để tạo ra giao diện cho app của mình.

Tuy nhiên, có thể do vô tình mà chúng ta gây ra tình trạng conflict constraint( breaking constraint). Đó là tình trạng mà autolayout engine không biết nên xử lý layout như thế nào do có hai hoặc nhiều constraint bị conflict nhau.

Khi bị conflict constraint thì sẽ có log tương tự như thế này:

2015-08-26 14:27:54.790 Auto Layout Cookbook[10040:1906606] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSLayoutConstraint:0x7a87b000 H:[UILabel:0x7a8724b0'Name'(>=400)]>",
    "<NSLayoutConstraint:0x7a895e30 UILabel:0x7a8724b0'Name'.leading == UIView:0x7a887ee0.leadingMargin>",
    "<NSLayoutConstraint:0x7a886d20 H:[UILabel:0x7a8724b0'Name']-(NSSpace(8))-[UITextField:0x7a88cff0]>",
    "<NSLayoutConstraint:0x7a87b2e0 UITextField:0x7a88cff0.trailing == UIView:0x7a887ee0.trailingMargin>",
    "<NSLayoutConstraint:0x7ac7c430 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7a887ee0(320)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7a87b000 H:[UILabel:0x7a8724b0'Name'(>=400)]>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

Để bắt đầu chúng ta hãy down demo project ở đây: https://github.com/ThanhDev2703/BreakConstraintDemo

Tìm view bị lỗi constraint

Chúng ta chạy demo bên trên, và thấy có báo log

2019-09-14 21:29:04.844834+0700 BreakConstraintDemo[3532:58926] [LayoutConstraints] Unable to simultaneously satisfy constraints.
  Probably at least one of the constraints in the following list is one you don't want. 
  Try this: 
  (1) look at each constraint and try to figure out which you don't expect; 
  (2) find the code that added the unwanted constraint or constraints and fix it. 
 (
     "<NSLayoutConstraint:0x600000e877f0 UIView:0x7fa9bc51dd10.width == 10   (active)>",
     "<NSLayoutConstraint:0x600000e87a70 UIView:0x7fa9bc51dfe0.width == 10   (active)>",
     "<NSLayoutConstraint:0x600000e87de0 UIView:0x7fa9bc51e1c0.width == 10   (active)>",
     "<NSLayoutConstraint:0x600000e880a0 UIView:0x7fa9bc51e3a0.width == 10   (active)>",
     "<NSLayoutConstraint:0x600000e86490 PINView:0x7fa9bc507c60.width == 240   (active)>",
     "<NSLayoutConstraint:0x600000e87980 H:|-(0)-[UIView:0x7fa9bc51dd10]   (active, names: '|':PINView:0x7fa9bc507c60 )>",
     "<NSLayoutConstraint:0x600000e87c50 H:[UIView:0x7fa9bc51dd10]-(10)-[UIView:0x7fa9bc51dfe0]   (active)>",
     "<NSLayoutConstraint:0x600000e88050 H:[UIView:0x7fa9bc51dfe0]-(10)-[UIView:0x7fa9bc51e1c0]   (active)>",
     "<NSLayoutConstraint:0x600000e88190 H:[UIView:0x7fa9bc51e1c0]-(10)-[UIView:0x7fa9bc51e3a0]   (active)>",
     "<NSLayoutConstraint:0x600000e881e0 UIView:0x7fa9bc51e3a0.trailing == PINView:0x7fa9bc507c60.trailing   (active)>"
 )
 
 Will attempt to recover by breaking constraint 
 <NSLayoutConstraint:0x600000e880a0 UIView:0x7fa9bc51e3a0.width == 10   (active)>
 
 Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
 The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
 (lldb) 

Vậy là chương trình của chúng ta đang bị lỗi constraint rồi đó. Giờ cùng tìm xem bị lỗi ở đâu nào.

Nhìn vào log thì thấy nó bị lỗi constraint liên quan tới width constraint của view tại địa chỉ 0x7fa9bc51e3a0. Từ đây chúng ta có thể xem view đó là view nào bằng 2 cách sau đây.

Cách 1: Sử dụng chức năng quicklook.

Chúng ta bật chế độ memory graph trên Xcode và tìm địa chỉ 0x7fa9bc51e3a0 và chuột phải chọn quicklook tại node đó.

Tìm view bị break constraint

Cách 2: Set backgroundColor cho view

Pause chương trình lại và thực hiện lệnh sau tại cửa sổ lldb

expression [(UIView*)0x7fa9bc51e3a0 setBackgroundColor:[UIColor redColor]]

WTF. sao k thấy thay đổi gì.

Thực ra thực hiện lệnh trên mới chỉ là set property thôi. Còn bạn muốn update ngay lúc đang pause chương trình thì thực hiện thêm

expression (void)[CATransaction flush]
Sau khi thực hiện lệnh
?

Giờ thì ta bật chế độ debug UI lên để xem cho rõ và xem constraint nào bị conflict để fix ?. Sau khi check giao diện thì mình phát hiện ở đây bị conflict constraint của item pin (có width = 10) và PINView có width = 240. Giờ chỉ cần bỏ constraint 240 đi là xong.

Bài viết này mang mục đích chia sẻ về thủ thuật debug Autolayout, còn việc phát hiện ra lỗi ở đâu thì tuỳ thuộc vào skill của các bạn.

Tham khảo thêm:

https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/DebuggingTricksandTips.html

https://jayeshkawli.ghost.io/debugging-breaking-constraints-in-the-xcode/

Leave a Reply