9/26/2011

iOSデバイスを判定するSnippet

主にiPadなのかそれ以外なのかを判定するのに使っている。


BOOL isPad = [[UIDevice currentDevice]userInterfaceIdiom] == UIUserInterfaceIdiomPad;
if (isPad) {
} else {
}

読み込むnibファイルを切り替えたり
NSString *nibName = [[UIDevice currentDevice]userInterfaceIdiom] == UIUserInterfaceIdiomPad ? @"viewController_iPad" : @"viewController_iPhone";
UIViewController *viewController = [[UIViewController alloc]initWithNibName:nibName bundle:nil];

短縮URLを作るNSString (shortURL) カテゴリ

昨日の短縮URLを展開するNSString (expandURL) カテゴリと対となるカテゴリ。
Bitly APIを使用しているので、事前にBitlyのアカウントとAPIキーを取得しておく必要がある。また、JSON形式の返り値をNSDictionaryに変換するためにSBJSONを使用している。


NSString+shortURL.h
#import <Foundation/NSString.h>

@interface NSString (shortURL)
+ (NSString *)shortURL:(NSString *)longUrl;
@end

NSString+shortURL.m
#import "NSString+shortURL.h"
#import "SBJson.h"

static NSString *kBitlyWrapperString = @"http://api.bit.ly/shorten?version=2.0.1&format=json&longUrl=";
static NSString *kBitlyFootterString = @"&login=ログイン名&apiKey=APIキー";

@implementation NSString (shortURL)

+ (NSString *)shortURL:(NSString *)longUrl{
NSString *aaa = nil;
NSString *escapeStr = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)longUrl, NULL, CFSTR(":/?#[]@!$&’()*+,;="), kCFStringEncodingUTF8);
NSString *str = [NSString stringWithFormat:@"%@%@%@",kBitlyWrapperString,escapeStr,kBitlyFootterString];
NSURL *url = [NSURL URLWithString:str];
NSData *data = [NSData dataWithContentsOfURL:url];
if (data) {
NSString *tempStr = [[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]autorelease];
NSDictionary *aDic = [tempStr JSONValue];
NSLog(@"%@",[aDic description]);
NSNumber *errorCode = [aDic objectForKey:@"errorCode"];
NSString *statusCode = [aDic objectForKey:@"statusCode"];
if ([errorCode intValue] == 0 && [statusCode isEqualToString:@"OK"]) {
NSDictionary *results = [[aDic objectForKey:@"results"]objectForKey:longUrl];
NSString *shortUrl = [results objectForKey:@"shortUrl"];
aaa = shortUrl;
} else {
aaa = longUrl;
}
} else {
aaa = longUrl;
}
return aaa;
}

@end

9/25/2011

短縮URLを展開するNSString (expandURL) カテゴリ

短縮URLを見ない日はなくなった。
Twitterなどは自前でt.coなる短縮URLを用意して悪意のある短縮URLからユーザーを守ろうという試みも行われている。また、アクセスの解析も行われているようだ。

iOSアプリケーションを開発する上では、短縮URLを展開して元URLのURLスキームを解析することにより、UIApplicationの-(BOOL)OpenURL:(NSURL *)メソッドを使用するか否かの判断が可能になる。
これを使用するとSafariやその他の外部アプリケーションが起動されるため、ユーザーが自前のアプリケーションから離れてしまうからだ。

この短縮URLを展開してくれるAPIが幾つか存在するので、その中の2つを使ってNSStringのカテゴリとしてまとめてみた。

APIが返す値はJSON形式で返ってくるものを選び、SBJSONを利用してNSDictionary形式に変換、後続の処理を行なっている。失敗した場合は展開前の短縮URLが返るようにしてある。展開できる短縮URLの種類はAPIに依存しているので注意されたし。またAPIサービスそのものの業務継続性にも注意が必要。

NSString+expandURL.h

#import <Foundation/NSString.h>

@interface NSString (expandURL)
+ (NSString *)expandURL:(NSString *)urlStr;
@end

NSString+expandURL.m

#import "NSString+expandURL.h"
#import "SBJson.h"

@implementation NSString (expandURL)

