Phần 3: Viết DSL bố cục tự động với Trình tạo kết quả và quá tải toán tử của Swift ️

Nov 26 2022
Bạn cũng có thể xem bài viết này tại đây: Lần trước chúng tôi đã xây dựng phần chính của DSL, cho phép người dùng cuối thể hiện các ràng buộc theo kiểu số học. Trong bài viết này, chúng tôi sẽ mở rộng vấn đề này để bao gồm: Đầu tiên, chúng tôi tạo một loại để giữ một cặp neo tuân theo giao thức LayoutAnchor được xác định trong Phần 1.

Bạn cũng có thể xem bài viết này tại đây:

Lần trước chúng tôi đã xây dựng phần chính của DSL, cho phép người dùng cuối thể hiện các ràng buộc theo kiểu số học.

Trong bài viết này, chúng tôi sẽ mở rộng về điều này để bao gồm:

  • Các neo kết hợp — kích thước, trung tâm, các cạnh ngang, các cạnh dọc
  • Miếng lót — cho phép người dùng đặt miếng lót trên các neo cạnh kết hợp
  • Trình tạo kết quả để xử lý đầu ra của các neo khác nhau, cho phép dễ dàng kích hoạt và tạo khối ràng buộc.

Trước tiên, chúng tôi tạo một loại để giữ một cặp neo phù hợp với giao thức LayoutAnchor được xác định trong Phần 1.

Ở đây tôi đã xem xét sửa đổi LayoutBlock để giữ một mảng các điểm neo. Sau đó, khi tạo một ràng buộc từ 2 khối như vậy, chúng ta có thể nén các neo lại với nhau và lặp lại chúng, ràng buộc các neo với nhau và chuyển các hằng số/hệ số nhân có liên quan.

Có 2 nhược điểm:

  • Ngay cả các biểu thức đơn lẻ với các neo cơ bản cũng sẽ trả về một loạt các ràng buộc. Điều này làm phức tạp DSL theo quan điểm của người dùng.
  • Người dùng có thể thử và sử dụng một neo tổng hợp (có 2 hoặc 4 neo) với một neo đơn lẻ. Chúng tôi có thể giải quyết vấn đề này bằng cách bỏ qua các neo bổ sung. Trình biên dịch sẽ không đưa ra cảnh báo nào. Tuy nhiên, các hoạt động này và các ràng buộc kết quả sẽ là vô nghĩa. Điều này có khả năng đưa các lỗi khó chịu vào mã của người dùng cuối — điều mà chúng tôi muốn tránh!!

Bước 11: Mở rộng giao thức LayoutAnchorPair.

Tiện ích mở rộng này sẽ phục vụ mục đích giống như tiện ích mở rộng giao thức LayoutAnchor đã xác định trước đó — nó hoạt động như một trình bao bọc gọi các phương thức của loại cơ sở và trả về các ràng buộc kết quả.

Sự khác biệt chính ở đây là mỗi phương thức trả về một mảng các ràng buộc, kết hợp các ràng buộc của 2 loại LayoutAnchor.

Bởi vì các neo mà chúng tôi chuyển đến LayoutAnchorPair bị hạn chế đối với các loại LayoutAnchor, nên chúng tôi có thể dễ dàng cung cấp triển khai mặc định.

Ngoài ra, thay vì các hằng số, các phương thức này sử dụng EdgeInsetPair sẽ cung cấp khả năng cung cấp các hằng số khác nhau cho từng ràng buộc.

Mỗi phương thức ánh xạ hằng1 tới ràng buộc anchor1 và hằng2 trên anchor2.

Bước 13: Tạo các kiểu LayoutAnchor cụ thể.

Trong Phần 1 & 2, chúng tôi không phải tạo các loại LayoutAnchor cụ thể vì chúng tôi chỉ tạo NSLayoutAnchor mặc định phù hợp với giao thức LayoutAnchor. Tuy nhiên, ở đây, chúng tôi cần cung cấp các neo riêng phù hợp với giao thức AnchorPair.

Lời nhắc về các bí danh được sử dụng trước đây:

Chúng tôi xác định một loại lồng nhau đáp ứng giao thức EdgeInsetPair. Điều này đáp ứng yêu cầu loại liên quan đến AnchorPair - Insets. Loại bê tông tuân thủ giao thức này sẽ được sử dụng trong quá trình vận hành quá tải để đặt các miếng lót.

