2010年4月23日 星期五

iOS 開發教學 - 使用 JSON 處理 Web Service 資料,以 Flickr API 為例

flickr api on iphone

這筆記來自於 CS 193P iPhone Application Development 2010 Winter 課程 - 9. Data in Your iPhone App (February 2, 2010) ,另外,關於申請使用 Flickr API 的部份,可以參考 [PHP] 申請使用 Flickr API 教學筆記 - 以 flickr.photos.search 為例 ,另外,此例還會用到 UITableView ,可參考 iPhone 開發教學 - 使用 UITableView & UITableViewController 提供表單服務

越來越多網路服務提供 API 讓其他平台可以更簡單地連結到,並且常常使用 JSON 的資料格式來提供多元且方便的 UI 應用,在此,挑選 Flickr API 當作範例,透過對 Flickr 搜尋特定 Tag 後,把找到的圖片資訊利用 UITableView 顯示出來!

此篇目的:


  • 使用 JSON 的方式

  • 使用 NSDictionary 的 Key-Value Pair 來製作 REQUEST URL(如 PHP - http_build_query

流程:


  1. 下載 JSON (json-framework)

    • This framework implements a strict JSON parser and generator in Objective-C.

  2. [Xcode]->[Create a new Xcode project]->[iPhone OS]->[Application]->[Window-based Application]-> 此例以 MyJSONTest 為例

    • [Xcode]->[File]->[Cocoa Touch Class]->[UIViewController subclass] (勾選 UITableViewController subclass) -> 此例以 MyTableList 為例

  3. 將下載回來的 JSON 打開,並把其中一個目錄 JSON 移到 Project 適當的位置

    • 可以一開始先用 #import "JSON.h" 並編譯看看,若沒問題代表可以找到
      add json framework

程式碼:

MyJSONTestAppDelegate.h

#import <UIKit/UIKit.h>
#import "MyTableList.h"

@interface MyJSONTestAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    MyTableList *myList;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;

@end

MyJSONTestAppDelegate.m

#import "MyJSONTestAppDelegate.h"

@implementation MyJSONTestAppDelegate

@synthesize window;

- (void)applicationDidFinishLaunching:(UIApplication *)application {  
    myList = [[MyTableList alloc] init];
  
    [window addSubview:myList.view];
    // Override point for customization after application launch
    [window makeKeyAndVisible];
}

- (void)dealloc {
    [myList release];
    [window release];
    [super dealloc];
}

@end

MyTableList.h

#import <UIKit/UIKit.h>
#define FLICKR_API_KEY @"YOUR_API_KEY"

@interface MyTableList : UITableViewController {
    NSMutableArray *dataSource;
}

@end

MyTableList.m

#import "MyTableList.h"
#import "JSON.h"

@implementation MyTableList

- (NSString*)http_build_query:(NSString *)targetUrl options:(NSMutableDictionary *) args {
    NSMutableString * url = [[NSMutableString alloc] initWithString:targetUrl];
    NSStringEncoding encoding = NSUTF8StringEncoding;
    BOOL first_in = YES;
    for ( id key in [args allKeys] ) {
        if (first_in) {
            first_in = NO;
            [url appendFormat:@"%@=%@",
                [[NSString stringWithFormat:@"%@", key] stringByAddingPercentEscapesUsingEncoding:encoding],
                [[NSString stringWithFormat:@"%@",[args objectForKey:key]] stringByAddingPercentEscapesUsingEncoding:encoding]
            ];
        } else {
            [url appendFormat:@"&%@=%@",
                [[NSString stringWithFormat:@"%@", key] stringByAddingPercentEscapesUsingEncoding:encoding],
                [[NSString stringWithFormat:@"%@",[args objectForKey:key]] stringByAddingPercentEscapesUsingEncoding:encoding]
            ];
        }
    }
    return [url autorelease];
}


- (void)viewDidLoad {
    [super viewDidLoad];

    dataSource = [[NSMutableArray alloc] init];

    NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
    [data setValue:@"flickr.photos.search" forKey:@"method"];
    [data setValue:@"json" forKey:@"format"];
    [data setValue:@"1" forKey:@"nojsoncallback"];
    [data setValue:FLICKR_API_KEY forKey:@"api_key"];
    [data setValue:@"10" forKey:@"per_page"];
    [data setValue:@"台灣 正妹" forKey:@"tags"];
  
    NSURL *target = [[NSURL alloc] initWithString:[self http_build_query:@"http://api.flickr.com/services/rest/?" options:data]];
    //NSLog( [NSString stringWithFormat:@"%@" , target] );
  
    NSString *jsonResult = [NSString stringWithContentsOfURL:target encoding:NSUTF8StringEncoding error:nil];
    //NSLog( jsonResult  );
  
    //SBJSON *parser = [[SBJSON alloc] init];
    //NSDictionary *formatedData = [parser objectWithString:result error:nil];
  
    NSDictionary *result = [jsonResult JSONValue];
    //NSLog( [NSString stringWithFormat:@"JSONParsing:%d", [result count] ]);
    //NSLog( [NSString stringWithFormat:@"Photos:%d", [[result objectForKey:@"photos"] count] ]);
    //NSLog( [NSString stringWithFormat:@"PhotosArray:%d", [[[result objectForKey:@"photos"] valueForKeyPath:@"photo"] count]]);
  
    // http://www.flickr.com/services/api/misc.urls.html
    id obj;
    for( NSDictionary *photo in [[result objectForKey:@"photos"] valueForKeyPath:@"photo"] ) {
        //NSLog( [NSString stringWithFormat:@"Title:%@", [photo objectForKey:@"title"] ] );
        //NSLog( [NSString stringWithFormat:@"URL:%@", [NSString stringWithFormat:@"http://farm%@.static.flickr.com/%@/%@_%@_s.jpg", [photo objectForKey:@"farm"] , [photo objectForKey:@"server"] , [photo objectForKey:@"id"] , [photo objectForKey:@"secret"]] ] );
      
        [dataSource addObject:
            [[NSDictionary alloc] initWithObjectsAndKeys:
                ( ( (obj = [photo objectForKey:@"title"]) && [obj length] > 0 ) ? obj : @"Untitled" ) ,
                @"title" ,
                [NSString stringWithFormat:@"http://farm%@.static.flickr.com/%@/%@_%@_s.jpg", [photo objectForKey:@"farm"] , [photo objectForKey:@"server"] , [photo objectForKey:@"id"] , [photo objectForKey:@"secret"]],
                @"url" ,
                nil
            ]
        ];
    }
  
    [target release];
    [data release];

  
    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem;
}

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
  
    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}


