Saturday, June 25, 2016

Viết Auto Layout bằng code và cách viết UnitTest để kiểm tra giao diện - Phần 3

Trước đây mình có 2 bài viết hướng dẫn cách dùng Auto Layout và Size Class bằng cách thiết lập trên storyboard hay file xib. Nếu các bạn chưa xem có thể xem qua AutoLayout và Size Class Phần 1, Phần 2.

Nếu các bạn làm project từ iOS 9 trở lên thì nên dùng UIStackView để thiết kế giao diện cho dễ, ngược lại từ iOS 8.4 trở xuống thì bạn phải dùng view và những constraint để giả như StackView, để có thể thiết kế giao diện 1 cách đơn giản nhất, vì khi trên 1 màn hình có nhiều thành phần nhỏ nếu bạn liên kết theo từng nhóm trong view thì dễ quản lý hơn. Hoặc bạn có thể dùng thư viện OAStackView, ý tưởng viết thư viện này từ UIStackView.

Viết giao diện nếu bạn muốn trực quan thì nên dùng storyboard hay xib, nhưng nhiều khi có những trường hợp bạn cần viết giao diện bằng code. Nếu bạn nắm được cách viết giao diện bằng code bạn có thể hiểu sâu hơn những vấn đề khi làm giao diện.

Khi làm autolayout trên iOS bằng constraint, có những khái niệm bạn cần phải hiểu như:

* Pin, Align và Ratio:

- Pin: bạn gắn 1 đối tượng giao diện (view, label, button...) vào 1 vị trí cố định nào đó (ví dụ như góc trên bên trái, góc trên bên phải, hay canh giữa...). Cố định chiều cao và chiều rộng với con số mong muốn.
- Align: bạn canh chỉnh từ 2 đối tượng trở lên, đối tượng này sẽ canh chỉnh theo đối tượng kia bằng những thuộc tính như trái, phải, trên, dưới, canh giữa, hay baseline.
- Ratio:  tỷ lệ chiều cao và chiều rộng của 1 đối tượng, nếu bạn muốn tỷ lệ giữ đối tượng này và đối tượng kia thì dùng chức năng Equal Widths/Heights.
  + Nếu bạn dùng storyboard thì sau khi thiết lập chiều rộng và chiều cao mong muốn rồi thì chọn thêm chức năng Aspect Ratio trong phần Pin để đối tượng đó luôn giữ đúng tỷ lệ đó, như hình bên dưới:

  + Equal Heights/Widths: mặc định là chiều cao/chiều rộng của đối tượng này bằng với chiều cao/chiều rộng của đối tượng kia, vì giá trị multiplier là 1. Bạn có thể dùng chức năng này để thiết lập tỷ lệ của đối tượng này với đối tượng khác bằng cách chỉnh giá trị của thuộc tính multiplier trong NSLayoutConstraint hay NSLayoutAnchor.

* Constraint:


Mỗi 1 constraint là một phương trình đơn giữa 2 đối tượng Blue và Red gồm có Item, Attribute, Relationship, Multiplier và Constant.

* Tọa độ: 

- Nếu viết giao diện theo frame thông thường thì theo chiều từ trên xuống, từ trái sang phải. Còn khi dùng autolayout bằng Constraint thì tọa độ bị đảo ngược lại là: từ dưới lên trên, từ trái sang phải.
- Các bạn nên lưu ý điểm này để có thể sắp xếp thứ tự những đối tượng cho đúng thứ tự, nếu không sẽ chạy sai.
Lưu ý: Nếu bạn dùng Visual Format Language thì vẫn dùng tọa độ từ trên xuống, từ trái sang phải.

* Độ ưu tiên của những constraint:

Bạn nên sắp xếp độ ưu tiên cho những constraint của Pin cao hơn so với Algin, Ratio, Equal Heights/Widths.

* Những đối tượng hỗ trợ Autolayout bằng constraints:

- Hiện tại có 3 đối tượng hỗ trợ autolayout bằng code như NSLayoutConstraint, Visual Format Language (VFL) và NSLayoutAnchor (chỉ hỗ trợ từ iOS 9 trở lên). NSLayoutAnchor khá giống với NSLayoutConstraint nhưng các hàm của nó được viết rút gọn lại và có nhiều hàm để hỗ trợ như NSLayoutConstraint.
  + Từ 1 đến 2 đối tượng thì bạn nên dùng NSLayoutConstraint hay NSLayoutAnchor.
  + Nếu từ 3 đối tượng trở lên thì dùng Visual Format Language thì nhìn gọn và dễ hiểu. Nhưng hiện tại VFL còn nhiều hạn chế như không thể chỉnh ratio được.
