2014年3月28日 星期五

Tweaks Framework

Tweaks::Facebook

Tweaks 是由 Facebook 三天前(2014.3.25)所發佈的一套用於協助 prototyping 的框架。

在進行 App 的視覺設計時,最準確的方式就是:將 App 佈署到行動裝置上,並且實際在各種場合下進行各種操作。

什麼叫「各種場合」?

例如:
一套支援計步健身的 App,其色彩設計上的規劃必須考慮使用者在大太陽之下能不能看得清楚?
以銀髮族為角色的 App,其字體大小的最小尺寸、以及不會破壞排版的最大尺寸為多少?
App 內的動畫播放速度要多快不會太擔誤使用者的時間?但是又能看得清楚、得到預想的效果?

什麼叫「各種操作」?

例如:
對於單手持握裝置的使用者而言,App 內提供的按鈕位置是否容易觸發?是否操作旅行的路徑最短?

上述需求,可能會引起的因應動作是:開發者得不斷在程式碼/設定檔中調整各項參數,然後再一次進行至行動裝置的佈署及執行。顯然,有點花時間,對吧?而且,決定視覺設計的人,也可能不是程式開發者本身,這樣一來,要確定哪一個參數合適的過程中的溝通成本就不低。

Tweaks 可以讓上述情境 smooth 一些。

我從 FBTweakExample 中的程式碼來進行解釋。

一、FBTweakValue

_rootViewController.view.backgroundColor = [UIColor colorWithRed:0.9
                                                        green:0.9
                                                         blue:0.9
                                                        alpha:1.0];

上面的程式碼設定了 root 這個視圖控制器所管理的視圖的背景色,以RGB & Alpha 值進行設定。
改寫成下方 statement :

_rootViewController.view.backgroundColor