+ (NSString *)expandURL:(NSString *)urlStr{
NSString *str = nil;
NSURL *url = nil;
NSData *data = nil;
NSString *aaa = nil;
str = [NSString stringWithFormat:@"http://api.unshort.me/?r=%@&t=json",urlStr];
url = [NSURL URLWithString:str];
data = [NSData dataWithContentsOfURL:url];
if (data) {
NSString *result = [[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]autorelease];
NSDictionary *aDic = [result JSONValue];
NSString *status = [aDic objectForKey:@"success"];
if ([status isEqualToString:@"true"]){
aaa = [aDic objectForKey:@"resolvedURL"];
} else {
aaa = urlStr;
}
} else {
str = [NSString stringWithFormat:@"http://expandurl.appspot.com/expand?url=%@",urlStr];
url = [NSURL URLWithString:str];
data = [NSData dataWithContentsOfURL:url];
if (data) {
NSString *result = [[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]autorelease];
NSDictionary *aDic = [result JSONValue];
NSString *status = [aDic objectForKey:@"status"];
if ([status isEqualToString:@"OK"]){
aaa = [aDic objectForKey:@"end_url"];
} else {
aaa = urlStr;
}
} else {
aaa = urlStr;
}
}
return aaa;
}

@end

9/14/2011

UITableViewのtableFooterViewにUIButtonを追加する

参考画像

このように追加でデータを読み込むような使い方をするケースが多い。
テーブルを引っ張って更新するのが流行りだが、Footerの場合はCellの数によって
トリガーとなる基準の位置座標が変わるので、UIButtonで実装する方が簡単。

- (void)viewDidLoad{}内でUIButtonを作成してtableViewFooterに設定する。
- (void)viewDidLoad
{
    [super viewDidLoad];
readMoreButton = [UIButton buttonWithType:UIButtonTypeCustom];
[readMoreButton addTarget:self action:@selector(readMoreButtonPushed) forControlEvents:UIControlEventTouchUpInside];
[readMoreButton setTitle:@"Read more" forState:UIControlStateNormal];
[readMoreButton setTitle:@"Load" forState:UIControlStateHighlighted];
[readMoreButton setTitle:@"Loading" forState:UIControlStateDisabled];
readMoreButton.backgroundColor = LIGHT_BACKGROUND;
readMoreButton.frame = CGRectMake(0, 0, self.tableView.frame.size.width, 44);
self.tableView.tableFooterView = readMoreButton;
}

- (void)readMoreButtonPushed{
readMoreButton.enabled = NO;
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"message" message:@"Read more button pushed." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alert show];
[alert release];
readMoreButton.enabled = YES;
}


上の実装例は設定したボタンを押すとアラートがポップアップする。
実際にはデータを読み込む処理や、読み込んだ後に追加されたデータの直前まで
テーブルをスクロールさせるような処理を追加する。

9/12/2011

UITableViewCell内部にあるUIImageViewをタップした時の処理

UIImageViewを含むカスタマイズされたUITableViewCellがあったとする。
このUIImageViewをタップした時に何らかの処理を実行したい時の実装例。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = CELLIDENTIFIER;
    
    MyTableViewCell *cell = (MyTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
UIViewController* viewController = [[UIViewController alloc] initWithNibName:CellIdentifier bundle:nil];
        cell = (MyTableViewCell *)viewController.view;
        [viewController release];
    }
    
    // Configure the cell...
cell.imageURL = @"http://sample.com/sample.png";
cell.iconImageView.userInteractionEnabled = YES;
cell.iconImageView.image = [UIImage imageNamed:@"tapme.png"];
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapImage:)];
tapGesture.numberOfTapsRequired = 1;
[cell.iconImageView addGestureRecognizer:tapGesture];
[tapGesture release],tapGesture = nil;
    return cell;
}

- (void)tapImage:(id)sender{
UIImageView *imgView = (UIImageView *)[sender view];
if ([imgView.image isEqual:noImage]) {
BooksTableViewCell *cell = (BooksTableViewCell *)[[imgView superview]superview];
NSLog(@"%@",[[imgView superview]class]);
NSLog(@"%@",[[[imgView superview]superview]class]);
NSURL *imgURL = [NSURL URLWithString:cell.imageURL];
NSLog(@"%@",cell.imageURL);
UIImage *img = nil;
NSData *data = [NSData dataWithContentsOfURL:imgURL];
if (data) {
img = [UIImage imageWithData:data];
[cell.iconImageView setImage:img];
} else {
return;
}
}
}

