Tôi đã viết app cho Apple Watch như thế nào(P2)?

Ở bài trước tôi đã giới thiệu tổng quan về kiến trúc và cách thức hoạt động cơ bản của Apple Watch. Sau khi ngủ một giấc đã đời, đọc lại bài thì thấy vẫn còn chỗ chưa thật sự chính xác cũng như thiếu xót. Nhưng thôi, kệ! Bạn nào có thắc mắc hay chỗ nào chưa hiểu thì có thể comment, email hoặc gặp trực tiếp để trao đổi. Mong là câu hỏi của các bạn dễ dễ 1 chút để tôi có thể trả lời được.

Tôi sẽ cố gắng chỉnh sửa lại phần 1 nếu không có ai rủ đi chơi :D.

Ở phần 2 này chúng ta sẽ tiếp tục làm quen với các thành phần giao diện cơ bản của WatchKit, các thuộc tính của WKInterfaceObjectWKInterfaceGroup

1. UI Control – WKInterfaceObject

Ngày xưa, thời còn đi học tôi rất thích học lý thuyết, nếu nó không quá dài và phức tạp, nên xin phép nói 1 chút lý thuyết trước khi đi vào thực hành cụ thể.

Trên UIKit khi làm việc với UI bạn có khá nhiều thành phần, trong đó cơ bản nhất là: UIView, UIControl và UIResponder. Bạn có thể kế thừa lại, override và tùy biến rất nhiều. Code sau khi thực hiện xong sẽ trực tiếp thay đổi UI ngay lập tức. Tuy nhiên với WatchKit thì lại là một câu chuyện khác…

WatchKit chỉ có 11 UI class và đều kế thừa từ WKInterfaceObject, WKInterfaceObject thì lại kế thừa NSObject. Như phần trước thì chúng ta đã biết Apple Watch không thực hiện chạy code(excute code) mà giao phó hoàn toàn cho iPhone. Sau khi xử lý xong thì iPhone mới gửi lại các thông tin, trạng thái của UI để Apple Watch render và hiển thị lên màn hình. Chắc bạn cũng chưa thấy rõ có gì khác biệt ở đây đúng không?

Điều đặc biệt ở đây là khi gửi thông tin cho Watch, iPhone chỉ gửi những thông tin cần thiết thay vì tất cả UI object. Ví dụ 1 Label sẽ chỉ có: {title, color, font size} cùng các setter methods cơ bản. Watch chỉ có được các thông tin ở trên để dựng UI mà thôi! Thêm một lưu ý là các thông tin trên được cung cấp cho WatchKit Extension, không phải cho Developer. Là sao? Bạn muốn lấy Frame? Không có! Lấy title hiện tại của label? Không có nốt! Rất nhiều cái không có!… Đúng ra là bạn chẳng lấy được thuộc tính nào cả.

Vậy thì làm đc cái gì? Bạn được cung cấp 1 số lượng hạn chế setter methods để thay đổi các thuộc tính của UI. Trong đó cơ bản nhất là:

– (void)setHidden:(BOOL)hidden;

– (void)setAlpha:(CGFloat)alpha; 

– (void)setWidth:(CGFloat)width;

– (void)setHeight:(CGFloat)height;

Sao éo gì ít thế? Trên là các phương thức cơ bản, còn đâu các UI Element khác nhau sẽ có thêm các method đặc thù riêng. Ví dụ WKInterfaceImage thì có mấy hàm setImageWKInterfaceButton thì có setTitle…. Tuy nhiên nói chung là rất ít, có thể đếm trên đầu ngón tay và bạn có thể dễ dàng học thuộc chúng để code mà không cần AutoComplete Suggestion của XCode.

2. Layout trên Apple Watch

Sau khi đọc phần trên hẳn 1 số bạn sẽ cảm thấy mất hứng khi API của WatchKit quá ít. Tuy nhiên nên nhớ là Apple Watch mới được giới thiệu, 24/4 này mới được bán ra, WatchKit cũng mới được phát triển nên còn hạn chế, mong rằng sau này Apple sẽ cải thiện nhược điểm cũng như đi kèm thêm nhiều tính năng hấp dẫn nữa.

Phần này sẽ thêm 1 chút hình ảnh nhằm tránh gây buồn ngủ cho người đọc, để con tim sẽ vui trở lại. Tuy nhiên, trước tiên hãy đọc thêm 1 chút lý thuyết nữa….

Hãy lưu ý thêm 1 lần nữa, Apple Watch có rất nhiều khác biệt trong đó đáng kể nhất là không có UIView hay WKView. Không có UIView thì sẽ không có Autolayout, không có Animation bằng code. Muốn có Animation hãy cắt thành vài chục – vài trăm tấm ảnh để cho nó chạy lần lượt giống như người ta làm phim ngày xưa. Thật là lạc hậu cổ lỗ sĩ.

 Không có Autolayout nhưng có giải pháp tạm thời là sử dụng WKInterfaceGroup –  layout chính trên WatchKit.

Nếu bạn nào đã làm Android thì sẽ dễ dàng nhận thấy WKInterfaceGroup sẽ khá giống LinearLayout. Trong 1 group sẽ có thể chứa được các Group hoặc các Button, Label khác. Chúng được sắp xếp theo chiều ngang hoặc chiều dọc, không có sự chồng chéo, nằm đè lên nhau. Tất cả đều theo 1 trật tự nhất định. Nhìn hình sau bạn sẽ nhận ra sự khác biệt:

Rất dễ hiểu đúng không nào! Tiếp tục nhìn qua panel Attribute Inspector:

Nhìn vào hình bạn cũng nắm được phần nào các thuộc tính rồi. Tôi xin bỏ qua các thuộc tính cơ bản mà chỉ giới thiệu những cái tôi cho là hữu ích và cần hiểu rõ.