[UIColor colorWithRed:FBTweakValue(@"Window", @"Color", @"Red", 0.9, 0.0, 1.0)       
                green:FBTweakValue(@"Window", @"Color", @"Green", 0.9, 0.0, 1.0)
                 blue:FBTweakValue(@"Window", @"Color", @"Blue", 0.9, 0.0, 1.0alpha:1.0];

這裡使用了 FBTweakValue 。是什麼意思呢?我們直接來看輸出:



第一個畫面是由 Tweaks 這個框架所提供的 (FBTweakViewController)。請注意看到第一個畫面中的「Window」row 和第二畫面中的「COLOR」session,請對應於上面程式碼中 FBTweakValue 的第一、第二個參數。而第二畫面中的 Red 就是對應第三個參數。我想大家也能猜出:第四個參數就是「預設值 0.9」。第五、第六個參數是最小值和最大值。

也就是說:只要我們將程式裡的某個值 (value) 代換成 FBTweakValue 這個 macro 的話,在 FBTweakViewController 啟動後就會把前三項參數產生為第一畫面列表中的一個列 (UITableViewCell)、第二畫面中的分段(Section) 及 分段 中的列。

以此例來說,提供了浮點數型別的三個數字作為第4~6個參數,FBTweakViewController 會自動生成 step 元件(兩個按鈕,一個減一個加)。

我們可以透過點按 step 元件來改變顏色的RGB值。

要注意的一點是:設定完之後,要把 App 先從背景移除後再開啟,該值才會生效。什麼?不能直接生效嗎?可以,不過得換個 macro 來用:FBTweakBind。


二、FBTweakBind

_label.text = @“Tweaks”;

上方的程式碼中,把標籤 _label 的文字設定為 Content。我們以下方的程式進行改寫:

  FBTweakBind(_label, text, @"Content", @"Text", @"String", @"Tweaks");

第一個參數是 UI 元件,第二個參數則是該元件的屬性第三 ~ 五個參數…直接看圖吧…,第六個參數則為預設值。


可以看出來,第三~五個參數是怎麼被產生出設定用的 UI 元件的。
這裡因為用的是 FBTweakBind 這個 macro ,所以值被變更的同時,回到 App 去看的話,就能馬上看到效果的變化。


三、FBTweakInline

上面提及的兩個 macro 有個共同之處:都是和某個 UI 元件的特定屬性相關。透過 FBTweakInline 可以用觀察者模式對某個設定項的值進行偵測,並於該值改變時調整 App 內任何的設定參數(不綁特定 UI 元件)。直接看實例吧:

_flipTweak = FBTweakInline(@"Window", @"Effects", @"Upside Down", NO);
  [_flipTweak addObserver:self];

- (void)tweakDidChange:(FBTweak *)tweak
{
  if (tweak == _flipTweak) {
    _window.layer.sublayerTransform = CATransform3DMakeScale(1.0, [_flipTweak.currentValue boolValue] ? -1.0 : 1.0, 1.0);
  }
}

  

畫面上的  EFFECTS -> Upside Down 預設值為 NO,並由程式碼的 host class 作為觀察者。只要 Upside Down 的值發者變化,則 call-back method : tweakDidChange 會被呼叫,然後就可以在該 method 內指定 App 要因應做何改變了(此範例是把螢幕轉180度,變成上下顛倒)。


以上就是 FBTweakExample 中和 FBTweaks 框架有關的示範了。


結語:

1. 可以用於 App 的設計階段。假設 coder 運用地夠純熟,大概不會太影響開發速度;UI designer 可以自行透過介面做各項參數的調整而不需要一再和 coder 反複針對單一參數進行溝通。
2. FBTweakExample 的寫法是為了容易說明,不過在程式碼中嵌入 FBTweak macro 時也會對程式碼 intention 的表達有影響:閱讀流暢度會受影響。要有更好的封裝:同時也得防止封裝影響偵錯。
3. 用於 Production … 嗯…持保留態度。
4. 可以看出這套框架應該能減少 UI design coder 之間的溝通成本,這個應該很合適以不斷修正體驗設計的開發團隊。對於 UI design program develop 流程分開且 one-step 執行 (non-iteration) 的團隊則不適用。






2014年3月18日 星期二

Objective-C Block 運用

這篇文章試圖用一個很簡單的範例來介紹:在 Objective-C 中怎麼利用 Block 進行 API 的設計。這篇文章的小小價值,在於不從 Block 的語法、特性切入,而是直接以 API 設計的思考做為起點來舖陳,希望閱讀者能盡快抓到 Block 用於設計的精要。

一、起點:以「書名」來找一本書

目前 API 還沒有任何一行 code 被產生。沒關係,我們先在 API 的 client  寫下欲設計的結果。

    for (NSString *bookName in @[@"Objective-C 真簡單", @"Objective-C 學不會"]) {
        [self printReportHeader:bookName];
        [BookFinder bookWithName: bookName          //以書名找書
                   actionIfFound: foundAction       //若有找到書,就做 foundAction
                actionIfNotFound: notFoundAction];  //若找不到書,就做 notFoundAction
    }

    - (void)printReportHeader:(NSString *)bookName
    {
       NSLog(@"======================================");
        NSLog(@"==> 欲尋找的書籍: %@", bookName);
    }

上面就是我想要的結果:一個 BookFinder 以 bookWithName:actionIfFound:actionIfNotFound 這個 Class Method 查找書籍。這個 Method 以書名(bookName)作為參數傳入,若書有找到,就執行 actionIfFound;反之,沒找到書,就執行 actionIfNotFound。

注意,上面我用「執行」作為及物動詞,而 actionIfFound 和 actionNotFound 為受詞。可見這兩個 action 不是單純的 object instance;事實上,它們是 block 。

繼續,我們進一步設計 actionIfFound 和 actionNotFound 這兩個 block 的工作內容。這兩個 block 是所謂的 in-line block :

    void (^foundAction)(Book *) = ^(Book *book) {
        NSLog(@"==> 書籍售價: %@", book.price);
    };
    
    void(^notFoundAction)(BookInfo *) = ^(BookInfo *bookInfo) {
        NSLog(@"==> 找不到的原因: %@", bookInfo.reason);
    };

這裡設計:若找到書,則把 Book 的 object 作為參數傳入 block ,我們就可以印出書籍的價格;若沒找到書,block 內應該能從 BookInfo 的 object 中取得找不到的原因並印出來。

然後我預想一下我要的輸出結果:

 ======================================
 ==> 欲尋找的書籍: Objective-C 真簡單
 ==> 書籍售價: 100
 ======================================
 ==> 欲尋找的書籍: Objective-C 學不會
 ==> 找不到的原因: 絕版

嗯,差不多了,開始把其他的程式碼長出來吧!

in BookFinder.h

宣告出我們在 Client 中使用的 Class Method :

+(void) bookWithName:(NSString *)name actionIfFound:(BookFindAction)actionIfFound actionIfNotFound:(BookNotFindAction)actionIfNotFound;

上面的 BookFindAction 和 BookNotFindAction 是自訂的 Block,使用 C 語言的 typedef 進行定義:

typedef void(^BookFindAction)(Book *);
typedef void(^BookNotFindAction)(BookInfo *);

從上式可知,這兩個 Block 都沒有回傳值 (void),傳入的參數則是遵照在 Client 端程式碼怎麼用它們來設計。記得,上面兩式得寫在 @interface BookFinder : NSObject 區段的上方才行哦!


in BookFinder.m

+(void) bookWithName:(NSString *)name actionIfFound:(BookFindAction)actionIfFound actionIfNotFound:(BookNotFindAction)actionIfNotFound
{
    Book *book = [BookFinder findByName: name];
    
    if (book != nil) {
        actionIfFound(book);
    } else {
        BookInfo *bookInfo = [BookInfo new];
        bookInfo.reason = @"絕版";
        actionIfNotFound(bookInfo);
    }
}

+(Book *)findByName:(NSString *)name
{
    if ([name isEqual:@"Objective-C 真簡單"]) {
        Book *book = [Book new];
        book.name = name;
        book.price = @100;
        return book;
    }
    
    return nil;

}

這裡的實作很簡單,透過一個簡單的比對,只要書名不是「Objective-C 真簡單」,就當那本書絕版。其餘的實作邏輯也是完全遵照一開始在 Client 端程式碼的設定。

至於 Book 和 BookInfo 只是兩個設定了 Property 的簡單類別,就不列出來佔版面了。

上面所謂「Client 端的程式碼」,若是寫在 Unit Test 中當然更好,不但能更佳的突顯、收納設計意圖,也能時時驗證。

這裡展示的是如何在對於 Block 最少量所知、而且尚未有任何程式碼前進行設計,並一步步把設計實現的思維及步驟,希望對於尚未使用 Block 在自己的程式裡進行設計的人能有所助益。


2014年3月10日 星期一

Java Spark Framework


簡介


Github URL: https://github.com/perwendel/spark

Spark 是以 Ruby Sinatra 為範本的 Java 版本,可以用很少的程式碼量快速建構 Web 系統。
舉個簡單的例子:

假設使用 http://主機名稱:埠號/hello 這樣的 URL 作為一項 Web 服務,其回傳 "Hello World!",其程式碼寫法為:

get(new Route("/hello"
   @Override
   public Object handle(Request request, Response response) {
      return "Hello World!";
   }

});

意思是:建立 Http Get 能進行存取的 /hello 服務。
基本上把這段程式放在 main() 中後啟動,就會啟動 spark 內搭的 jetty 完成程式的佈署及服務開啟。

安裝

1. 建立 Maven Project
2. 加入 dependency 設定:
<dependency>
        <groupId>com.sparkjava</groupId>
        <artifactId>spark-core</artifactId>
        <version>1.1.1</version>
    </dependency>
3. 可以試著執行 mvn clean 讓相依的 Jar 檔下載至 local 。

執行運作

 mvn exec:java -Dexec.mainClass="${完整.類別名稱}"

程式範例

1. 存取網址的一部分作為參數。例:http://主機名稱:埠號/users/Sherlock,這裡希望 Sherlock 作為參數,讓程式進行處理:

get(new Route("/users/:name") {
            @Override
            public Object handle(Request request, Response response) {
                return "Selected user: " + request.params(":name");
            }

        });

意思是:視 :name 為參數,在程式裡能透過 Request 物件的 params 這個 Map 物件取得。(:name為 key, Sherlock 為 value)

2. 存取網址中的 query string:

//取得所有 query 參數           
Set<String> queryParams = request.queryParams();

//取得指定名稱的參數值
request.queryParams("參數名稱");

//取得整串 query string

request.queryString();

3. 存取各個 scope 的 attribute 值:

//getter
request.attribute("attribute_name_to_read");

//setter
request.attribute("attribute_name", "attribute_value");

//取得 Session scope 的 attribute
Session session = request.session();
session.attribute("session_attribute_name");

4. 存取 header

//getter
request.headers("header_name");

//setter (response物件)
response.header("header_name", "header_value");

5. 存取 cookie

//getter
request.cookie("cookie_name");

//setter (response物件)
response.cookie("cookie_name", "cookie_value");

//指定 cookie 有效期限
final int MAX_AGE = 1000;
response.cookie("cookie_name", "cookie_value", MAX_AGE);

6. 實作 filter

//前置過濾器
before(new Filter("/hello") {
  @Override
  public void handle(Request request, Response response) {
    //implements ...
  }
});

//後置過濾器
after(new Filter("/hello") {
 @Override
  public void handle(Request request, Response response) {
    //implements ...
 }

});

7. View 的實作

Route 有不同的子類支援不同的輸出格式:
 1) TemplateViewRoute : 輸出 HTML 範本
 2) ResponseTransformerRoute


8. 變更 listen port

直接呼叫 setPort(埠號); 



這裡對於不同的 View 沒有舉太多例子,視需求再到網路上找找資訊即可。