boreal-kiss.com

Flash, Flex, AIRのプログラミングメモ。最近はCocoa, Cocoa touchに傾倒中。

IconUtility ver. 0.9.3 – 画像縮小時の質感のコントロール

iPhone、iPad用にアイコンをリサイズするMacアプリケーションIconUtilityをver. 0.9.3にアップデート。画像縮小時の質感のコントロールできるようになりました。具体的にはCoreGraphicsが描画時に使用するinterpolationアルゴリズムを選択できるようにしています1。コアになっているのは以下の関数。

void CGContextSetInterpolationQuality (
   CGContextRef c,
   CGInterpolationQuality quality
);
 
enum CGInterpolationQuality {
   kCGInterpolationDefault = 0,
   kCGInterpolationNone = 1,
   kCGInterpolationLow = 2,
   kCGInterpolationMedium = 4,
   kCGInterpolationHigh = 3
};
typedef enum CGInterpolationQuality CGInterpolationQuality;

CGInterpolationQualityの具体的な使用例はResize a UIImage the right way — Trevor’s Bike Shedで紹介されているUIImage+Resize.h, UIImage+Resize.mが参考になります。

以下IconUtility ver.0.9.3で作成したアイコン例を挙げておきます。

- None (kCGInterpolationNone)を使用した場合のIcon@2x.png

- High (kCGInterpolationHigh)を使用した場合のIcon@2x.png

ソースとアプリケーション本体は以下に置いてあります。

Footnotes

  1. 以前のバージョンではkCGInterpolationDefaultのみを使用。 []

IconUtility – iPhone/iPad用のアイコンイメージ作成アプリケーション

画像をドロップするとiPhone/iPad用のアイコン用にリサイズするMac用アプリケーションです。対応サイズと出力ファイル名は以下の通り1

  • Icon.png – 57×57ピクセルのiPhoneアイコン
  • Icon@2x.png – 114×114ピクセルのiPhone 4アイコン
  • Icon-72.png – 72×72ピクセルのiPadアイコン
  • Icon-Small.png – 29×29ピクセルのiPhoneセッティングアイコン
  • Icon-Small@2x.png – 58×58ピクセルのiPhone 4セッティングアイコン
  • Icon-Small-50.png – 50×50ピクセルのiPadセッティングアイコン

ソースとアプリケーション本体は以下に置いてあります。

Footnotes

  1. Updating for the iPhone 4 retinal display – Blog – Use Your Loaf []

[iPad][App] Pscope for iPad

» Demo movie for iPad users
» Demo movie for iPhone users

Pscope for iPadはフォトライブラリの写真から万華鏡ライクなイメージを生成するiPadアプリケーションです。Pscopeの名前の由来は「Pentagonal kaleidoscope」です。万華鏡でいうところの鏡の形状が五角形になっているので一般的な万華鏡とは違った雰囲気が楽しめます。五角形の形状は現在知られているパターンすべて(注1)を網羅しておりサイズや形状などの細かいカスタマイズも可能です。さらにスクリーン上のタッチポイントによって描画される画像が変化するので同じ写真素材からいくらでも異なる万華鏡イメージを作り出します。

(注1. 2010.07.02 – 五角形タイルの全14パターンの内、Type 2と呼ばれるものについては現在修正中につきアプリケーション内で使用できません。修正完了までお待ちください)

Demo images

いくつかの有名な五角形タイルを用いて実際に万華鏡イメージを出力させてみました。

- 入力に用いたのは以下の画像 (from [Flash] fla06 – boreal-kiss.com)。

- Cairo pentagonal tilingを用いて描画したもの。

- Floret pentagonal tilingを用いて描画したもの。

- Prismatic pentagonal tilingを用いて描画したもの。

App Storeで販売中

Pscope for iPadは現在230円で販売中です。

アプリケーション詳細はこちら。» Pscope for iPad

参考

iPadアプリケーションでのUITabBarControllerの問題点

Important note at July 05, 2010

It is found the problem below does not come from SDK but ME. Specifically when implementing UITabBarController, I’ve ignored to set its view’s autoresizesSubviews property to YES. The repository of BKTabBarController at github.com will be also closed soon.

——————- The original artice stats here. ——————-

SDK 3.2ではiPadをターゲットにした場合UITabBarControllerが正しく動作しない。具体的に言うと以下のような問題が発生する。

  • デバイスの回転が生じた際、配下のUIViewControllerのビューフレームが再配置されない。
  • 回転後に強制的に子ビューのフレームを変更すると、子ビュー中のボタン(UINabigationBarの場合右側ボタンのみ)が動作しなくなる。

