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 秒鐘才跑出來,或許還有一些技術可以嘗試看看。

2 則留言:

  1. 請問一下除了你KEY的程式碼以外還要再加什麼嗎??
    因為KEY完程式碼之後我只有出現格子,沒有出現圖片跟台灣正妹的字
    謝謝

    版主回覆:(08/07/2012 01:32:52 AM)


    你可以看看 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 這個 function, 裡頭就是在更新格子裡的圖片跟文字

    當然也有可能從 Flickr 取得的資料有問題或是他的結構有更新, 你在把 dataSource 印出來看看比對, 例如查詢完資料後(viewDidLoad裡), 使用 NSLog( @"%@" , dataSource )

    回覆刪除
  2. 剛剛TRY了一下
    發現程式根本就不會進入- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 這個 function裡
    所以可以請問一下這個function在哪裡呼叫的

    版主回覆:(08/31/2010 03:46:36 AM)


    這個是程式架構上自動會呼叫的, 不然你可以試試自己建立一個 UITableViewController, 再把一些藍色標記的填補進去試試囉

    回覆刪除