#pragma mark Table view methods

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}


// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    //NSLog( [NSString stringWithFormat:@"Cnt:%d" , [dataSource count]] );
    return [dataSource count];

}


// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  
    static NSString *CellIdentifier = @"Cell";
  
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }

    // Set up the cell...
    cell.textLabel.text = [[dataSource objectAtIndex:indexPath.row] objectForKey:@"title"];
    //NSLog( [NSString stringWithFormat:@"%@", [[dataSource objectAtIndex:indexPath.row] objectForKey:@"title"]] );

    cell.imageView.image = [UIImage imageWithData:
                                [NSData dataWithContentsOfURL:
                                    [NSURL URLWithString:
                                        [[dataSource objectAtIndex:indexPath.row] objectForKey:@"url"]
                                    ]
                                ]
                            ];


    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    // Navigation logic may go here. Create and push another view controller.
    // AnotherViewController *anotherViewController = [[AnotherViewController alloc] initWithNibName:@"AnotherView" bundle:nil];
    // [self.navigationController pushViewController:anotherViewController];
    // [anotherViewController release];
}

- (void)dealloc {
    [dataSource release];
    [super dealloc];
}

@end

在此實作一個 http_build_query 來使用,目前流程上可以取得 Flickr API 回傳的訊息,並且在組出照片出來,但是,我在模擬器上跑的結果還滿慢的,此例利用 "台灣正妹" 找 10 張照片來呈現,若只印照片名稱不慢,但加上讀出縮圖部分,花 5 秒鐘才跑出來,或許還有一些技術可以嘗試看看。

2010年4月22日 星期四

[PHP] 申請使用 Flickr API 教學筆記 - 以 flickr.photos.search 為例