Chúng tôi sử dụng các thuộc tính được tính toán để phù hợp với giao thức LayoutAnchorPair và EdgeInsetPair. Các thuộc tính được tính toán này trả về các thuộc tính bên trong của LayoutAnchorPair và EdgeInsetPair.

Ở đây, điều quan trọng là đảm bảo rằng các hằng số được cung cấp bởi loại bên trong khớp với các neo được xác định trên cặp neo này. Cụ thể là trong ngữ cảnh của tiện ích mở rộng được xác định ở bước cuối cùng, trong đó hằng1 được sử dụng để hạn chế anchor1 .

Giao thức “chung” này cho phép chúng tôi xác định một phần mở rộng giao thức hợp lệ cho tất cả các cặp neo. Miễn là chúng tôi tuân theo quy tắc đã thảo luận ở trên. Đồng thời, chúng tôi có thể sử dụng các nhãn neo cụ thể có ý nghĩa hơn — dưới cùng/trên cùng — bên ngoài tiện ích mở rộng. Chẳng hạn như khi xác định quá tải toán tử.

Một giải pháp thay thế sẽ đòi hỏi phải có các tiện ích mở rộng riêng cho tất cả các loại — điều này không quá tệ vì số lượng neo bị hạn chế. Nhưng tôi đã lười biếng ở đây và cố gắng tạo ra một giải pháp trừu tượng. Dù bằng cách nào, đây là một chi tiết triển khai nội bộ của thư viện và có thể được thay đổi trong tương lai mà không phá vỡ các thay đổi.

Vui lòng để lại nhận xét nếu bạn tin rằng một thiết kế khác sẽ là tối ưu.

Thêm điểm quyết định kiến ​​trúc:

  • Các phần tử bên trong phải là các loại riêng biệt khi chúng được khởi tạo và sử dụng bên ngoài một neo.
  • Các loại lồng nhau được sử dụng để bảo vệ không gian tên và giữ cho nó sạch sẽ, đồng thời làm nổi bật thực tế rằng việc triển khai loại Inset phụ thuộc vào việc triển khai PairAnchor cụ thể.

Lưu ý: Thêm phần phụ không giống như thêm hằng số vào một biểu thức.

Bạn có thể nhận thấy rằng chúng tôi đã thêm một toán tử trừ vào hằng số trên cùng trước khi trả về nó như một phần của giao diện EdgePair. Tương tự, việc triển khai XAxisAnchorPair sẽ thêm một điểm trừ vào dấu neo theo sau. Đảo ngược các hằng số có nghĩa là các phần tử lót sẽ hoạt động như các phần tử lót thay vì dịch chuyển từng cạnh theo cùng một hướng.

Chế độ xem màu đỏ bị giới hạn ở mức 200 trong hình ảnh một và hai.

Trong hình ảnh bên trái, tất cả các điểm neo cạnh của chế độ xem màu xanh lam được đặt bằng với điểm neo của chế độ xem màu đỏ cộng với 40. Điều này dẫn đến chế độ xem màu xanh lam có cùng kích thước nhưng được dịch chuyển 40 dọc theo cả hai trục. Mặc dù điều này có ý nghĩa về các ràng buộc, nhưng bản thân đây không phải là một hoạt động phổ biến.

Đặt phần phụ xung quanh chế độ xem hoặc thêm phần đệm xung quanh chế độ xem phổ biến hơn nhiều. Do đó, trong bối cảnh cung cấp API cho các tác nhân kết hợp sẽ có ý nghĩa hơn.

Trong hình ảnh bên phải, chúng tôi đặt chế độ xem màu xanh lam bằng với chế độ xem màu đỏ cộng với phần phụ là 40 khi sử dụng DSL này. Điều này đạt được sự đảo ngược của các hằng số được mô tả ở trên.

Bước 14: Mở rộng View & LayoutGuide để khởi tạo các neo tổng hợp.

Giống như chúng tôi đã làm trước đây, chúng tôi mở rộng các loại ViewLayoutGuide với các thuộc tính được tính toán khởi tạo LayoutBlocks khi được gọi.

Bước 15: Nạp chồng các toán tử +/- để cho phép các biểu thức có phần trong cạnh.

Đối với các neo cạnh ngang và cạnh dọc, chúng tôi muốn người dùng có thể chỉ định các phần phụ. Để đạt được điều này, chúng tôi mở rộng loại UIEdgeInsets vì nó đã quen thuộc với hầu hết người dùng DSL này.