全く同じ現象に悩んでいる人が他にもいたので自分の勘違いというわけではなさそうだ。

View controllers inside tab bar controller not auto-resizing on rotation – Stack Overflow

もし勘違いでないとすると結構な問題だ。というのもiPadではPortraitとLandscapeどちらのデバイス方向にも対応したアップリケーションでなければ提出できないのだが、UITabBarControllerを使った場合回転時に全く使い物にならないアプリケーションになってしまうからだ。

BKTabBarController

そこで上記問題点を解消するようなTabBarControllerクラスを作った。次回SDKアップデートでUITabBarControllerの不具合が修正されるまでは役立つと思う。UITabBarControllerと同じインターフェースで、現時点で実装してあるものを以下に示す(タブのカスタマイズ系は未実装)。

Properties / methods

@interface BKTabBarController : UIViewController {
 
}
 
@property (readonly, retain) UITabBar *tabBar;
@property (nonatomic, copy) NSArray *viewControllers;
@property (nonatomic, assign) NSUInteger selectedIndex;
@property (nonatomic, assign) UIViewController *selectedViewController;
@property (nonatomic, assign) id <BKTabBarControllerDelegate> delegate;
 
-(void)setViewControllers:(NSArray *)controllers animated:(BOOL)animated;
@end

Delegate

@protocol BKTabBarControllerDelegate <NSObject>
@optional
 
-(void)bkTabBarController:(BKTabBarController *)tabBarController 
	didSelectViewController:(UIViewController *)viewController;
-(BOOL)bkTabBarController:(BKTabBarController *)tabBarController
	shouldSelectViewController:(UIViewController *)viewController;
@end

ソースコードを含むサンプルプロジェクトとスタティックライブラリ化したライブラリは以下においてあるのでご自由にお使いください。

Associative Storage: カテゴリに疑似インスタンス変数を追加

イントロダクション

Objective-Cのカテゴリはサブクラスを作ることなくメソッドの拡張(またはメソッドごとの分類)が行える点で強力だ。しかしメソッドの定義はできてもインスタンス変数の追加ができない。そこでCocoaのKey Value Coding (KVC)の機能を用いてカテゴリに擬似的にインスタンス変数を生成させる。これにより元クラスを手直しすることなくインスタンス変数を追加することができる。なおこの手法は、よりフレキシブルという理由でNSMapTableを用いるのが一般的なようだが、ここではNSMutableDictionaryを代用した。理由は使い慣れているからだ。NSMapTableを用いた方法はBuck and Yacktman – Cocoa Design Patternsが詳しい。

具体例

簡単な例としてボタンを押すとアラートを表示するアプリケーションを考える。ボタンプッシュ後の動作はAppControllerクラスで以下のように定義されているとする。

//AppController.m
 
@implementation AppController
 
-(IBAction)buttonPressed:(id)sender{
	NSRunAlertPanel(@"Alert", @"This is an alert.", @"OK", nil, nil);
}
 
@end

ここで「一度アラート表示された場合は再度アラートを表示させない」仕様に変更するとしよう。AppControllerにBOOL値を格納するインスタンス変数を追加して制御するのが手っ取り早いが、ここではAppControllerのカテゴリ(ShowAtOnce)を新規に定義して制御することにする。カテゴリ作成の大まかな手順は以下のようになる。

  1. 疑似インスタンス変数格納用にNSMutableDictionary変数をカテゴリに定義
  2. 疑似インスタンス変数としたいオブジェクトを上記NSMutableDictionary変数に格納。その際のキーストリングはAppControllerインスタンスに固有のものを用いる
  3. 格納したオブジェクトのAccesors (Setters/Getters)をカテゴリに定義
  4. インスタンスの解放時にNSMutableDictionaryからオブジェクトを消去するメソッドをカテゴリに定義

以下順にみていこう。

1. 疑似インスタンス変数格納用変数の定義

疑似インスタンス変数にしても情報を格納する場所は必要になる。そこで疑似インスタンス変数を格納する場所としてNSMutableDictionaryオブジェクトをカテゴリ(ShowAtOnce)に定義する。さらにこのオブジェクトのAccesorsをクラスメソッドとして定義する。つまりこの格納用のNSMutableDictionaryオブジェクトはAppControllerインスタンスすべてで使い回されることになる。

//AppController-ShowAtOnce.m
 
@implementation AppController (ShowAtOnce)
 
static NSMutableDictionary *_simulatedIVars = nil;
 
+(NSMutableDictionary *)_simulatedIVars{
	if (_simulatedIVars == nil){
		_simulatedIVars = [[NSMutableDictionary alloc] init];
	}
	return _simulatedIVars;
}
 