The App Garden on Flickr - http://www.flickr.com/services/


想要使用 Flickr API 來玩一些有趣的東西,那必須先到官網申請一組 KEY 來使用。



  1. 連到 The App Garden on Flickr - http://www.flickr.com/services/

  2. 點選 [Create an App] -> [Get your API Key] -> [Request an API Key] ,在此挑選 [Non-Commercial] ,接著輸入你想要的應用程式的名字跟簡介。

  3. 最後,就會得到一組序號,分別是 Key 和 Secret


接著寫簡單的 PHP 程式作為 Demo 方式:



程式碼(在此使用 REST Request
Format
與 JSON 格式):


<?php

$target_url = 'http://api.flickr.com/services/rest/?';
$data = array(
        'method' => 'flickr.photos.search' ,
        'format' => 'json' ,
        'nojsoncallback' => 1 ,
        'api_key' => 'YOUR_API_KEY' ,
        'per_page' => 3 ,
        'tags' => 'hello world'
);      

$target_url .= http_build_query( $data );

$ch = curl_init();
curl_setopt( $ch , CURLOPT_URL , $target_url );
curl_setopt( $ch , CURLOPT_RETURNTRANSFER , true );
$d = curl_exec( $ch );
curl_close( $ch );

$d = json_decode( $d );
print_r( $d );
exit;
?>


輸出:


stdClass Object
(
    [photos] => stdClass Object
        (
            [page] => 1
            [pages] => 391
            [perpage] => 3
            [total] => 1171
            [photo] => Array
                (
                    [0] => stdClass Object
                        (
                            [id] => 4539740346
                            [owner] => 21229296@N03
                            [secret] => 3b10921450
                            [server] => 4040
                            [farm] => 5
                            [title] => ~ Berry One ~
                            [ispublic] => 1
                            [isfriend] => 0
                            [isfamily] => 0
                        )

                    [1] => stdClass Object
                        (
                            [id] => 4539723084
                            [owner] => 21229296@N03
                            [secret] => d5fe05dba2
                            [server] => 2698
                            [farm] => 3
                            [title] => ||  Narcissistic Paradox - Single Flower  ||
                            [ispublic] => 1
                            [isfriend] => 0
                            [isfamily] => 0
                        )

                    [2] => stdClass Object
                        (
                            [id] => 4538993631
                            [owner] => 21229296@N03
                            [secret] => afe8fd4a2a
                            [server] => 2793
                            [farm] => 3
                            [title] => Snowzuki X90 #3
                            [ispublic] => 1
                            [isfriend] => 0
                            [isfamily] => 0
                        )

                )

        )

    [stat] => ok
)


另外,也可以直接用以下方便的 framework




以 phpFlickr 為例:


程式碼:


<?php

require_once( "phpFlickr/phpFlickr.php" );

$o = new phpFlickr( 'YOUR_API_KEY' );
$d = $o->photos_search( array(
                                'tags' => 'hello world' ,
                                'per_page' => 3
                        )   
        );  
print_r( $d );
?>


輸出:


Array
(
    [page] => 1
    [pages] => 391
    [perpage] => 3
    [total] => 1171
    [photo] => Array
        (
            [0] => Array
                (
                    [id] => 4539740346
                    [owner] => 21229296@N03
                    [secret] => 3b10921450
                    [server] => 4040
                    [farm] => 5
                    [title] => ~ Berry One ~
                    [ispublic] => 1
                    [isfriend] => 0
                    [isfamily] => 0
                )

            [1] => Array
                (
                    [id] => 4539723084
                    [owner] => 21229296@N03
                    [secret] => d5fe05dba2
                    [server] => 2698
                    [farm] => 3
                    [title] => ||  Narcissistic Paradox - Single Flower  ||
                    [ispublic] => 1
                    [isfriend] => 0
                    [isfamily] => 0
                )

            [2] => Array
                (
                    [id] => 4538993631
                    [owner] => 21229296@N03
                    [secret] => afe8fd4a2a
                    [server] => 2793
                    [farm] => 3
                    [title] => Snowzuki X90 #3
                    [ispublic] => 1
                    [isfriend] => 0
                    [isfamily] => 0
                )

        )

)


