12/08/2018, 16:31

[Swift] Tối ưu việc chỉnh sửa UI bằng cách tự tạo một API

Nếu bạn không hình dung và nắm bắt được giao diện trong ứng dụng của bạn như ( font chữ, màu nền, kích cỡ khung viền..) thì việc chỉnh sửa chúng quả là một điều khó chịu. Tin tôi đi, tôi nói điều này từ chính những kinh nghiệm mà mình từng trải qua. Do vậy, tôi đã nghĩ về một kiểu API chung cho tất ...

Nếu bạn không hình dung và nắm bắt được giao diện trong ứng dụng của bạn như ( font chữ, màu nền, kích cỡ khung viền..) thì việc chỉnh sửa chúng quả là một điều khó chịu. Tin tôi đi, tôi nói điều này từ chính những kinh nghiệm mà mình từng trải qua. Do vậy, tôi đã nghĩ về một kiểu API chung cho tất cả các đối tượng UIView.

body {background-color: powderblue;}
h1   {color: blue;}
p    {color: red;}

Trên nền tảng web chúng ta có CSS, một khuôn mẫu chuẩn cho thiết kế giao diện. Nó cho phép bạn định nghĩa ra các class view và áp dụng một mẫu thiết kế cho nhiều view và các subclass của chúng. Công cụ cần thỏa mãn những thứ sau:

  • Dễ khởi tạo, thay đổi và maintain.
  • Tất cả đều được khai báo riêng biệt, và không động chạm gì đến code của project.
  • Một style có thể thừa kế, override từ style khác, không có code lặp lại. Đầu tiên là chúng ta đóng gói các style lại thành một struct, chứa những properties của UIView cần cho việc thiết kế giao diện:
struct UIViewStyle {
  let backgroundColor: UIColor
  let cornerRadius: CGFloat
}

Dễ nhận là: chúng ta có rất nhiều những property dạng như vậy. Ví dụ như với UILabel, chúng ta phải khởi tạo một struct mới , và một function mới để apply vào đối tượng label đó. Như vậy sẽ rất mất công và khiến code cồng kềnh hơn.

Nó cũng rất khó để mở rộng. Nếu tôi muốn thiết kế ở một property mới, tôi phải thêm property đó vào tất cả struct. Việc này vi phạm vào quy tắc mở/đóng.

Khó khăn cuối cùng đó là không dễ để nhiều style tự động kết hợp lại với nhau một cách hoàn chỉnh. Chúng ta phải tạo nhiều struct, sau đó lại gán property của những struct đó vào thành một struct mới, rất phức tạp.

Do vậy tôi bắt đầu nghĩ về vấn đề một cách cơ bản hơn. Công việc của style là gì? Đi tới một subclass của UIView và thay đổi một số thuộc tính ở đó. Nói theo cách khác là thao tác làm ảnh hưởng đến UIView. Đến đây thì chắc có nhiều bạn đã liên tưởng đến sự tương đồng với function.

typealias UIViewStyle<T: UIView> = (T)-> Void

"Style" không khác gì một function áp dụng vào một đối tượng UIView. Ví dụ nếu chúng ta muốn thay đổi kich cỡ chữ của UILabel:

let smallLabelStyle: UIViewStyle<UILabel> = { label in
  label.font = label.font.withSize(12)
}

Với cách này, chúng ta không phải tạo property cho từng subclass của UIView. Function này sẽ nhận đúng kiểu mà chúng ta cần, các property sẽ sẵn sàng thay đổi.

Điểm hay của việc sử dụng một function này đó là: một style có thể thừa kế từ những style khác một cách dễ dàng bằng việc gọi một function.

let smallLabelStyle: UIViewStyle<UILabel> = { label in
  label.font = label.font.withSize(12)
}
let lightLabelStyle: UIViewStyle<UILabel> = { label in
  label.textColor = .lightGray
}
let captionLabelStyle: UIViewStyle<UILabel> = { label in
  smallLabelStyle(label)
  lightLabelStyle(label)
}

Để API của chúng ta dễ sử dụng hơn, chúng ta sẽ thêm một struct UIViewStyle chứa tất cả những function chỉnh sửa style.

struct UIViewStyle<T: UIView> {
  
  let styling: (T)-> Void
}

Style của chúng ta nhìn sẽ khác một chút, nhưng về bản chất là giống nhau.

let smallLabelStyle: UIViewStyle<UILabel> = UIViewStyle { label in
  label.font = label.font.withSize(12)
}
 
let lightLabelStyle: UIViewStyle<UILabel> = UIViewStyle { label in
  label.textColor = .lightGray
}
 
let captionLabelStyle: UIViewStyle<UILabel> = UIViewStyle { label in
  smallLabelStyle.styling(label)
  lightLabelStyle.styling(label)
}

Chỉ cần làm hai thay đổi nhỏ như sau:

Đầu tiên tạo ra một instance của UIViewStyle, và truyền vào một closure như một parameter trong hàm UIViewStyle.init. Sau đó, tại caption style, thay vì gọi style như một function, chúng ta gọi tới biến styling ở trong UIViewStyle instance.

Lúc này chúng ta có thể khai báo một compose function chưa một tập hợp các parameter( variadic parameter) và gọi chúng trong trường hợp success, trả về một UIViewStyle mới .

struct UIViewStyle<T: UIView> {
 
  let styling: (T)-> Void
 
  static func compose(_ styles: UIViewStyle<T>...)-> UIViewStyle<T> {
  
    return UIViewStyle { view in
      for style in styles {
        style.styling(view)
      }
    }
  }
}

Có thể gọi đây là một factory method. Nó sẽ làm cho việc khai báo các kiểu style khác nhau trở nên sáng sủa và tường minh hơn.

let captionLabelStyle: UIViewStyle<UILabel> = .compose(smallLabelStyle, lightLabelStyle)

Bên cạnh đó, chúng ta cũng thêm vào một apply function thuộc kiểu UIView và gọi những phương thức styling trong UIView

struct UIViewStyle<T: UIView> {
 
  //...
 
  func apply(to view: T) {
    styling(view)
  }
}

Bây giờ chúng ta đã có một cách khai báo mẫu thiết kế gọn gàng, an toàn và tối ưu trong ứng dụng của chúng ta. Và cũng rất dễ dàng để áp dụng những style này vào trong các item của chúng ta tại UIViewController hoặc UIView.

class ViewController: UIViewController {
    
    let captionLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        captionLabelStyle.apply(to: captionLabel)
    }
    
}

Nguồn: http://marinbenc.com/composable-typesafe-uiview-styling-with-swift-functions

0