2011-07-30 [iOS][CoreAnimation][Quartz]UIView画像加工レシピ集
リッチなインターフェースにするために。
アプリを作る以上、ダウンロードしてもらったユーザには一回でも多くタップしてもらいたいですよね。
タップワーシィな UI を作るためには、現実のメタファーを反映させたコントロールを作る必要があります。
もちろん iOS SDK に付属している UIKit を用いるだけで十分な場合も多いのですが、今までにない独創的なアプリを作るためには、自分でイチから UI を作り込まなければいけない場合もあります。
しかし、 イチから作るとなると CoreAnimation や Quartz など今まであまり触ったことのないレイヤまで手を加えないといけないような気がして、腰が引けてしまったりすると思います。(自分がそうだっただけですが…)
そこで「CoreAnimation とか良く分かってないけど、ちょっとリッチな UI にしてみたい」っていう人のために、簡単な画像加工のレシピを作ってみました。
基本的にコピペで行けるようにしたつもりなので、試して下さい。
また github にViewRecipeというサンプルアプリを置いておいたので、参考にして下さい。
概要説明。
とはいいつつも、概要程度でも理解していた方が分かりやすいと思うので箇条書きで。
Quartz は Mac OS X の描画コアエンジン。
Objective-C から Quartz を操作する Framework が QuartzCore.framework 。
QuartzCore には、 CoreAnimation, CoreImage, CoreVideo の3つのコンポーネントが含まれている。
CoreAnimation から、大きく分けて4つのクラスが提供されており、その中にアニメーションクラス( CAAnimation ) とレイヤークラス( CALayer )が含まれている。
CoreAnimation は CALayer を扱ってアニメーション処理を行っている。
CALayer は UIView のプロパティに入っており、CALayer も UIView と同じような階層関係を持つことが出来る。
UIView のアニメーション関数は CoreAnimation のラッパーであり、 CoreAnimation を直接扱うのに比べてできないことがある。
こんな感じでしょうか。詳しくは以下のドキュメントを読んでみて下さい。
まずは View のセットアップ。
//setup recipeView
recipeViewImage = [UIImage imageNamed:@"jarinosuke.tron.png"];
recipeSubLayer = [CALayer layer];
recipeSubLayer.frame = CGRectMake(0, 0, recipeView.frame.size.width, recipeView.frame.size.height);
recipeSubLayer.contents = (id)recipeViewImage.CGImage;
recipeSubLayer.masksToBounds = YES;
[recipeView.layer addSublayer:recipeSubLayer];
この後の角丸だったり影を付けるために、あえてもう一枚レイヤーを作成し、その contents に画像を置いています。
これを行わずに UIView の レイヤーに直接画像を代入すると、後で角丸加工を加えた後に影を付けようとすると、陰までマスクされてしまい上手くいかないので、このようにしています。
画像を角丸にする。
- (void)makeCornerToRound { recipeView.layer.cornerRadius = 10; recipeSubLayer.cornerRadius = 10; } iOS 開発では画像の四隅を丸くしたい、と思うことは多々あると思います。 そんなときは CALayer のプロパティである cornerRadius に角丸の半径を与えてあげるだけで可能になります。
影を付ける。
- (void)addShadow { recipeView.layer.shadowOpacity = 0.4; recipeView.layer.shadowOffset = CGSizeMake(15.0, 15.0); }
影を付けるのなら、 CALayer プロパティの shadowOffset に値を代入します。デフォルトでは(0, 0)なので、少しずらしてあげることで、影が付くようになります。
押し込みを付ける。
- (void)pushView { recipeView.layer.frame = CGRectMake(recipeView.layer.frame.origin.x + 5, recipeView.layer.frame.origin.y + 5, recipeView.frame.size.width, recipeView.frame.size.height); recipeView.layer.shadowOffset = CGSizeMake(5.0, 5.0); }
UIButton では touchEvent にともない画像にエフェクトがかかり押したかどうかのフィードバックを行っています。
しかし、 UIView で単純に toucheEvent を実装しただけでは、 UIView は何のフィードバックも行いません。
ここでは一例として、画像をタップした際に画像が画面の奥にへこむような実装を行います。
内容自体は単純でレイヤーと影の位置を少しずらしているだけです。
画像を回転させる。
- (void)rotate { recipeView.transform = CGAffineTransformMakeRotation(0); [UIView animateWithDuration:0.5 animations:^{ recipeView.transform = CGAffineTransformMakeRotation(2 * M_PI * 180.0 / 360.0 -0.000001); //-0.000001 is magic number to rotate right. } completion:^(BOOL finised){ [UIView animateWithDuration:0.5 delay:0.5 options:nil animations:^{ recipeView.transform = CGAffineTransformMakeRotation(0); } completion:^(BOOL finished){ }]; }]; }
画像の回転のアニメーションも充分にユーザへのフィードバックとなり得ます。
たとえば、 Twitter for iPhone ではテーブルビューの更新のために下へフリックすると、更新テーブルセルが出てきてそこにのっている矢印アイコン下から上へ、クルっと回転します。そうすることでユーザへ、今アップデートを行っていることを視覚的に伝えています。
この例では、 UIView のアニメーションを行っています。
UIView のプロパティである transform に、アフィン変換のキー値を入れることで回転の他にも様々なアニメーションが可能になります。
レシピ
一貫性を意識しながら現実のメタファーに沿って、アニメーションや画像加工を行うことで確実にユーザ体験は向上すると思っています。
他にも画像加工に関してはたくさんの実装方法や、アイディアがあると思いますので今後も追っていきたいと思います。
購入: 25人 クリック: 291回
2011-03-29
■[Objective-C][iPad][UIKit]UISplitView, UIPopoverView Tutorial
iPad Programming
iPadアプリケーションの開発はiPhoneでのそれを応用できることも多数ありますが、変更しなくてはいけないものも多いです。
詳しくはiOS Human Interface Guidlineに書かれていますが、iPadの画面でiPhoneと同様のUIを提供するのはユーザーに対して優しくありません。
たとえばiPhoneではUITableViewを画面全体に表示しますが、それをiPadでも同じ事は良いことではありません。
画面の大きさを利用した適切なUIを提供するべきです。
iPad上での開発ではiPhone上では使えないクラスがいくつかあります。
SplitViewやPopoverViewなどがそれにあたり、これらはiPadの広い画面に適したUIを提供してくれます。
ということで、それらをiPadで有効に活用するために使い方も含めた簡単な説明を以下から始めます。
ちなみにXcode4 + iOS4.3で行っているので若干勝手が違う部分もあるかもしれませんがご理解を。
This Tutorial Flow
このチュートリアルは大きく分けて以下の三つのパートに分かれます。
PART 1.display SplitView.
PART 2.Using PopoverView when device orientation is portrait.
PART 3.Programming PopoverView.
PART 1.display SplitView.
NO XIB
テンプレートにはSplit View-based Applicationが存在しますが、全体像を把握するためにWindow-based ApplicationテンプレートからXIBを使わずに開発しましょう。
みなさんご存じかとは思いますが、僕はたまに忘れるので以下に簡単に書いておきます。
- New Project→WIndow-based Applicationを作成
- info.plistの「Main nib file base name」のカラム削除
- MainWindow.xibを削除
- main.mを以下のように変更
第四引数をアプリケーション名AppDelegateにする。
int retVal = UIApplicationMain(argc, argv, nil, @"SaveJapanAppDelegate");
- AppDelegateのdidFinishLaunchingWithOptions内でWindowを作成する。
CGRect screenBounds = [[UIScreen mainScreen] bounds]; _window = [[UIWindow alloc] initWithFrame:screenBounds];
これでXIB無しで開発する準備が整いました。
What's UISplitView?
ちょっと先に進む前にUISplitViewについてもう少し理解を深めましょう。
UISplitViewは左右にViewControllerを持つViewControllerのControllerのようなものです。
一般的な使い方としては左側にUITableVIewを配置し、そこで発生したイベントを右のViewに反映させます。
左右のViewControllerのイベント通知を行うための方法はいくつかありますが、テンプレート同様、左のViewControllerに右のViewControllerのポインタを持たせることで実装します。
Initialize SplitView
ではSplitViewを作っていきます。
左のUITableViewControllerと、右のUIViewControllerのファイルを新規作成しましょう。
ファイルを追加したら、それらのviewDidLoadメソッドを実装しましょう。
LeftViewController
まずは左側のビューファイルを実装していきましょう。
////.h file///// #import "RightViewController.h" //class variable NSArray *lyrics_; RightViewController *rightViewController; @property(nonatomic, retain) RightViewController *rightViewController;
////.m file///// //viewDidLoad [super viewDidLoad]; self.title = @"Harder Better Faster Stronger"; lyrics_ = [[NSArray alloc] initWithObjects:@"Work it", @"Make it", @"Do it", @"Makes Us", @"Harder", @"Better", @"Faster", @"Stronger", @"More than", @"Ever", @"Hour", @"Our", @"Never", @"Work Is", @"Over",nil]; self.clearsSelectionOnViewWillAppear = NO; self.contentSizeForViewInPopover = CGSizeMake(300.0, 700); //numberOfRowsInSection return [lyrics_ count]; //cellForRowAtIndexPath above return cell.backgroundColor = [UIColor blackColor]; cell.textLabel.textColor = [UIColor colorWithRed:0.25 green:0.8 blue:0.25 alpha:1.0]; cell.textLabel.text = [lyrics_ objectAtIndex:indexPath.row]; cell.textLabel.textAlignment = UITextAlignmentCenter; //didSelectRowAtIndexPath NSString *lyricSelected = [lyrics_ objectAtIndex:indexPath.row]; self.rightViewController.messageLabel.text = lyricSelected; //Developer must off the highlight cell, if needed. [tableView deselectRowAtIndexPath:indexPath animated:YES];
LeftViewControllerはUITableViewControllerです。
セルに挿入するタイトルをlyrics_に入れています。
TableViewで少し注意しなければいけないのは、セルがタップされてハイライト状態になるのですが、それを解除するための処理は開発者が行わなければいけない点です。
またRightViewControllerのポインタをクラス変数に持たせているのは上記で書いたとおり、これを介して右側のビューを操作するためです。
RIghtViewController
次は右側の主役となるビューを実装していきます。
////.h file//// //class variable @interface RightViewController : UIViewController <UISplitViewControllerDelegate>{ UILabel *messageLabel; } @property(nonatomic, assign)UILabel *messageLabel;
////.m file//// //synthesize @synthesize messageLabel; //viewDidLoad messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(self.view.bounds.size.width/2 - 150, self.view.bounds.size.height/2 - 50, 300, 100)]; messageLabel.text = @"Punk"; messageLabel.textAlignment = UITextAlignmentCenter; messageLabel.textColor = [UIColor colorWithRed:0.25 green:0.8 blue:0.25 alpha:1.0]; messageLabel.backgroundColor = [UIColor blackColor]; messageLabel.font = [UIFont systemFontOfSize:64.0f]; self.view.backgroundColor = [UIColor blackColor]; [self.view addSubview:messageLabel];
特に目立って特殊な所はありません。UILabelをプロパティとして宣言することで、左側のテーブルビューコントローラーからの操作を受け付けています。
AppDelegate
ここで二つのビューコントローラーをつなぎ合わせたスプリットビューをウィンドウにアタッチします。
////.h file//// #import "LeftViewController.h" #import "RightViewController.h" //Property Declaration @property (nonatomic, retain) IBOutlet UIWindow *window; @property (nonatomic, retain) LeftViewController *leftViewController; @property (nonatomic, retain) RightViewController *rightViewController; @property (nonatomic, retain) UISplitViewController *splitViewController;
////.m file//// //didFinishLaunchingWithOptions CGRect screenBounds = [[UIScreen mainScreen] bounds]; _window = [[UIWindow alloc] initWithFrame:screenBounds]; self.rightViewController = [[RightViewController alloc] init]; self.rightViewController.title = @"Daft Punk"; self.leftViewController = [[LeftViewController alloc] init]; //set right VC pointer to left VC. self.leftViewController.rightViewController = self.rightViewController; //set lyric's tableView into NavigationVC UINavigationController *lyricNav = [[[UINavigationController alloc] initWithRootViewController:leftViewController] autorelease]; lyricNav.navigationBar.barStyle = UIBarStyleBlack; lyricNav.navigationBar.translucent = YES; //set the LR VC to the SplitView splitViewController = [[UISplitViewController alloc] init]; self.splitViewController.viewControllers = [NSArray arrayWithObjects:lyricNav, self.rightViewController, nil]; self.splitViewController.delegate = self.rightViewController; [self.window addSubview:splitViewController.view];
左のビューコントローラーには右のビューコントローラーのポインタを代入し、それと、左のテーブルビューコントローラーはナビゲーションコントローラーに入れて、スプリットビューコントローラーを作成します。
そして、スプリットビューコントローラーのデリゲート先は上記の通り右のビューコントローラーに設定します。
これでビルドしてみましょう。
ランドスケープモードにすると左側からテーブルビューが出てきます。そして、そのセルをタップすると、右側のラベルにそのセルのタイトルが反映されますね。
PART 2.Using PopoverView when device orientation is portrait.
ランドスケープモードでは幅が広いのでSplitViewが活躍しますが、同じ幅を取ったままポートレートモードにすると左側が幅を取りすぎてしまいます。なので、スプリットビューコントローラーは傾きがポートレートモードになるのを検知すると左側のビューをしまい、代わりにポップオーバービューをひょうじするボタンを作成するビルトインAPIが提供されています。
このビルトインAPIの実装はとても簡単です。SplitViewのDelegate先である右ビューコントローラーにPopoverViewを表示・非表示させるDelegateメソッドを記述するだけです。
RightViewController
////.h file//// //Add Delegate Protocol @interface RightViewController : UIViewController <UISplitViewControllerDelegate> //class variables UIPopoverController *_popover; UIToolbar *_toolbar; //Property Declaration @property(nonatomic, assign)UIPopoverController *popover; @property(nonatomic, assign)UIToolbar *toolbar;
////.m file//// //viewDidLoad self.toolbar = [[UIToolbar alloc] init]; self.toolbar.barStyle = UIBarStyleBlack; [self.toolbar sizeToFit]; self.toolbar.translucent = YES; UIBarButtonItem *space = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil]; NSArray *buttons = [NSArray arrayWithObjects:space, nil]; [self.toolbar setItems:buttons animated:YES]; [self.view addSubview:self.toolbar]; //SplitViewDelegate //add following method - (void)splitViewController:(UISplitViewController *)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController:(UIPopoverController *)pc{ barButtonItem.title = @"lyrics"; NSMutableArray *items = [[_toolbar items] mutableCopy]; [items insertObject:barButtonItem atIndex:0]; [_toolbar setItems:items animated:YES]; [items release]; self.popover = pc; } - (void)splitViewController:(UISplitViewController *)svc willShowViewController:(UIViewController *)aViewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem{ self.popover = nil; }
新しくRightViewControllerにクラス変数としてUIToolbarとUIPopoverControllerを加えています。
そしてSplitViewのデリゲートメソッドとして二つの関数を追加しています。
willHideViewControllerは左のビューが隠れたときすなわちデバイスがポートレートモードに変化したときに実行されます。なので、ここでバーボタンをツールバーに追加し、ポッポオーバービューをクラス変数に代入しています。
willShowViewControllerは上の関数の逆で、左のビューがランドスケープモードになって現れたときに実行されます。
http://twitter.com/#!/jarinosuke/status/51868274618679296
ここで一つ分からない点があったのですが、Toolbarに何もbarButtonItemを付けない状態だと、SplitViewDelegateメソッドがPopoverViewのボタンを表示してくれませんでした。
なので、ここではボタンの配置の設定に用いる目に見えないFlexibleSpaceを加えています。
どのように対処すれば良いか、分かる方がいましたら教えて頂けると助かります。
追加するコードはこれだけです。ビルドしてみましょう。ポートレートモードにすると左上にボタンが表示され、それをタップするとポップオーバービューが表示され中にスプリットビューコントローラーの左側のビューと同じテーブルビューが表示されていると思います。このポップオーバービューの大きさはどこで決めているかというと、実は左側のテーブルビューコントローラーのviewDidLoadメソッド内の
self.contentSizeForViewInPopover = CGSizeMake(300.0, 700);
で設定が可能です。
PART3.Programming PopoverView.
チュートリアル最後のパートでは右ビューの文字の色を変えるポップオーバービューを一から実装してみます。
さきほどの容量と変わらないので難しくはないはずです。
まずはUITableViewControllerを継承したColorPickerControllerを自作クラスとして作成しましょう。
ColorPickerController
////.h file//// #import <UIKit/UIKit.h> @protocol ColorPickerDelegate - (void)colorSelected:(NSString *)color; @end @interface ColorPickerController : UITableViewController { NSMutableArray *_colors; id <ColorPickerDelegate> _delegate; } @property(nonatomic, retain)NSMutableArray *colors; @property(nonatomic, assign)id <ColorPickerDelegate> delegate; @end
////.h file//// //viewDidLoad self.clearsSelectionOnViewWillAppear = NO; self.contentSizeForViewInPopover = CGSizeMake(150.0, 140.0); self.colors = [NSMutableArray array]; [_colors addObject:@"Red"]; [_colors addObject:@"Blue"]; [_colors addObject:@"Yellow"]; //numberOfSectionsInTableView return 1; //numberOfRowsInSection return [_colors count]; //cellForRowAtIndexPath NSString *color = [_colors objectAtIndex:indexPath.row]; cell.textLabel.text = color;
TableViewControllerを自作した理由はDelegateプロトコルを用意したからです。
そうすることで、右ビューコントローラーにデリゲートさせて色の反映を有効にします。
では、右ビューコントローラーの実装をみてみましょう。
RIghtViewController
////.h file//// //class variables ColorPickerController *_colorPicker; UIPopoverController *_colorPickerPopover; UIBarButtonItem *colorButton; //Property and Method Declaration @property(nonatomic, retain) ColorPickerController *colorPicker; @property(nonatomic, retain)UIPopoverController *colorPickerPopover; - (void)setColorButtonTapped;
////.m file//// //synthesize @synthesize colorPicker = _colorPicker; @synthesize colorPickerPopover = _colorPickerPopover; //viewDidLoad colorButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonItemStylePlain target:self action:@selector(setColorButtonTapped)]; NSArray *buttons = [NSArray arrayWithObjects:space, colorButton, nil]; //####ColorPickerDelegate####// - (void)colorSelected:(NSString *)color{ if ([color compare:@"Red"] == NSOrderedSame) { messageLabel.textColor = [UIColor redColor]; }else if ([color compare:@"Blue"] == NSOrderedSame ) { messageLabel.textColor = [UIColor blueColor]; }else if ([color compare:@"Yellow"] == NSOrderedSame ) { messageLabel.textColor = [UIColor yellowColor]; } [self.colorPickerPopover dismissPopoverAnimated:YES]; } - (void)setColorButtonTapped{ if (_colorPicker == nil) { self.colorPicker = [[[ColorPickerController alloc] initWithStyle:UITableViewStylePlain] autorelease]; _colorPicker.delegate = self; self.colorPickerPopover = [[[UIPopoverController alloc] initWithContentViewController:_colorPicker] autorelease]; } [self.colorPickerPopover presentPopoverFromBarButtonItem: colorButton permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; }
新たにカラーピッカーとポップオーバービューをクラス変数に加えています。
バーボタンももう一つ加え、セレクタにカラーピッカーを生成する関数を指定します。
そしてそのsetColorButtonTapped関数の中でカラーピッカーとポップオーバービューを生成します。
実際に右ビューのラベルの色を変える行為はデリゲートメソッドであるcolorSelectedメソッドで実装しています。
Show Me the Code!
githubに上記のコードを挙げましたので、もし良かったらみて下さい。
おかしなところなどあったら、指摘して頂けると助かります。
https://github.com/jarinosuke/iPad-Tutorial
参考
http://www.raywenderlich.com/1056/ipad-for-iphone-developers-101-uipopovercontroller-tutorial
http://www.raywenderlich.com/1040/ipad-for-iphone-developers-101-uisplitview-tutorial
2011-02-11
■[iPhone][Yahoo][MapKit]Yahoo!地図SDK for iPhone、触ってみました。
Yahoo!地図 SDK for iPhone
先日公開されたYahoo!地図 SDK for iPhoneのチュートリアルを行いました。
このSDKはYMapKitとして配布されているので、それをプロジェクトに追加して使用します。
チュートリアルについての詳細は付属のドキュメントが分かりやすいので、そちらを参照しましょう。
以降Yahoo!地図 SDK for iPhoneのことを省略してYMapKitと呼びます。
このSDKを触ってみて、iOS SDK付属のMapKitとの違いなども含めて気がついた特徴を書きたいと思います。
また、ここに書いてあるYMapKitに関する感想などは個人の感想です。
個人的感想
MapKit互換がすごい
ほぼiOS SDK付属フレームワークのMapKitフレームワークと互換しています。
戸惑うことはほとんどありません。クラス名なども基本的にYが頭に付くだけです。
ただ一点気になることがあったとすれば、地図のプロパティ宣言の方法です。
以下のように、MapViewを定義します。
map = [[YMKMapView alloc] initWithFrame:CGRectMake(0, 0, 400, 400) appid:appID]; NSMutableArray* ary=[NSMutableArray array]; [ary addObject:[NSString stringWithFormat:@"off:figure"]]; [ary addObject:[NSString stringWithFormat:@"off:label"]]; [ary addObject:[NSString stringWithFormat:@"on:road"]]; [ary addObject:[NSString stringWithFormat:@"on:rail"]]; [ary addObject:[NSString stringWithFormat:@"on:station"]]; [ary addObject:[NSString stringWithFormat:@"on:water"]]; [ary addObject:[NSString stringWithFormat:@"on:pref_boundary"]]; [ary addObject:[NSString stringWithFormat:@"on:railway_name"]]; [ary addObject:[NSString stringWithFormat:@"on:station_name"]]; [ary addObject:[NSString stringWithFormat:@"off:shading"]]; [map setMapType:YMKMapTypeStyle MapStyle:@"standard" MapStyleParam:ary];
どのオブジェクトを表示するか、表示しないか、などの設定を、
[on | off] : <property_name>
で配列にappendします。順番なども関係ありません。
Web APIを基準で考えられたなのか詳しくは分かりませんが、このような設定方法だと表示非表示を変えるだけでも、文字列の置換などを行わなければならず、少し難しいなぁと感じてしまいました。
地図がオブジェクトで管理されている
触ってみて一番印象に残ったのは、これでした。
たとえばYMapKitでは地図のStyleを変更できます。
この機能はもちろんGoogle Mapを用いているMapKitでも、
enum { MKMapTypeStandard, MKMapTypeSatellite, MKMapTypeHybrid }; typedef NSUInteger MKMapType;
と、三種類ありました。
YMapKitもtypeは、StandardとSatelliteとChikaの三つですが、StandardTypeでは多くのStyleを設定することが出来ます。
これも地図がオブジェクト指向で管理されているからだと思います。
またChikaマップについてもカバーする範囲こそ狭いものの、標準MapKitには無い機能です。
そのStyleですが、以下の通りです。見本としていくつか画像として残しました。これ以外にもたくさんあります。
次にその肝心のオブジェクトについても書きます。
地図上で管理されているオブジェクトは大きく三つのツリーに分けられていて、Label(表記)、Figure(表面)、Shading(画像)になります。
全て挙げていくとキリが無いので、以下に簡単な図を載せてみました。
このような感じで管理されており、親オブジェクトをONにすれば子は全てONになり地図上に表示されます。
ちなみにLabelとShadingでは同じオブジェクト名(たとえばstore01など)があるのですが、これは表記と画像が別々で管理されているためです。
終わりに
まだまだ触り始めたばかりですが、なかなか良いと思います。
特に地図の表示部分がカスタマイズしやすいので、デベロッパーにはありがたいですね。
もう少し触ってみたいと思いました。