最後,則是要組出圖片的 static url ,請參考相片來源 http://www.flickr.com/services/api/misc.urls.html



  • http://farm{farm-id}.static.flickr.com/{server-id}/{id}_{secret}.jpg

  • http://farm{farm-id}.static.flickr.com/{server-id}/{id}_{secret}_[mstb].jpg

  • http://farm{farm-id}.static.flickr.com/{server-id}/{id}_{o-secret}_o.(jpg|gif|png)


2010年4月19日 星期一

不知覺地過了半年

公園宿舍人工池


沒想到工作已超過半年了!日子過得真快。


最近的工作內容中,有一個案子跟文化有點相關,讓我覺得能夠把自身的技術或學習的事物,跟當地文化結合應用,真的很讚。記得大學時,總是在全校臨時停電時,跟室友就在寢室床上聊天,一開始是一聲聲的髒話,後來是不間斷的拖鞋聲,大家齊步去清夜買東西吧!偶後在久一點點,開始聽到吉他聲。我喜歡,也佩服那些不需要電腦的情境下,還可以豐富生活的方式。


最近工作有感 OOP 的特性,封裝、繼承、多型和資訊隱藏,說穿了根本就是人生的體悟啊!以 Java 來說,當你進入一個單位時,若有完善的訓練時,那就可以"繼承"到單位的基本能力,倘若不行,那只好用"實作"來實現,接著,漸漸地與人互動多了,開始要把一些技能做成多型,見招拆招啊!畢竟本質上是一樣的東西,至於"封裝"呢?算是瞭解單位生態後,那就要用合適的方式跟大家互動,才不至於溝通不良,最後最為重要是資訊隱藏,有些訊息,不再像小娃般地一五一十地回報出來,因為太多的資訊也會使得組織裡的人方向不明,而面對客戶時,只需適時提供恰當的支援,而不是一股腦兒地全部都把訊息丟給對方,以免資訊過多反而變成雜訊了,這我稱作資訊隱藏。


也難怪, OOP 是人設計出來的,其實便會帶有人性了嘛


秒速5センチメートル One More Time, One More Chance








因緣際會,讓我看到這個 秒速5センチメートル ,雖然圖片中不是夏天的情景,卻讓我感到很有年輕的活力氣息?後來去找找這部動畫的資訊 - 新海誠,也找到了這首歌 "One More Time, One More Chance",細細體會歌詞中的意境,恰恰好跟現在的心境可以結合,但不是愛情類的啦,只是最近因為工作內容不是自己習慣的,而有一點點倦怠感。在淡淡的心境中,不停地繞著耳邊轉著,像似告訴自己有多少東西該珍惜、接觸以及學習,還有,活著真好!哈,完全跟歌詞沒啥關係,大概又是另一種人生體悟吧。


歌詞:


これ以上何を失えば 心は許されるの
究竟還要再失去什麼 我的心才會得到寬恕
どれ程の痛みならば もういちど君に會える
到底要痛到什麼程度 才能夠再次見到妳
One more time 季節よ うつろわないで
One more time 季節啊 希望你別轉變
One more time ふざけあった 時間よ
One more time 與妳嬉鬧的時光啊

くいちがう時はいつも 僕が先に折れたね
發生爭執的時候 每次都是我先讓步
わがままな性格が なおさら愛しくさせた
這種任性的個性 卻更加地讓人憐愛
One more chance 記憶に足を取られて
One more chance 被記憶絆住
One more chance 次の場所を選べない
One more chance 無法選擇下一個地方

いつでも搜しているよ どっかに君の姿を
無論何時都在尋找 希望能在某處找到妳
向いのホーム 路地裡の窓
對面的月台 小巷的窗戶裡
こんなとこにいるはずもないのに
明明知道妳不可能會在這裡
願いがもしも葉うなら 今すぐ君のもとへ
如果願望能夠實現 我希望馬上到妳身邊
できないことは もうなにもない
如今沒有我辦不到的事
すべてかけて抱きしめてみせるよ
我會賭上一切緊緊擁抱妳