Tiện ích mở rộng cho phép khởi tạo chỉ với phần trên cùng/dưới cùng hoặc bên trái/phải — phần còn lại mặc định là 0.

Chúng ta cũng cần thêm thuộc tính mới vào LayoutBlock để lưu trữ edgeInsets.

Tiếp theo, chúng tôi nạp chồng toán tử cho đầu vào: LayoutBlock với UIEdgeInsets .

Chúng tôi ánh xạ phiên bản của UIEdgeInsets do người dùng cung cấp tới loại lồng nhau có liên quan được xác định là một phần của LayoutAnchorPair cụ thể .

Các thông số bổ sung hoặc không chính xác được người dùng chuyển vào như một phần của loại UIEdgeInset sẽ bị bỏ qua.

Bước 15: Nạp chồng toán tử so sánh để xác định quan hệ ràng buộc.

Nguyên tắc vẫn như cũ. Chúng tôi quá tải các toán tử quan hệ cho đầu vào LayoutBlocksLayoutAnchorPair .

Nếu người dùng cung cấp một cặp phần tử chèn cạnh — chúng tôi sẽ sử dụng chúng, nếu không, chúng tôi sẽ tạo một cặp phần tử chèn chung từ các hằng số LayoutBlocks . Cấu trúc bên trong chung là một trình bao bọc, không giống như các phần bên trong khác, nó không phủ nhận một trong các bên.

Bước 16: Bố cục kích thướcAnchorPair

Giống như một neo kích thước duy nhất, một cặp neo kích thước — widthAnchorheightAnchor — có thể bị ràng buộc ở các hằng số. Do đó, chúng tôi phải cung cấp các quá tải toán tử riêng biệt để xử lý trường hợp sử dụng này.

  • Cho phép người dùng DSL cố định cả chiều cao và chiều rộng thành cùng một hằng số — tạo một hình vuông.
  • Cho phép người dùng DSL sửa SizeAnchorPair thành loại CGSize — có ý nghĩa hơn trong hầu hết các trường hợp vì chế độ xem không phải là hình vuông.

Bước 17: Sử dụng Trình tạo kết quả để xử lý các loại [NSLayoutConstraint] và NSLayoutConstraint.

Neo tổng hợp tạo ra một vấn đề thú vị. Việc sử dụng các điểm neo này trong một biểu thức sẽ dẫn đến một loạt các ràng buộc. Điều này có thể trở nên lộn xộn đối với người dùng cuối của DSL.

Chúng tôi muốn cung cấp một cách để kết hợp các ràng buộc này lại với nhau mà không cần mã soạn sẵn và kích hoạt chúng một cách hiệu quả — trong một lần thay vì riêng lẻ hoặc theo từng đợt riêng biệt.

Trình tạo kết quả được giới thiệu trong Swift 5.4 (còn được gọi là trình tạo hàm) cho phép bạn tạo kết quả bằng cách sử dụng 'khối tạo' hoàn toàn từ một loạt thành phần. Trên thực tế, chúng là khối xây dựng cơ bản đằng sau Swift UI.

Trong DSL này, kết quả cuối cùng là một mảng các đối tượng NSLayoutConstraint .

Chúng tôi cung cấp các hàm xây dựng, cho phép trình xây dựng kết quả giải quyết các ràng buộc riêng lẻ và mảng các ràng buộc thành một mảng. Tất cả logic này được ẩn khỏi người dùng cuối của DSL.

Hầu hết các chức năng này tôi đã sao chép trực tiếp từ ví dụ đề xuất trình tạo kết quả tiến hóa nhanh . Tôi đã thêm các bài kiểm tra đơn vị để đảm bảo chúng hoạt động chính xác trong DSL này.

Đưa tất cả các kết quả này vào như sau:

Trình tạo kết quả cũng cho phép chúng tôi bao gồm luồng điều khiển bổ sung trong bao đóng, điều không thể thực hiện được với một mảng.

Đó là nó, cảm ơn bạn đã đọc! Bài viết này mất nhiều ngày để viết — vì vậy nếu bạn học được điều gì mới, tôi sẽ đánh giá cao ⭐ trên repo này!

Nếu bạn có bất cứ lời khuyên nào cho tôi, đừng ngại: và chia sẻ kinh nghiệm của bạn!

Phiên bản Cuối cùng của DSL này bao gồm một neo bốn chiều được tạo từ một cặp loại AnchorPair

Bạn có thể tìm thấy tất cả các mã ở đây: