UI Components và bước đầu hướng đến MicroViewControllers trong lập trình iOS
ios
53
UI
29
White

Vu Nhat Minh viết ngày 05/01/2019

UI Components là 1 khái niệm không có gì mới và đã có ứng dụng rộng rãi trong lập trình Web (FrontEnd), Tuy nhiên trong lập trình mobile nói chung và iOS nói riêng đôi khi vẫn có những ranh giới cần phải vượt qua để hiện thực hoá được phong cách lập trình này.

Trong bài này mình sẽ đưa ra những khó khăn cụ thể bạn sẽ gặp phải và cách để vượt qua, dựa theo Prototype App mình đang làm cho chính Kipalog. Đây là demo của phần vỏ UI:
alt

Quy ước

Trước hết mình sẽ trả lời trước 1 số câu hỏi định hình cho bài viết

Để làm giao diện nên dùng UITableView hay UICollectionVIew?

Trả lời: UICollectionVIew. Tất cả những gì làm được với UITableView đều làm được với UICollectionView. Với UICollectionVIew khả năng điều chỉnh layout linh hoạt hơn, và khi cần có thể tạo UICollectionView bên trong cell của UICollectionView 1 cách đơn giản.

Design cell của UICollectionView như thế nào?

Trả lời: Mình tách cell ra làm 1 file xib và 1 file ViewController riêng (ViewController chứ không phải View nhé) sau đó cài cắm vào trong ViewController chính.

Lập trình giao diện bằng code, storyboard hay xib?

Trả lời: Xib. Lập trình layout bằng code tốn công, thiếu trực quan và quan trọng nhất là không nhìn thấy layout warning, Storyboard khiến cho khởi tạo ViewController bị rối. Trong khi đó Xib hiện giờ đã hỗ trợ Safe Area Layout như Storyboard và không có nhược điểm nào như 2 cách còn lại.

Reactive Programming dùng thư viện gì?

Trả lời: RxSwift. Tuy vậy ReactiveSwift cũng okie và nội dung về UI Components trong bài viết này không liên quan và không bị ảnh hưởng bởi thư viện Reactive Programming nào

Dùng architecture gì thế?

Trả lời: MVVM ở mức tối gỉản.

Đây là Layout capture dùng Reveal

Khởi tạo ViewController từ xib

Phần này không có gì khó và bạn có thể tìm thấy câu trả lời trên StackOverFlow rất dễ dàng. Đây là protocol mình hay sử dụng

protocol XibViewController {
static var className: String { get }
static func make() -> Self
}
extension XibViewController where Self: UIViewController {
static var className: String {
return String(describing: self).components(separatedBy: ".").last!
}
static func make() -> Self {
return self.init(nibName: Self.className, bundle: nil)
}
}
extension UIViewController: XibViewController {}
view raw Xib.swift hosted with ❤ by GitHub

Như vậy để mỗi lần tạo 1 file ABCViewController.swiftABCViewController.xib, mình sẽ

  • Mở file xib > Identity Inspedctor > set class thành file swift
  • Mở file xib > Connection Inspector > kéo nối vIew của file owner vào View chính của xib
  • Khởi tạo ViewController bằng let vc = ABCViewController.make()

Khởi tạo CollectionViewCell bằng ViewController

Nếu bạn làm iOS theo sách giáo khoa của Apple chắc sẽ biết cách đơn giản nhất khi tạo UICollectionViewCell là design trực tiếp vào storyboard đúng không? Khi quen hơn chút nữa chắc sẽ thiết kế Cell trong 1 file xib riêng rồi tạo 1 file View kế thừa lớp UICollectionViewCell và cài cắm vào lớp chính kế thừa UICollectionViewController.
Nhưng ở đây mình sẽ tạo Cell bằng xib và ViewController như bước trên, và đem cả ViewController đó bỏ vào trong 1 UIView của lớp UICollectionViewCell. Để làm điều đó mình sẽ trừu tượng hoá Cell 1 chút. Trước hết là 1 protocol

protocol UIComponent: class {
associatedtype Content: UIViewController
associatedtype Parent: UIViewController
var contentView: UIView { get }
var content: Content? { get set }
var parent: Parent? { get set }
}
extension UIComponent where Self: UIView {
func embed(content: Content, parent: Parent) {
self.content = content
self.parent = parent
content.view.translatesAutoresizingMaskIntoConstraints = false
if superview == nil {
content.willMove(toParent: parent)
} else {
parent.addChild(content)
}
contentView.addSubview(content.view)
NSLayoutConstraint.activate(
[
content.view.topAnchor.constraint(equalTo: contentView.topAnchor),
content.view.leftAnchor.constraint(equalTo: contentView.leftAnchor),
content.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
content.view.rightAnchor.constraint(equalTo: contentView.rightAnchor)
]
)
if superview == nil {
content.removeFromParent()
} else {
content.didMove(toParent: parent)
}
}
}
view raw UIComponent.swift hosted with ❤ by GitHub

Đoạn code bên trên làm gì ở đây?

Trước hết mình có 1 protocol UIComponent, luôn chắc chắn có biến số contentparent với kiểu kế thừa UIVIewController. Bạn có thể hiểu nôm na content sẽ là ViewController của cell, và parent là ViewController của lớp chứa UICollectionView. Cả 2 đều là ViewController.
TIếp đến trong phương thức embed(), mình để content trở thành con của parent, cho view của content trờ thành subView của contentView và tạo 4 constraints để ghim view của content vào contentView. contentView sẽ là biến số có sẵn nếu như UIComponent cũng là 1 lớp kế thừa UICollectionViewCell

Tiếp theo sẽ là 1 lớp CollectionViewCell tổng quát
https://gist.github.com/orakaro/176f84057dc12dea990b7184c511a58e
Bạn sẽ muốn để ý thấy lớp CollectionViewCell vừa là UICollectionViewCell vừa là UIComponent. phương thức register đơn giản là đăng ký Cell và phương thức deque sẽ gọi dequeueReusableCell() bên trong cùng với embed() để cài đặt content vào trong cell và nối luôn với parent.

Nghe có vẻ hay đấy, vậy sử dụng lớp này như thế nào

Bạn sẽ chỉ cần đăng ký và hàm cellForItemAt indexPath mặt mũi sẽ như này:

override func viewDidLoad() {
super.viewDidLoad()
CollectionViewCell<FeedCellViewController, FeedViewController>.register(to: collectionView)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let post = posts[indexPath.row]
let cell = CollectionViewCell<FeedCellViewController, FeedViewController>.dequeue(from: collectionView, for: indexPath, parent: parent)
cell.content?.post = post
return cell
}

Lợi ích

Đọc đến đây các bạn có thể sẽ tự hỏi, chúng ta làm những thứ loằng con nhà bà ngoằng này để làm gì?
Lợi ích lớn nhất thu được ở thời điểm này là, mọi thành phần UI nhỏ đều có thể cắt ra thành Xib+ViewController.

Bạn có 1 TableViewCell? Cắt ra thành XIb+ViewController. Bạn có CollectionVIewCell? Cắt ra thành Xib+ViewController. Bạn có TableVIewHeader/Footer? hay là CollectionViewHeader/Footer? Cắt tiếp ra thành Xib+ViewController. Bạn có 1 VIew lẻ? Cắt ra thành XIb+ViewController.

Và trong mỗi XIb+ViewController, bạn có thể tuỳ ý cắt tiếp, hay ném vào 1 CollectionView nữa, và rồi mỗi Cell lại có thể là Xib+ViewController. Tiềm năng là vô hạn. Mặc dù mình thấy code đến 3 mức CollectionVIew bên trong CollectionView bên trong CollectionVIew là quá lắm rồi :sweat_smile:

Và vì bản thân mỗi thành phần là 1 ViewController, bạn có thể làm gì tuỳ ý, gói logic vào bên trong, gọi API, lưu dữ liệu, dùng mô hình cấu trúc khác nhau v.v... Và bức tranh hoàn chỉnh hiện ra là bạn sẽ có rất nhiều các MicroViewControllers. Nghe na ná MicroServices phải không nào? :smile:

Tuy nhiên đây mới là bước đầu của MicroViewControllers. Để có tính ứng dụng cao hơn chúng ta còn cần StackView, Container, và cách lưu chuyển dữ liệu giữa các MicroVIewControllers dùng kiểu Redux v.v... Mình sẽ cố gắng chia sẻ nếu nhiều bạn quan tâm.

Tham khảo

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Vu Nhat Minh

56 bài viết.
900 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
138 32
Nếu bạn thường vào trang mua sắm của amazon, chắc sẽ chẳng lạ gì với menu Shop by Department. Tốc độ hiển thị nội dung của menu là tức thì so với d...
Vu Nhat Minh viết gần 4 năm trước
138 32
White
109 4
Lời người dịch Người dịch là một developer , sau khi tìm đọc được bài viết này bằng bản gốc tiếng Anh đã cảm thấy như được "khai sáng" về khả năng...
Vu Nhat Minh viết 4 năm trước
109 4
White
73 7
Form là thành phần quan trọng nhất khi design flow đăng ký của 1 web hay 1 app, dù là view gồm nhiều bước hay chỉ là một màn hình đơn điệu. Bài này...
Vu Nhat Minh viết hơn 2 năm trước
73 7
Bài viết liên quan
White
2 3
Xin chào mọi người. Mình xin chia sẽ một UILabel Helper nhỏ dùng trong truờng hợp cần tính chiều cao của UILabel để xác định "Show More" button có ...
DonDinh viết hơn 4 năm trước
2 3
White
13 4
(Link) (Link) (Link) Ở 2 phần tut trước, mình đã hướng dẫn khá chi tiết cách viết một ứng dụng camera có tích hợp chức năng nhận diện khuôn mặ...
HoangPH viết 4 năm trước
13 4
White
2 0
Có nhiều cách viết blog công nghệ hơn là làm bánh hay làm tình. Những ngày này Hà Nội mưa liên miên, được cái mát giời, mình lại tức cảnh sinh tìn...
VietHQ viết 2 năm trước
2 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
56 bài viết.
900 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!