@end

2. インスタンス固有のキーストリング

上記例のアプリケーションの仕様を実現するためには、一度アラートが表示されたかどうかを調べるBOOL値を(NSMutableDictionary *)_simulatedIVarsに格納できればよさそうだ。しかし今は疑似インスタンス変数を作りたいのでAppControllerインスタンスごとに異なる値を格納できるようにする必要がある。そこでまず「AppControllerインスタンスに固有なNSStringオブジェクト」を生成するメソッドを定義する。このメソッドは-[AppController self]のストリング表現を返す。

//AppController-ShowAtOnce.m
 
-(NSString *)_instanceID{
	return [NSString stringWithFormat:@"%@", self];
}

さらに「疑似インスタンス変数名に相当するNSStringオブジェクト」を用意する。これはクラス全体で使い回しができるようにすればよい。ここではAlertHasEverBeenShownKeyStringという名前で定義しておこう。

//AppController-ShowAtOnce.m
 
static NSString * const AlertHasEverBeenShownKeyString = @"alertHasEverBeenShown";

あとは「AppControllerインスタンスに固有なNSStringオブジェクト」と「疑似インスタンス変数名に相当するNSStringオブジェクト」を適当に繋げて「あるAppControllerインスタンスの疑似インスタンス変数に固有なNSStringオブジェクト」を作成するメソッドを定義する。このメソッドの返り値が(NSMutableDictionary *)_simulatedIVarsへの格納用キーストリングとして利用されるわけだ。

//AppController-ShowAtOnce.m
 
-(NSString *)_objectKeyForKeyString:(NSString *)aKeyString{
	return [NSString stringWithFormat:@"%@:%@", 
		[self _instanceID], aKeyString];
}

現在までのAppController (ShowAtOnce)の中身をまとめると以下のようになっているはずだ。

//AppController-ShowAtOnce.m
 
static NSString * const AlertHasEverBeenShownKeyString = @"alertHasEverBeenShown";
 
@implementation AppController (ShowAtOnce)
 
static NSMutableDictionary *_simulatedIVars = nil;
 
+(NSMutableDictionary *)_simulatedIVars{
	if (_simulatedIVars == nil){
		_simulatedIVars = [[NSMutableDictionary alloc] init];
	}
	return _simulatedIVars;
}
 
#pragma mark -
#pragma mark Utilities
 
-(NSString *)_instanceID{
	return [NSString stringWithFormat:@"%@", self];
}
 
-(NSString *)_objectKeyForKeyString:(NSString *)aKeyString{
	return [NSString stringWithFormat:@"%@:%@", 
		[self _instanceID], aKeyString];
}
 
@end

3. 疑似インスタンス変数のAccesors

上記メソッド群を利用してカテゴリ(ShowAtOnce)に定義する。Settersは-[AppController _setAlertHasEverBeenShown:]、Gettersは-[AppController _alertHasEverBeenShown]としよう。ここはstraightforwardだと思う。

//AppController-ShowAtOnce.m
 
-(BOOL)_alertHasEverBeenShown{
	NSString *aKey = 
		[self _objectKeyForKeyString:AlertHasEverBeenShownKeyString];
	id object = [[[self class] _simulatedIVars] objectForKey:aKey];
	if (object == nil){
		return NO;
	}
 
	return [object boolValue];
}
 
-(void)_setAlertHasEverBeenShown:(BOOL)yn{
	NSString *aKey = 
		[self _objectKeyForKeyString:AlertHasEverBeenShownKeyString];
	[[[self class] _simulatedIVars] 
		setObject:[NSNumber numberWithBool:yn] forKey:aKey];
}

あとはAppController内でこのメソッドを使ってやればよい。具体的には以下のようになるだろう。

//AppController.m
 
#import "AppController-ShowAtOnce.h"
 
@implementation AppController
 
-(IBAction)buttonPressed:(id)sender{
	if (![self _alertHasEverBeenShown]){
		NSRunAlertPanel(@"Alert", @"This is an alert.", @"OK", nil, nil);
		[self _setAlertHasEverBeenShown:YES];
	}
}
 
@end

4. 疑似インスタンス変数を解放

AppControllerインスタンスごとに(NSMutableDictionary *)_simulatedIVarsに登録されるのだからAppControllerインスタンスが不要になったら疑似インスタンス変数も解放する必要がある。そのために以下のようなメソッドをAppController(ShowAtOnce)に定義しておく。このメソッドは呼ばれれば不要になった疑似インスタンス変数を解放する。

//AppController-ShowAtOnce.m
 
-(void)_removeSimulatedIVars{
	NSString *aKey = 
		[self _objectKeyForKeyString:AlertHasEverBeenShownKeyString];
	id object = [[[self class] _simulatedIVars] objectForKey:aKey];
 
	if (object){
		[[[self class] _simulatedIVars] removeObjectForKey:aKey];
	}
}

さてこれまでAppControllerそのものを一切変更しないように実装すると言ってきたが実は嘘で、AppControllerそのものを手直しする必要がでてくる。というのも疑似インスタンス変数の解放を呼ぶメソッドをカテゴリに書くことは危険だからだ。例えば-[AppController dealloc]をカテゴリ(ShowAtOnce)に再定義することは可能だが、もし他に疑似インスタンス変数を持つ別カテゴリが同様の-[AppController dealloc]を実装した場合どちらの-[AppController dealloc]が呼び出されるかわからなくなるからだ(結果どちらかのカテゴリの疑似インスタンス変数は解放メソッドを呼ばれないがために生き残ってしまう)。したがって疑似インスタンス変数の解放メソッドを呼び出すのはAppControllerとするのがよいだろう。

//AppController.m
 
-(void)dealloc{
	[self _removeSimulatedIVars];
	[super dealloc];
}

全ソースコード

最終的なコードは以下のようになっている。

//AppController.h
 
#import <Cocoa/Cocoa.h>
 
@interface AppController : NSObject {
 
}
 
-(IBAction)buttonPressed:(id)sender;
@end
//AppController.m
 
#import "AppController.h"
#import "AppController-ShowAtOnce.h"
 
@implementation AppController
 
-(IBAction)buttonPressed:(id)sender{
	if (![self _alertHasEverBeenShown]){
		NSRunAlertPanel(@"Alert", @"This is an alert.", @"OK", nil, nil);
		[self _setAlertHasEverBeenShown:YES];
	}
}
 
-(void)dealloc{
	[self _removeSimulatedIVars];
	[super dealloc];
}
 
@end
//AppController-ShowAtOnce.h
 
#import <Cocoa/Cocoa.h>
#import "AppController.h"
 
@interface AppController (ShowAtOnce)
 
+(NSMutableDictionary *)_simulatedIVars;
-(void)_removeSimulatedIVars;
-(BOOL)_alertHasEverBeenShown;
-(void)_setAlertHasEverBeenShown:(BOOL)yn;
-(NSString *)_instanceID;
-(NSString *)_objectKeyForKeyString:(NSString *)aKeyString;
@end
//AppController-ShowAtOnce.m
 
#import "AppController-ShowAtOnce.h"
 
//Private
static NSString * const AlertHasEverBeenShownKeyString = @"alertHasEverBeenShown";
 
@implementation AppController (ShowAtOnce)
 
static NSMutableDictionary *_simulatedIVars = nil;
 
+(NSMutableDictionary *)_simulatedIVars{
	if (_simulatedIVars == nil){
		_simulatedIVars = [[NSMutableDictionary alloc] init];
	}
	return _simulatedIVars;
}
 
-(void)_removeSimulatedIVars{
	NSLog(@"%s", __FUNCTION__);
 
	NSString *aKey = 
		[self _objectKeyForKeyString:AlertHasEverBeenShownKeyString];
	id object = [[[self class] _simulatedIVars] objectForKey:aKey];
 
	if (object){
		[[[self class] _simulatedIVars] removeObjectForKey:aKey];
	}
}
 
#pragma mark -
#pragma mark Setters Getters
 
-(BOOL)_alertHasEverBeenShown{
	NSString *aKey = 
		[self _objectKeyForKeyString:AlertHasEverBeenShownKeyString];
	id object = [[[self class] _simulatedIVars] objectForKey:aKey];
	if (object == nil){
		return NO;
	}
 
	return [object boolValue];
}
 
-(void)_setAlertHasEverBeenShown:(BOOL)yn{
	NSString *aKey = 
		[self _objectKeyForKeyString:AlertHasEverBeenShownKeyString];
	[[[self class] _simulatedIVars] 
		setObject:[NSNumber numberWithBool:yn] forKey:aKey];
}
 
#pragma mark -
#pragma mark Utilities
 
-(NSString *)_instanceID{
	return [NSString stringWithFormat:@"%@", self];
}
 
-(NSString *)_objectKeyForKeyString:(NSString *)aKeyString{
	return [NSString stringWithFormat:@"%@:%@", 
		[self _instanceID], aKeyString];
}
 
@end
Get Adobe Flash playerPlugin by wpburn.com wordpress themes