Đầu tiên là phần Size. Không có frame, không có Autolayout thì đây là phần quan trọng để bạn có thể thiết lập kích thước các thành phần sao cho đúng ý mình. Mỗi thành phần UI khi sẽ có Width và Height với 3 cách tính kích thước khác nhau:

– Fixed: không gì đơn giản bằng, kích thước cố định không thay đổi.

– Size To Fit Content: Kích thước của view sẽ tự động co giãn để có thể hiển thị hết nội dung.

– Relative To Container:

  • Bạn thấy ở đây dưới dòng Width –  Relative To Container có 1 dòng cho ta set   <=0 number <=1. Đây chính là trọng số của InterfaceObject ~ Weight trong Linear layout của Android. Bạn set Width = 0.8 nghĩa là nó sẽ chiếm 0.8 * Container’s Width.
  • Adjustment: Tăng giảm kích thước thêm 1 số pixel  nhất định.

Nếu đã làm quen với Autolayout bạn sẽ không xa lạ với hàng loạt các dấu “+” ở trong các hình trên. Khi ấn vào đó, bạn sẽ có thể tinh chỉnh tỉ lệ kích thước cho từng màn hình cụ thể. Ở đây là 2 kích thước màn hình của Apple Watch : 38 – 42mm:

Tôi sẽ không giới thiệu kĩ mà sẽ để cho các bạn tự mày mò tìm hiểu các thuộc tính trên.

3. Ví dụ cụ thể

Hãy nhớ lại màn hình Crisis List mà phần 1 tôi đã giới thiệu.

 Hẳn bạn nào đã về tạo thử 1 project để nghịch xem Apple Watch nó là như thế nào, tạo được 1 Table  hoặc xem ảnh demo của Apple cũng sẽ tự hỏi là sao tất cả các Row đều được bo tròn 4 góc mà ảnh của thằng này lại không? Có thể có nhiều cách nhưng tôi xin giới thiệu cách đơn giản nhất nếu bạn muốn.

Hãy để ý đến thuộc tính Radius:

Nó chính là yếu tố giúp xác định mức độ bo tròn của giao diện. Tùy thuộc vào các InterfaceObject khác nhau là nó sẽ có mức bo tròn mặc định là 4-6.

Crisis List của tôi mặc định các Row sẽ được bo tròn 4 góc còn ảnh server trả về là hình vuông. Xin phép để background màu xanh cho dễ nhìn.

Nhìn không liên quan lắm, ông PM bắt  tôi làm cho nó vuông hết? Hãy set Radius = 0 cho Table Group và Main Group:

 Cơ mà màn hình Apple Watch bo tròn 4 góc, ảnh demo build-in apps cũng  bo tròn khá nhiều. Có nên cho ảnh và Row bo tròn theo. Set Radius = 6 cho MainGroup, chỉnh cho ảnh ăn sát các lề: Relative Width = 1.0, Padding/Inset = 0:

Xem lại các ảnh công bố của Apple thì icon của họ là hình tròn. Ta có thể crop các ảnh thành hình tròn bằng Photoshop hoặc bằng Cơm – Code(mệt lắm) hoặc đơn giản nhất là set Radius = Width /2 cho ImageGroup. Do ta không thể lấy được Frame nên buộc lòng bạn phải set Fixed Size là 32×32 như hình:

Đây là kết quả khi so sánh với ảnh demo của Apple:

Không tệ đúng không! Chắc khi xem trên thiết bị thật khéo còn đẹp hơn ý chứ.

 

4. setHidden vs setAlpha

Quẩy với WKInterfaceGroup như vậy chắc các bạn cũng nắm được sơ sơ rồi. Xin phép nói thêm về 2 method: setHidden và setAlpha của WKInterfaceObject.

Đâu là sự khác nhau khi setHidden = YES vs setAlpha = 0? Cả 2 InterfaceObject đều bị ẩn đi? Dĩ nhiên!.

Có một điều khác biệt ở đây là khi sử dụng setHidden = YESInterfaceObject sẽ tạm thời bị remove ra khỏi container, phần trống mà nó để lại sẽ được các thành phần khác tự động chiếm mất. Còn setAlpha = 0 thì nó vẫn ở đó, không bị remove đi và các thành phần khác vẫn giữ nguyên vị trí. Hãy để ý vị trí của button Reload:

 Tận dụng tốt setHidden và setAlpha sẽ giúp bạn cải thiện cả khả năng tùy biến giao diện trên Apple Watch  

5. Tips

Thuộc tính Lines của Label: dùng để set số dòng mà Label có thể hiện tị. set Lines = 0 thì Label đó sẽ hiển thị không giới hạn số dòng => Lines = ∞.

===============================================================

Viết đến đây cũng hơi dài rồi nên xin phép ngừng phím. Hy vọng các bạn tận dụng được các thuộc tính Size, Radius, setHidden, setAlpha và đặc biệt là WKInterfaceGroup để tạo được giao diện Apple Watch mà mình mong muốn. Bài viết hạn chế sử dụng thuật ngữ tiếng Anh do sở thích cá nhân. Nếu bạn nào thấy khó hiểu hoặc thích đọc có nhiều tiếng Anh cho quen, đỡ chuối thì cứ cho ý kiến. Tôi không hứa sẽ làm bạn hài lòng nhưng hứa sẽ chiều bạn tới bến.

= > Phần 3 tiếp theo tôi sẽ giới thiệu cách xử lý sự kiện cho Table, Button, truyền dữ liệu giữa 2 Controller sử dụng Segues Context. Tối ưu hóa tốc độ khi load ảnh.

Xin một tràng pháo tay nếu thấy hay :D.

 


Continuous Integration – The future of software development(Updated)