Tóm lại: bạn nên kết hợp giữa VFL và NSLayoutConstraint để làm giao diện.

- Bạn nên hiểu sơ cú pháp dùng Visual Format Language tại trang của Apple như:
- views và metrics trong VFL: dùng để truyền đối tượng hay hằng vào cú pháp VFL.
- options dùng thể thiết lập thuộc tính align giữa những đối tượng trong cú pháp VFL.

- Một thuộc tính rất quan trọng khi bạn khởi tạo đối tượng translatesAutoresizingMaskIntoConstraints bạn phải thiết lập là false, mặc định là true theo tọa độ thông thường là frame và dùng AutoresizingMask. Nếu bạn dùng XCode 8 và iOS 10 thì bạn có thể dùng được Constraints và AutoresizingMask chung với nhau, cái này mình thấy cũng rất hay, chi tiết bạn có thể vào đây xem.

Mình có những ví dụ để có thể hiểu Autolayout như Pin, Align:

* Ví dụ 1: Cách dùng Pin ở các góc bằng code. Ví dụ mình muốn pin những label ở các góc và ở chính giữa như hình sau:

Nếu như bạn làm trên storyboard thì rất đơn giản, nhưng mình đưa ví dụ này ra để xem sẽ viết autolayout bằng code như thế nào. Theo những đối tượng này mình phân ra thành 2 nhóm như: Pin đối tượng ở 4 góc (label màu xanh lá cây) , Pin đối tượng ở chính giữa (label màu vàng và tím).
- Pin đối tượng ở 4 góc tương đối đơn giản, code ví dụ mình dùng NSLayoutConstraint, Visual Format Language, NSLayoutAnchor thì viết như sau:
Khi làm canh ở top, bạn để ý thứ tự của item thứ 1 là label và thứ 2 là main view. Còn khi làm bottom thì bạn phải đảo ngược thứ tự này lại vì phải canh từ bottom lên label.
- Pin đối tượng ở chính giữa màn hình: + Bạn để ý mình dùng đối tượng như LeadingMargin, TrailingMargin để dùng giá trị margin mặc định của hệ thống. Lưu ý: Nếu bạn chạy giao diện từ 4.7 inch trở xuống (iPhone 6, iPhone 5, ...) thì giá trị margin là 16px, còn từ 5.5 inch trở lên (iPhone 6 plus, iPad, ...) thì giá trị margin là 20px. Mình có lưu ý cái này trong phần UI tại đây.
+ Còn đối với top và bottom bạn nên dùng topLayoutGuide, bottomLayoutGuide để có thể tự động lấy chính xác vị trí tại màn hình khi có Navigation hay không có.

- Canh đối tượng label ở giữa màn hình chính như sau:
Mình tham khảo tại đây.
+ 3 loại này đặc trưng nên mình thể hiện code tại đay, nếu các bạn muốn đối tượng còn lại thì có thể tải source code của mình ở bên dưới.

* Ví dụ 2: Cách dùng Align bằng code. Ví dụ này mình muốn sử dụng những cách để làm những thuộc tính như fix height, top align, bottom align, ...
- Đối tượng 1 (1:Left Top): Canh đối tượng ở vị trí bên trái và ở trên, chiều cao giới hạn là 50px. Viết code ý như trên nhưng có thêm 1 thuộc tính để cố định chiều cao của label lại:
- Đối tượng 2 (2:Top Edges): Canh top edges với đối tượng 1 - Đối tượng 3 (3:Leading + Trailing Edges): giả sử canh lề bên trái với đối tượng 1 và canh lề bên phải với đối tượng 2. Cách viết như sau:
- Đối tượng 4 (4:Bottom Edges): Canh lề phải với màn hình chính và bottom edges với đối tượng 1. Cách viết như đối tượng 2, tương đối dễ.

- Đối tượng 5 (5:CenterX): Giới hạn chiều cao là 50px, canh centerX và khoảng cách là 130px với đối tượng 2.
- Đối tượng 6 (6:CenterY): Canh lề bên trái với đối tượng 1,3, và centerY với đối tượng 5. Các bạn để ý đối tượng thứ 1,3,6 mình dùng VFL và canh chỉnh theo lề bên trái: - Đối tượng 7 (7:Baseline): Canh baseline với đối tượng 5 và bên phải đối tượng 4 với khoảng cách 10px. Hiện tại mình chưa biết cách dùng VFL với canh bên trái và phải với 1 giá trị nào đó.

