Xây dựng design system hỗ trợ một sản phẩm không dễ dàng – nó phải vừa vững chắc vừa linh hoạt để mở rộng. Dù đầy thách thức, nhiều nguồn tuyệt vời đã chia sẻ nguyên tắc và phương pháp hữu ích giúp các team xây dựng hệ thống tốt cả về mặt trực quan và lập trình. Đứng trên vai những người đi trước, bài viết này cố gắng đóng góp vào một lĩnh vực chưa được khai thác bằng cách tập trung vào việc xây dựng hệ thống tốt trong SwiftUI.
Tại sao tôi viết bài này
Trong mùa hè đầu tiên tại ITP New York, tôi may mắn có cơ hội làm thực tập sinh iOS tại Line Break Studio. Một nhiệm vụ được giao là xây dựng design system trong hai bước: trước tiên trực quan trong Sketch, sau đó lập trình trong SwiftUI. Trải nghiệm thử nghiệm framework mới và xây dựng design system với nó đã tuyệt vời, nhưng cũng đầy lỗi (buggy) trên đường đi. Đó là lý do chúng tôi muốn chia sẻ kinh nghiệm với cộng đồng, hy vọng làm cho quy trình phát triển của bạn dễ dàng hơn.
SwiftUI là gì
Apple phát hành framework đột phá này tại WWDC 2019, một trong những framework tốt nhất trong nhiều năm. Từ góc nhìn nhà phát triển web, trải nghiệm phát triển dự án trong SwiftUI gần với stack front-end truyền thống và các framework.
Đây chắc chắn là bước tiến tuyệt vời vì lập trình giao diện và quản lý state (trạng thái) dễ dàng hơn bao giờ hết. Và phần tốt nhất của cải tiến này là có thể tích hợp UIKit và SwiftUI mượt mà. Để học cơ bản SwiftUI, các hướng dẫn chính thức của Apple rất hữu ích.
Dự án demo
Với mục đích demo, tôi đưa ra phiên bản đơn giản hóa của design system chúng tôi xây dựng tại Line Break Studio. Nó là một tập hợp các component button ở các dạng khác nhau, được xây dựng trên hai phần cấp thấp hơn: typography (chữ) và colorPalette (bảng màu).
Dự án công khai trên GitHub, tôi dùng Xcode 11 Beta 5 cho phát triển. Một Airtable base làm trung tâm quản lý design system (đọc thêm về quản lý quy trình) cũng công khai để tham khảo.
Nguyên tắc xây dựng design system
Design system trong code là phần mềm trung gian (middleware) giữa designer và developer. Nhà phát triển hệ thống nhận input từ design system dạng trực quan, và tạo ra API giống hệt nhau cho phát triển tiếp theo. Hai nguyên tắc sau nên được nhận ra để hoàn thành hệ thống trong code:
1. Giao tiếp bằng token
Cơ bản, mục đích có design system trong code không phải về quản lý code tốt hơn hay hiệu suất phát triển, mà để đảm bảo view nhất quán với file thiết kế. Để đạt mục tiêu đó, dùng token để biểu thị màu, font, kích thước hoặc bất kỳ yếu tố trực quan nào là then chốt để duy trì chất lượng giao tiếp giữa developer, designer và manager trong team.
2. Các cấp độ phân cấp
Trong bài viết của EightShapes, chỉ ra rằng chúng ta nên "Hiện lựa chọn trước, quyết định sau", vì "Bạn không thể quyết định nếu không có lựa chọn."
Kiến trúc phân cấp thứ tự này làm lỏng độ kết dính (coupling) giữa các cấp độ, do đó cung cấp sự linh hoạt và động lực hơn cho các sửa đổi có thể. Cách tôi cấu trúc các cấp độ theo thứ tự từ dưới lên: material → base → token. Nhưng có thể theo bất kỳ cách nào team thoải mái.
Đi sâu vào code
Phần sau là danh sách những điểm nhấn chúng tôi muốn chỉ ra dựa trên kinh nghiệm. Vui lòng truy cập GitHub repo để xem mã hoàn chỉnh. Mọi phản hồi hoặc góp ý đều hoan nghênh để cải tiến.
1. Kiến trúc các cấp độ phân cấp
Có hai cách xếp chồng vật liệu ở cấp thấp để xây dựng token ở cấp cao nhất:
- Dùng
enumcho an toàn kiểu (type safety) và dễ đọc code
Ưu điểm dùng enum trong code như wrapper nhóm hoặc tham số hàm đã được công nhận. Một điểm đáng chú ý là triển khai cấp độ phân cấp.
Chúng tôi luôn lưu giá trị gốc, bao gồm kích thước font (CGFloat) và tên font (String), ở cấp thấp nhất, vì không muốn làm rối nó. Nhưng vì raw value phải là literal trong enum, chúng ta không thể gán một case bằng giá trị từ enum khác.
Để khắc phục, triển khai hàm getValue, trả về raw value trong switch case khi cần.
- Dùng
structcho cấu trúc dễ dàng hơn
Mặc dù enum tuyệt vời, chúng ta không cần tính năng độc đáo của nó trong một số trường hợp. Ví dụ, vì Xcode lo phần nặng nề xử lý dynamic color, và không yêu cầu tùy chọn tham số ở endpoint API, chúng ta có thể thiết lập color palette bằng hai cấp struct đơn giản.
2. Tên gọi API endpoint rõ ràng, trực quan
Quy ước đặt tên là chủ đề rộng để thảo luận và tranh luận. Ngoài quy ước Swift cơ bản, hai quy tắc chúng tôi tuân thủ là: 1) không viết tắt và 2) làm cho đơn giản. Ví dụ, để dùng hệ thống typography và color, thay vì tạo endpoint mới, chúng ta làm extension từ struct Font và Color. Cách này giảm nỗ lực ghi nhớ tên API lạ cho developer.
3. Quản lý color set động trong hai chế độ
Dark mode đã thành tiêu chuẩn ngành, cả team iOS và Android đều triển khai. Đây là xu hướng tốt cho người dùng, nhưng có thể gây thách thức cho designer và developer, bao gồm quản lý và đặt tên color set, đặc biệt là thang xám (gray scale).
Để suy nghĩ và giao tiếp về màu xám động, dùng thuật ngữ như white, light, black hoặc dark không hiệu quả. Vì nếu chúng ta gọi một màu động #000000 (đen trong HEX) là black hoặc dark trong light color scheme, làm sao nói về màu cụ thể này, mà nên chuyển thành #FFFFFF (trắng trong HEX), trong dark color scheme? defaultDark hay lightDark?
Rất khó đặt tên color set xám động theo cách truyền thống. Để tránh nhầm lẫn, chúng ta dùng theme và contrast quản lý một bộ màu trong cả hai scheme light và dark.
Lưu ý màu xám không luôn cần đảo ngược trong chế độ màu đối lập. Trong trường hợp màu sáng vẫn sáng và tối vẫn tối, chúng ta đơn giản đặt tên là light hoặc dark.
Khi đã nắm được phương pháp đặt tên này, quản lý kiến trúc color palette dễ dàng trong Xcode. Để tạo color set, chỉ cần tạo file Asset Catalog mới → thêm Color Set mới → đổi Appearances thành Any, Light, Dark.
4. Cài đặt environment
Một tính năng tuyệt vời trong framework SwiftUI là environment modifier, cung cấp khả năng kiểm soát giá trị environment trên view mục tiêu. Về việc xây dựng design system, khả năng này cung cấp cách tiếp cận thuận tiện để thay đổi font app ở cấp gốc (root). Và lợi thế khác của environmentValue là thay đổi và test light/dark color scheme trong phát triển.
5. buttonStyle và button label
So với thời UIKit cũ, xây dựng button tái sử dụng trong SwiftUI dễ dàng hơn rất nhiều. View Button gồm hai phần: closure action (sự kiện kích hoạt khi bấm button) và label (thân button). View sau đó có thể chuỗi với modifier buttonStyle. Để tìm hiểu chi tiết xây dựng button tái sử dụng, khuyến khích đọc tutorial của Alejandro, rất toàn diện và hữu ích.
Trong component button tùy chỉnh của chúng tôi, bước đầu là tạo hai struct: TokenButtonLabel và TokenButtonStyle. Hai struct này được lập trình theo loại button trong file thiết kế. Ví dụ, chỉ có hai loại label: icon và text. Mỗi loại có hàm init riêng với tham số khác nhau cho instance mới.
Phía khác, có bốn loại button style chính: circle icon, icon, capsule và text. Để tuân theo protocol ButtonStyle, hàm makeBody phải được triển khai. Hàm này mang lại property configuration, cung cấp giá trị isPressed gốc để theo dõi xem button có được bấm không.
Cuối cùng, xếp chồng trên TokenButtonLabel và TokenButtonStyle, endpoint của component button API sẽ là TokenButton – một nhóm gom nội dung và style button cùng nhau, tuân theo loại button trong visual design system.
6. AnyView làm wrapper
Khi xử lý hàm makeBody do protocol ButtonStyle mang lại, chúng tôi tìm thấy mẹo hữu ích làm việc với View. Để lưu view trong biến, chú thích kiểu có thể chỉ là AnyView, hoạt động như container chung của view trong SwiftUI.
Trong trường hợp chúng tôi, vì muốn thêm modifier opacity cho configuration.label cho mọi loại button, thay vì làm lặp lại trong mỗi switch case, hợp lý hơn là chuỗi modifier ở cuối tất cả. Chúng ta đạt pattern này bằng lợi thế của AnyView.
7. Xây dựng view modifier với hàm mutating
Để cập nhật style button động, có thể xây dựng modifier riêng. Đầu tiên khởi tạo mutable state properties tùy chỉnh trong view, sau đó tạo hàm mutating trả về kiểu Self sau khi cập nhật state property mục tiêu.
8. Border style khó xử
Một nhược điểm của SwiftUI là styling hình tròn với border tròn không trực quan chút nào. Tôi vật vã một lúc, cuối cùng tìm thấy giải pháp trên StackOverflow. Cần modifier clipShape và overlay để làm nó hoạt động.
Kết luận
SwiftUI là cải tiến tuyệt vời của Apple. Dù vẫn còn khuyết điểm, xây dựng design system vững chắc, linh hoạt với nó, và hơn nữa UI phức tạp trên iOS hiệu quả hơn bao giờ hết. Tôi hy vọng bài viết hữu ích cho bất kỳ team iOS nào cố gắng xây dựng UI, và luôn chào đón mọi phản hồi!
? Đọc thêm tác phẩm của tôi tại vinceshao.com / Theo dõi tôi trên Twitter hoặc LinkedIn