上の実装例ではMyTableViewCellに画像URLを持たせて、UIImageViewをタップしたらsample.pngをダウンロードしてtapme.pngと差し替えるといった処理を行っている。
UITableViewは画像を持つセルを多数表示するとメモリ不足で落ちたりするので、このような処理もメモリ使用量を節減する選択肢となり得る。
実際の実装では画像URLは配列から取り出すことになるだろうし、tapme.pngも1つのインスタンスを使い回すことになるだろう。

- (void)tapImage:(id)senderメソッドについては、[sender view]は確かにここでいうタップしたiconImageViewなのだが、iconImageViewとMyTableViewCellの間にUITableViewCellContentViewが挟まっているので、それをNSLogで明示している。

UIImageを指定サイズにリサイズするSnippet


- (UIImage *)resizeImage:(UIImage *)image width:(CGFloat)width height:(CGFloat)height{
UIImage *returnImage = nil; //リサイズ後のUIImage
UIGraphicsBeginImageContext(CGSizeMake(width, height));
[image drawInRect:CGRectMake(0, 0, width, height)];
returnImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return returnImage;
}

PropertyListからNSDictionaryを作り、Keyをアルファベット順に並べ替えてNSArrayに格納する

まずはPropertyListを作る。
仮にDictionary.plistとする。
Dictionary.plist

次にDictionary.plistからNSDictionaryを作る。

NSString *path = [[NSBundle mainBundle]pathForResource:@"Dictionary" ofType:@"plist"];
NSDictionary *myDictionary = [[NSDictionary alloc]initWithContentsOfFile:path];

最後にKeyをアルファベット順に並べ替えてNSArrayに格納する。

NSCountedSet *cset = [[NSCountedSet alloc] initWithArray:[myDictionary allKeys]];
NSArray *keyArray = [[NSArray alloc]initWithArray:[[cset allObjects]sortedArrayUsingSelector:@selector(compare:)]];
[cset release];

このmyDictionaryとkeyArrayをUITableViewControllerクラスのメンバとしておけば、keyArrayをUITableViewのテーブルの表示やセルタイトルに使い、テーブルセル選択時にセルのタイトルをkeyとしてmyDictionaryのvalueが取り出せる。
NSDictionaryのKeyを配列として持ちたい、かつそれがアルファベット順であってほしい時にこれを使う。あるいはテーブルの構成要素をプロパティリストで表現したい時にも使える。

NSXMLParserとBitlyAPIを使って短縮URLを作る

BitlyShortURLというNSObjectのサブクラスを作る。

まずはBitlyShortURL.h

#import <Foundation/Foundation.h>

@interface BitlyShortURL : NSObject <NSXMLParserDelegate>
{
NSString *_baseURL; //短縮される元URL
NSString *_resultURL; //APIによって短縮されたURL
}
- (id)initWithLongURL:(NSString *)longURL; //URLを使って初期化
- (NSString *)parse; //パースして短縮URLを返す

@end

次にBitlyShortURL.m

#import "BitlyShortURL.h"

static NSString *kBitlyWrapperString = @"http://api.bit.ly/shorten?version=2.0.1&format=xml&longUrl=";
static NSString *kBitlyFootterString = @"&login=ログイン名&apiKey=APIキー";

@implementation BitlyShortURL

- (NSString *)returnLink:(NSString *)str{
return (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)str, NULL, CFSTR(":/?#[]@!$&’()*+,;="), kCFStringEncodingUTF8);
}

- (id)initWithLongURL:(NSString *)longURL{
self = [super init];
if (self) {
NSString *str = [NSString stringWithFormat:@"%@%@%@",kBitlyWrapperString,[self returnLink:longURL],kBitlyFootterString];
_baseURL = [[NSString alloc]initWithString:str];
NSLog(@"%@",_baseURL);
_resultURL = @"error";
}
return self;
}

- (NSString *)parse{
NSURL *url = [NSURL URLWithString:_baseURL];
NSXMLParser *parser = [[NSXMLParser alloc]initWithContentsOfURL:url];
[parser setDelegate:self];
[parser parse];
if (_resultURL == nil) {
_resultURL = @"error";
}
return _resultURL;
}

- (void)dealloc{
[_baseURL release],_baseURL = nil;
[_resultURL release],_resultURL = nil;
[super dealloc];
}

