Phần này tôi viết trong đợt nghỉ lễ 30/4-1/5 nhưng mà một phần là vì lười, mải đi chơi trà đá, về nhà thì lại chơi LoL nên lần nữa mãi vẫn chưa đưa lên blog được :D. Lười khổ thế đấy!
Phần 3 này tôi sẽ tiếp tục giới thiệu xử lý sự kiện cho Table kết hợp truyền dữ liệu giữa 2 ViewController dùng Segues Context, cuối bài sẽ là việc tối ưu hóa hiệu năng khi load ảnh sử dụng ImageCached của WatchKit.
Phần này chủ yếu thực hành code và là phần nối tiếp của phần 2, nên nếu bạn nào quên hoặc chưa đọc phần 2 thì tại đây: Phần 2.
1. Xử lý sự kiện cho Table
Việc cần làm khi người dùng chạm vào 1 Row trên danh sách Crisis thì sẽ chuyển đến màn hình Activate Crisis để thực hiện kích hoạt 1 sự kiện (Event) thông qua API của server. Bạn cần gửi Event_Id sang màn hình Activate Crisis để xác định xem Event(Crisis) nào sẽ được kích hoạt:
Bạn nào đã làm việc với Table Controller của UIKit thì cũng sẽ dễ dàng làm việc với Table của WatchKit vì nó rất rất đơn giản.
Do trong các phần trước khi làm việc với Table tôi có sử dụng thư viện IGTableController của Instagram nên trong phần này cũng sẽ tiếp tục sử dụng các phương thức mà thư viện này cung cấp cho đơn giản. IGTableController cung cấp cho ta phương thức delegate của Table WatchKit giống hệt trên UIKit chỉ có khác biệt là Instagram viết lại và sử dụng Runtime code – trên Objective – C hay gọi là Swizzle. Ta sẽ xử lý sự kiện trong hàm sau:
– (void)table:(WKInterfaceTable *)table didSelectRowAtIndexPath:(NSIndexPath *)indexPath.
Do WatchKit sử dụng 100% storyboard nên ta có 2 cách để di chuyển đến màn hình Activate Crisis:
- Kéo thả trên Storyboard.
- Dùng code
Chúng ta sẽ đi từ dễ đến khó trước:
– KÉO THẢ:
Chọn RowController trên Table => Giữ chuột phải / Ctrl + chuột trái rồi kéo vào màn hình Activate Crisis, chọn Modal:
Modal với Push giống hệt trên UIKit – iPhone.
– DÙNG CODE:
Bạn chọn màn hình Activate Crisis, trong Attribute Inspector bạn đặt Identifier cho Controller này. Bạn sẽ dựa vào Identifier này để xác định sẽ hiển thị màn hình nào tiếp theo:
[self presentControllerWithName:@” CRWActivateController” context:nil];
Như vậy là mỗi khi chạm vào 1 Row trên danh sách thì sẽ chuyển đến màn hình Activate Crisis theo cả 2 cách trên, điểm khác nhau là dùng code thì bạn sẽ kiểm soát được có chuyển đến màn hình đó hay không, còn dùng cách kéo thả thì mặc định là có dù bạn có muốn hay không. Việc sử dụng sao cho hợp lý là ở bạn.
Tiếp theo sẽ là truyền Event_Id sang cho màn hình Activate Crisis. Ở đây tôi sẽ lấy ra 1 object Event tương ứng với mỗi row, qua đó ta sẽ lấy được:
Do có 2 cách thực hiện chuyển màn hình khác nhau nên cách truyền dữ liệu cũng sẽ khác 1 chút, nhưng bản chất vấn đề vẫn là sử dụng Segues Context.
– KÉO THẢ:
Bạn phải override lại Table Delegate mặc định của WatchKit và truyền dữ liệu đi. Dữ liệu ở đây có thể là NSString, NSArray hay bất kì kiểu dữ liệu nào. Thường thì Dictionary sẽ hay được dùng nên tôi sẽ dùng luôn NSMutableDictionary cho hoành tráng:
ngoài ra thì còn có các hàm khác tương tự:
– DÙNG CODE:
Đơn giản hơn là bạn viết code trong hàm didSelectRowAtIndexPath và truyền Event_Id ngay trong đó:
Vậy là xong khâu truyền dữ liệu, còn nhận dữ liệu? Chúng ta sẽ nhận được dữ liệu ở màn hình Activate Crisis trong phương thức: – (void)awakeWithContext:(id)context và tất nhiên là cũng qua Segues Context:
Trong LifeCycle của WatchKit InterfaceController sẽ luôn có phương thức awakeWithContext:id ~ viewDidLoad, do đó ta có thể lấy dữ liệu tại đây. (id)context chính là context mà ở màn hình trước đó ta đã thực hiện thiết lập dữ liệu để truyền đi.
Vậy là xong, ta có được Event_Id và thực hiện việc kích hoạt. Sau khi thành công ta có thể trở lại màn hình Crisis List tuy nhiên lại có một vấn đề là Event vừa được kích hoạt vẫn còn trong danh sách. Ờ nhỉ!… phải get lại danh sách từ server và reload lại Table giống như trên Crisis iPhone. Đây là cách đơn giản nhất và chính xác nhất khi dữ liệu sẽ luôn là mới nhất từ trên server gửi về. Ta chỉ cần gọi reloadTable trong phương thức willActive ~ viewWillAppear:
=> Tuy nhiên nếu là mạng nhanh thì không vấn đề gì nhưng nếu là mạng chậm sẽ gây ức chế cho người dùng thêm nữa là chết tiền data 3G do dữ liệu lấy về có cả ảnh!
Giải pháp cũng rất đơn giản là sau khi thực hiện kích hoạt thành công thì ta xóa Event trong danh sách đi và chỉ reload lại Table, không cần request lên server nữa. Tôi chỉ thực hiện demo với phương pháp chuyển màn hình bằng code, phương pháp kéo thả các bạn thực hiện y hệt:
Ở trong hình tôi thực hiện việc truyền thêm dữ liệu bao gồm:
- void (^activateSuccessBlockForRowIndex)(NSIndexPath *rowIndex) – thực hiện xóa Event đã kích hoạt thành công ở crisisList(Table DataSource). Block cũng chính là 1 kiểu dữ liệu.
- indexPath – tham số cho block ở trên để xác định vị trí xóa Event trong NSMutableArray, nếu không có bạn sẽ phải thực hiện tìm kiếm thông qua Event_Id.
Ở màn hình Activate Crisis, sau khi thực hiện kích hoạt thành công, ta sẽ lấy successBlock ra và thực hiện 1 cách rất đơn giản:
Xong, rất đơn giản và gọn nhé.
Nếu không quen sử dụng block hoặc có vấn đề gì đó phát sinh, bạn có thể truyền đi cả 1 InterfaceController. Nó là cũng là data mà, (id)context xơi được hết. Cụ thể bạn sẽ truyền cả self – CRWCrisisListController và sau khi kích hoạt Crisis thành công bạn sẽ gọi phương thức public của chính nó:
Xóa Event trong crisisList xong thì tôi cần làm thêm 1 bước là reload lại Table trong phương thức willActive:
Câu hỏi đặt ra là tại sao không đặt phương thức reloadTable ngay trong successBlock đi cho gọn, còn phải bôi sang willActive làm chi? Lúc đầu tôi cũng làm vậy mà mất khá nhiều thời gian loay hoay mà nó không chạy. Sau đoán là không thể thay đổi được các thuộc tính giao diện của Controller khi mà nó đang không được hiển thị trên màn hình cho dù tôi đã cho vào mainQueue của GCD.
Thêm một cái oái oăm nữa là nếu bạn kéo 1 view thành IBOutlet và đặt là Instance Variable thì sẽ không thể thay đổi các giá trị thuộc tính được: setText, setTitle, setAlpha… Có thể là lỗi của SDK hoặc cơ chế nó vốn vậy và mình chưa tìm hiểu kĩ, chưa nắm bắt được vấn đề. Xin cho phép tôi nợ câu trả lời chính xác đến phần tiếp theo(nếu có :D)
Đây là clip thành quả:
2. Image Cache
Nếu như danh sách Crisis của bạn rất dài và bao gồm cả ảnh nữa thì mỗi lần tải sẽ mất rất nhiều thời gian + tiền bạc đặc biệt là khi mạng chậm. Người dùng sẽ có những trải nghiệm tồi tệ dẫn đến app của chúng ta bị hắt hủi. Do đó cần phải tìm cách cải thiện hiệu năng của ứng dụng mỗi khi phát triển.
Trên Crisis iPhone dùng SDImageCache để tăng tốc độ tải ảnh về, nên mình cũng thử cache lại ảnh xem sao? Cơ mà cái WatchKit bé tý tẹo teo dùng hẳn con SDImageCache to tướng thì có cần thiết không? Mày mò tài liệu của Apple thì phát hiện ra họ cũng đã có các phương thức cho việc cache ảnh trên Apple Watch. Chẳng biết hiệu năng ra sao nhưng nhu cầu thì đơn giản mà hàng nhà trồng ra đã được kiểm dịch rồi nên đỡ lo vấn đề tương thích, 3rd party license, blah blah…. Nói chung là hàng Apple lúc nào chả ngon…
Tôi thực hiện các bước đơn giản sau để cache lại ảnh:
Kiểm tra xem ảnh đã được cache lại hay chưa = > Load ảnh từ server và cache lại => set ảnh?
Flowchart: Comming soon
Đây là các phương thức tôi tạo ra để làm việc với WatchKit Image Cache:
Đây là cách sử dụng thực tế:
Chạy thử xem bạn sẽ thấy tốc độ được cải thiện khá nhiều :D. Viết đoạn này tôi thêm có thêm 1 lần review code và sửa lại khối lỗi, đặc biệt là lỗi Infinite Loop :D.
Bàn về hiệu năng tôi chợt nhớ ra là mình cần resize ảnh cho phù hợp với màn hình Apple Watch cũng như giảm thiểu độ trễ khi truyền tải ảnh từ iPhone sang. Cơ mà đi chơi đã, các bạn tự viết phần còn lại đi
Bài này viết chắc chắn còn nhiều chỗ thiếu xót, chưa chính xác rất mong nhận được sự đóng góp của các bạn, không nhất thiết phải về Objective-C, WatchKit, iOS mà có thể về thuật toán, Coding Style, Coding Standard… Bạn nào muốn xin source code tham khảo vui lòng liên hệ :D.
Hiện tại thì con Crisis app cho Apple Watch đang ở cuối giai đoạn IT trên device rồi nên nếu cảm thấy cần thiết tôi sẽ viết thêm 1 bài ngắn về Glance, Notification còn không thì đến đây là hết rồi.
Bài tiếp theo của tôi sẽ nói về Continuous Integration Test + UIAutomation Test, 1 món mà tôi rất tâm đắc và mong muốn được áp dụng rộng dãi cho các dự án mobile của công ty. Ngoài ra nếu hứng lên thì sẽ làm 1 bài giới thiệu về con iPhone 5 Jailbreak của tôi:D.