- Những đối tượng còn lại tương đối dễ thiết lập, bạn có thể xem chi tiết trong project demo của mình.

* Ví dụ 3: Ratio bằng code. Hiện tại VFL không thể làm ratio vì thế bạn phải dùng NSLayoutConstraint hay NSLayoutAnchor để làm việc này. Mình giả xử có label 1 được thiết lập theo tỷ lệ 1:1 với chiều rộng và chiều cao là 70px, còn label 2 thì sẽ canh theo tỷ lệ 2:3 với label thứ 1, như hình bên dưới:
Chắc các bạn cũng biết cách làm theo storyboard hay xib. Ở đây mình chỉ đề cập cách dùng NSLayoutConstraint và NSLayoutAnchor để làm giao diện ratio tương tự như vậy.

- Những constraint khi chỉnh cho label 1 (1:1):
  + Đầu tiên bạn phải cố định (pin) vào góc trên bên phải với giá trị margin mặc định.
  + Bước 2 bạn phải cố định chiều cao hoặc chiều rộng lại với giá trị là 70px.
  + Bước 3 bạn sẽ chỉnh chiều cao và chiều rộng của đối tượng đó với nhau theo tỷ lệ 1:1.
Code của những bước này như sau:
- Những constraint khi chỉnh cho label 2 (2:3) so với label 1:
  + Đầu tiên bạn phải cố định label 2 với label 1 theo bên trái và bên trên so với label 1.
  + Bước 2 bạn thiết lập chiều rộng của label 2 bằng chiều rộng của label 1 nhân cho 2.
  + Bước 3 bạn thiết lập chiều cao của label 2 bằng chiều rộng của label 1 nhân cho 3.
Code của những bước này như sau:
 - Khi các bạn hiểu nguyên tắc như vậy thì khi chỉnh theo ratio thì chỉnh lại thông số multiplier của constraint thay vì là 1 thì là 0.5, 2, 3 gì đó.

Cách viết Unit Test khi làm giao diện autolayout bằng constraint:

- Tại sao lại cần viết Unit Test UI?
  + Lợi ích khi viết unit test là khi bạn làm xong và thấy OK, bạn nên viết Unit Test kiểm tra lại tọa độ những đối tượng đó. Sau 1 thời gian khi bạn hoặc ai đó chỉnh lại những constraint, thì có thể  code sẽ chạy sai. Vì thế nếu bạn có viết Unit Test và chạy thường xuyên bạn sẽ sớm phát hiện ra vấn đề để có mà sửa lại liền. Tránh những bug khi phát hành cho người dùng.
  + Hiện tại mình có thiết lập Jenkins cho chạy test mỗi ngày nên sẽ phát hiện được liền.
  + Xcode Server cũng hỗ trợ cho các bạn chạy test trên nhiều device vì thế các bạn sẽ giảm thời gian để kiểm tra giao diện trên từng thiết bị. Và tự tin khi release sản phẩm có chất lượng cho khách hàng.

- Vậy làm thế nào để test được giao diện trên iOS?
  + Khi bạn lấy đối tượng từ ViewController trong Unit Test thì bạn phải gọi hàm layoutIfNeeded() để những đối tượng trên màn hình mới có thông số tọa độ frame.
  + Bạn khởi tạo vị trí và kích thước mong muốn và dùng hàm so sánh của XCTAssert để kiểm tra tính đúng sai.

- Ví dụ ở đây mình lấy view controller theo storyboard id. Sau đó lấy những label theo tag id, và kiểm tra vị trí và kích thước thông qua hàm CGRectEqualToRect.
Sau đó mình chạy test thử trên thiết bị iPhone 6 và iPhone 6s plus và kết quả thành công như:
Các bạn thấy 1 test case bị fail do mình chưa làm được VFL cho align có giá trị constaint là 10 của đối tượng thứ 7 (baseline).

Các bạn có thể tải project demo của mình tại đây.

Tài liệu tham khảo:

- Autolayout từ trang Apple.
- Autolayout từ trang Raywenderlich.
- Autolayout từ trang CodeTutplus.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.