Linh Tạ

Mẹo debug lỗi layout constraint

Sửa lỗi constraint conflict không khó nhưng vấn đề là xác định được nguyên nhân và các constraint không thể cùng thoả mãn. Mỗi khi constraint bị conflict, Xcode sẽ in ra console toàn bộ thông tin cần thiết để chúng ta tiến hành debug, tuy nhiên với mình những thông tin này thực sự trông chả khác gì kí tự Ai Cập 😥.


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:0x1436b6470 UIView:0x1436b9250.width == 1   (active)>",
    "<NSLayoutConstraint:0x143696ad0 UIImageView:0x1436bb620.height >= 40   (active)>",
    "<NSLayoutConstraint:0x14368c470 UIImageView:0x1436bb620.width == UIImageView:0x1436bb620.height   (active)>",
    "<NSLayoutConstraint:0x1436bbe20 UIView:0x1436b9250.centerX == UIView:0x1436b8ee0.centerX   (active)>",
    "<NSLayoutConstraint:0x1436bcc80 H:[UIImageView:0x1436bb620]-(0)-|   (active, names: '|':UIView:0x1436b8ee0 )>",
    "<NSLayoutConstraint:0x1436bcd80 H:[UIView:0x1436b9250]-(0)-[UIImageView:0x1436bb620]   (active)>",
    "<NSLayoutConstraint:0x1436bb810 H:|-(0)-[UIImageView:0x1436bb620]   (active, names: '|':UIView:0x1436b8ee0 )>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x143696ad0 UIImageView:0x1436bb620.height >= 40   (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.

Trong ví dụ trên, nếu cố gắng một chút, ta có thể đoán được Xcode đang nói về 2 UIView, 1 UIImageView và một vài constraint giữa chúng. Nếu bạn không thuần thục Visual Format Language thì việc dùng những thông tin này để truy ra các view và constraint cụ thể sẽ rất tốn thời gian. Nhưng đừng quá lo lắng, mình biết 1 mẹo nhỏ có thể giúp bạn tiết kiệm được rất nhiều công sức.

Bước 1: Xác định nơi xảy ra conflict

Bước đầu tiên chúng ta cần làm là tìm ra view và đoạn code gây ra xung đột bằng cách sử dụng một breakpoint đặc biệt.

How to add symbolic breakpoint
Thêm breakpoint để bắt lỗi constraint

Ở cột Navigator bên trái, ta chọn biểu tượng breakpoint, sau đó tạo Symbolic Breakpoint, gán UIViewAlertForUnsatisfiableConstraints ở mục Symbol và cuối cùng nhấn Add Action.

Symbolic breakpoint for UIViewAlertForUnsatisfiableConstraints
UIViewAlertForUnsatisfiableConstraints breakpoint

Dành cho bạn nào chưa biết, Symbolic breakpoint là các breakpoint được tự động kích hoạt khi một function cụ thể chạy. Đây có thể là function của hệ thống hoặc của chính bạn. UIViewAlertForUnsatisfiableConstraints là tên function UIKit gọi mỗi khi có conflict xảy ra. Với breakpoint này, ta sẽ truy cập được vào stack trace và tìm ra được thời điểm xuất hiện của conflict đó.

Breakpoint gives us access to stack trace
Stack trace từ breakpoint

Lưu ý đây không đồng nghĩa với việc các đoạn code trong strack trace gây ra vấn đề. Hoàn toàn có thể do việc add hoặc hiện view vô tình kích hoạt các constraint xung đột. Bạn chỉ nên dùng stack trace để lần ra view bị báo lỗi.


Bước 2: Xác định các view bị conflict

Để định hình các view tham gia, ta có thể vào file giao diện và gán cho từng view một accessibilityIdentifier cụ thể hoặc làm trực tiếp trong code với imageView.accessibilityIdentifier = "RightImageView".

How to add accessibility identifier
Cách gán accessibilityIdentifier

Khi lỗi constraint xảy ra, thay vì các UIView, UIImageView chung chung, Xcode sẽ sử dụng identifier vừa gán làm tên của view, giúp ta dễ dàng phân biệt các view khác nhau.


(
    "<NSLayoutConstraint:0x1612a05e0 DividerView.width == 1   (active, names: DividerView:0x161282ca0 )>",
    "<NSLayoutConstraint:0x161295a90 RightImageView.height >= 40   (active, names: RightImageView:0x161282010 )>",
    "<NSLayoutConstraint:0x161296fb0 RightImageView.width == RightImageView.height   (active, names: RightImageView:0x161282010 )>",
    "<NSLayoutConstraint:0x16127f230 DividerView.centerX == BackgroundView.centerX   (active, names: DividerView:0x161282ca0, BackgroundView:0x1612997e0 )>",
    "<NSLayoutConstraint:0x161298d20 H:[RightImageView]-(0)-|   (active, names: BackgroundView:0x1612997e0, RightImageView:0x161282010, '|':BackgroundView:0x1612997e0 )>",
    "<NSLayoutConstraint:0x16127ff00 H:[DividerView]-(0)-[RightImageView]   (active, names: RightImageView:0x161282010, DividerView:0x161282ca0 )>",
    "<NSLayoutConstraint:0x16128bf20 H:|-(0)-[RightImageView]   (active, names: RightImageView:0x161282010, BackgroundView:0x1612997e0, '|':BackgroundView:0x1612997e0 )>"
)


Bước 3: Xác định các constraint conflict

Bước cuối cùng chúng ta cần để giải mã layout conflict này là đọc hiểu danh sách các constraint bị xung đột. Để làm được điều này, hãy copy toàn bộ cụm constraint cung cấp bởi Xcode, truy cập vào WTF Autolayout và paste để chứng kiến điều kì diệu 😎.

WTF Autolayout

Tổng kết

Với toàn bộ thông tin trong tay, việc debug conflict lúc này đã trở nên đơn giản hơn rất nhiều. Mong rằng bài viết sẽ có ích với bạn và hẹn gặp lại!