#pragma mark -
#pragma mark NSXMLParserDelegate Protocol

- (void)parserDidStartDocument:(NSXMLParser *)parser{
}
- (void)parserDidEndDocument:(NSXMLParser *)parser{
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName 
  namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName 
attributes:(NSDictionary *)attributeDict {
NSLog(@"<%@>",elementName);
if ([elementName isEqualToString:@"shortUrl"]) {
[_resultURL release],_resultURL = nil;
}
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { 
NSLog(@"%@",string);
if (_resultURL == nil) {
_resultURL = [[NSString alloc]initWithString:string];
}
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName 
  namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
NSLog(@"</%@>",elementName);
}

@end

パースされた要素の通り、他にも色々な情報があるがとりあえず短縮URLを手に入れたい!って時にこれから始める。
異常時には結果に@"error"が入るので、これを後続の条件分岐に使うと良い。

bitlyのReferenceはGoogleCodeでどうぞ。

iOSのバージョン判定のSnippet

iOSのバージョンによって処理を変えたい時などに使うSnippet

NSString *versions = [[UIDevice currentDevice]systemVersion];
if ([versions compare:@"5.0"] != NSOrderedAscending) {
} else {
}

上の例はiOS5.0以上なら正とする場合の実装例。

NSXMLParserの基本形

NSXMLParserのサブクラスを作成する。
ここでは
XMLParser.h
XMLParser.m
とする。

まずはXMLParser.h

#import <Foundation/Foundation.h>


@interface XMLParser : NSXMLParser <NSXMLParserDelegate> {
}
@end

次にXMLParser.m

#import "XMLParser.h"

@implementation XMLParser

#pragma mark -
#pragma mark Object lifecycle

- (id)initWithContentsOfURL:(NSURL *)url {
self = [super initWithContentsOfURL:url];
if (self) {
[self setDelegate:self];
}
return self;
}
- (id)initWithData:(NSData *)data {
self = [super initWithData:data];
if (self) {
[self setDelegate:self];
}
return self;
}

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


#pragma mark -
#pragma mark NSXMLParserDelegate Protocol

- (void)parserDidStartDocument:(NSXMLParser *)parser{
}
- (void)parserDidEndDocument:(NSXMLParser *)parser{
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName 
  namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName 
attributes:(NSDictionary *)attributeDict {
NSLog(@"<%@>",elementName);
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { 
NSLog(@"%@",string);
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName 
  namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
NSLog(@"</%@>",elementName);
}

@end

まずは対象となるXMLの要素をNSLogで出力して、どの要素が必要なのかを確認してから中身を実装していくとよい。
必要な要素に対応したクラスを作成して、それをNSMutableArrayに格納するとUITableViewなどで使い勝手が良い。

NSLog()文を除去するSnippet

${PRODUCT_NAME}-Prefix.pch内に次のコードを挿入する。
不要時には/* */で囲んでコメントアウトしてやる。
デバッグ時にも使うときと使わない時があるのでこの方法を採用している。

//NSLog()文を除去
#ifndef NSLog
#define NSLog(m, args...)
#endif

NSNotificationCenterのサンプルSnippet

登録、通知、監視、登録解除の流れをごく簡単に列挙。

登録
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:@selector(valueChanged:) name:@"ValueChanged" object:nil];

通知
NSString *value = @"Value";
NSNotification *n = [NSNotification notificationWithName:@"ValueChanged" object:value];
[[NSNotificationCenter defaultCenter]postNotification:n];

監視
※事前に登録をしておいた上でselectorのメソッドを実装する。
- (void)valueChanged:(NSNotification *)n{
NSString *str = n.object;
NSLog(@"%@",str);
}

登録削除
- (void)viewDidUnload
{
    [[NSNotificationCenter defaultCenter]removeObserver:self];
    [super viewDidUnload];
}

NSUserDefaultsのサンプルSnippet

設定画面などでお世話になるNSUserDefaultsの使い方メモ

9/09/2011

7bunnies.comのブログ運用を始めました。

ここではiOSアプリ開発の近況を中心に活動内容を不定期に書いていきたいなと思います。
筆不精なので更新が途切れることもあるかと思いますが温かい目で見守ってくださることを期待しております。