寂しさ紛らすだけなら
如果只是為了排遣寂寞
誰でもいいはずなのに
應該不管是誰都無所謂
星が落ちそうな夜だから
但是在星辰要落下的夜晚
自分をいつわれない
我無法對自己說謊
One more time 季節よ うつろわないで
One more time 季節啊 希望你別轉變
One more time ふざけあった時間よ
One more time 與妳嬉鬧的時光啊

いつでも搜しているよ どっかに君の姿を
無論何時都在尋找 希望能在某處找到妳
交差點でも 夢の中でも
就算在路口 在算在夢中
こんなとこにいるはずもないのに
明知道妳不可能會在這裡
奇蹟がもしも起こるなら 今すぐ君に見せたい
如果奇蹟會發生的話 希望馬上能讓妳看到
新しい朝 これからの僕
全新的早晨 從今以後的我
言えなかった「好き」という言葉も
還有過去說不出口的「喜歡妳」

夏の想い出がまわる ふいに消えた鼓動
夏日的回憶在腦中盤旋 突然消失的悸動

いつでも搜しているよ どっかに君の姿を
無論何時都在尋找 希望能在某處找到妳
明け方の街 桜木町で
在黎明的街頭 櫻木町
こんなとこに來るはずもないのに
明明知道妳不可能會來這裡
願いがもしも葉うなら 今すぐ君のもとへ
如果願望能夠實現 我希望馬上到妳身邊
できないことはもう何もない
如今沒有我辦不到的事
すべてかけて抱きしめてみせるよ
我會賭上一切緊緊擁抱妳

いつでも搜しているよ
無論何時都在尋找
どっかに君の破片を
希望在某處找到妳的線索
旅先の店 新聞の隅
旅途上的小店 新聞的角落
こんなとこにあるはずもないのに
明明知道妳根本不可能會出現
奇蹟がもしも起こるなら 今すぐ君に見せたい
如果奇蹟會發生的話 希望馬上能讓妳看到
新しい朝 これからの僕
全新的早晨 從今以後的我
言えなかった「好き」という言葉も
還有過去說不出口的「喜歡妳」

いつでも搜してしまう どっかに君の笑顔を
無論何時都在尋找 希望在某處找到妳的笑容
急行待ちの 踏切あたり
在等待快車通過的 平交道
こんなとこにいるはずもないのに
明知道妳不可能會在這裡
命が繰り返すならば 何度も君のもとへ
如果生命能夠重來 無論幾次我都要到妳身邊
欲しいものなど もう何もない
現在我已經 別無所求
君のほかに大切なものなど
除了妳以外我什麼都不想要


2010年4月18日 星期日

Mac mini

Mac Mini


週日跟大學同學到飛翔的魚聚餐,聚餐前就是去敗了這個東西。扣除筆電外,這個算是第一次為了正版軟體使用而購買一台相關性的主機。雖然 Mac OS 並沒有很嚴格的安裝規定,但其中有一條就是要搭配使用他們出的機器,這陣子也請教過幾位高手後,決定花下這筆錢了,畢竟已經出了社會,有些資源必須砸錢去使用了。目前唯一對 Mac OS 感到自在的,大概是系統核心是 BSD 改來的吧!


大概已經有快一個月都用 Mac OS 當作工作上的桌機使用,偶而東西弄煩時,就看看 Stanford CS 193P iPhone Application Development 當作算半複習的方式吧,也是另一種為未來的工作提前準備。至於真的下定決心買 Mac mini ,主因約四分之一的是想用來開發 iPhone 程式,但賣不賣錢是另一回事,剩下的就學學 Mac OS 和實現自己想做的事。


只是這次購買主機的壓力重上許多,些前買電腦時,幾乎都是"已賺到這台主機的設備費"才下手,這次就變得比較冒險,或者可以歸類在"買玩具"的情境下了!這次總共的花費是教育版最低階的 Mac Mini + display port 轉 D-Sub (上圖中最右上角的那個,並非內附的 miniDVI 轉 DVI)。


呼,這半年花了許多錢,等學完開車要更加利用時間!未來打算把一些 PHP Code 先轉成 C ,然後接著用 iPhone 模擬器當作測試,試試 Objective C 來處理 UI 介面結合一些應用囉!