Cocoa練習帳 -34ページ目

[iOS]設定バンドル Settings Bundle

どの様な物かは知っていたが、ユーザーの立場からアクセスがしづらいのでは?と思えて縁がなかった物の一つに設定バンドル(Settings Bundle)がある。




プロジェクトの新規ファイルとして、ResourceのSettings Bundleを選択する。




新規文書




Setting.bundleのRoot.plistに項目を設定する。
デフォルトでは、テキストフィールドNameとトグルスイッチ、スライダーの3項目が用意されている。




Root.plist




実装すると、この3項目が表示される。




Settings




設定バンドルの値は、以下のコードで取得できる




- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSLog(@"%s", __func__);
    NSString    *name = [[NSUserDefaults standardUserDefaults] stringForKey:@"name_preference"];
    NSLog(@"Name:%@", name);
    BOOL    enabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"enabled_preference"];
    NSLog(@"Enabled:%d", (int)enabled);
    float    slider = [[NSUserDefaults standardUserDefaults] floatForKey:@"slider_preference"];
    NSLog(@"Slider:%f", slider);
    return YES;
}
 
- (void)applicationDidBecomeActive:(UIApplication *)application
{
    NSLog(@"%s", __func__);
    NSString    *name = [[NSUserDefaults standardUserDefaults] stringForKey:@"name_preference"];
    NSLog(@"Name:%@", name);
    BOOL    enabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"enabled_preference"];
    NSLog(@"Enabled:%d", (int)enabled);
    float    slider = [[NSUserDefaults standardUserDefaults] floatForKey:@"slider_preference"];
    NSLog(@"Slider:%f", slider);
}



ソースコード
GitHubからどうぞ。

https://github.com/murakami/workbook/tree/master/ios/SettingsBundle - GitHub


関連情報
Preferences and Settings Programming Guide

Apple Developerサイトの情報。

[iOS]AdMob

Google AdMobの魅力は、容易に自分のアプリケーションに組み込む事が出来る事だ。


以前は、使用しているオープンソースのフレームワークも一緒に組み込む等、面倒な部分があったが、現在はアーカイブのライブラリとヘッダファイルを追加するだけでOK。組み込むソースコードも、AdMobサイトで丁寧に説明してある。なので、ここで組み込む手順を説明するまでもないので、自分が工夫していることについて説明する。




AdMobは使い始めたら直ぐに広告が表示されるようになる訳ではないのと、小数の同じ端末から何度も広告が表示されると、不正な操作と判断されるということからだと思うが、デバッグ時は明示的にそのことを設定する必要がある。


著者は、Supporting Filesの<アプリ名>-Prefix.pchで、デバッグ出力用のマクロの定義時に、AdMob試験用のマクロを定義している。




#ifdef	DEBUG
#define DBGMSG(...) NSLog(__VA_ARGS__)
#define ADMOB_TESTDRIVE
#else /* DEBUG */
#define DBGMSG(...)
#endif /* DEBUG */




ViewControllerにAdMobのプロパティを定義する。




@interface MyViewController : UIViewController 
@property (strong, nonatomic) GADBannerView             *bannerView;
@end



ViewControllerの-(void)viewDidLoadメソッドでインスタンスを生成する。




- (void)viewDidLoad
{
    [super viewDidLoad];
 
    self.bannerView = [[GADBannerView alloc]
                   initWithFrame:CGRectMake(0.0,
                                            self.view.frame.size.height -
                                            GAD_SIZE_320x50.height,
                                            GAD_SIZE_320x50.width,
                                            GAD_SIZE_320x50.height)];
    self.bannerView.adUnitID = @"自分のAdMobパグリッシャーID";
    self.bannerView.rootViewController = self;
    [self.view addSubview:self.bannerView];
#ifdef ADMOB_TESTDRIVE
    GADRequest  *request = [GADRequest request];
    request.testDevices = [NSArray arrayWithObjects:
                           GAD_SIMULATOR_ID,
                           @"デバイスID(1)",
                           @"デバイスID(2)",
                           nil];
    [bannerView_ loadRequest:request];
#else   /* ADMOB_TESTDRIVE */
    [self.bannerView loadRequest:[GADRequest request]];
#endif  /* ADMOB_TESTDRIVE */
    self.bannerView.delegate = self;
}



その際、ADMOB_TESTDRIVEが定義されている。つまりデバッグ時は、デバッグで使用する自分のデバイスIDを上記のコードのように設定する。




広告が表示される場合と、表示されない場合で、画面レイアウトを変更する場合があると思が、以下のそれの例。




- (void)adViewDidReceiveAd:(GADBannerView *)bannerView
{
    [UIView beginAnimations:@"BannerSlideOn" context:nil];
    bannerView.frame = CGRectMake(0.0,
                                  self.view.frame.size.height - bannerView.frame.size.height,
                                  bannerView.frame.size.width,
                                  bannerView.frame.size.height);
    self.コントロール.frame = CGRectMake(self.コントロール.frame.origin.x,
                                      元のY座標の値 - bannerView.frame.size.height,
                                      self.コントロール.frame.size.width,
                                      self.コントロール.frame.size.height);
    [UIView commitAnimations];
}
 
- (void)adView:(GADBannerView *)bannerView
didFailToReceiveAdWithError:(GADRequestError *)error
{
    [UIView beginAnimations:@"BannerSlideOff" context:NULL];
    self.コントロール.frame = CGRectMake(self.コントロール.frame.origin.x,
                                      元のY座標の値,
                                      self.コントロール.frame.size.width,
                                      self.コントロール.frame.size.height);
     [UIView commitAnimations];
}



上記の例では、ボタン等の<コントロール>を広告の表示/非表示に合わせて位置を変更している。全てを計算で求めると、トラブルで順番が狂うとおかしくなるので、ハードコーディングしている。




ソースコード
AdMobが組み込まれていないプロジェクトだと、しょうがないので、今回はソースコードはなしだ。


関連情報
Google AdMob

Google AdMob Ads iOS Fundamentals

Google AdMobのサイト。

[iOS]座標と描画(その3)

デフォルト座標系の話、理由は分かったが、それを実際に試せないか、試行錯誤している。


Apple Developerサイトの文書でも、UIKitでの描画。つまり、UIViewサブクラスのdrawRect:メソッド内で描画する場合、デフォルト座標系は原点は左上で、座業は下と右方向に伸びるULO(upper-left-origin)となって、LLO(lower-left-origin)を選択する場合は独自の変換行列(CTM:Current Transformation Matrix)を適用すると説明されていた。




ということで、現在の描画環境以外にCoreGraphicsで描画すれば、LLOになると考え、ビットマップ・コンテキストに描画した物を表示してみた。




- (void)drawRect:(CGRect)rect
{
    DBGMSG(@"%s", __func__);
    CGContextRef    context = UIGraphicsGetCurrentContext();
 
    /* LLO(lower-left-origin) */
    size_t  witdh = rect.size.width;
    size_t  height = rect.size.height;
    size_t  bytesPerRow = witdh * 4;
    bytesPerRow = COMPUTE_BEST_BYTES_PER_ROW(bytesPerRow);
    unsigned char   *rasterData = calloc(1, bytesPerRow * height);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef    bitmapContext = CGBitmapContextCreate(rasterData, witdh, height,
                                                8, bytesPerRow,
                                                colorSpace,
                                                kCGImageAlphaPremultipliedLast);
 
    CGContextSetRGBStrokeColor(bitmapContext, 1.0, 0.0, 0.0, 1.0);
    CGContextSetLineWidth(bitmapContext, 4.0);
    CGContextBeginPath(bitmapContext);
    CGContextMoveToPoint(bitmapContext, 5.0, 25.0);
    CGContextAddLineToPoint(bitmapContext, 5.0, 5.0);
    CGContextDrawPath(bitmapContext, kCGPathStroke);
    CGContextMoveToPoint(bitmapContext, 5.0, 5.0);
    CGContextAddLineToPoint(bitmapContext, 25.0, 5.0);
    CGContextDrawPath(bitmapContext, kCGPathStroke);
 
    CGImageRef  cgimage = CGBitmapContextCreateImage(bitmapContext);
    CGContextDrawImage(context, rect, cgimage);
    CGContextRelease(bitmapContext);
    free(rasterData);
    CGColorSpaceRelease(colorSpace);
 
    /* ULO(upper-left-origin) */
    [self.upperLeftOriginImage drawAtPoint:CGPointMake(20.0, 20.0)];
 
    CGContextSetLineWidth(context, 4.0);
    CGContextBeginPath(context);
    CGContextMoveToPoint(context, 20.0, 40.0);
    CGContextAddLineToPoint(context, 20.0, 20.0);
    CGContextDrawPath(context, kCGPathStroke);
    CGContextMoveToPoint(context, 20.0, 20.0);
    CGContextAddLineToPoint(context, 40.0, 20.0);
    CGContextDrawPath(context, kCGPathStroke);
}



あれ、ULOみたい。

RUN




現在の描画環境に表示する際にULOに変換されるのでは?と考え、ビットマップ・コンテキストの内容をファイルに保存してみた。




    UIImage *uiimage = [[UIImage alloc] initWithCGImage:cgimage];
    NSData  *data = UIImagePNGRepresentation(uiimage);
    NSString    *filePath = [NSString stringWithFormat:@"%@/demo.png" ,
                          [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]];
    NSLog(@"%@", filePath);
    [data writeToFile:filePath atomically:YES];



確かに、LLOになっている。




demo.png





ソースコード
GitHubからどうぞ。

https://github.com/murakami/workbook/tree/master/ios/Coordinate - GitHub


関連情報
Programming with Quartz: 2D and PDF Graphics in Mac OS X

Drawing and Printing Guide for iOS

結局は、これらの一次情報を読んでいくのがベストのようですね。

[Mac][iOS]Xcode 4.3

Xcode 4.3 for Lionがリリースされた。


/Developerディレクトリでなく、/Applicationsディレクトリにインストールされるようになったり、バンドル内に各種ツールを置くようになったりと、OS X Mountain Lion時代を感じされる変化が興味深い。


そして、これが一番大きな事だが、バージョンアップに伴う様々が事で、本日は日記に新しい話題を投稿する事が出来なくなってしまった。残念!


[Mac][iOS]練習方法

今回は、自分のCocoa練習の場を紹介します。



■相互の学習の場。勉強になっています。

 Cocoa勉強会



■交流の場。

 特定非営利活動法人MOSA

MOSAdeBBという会員向け掲示板があるのですが、これが質問しやすく、直ぐに回答が得られて、助かります。



■Googleグループ

 cocoa-dev-japan



状況共有しやすい場として設立されました。


[iOS]座標と描画(その2)

前回の疑問、どのような動作をするのか考えれば、当たり前の結果だと分かった。

アドバイスしていただいたMOSAのMOSAdeBBの方々、cocoa-dev-japanの方々、ありがとうございます!


ただ、今回、これが確認できるサンプルを用意しようと考えたのだが、上手くいかなかったので、少し時間が欲しい。申し訳ない。




ソースコード
GitHubからどうぞ。

https://github.com/murakami/workbook/tree/master/ios/Coordinate - GitHub


関連情報
Drawing and Printing Guide for iOS

「Default Coordinate Systems and Drawing in iOS」の章を参照。

[iOS]座標と描画

Mac OS Xでは、基本的に描画の座標は左下が原点だ。


iOSでは、UIKitとCoreAnimationでは左上が原点(便宜上、ULO(upper-left-origin)と呼ぶ)、Core Graphicsでは左下が原点(便宜上、LLO(lower-left-origin)と呼ぶ)だそうだ。試してみよう。




まず、Mac OS Xから。NSViewのサブクラスを作成して、以下の描画コードを追加する。




- (void)drawRect:(NSRect)dirtyRect
{
    /* LLO(lower-left-origin) */
    NSGraphicsContext   *nsctx = [NSGraphicsContext currentContext];
    CGContextRef    context = (CGContextRef)[nsctx graphicsPort];
    CGContextSetLineWidth(context, 4.0);
    CGContextBeginPath(context);
    CGContextMoveToPoint(context, 10.0, 30.0);
    CGContextAddLineToPoint(context, 10.0, 10.0);
    CGContextDrawPath(context, kCGPathStroke);
    CGContextMoveToPoint(context, 10.0, 10.0);
    CGContextAddLineToPoint(context, 30.0, 10.0);
    CGContextDrawPath(context, kCGPathStroke);
}



実行。確かに左下が原点だ。







次にiPhoneで確認。画像を座標 (10.0, 10.0) に描画する。




- (void)drawRect:(CGRect)rect
{
    /* ULO(upper-left-origin) */
    [self.upperLeftOriginImage drawAtPoint:CGPointMake(10.0, 10.0)];
}



実行。向きも変えてみる。確かに左上に描画される。




ULO縦

ULO横




今度はパスを座標 (10.0, 10.0) に描画するコードを追加。




- (void)drawRect:(CGRect)rect
{
    /* ULO(upper-left-origin) */
    [self.upperLeftOriginImage drawAtPoint:CGPointMake(10.0, 10.0)];
 
    /* LLO(lower-left-origin) */
    CGContextRef    context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, 4.0);
    CGContextBeginPath(context);
    CGContextMoveToPoint(context, 10.0, 30.0);
    CGContextAddLineToPoint(context, 10.0, 10.0);
    CGContextDrawPath(context, kCGPathStroke);
    CGContextMoveToPoint(context, 10.0, 10.0);
    CGContextAddLineToPoint(context, 30.0, 10.0);
    CGContextDrawPath(context, kCGPathStroke);
}



実行。赤丸の箇所に描画。あれ?左上に描画されている。




LLO縦

LLO横




何かを勘違いしているのだろうか?




ソースコード
GitHubからどうぞ。

https://github.com/murakami/workbook/tree/master/ios/Coordinate - GitHub


関連情報
Drawing and Printing Guide for iOS

「Default Coordinate Systems and Drawing in iOS」の章を参照。

[iOS]Image Masking(その2)

実は、本題の画像処理のコードより、Storyboardを使った、ターブル一覧の実装に時間がかかってしまったが、いい経験となった。Storyboardについては、機会があれば続きをと考えている。




画像を任意の形に切り抜いて表示する方法の一つにクリッピングがある。
今回は、クリッピングの基本的な内容で、画像の四隅を丸くする例だ。




- (void)drawRect:(CGRect)rect
{
    CGContextRef    context = UIGraphicsGetCurrentContext();
    CGSize  imageSize = self.image.size;
    CGRect  imageRect = {10.0, 10.0, imageSize.width, imageSize.height};
    float   radius = 10.0;
    CGFloat minX = CGRectGetMinX(imageRect);
    CGFloat midX = CGRectGetMidX(imageRect);
    CGFloat maxX = CGRectGetMaxX(imageRect);
    CGFloat minY = CGRectGetMinY(imageRect);
    CGFloat midY = CGRectGetMidY(imageRect);
    CGFloat maxY = CGRectGetMaxY(imageRect);
    
    /* 現状の描画環境を保存 */
    CGContextSaveGState(context);
    
    /* 四角形の辺に接する、半径radiusの円弧を四隅に追加 */
    CGContextMoveToPoint(context, minX, midY);
    CGContextAddArcToPoint(context, minX, minY, midX, minY, radius);
    CGContextAddArcToPoint(context, maxX, minY, maxX, midY, radius);
    CGContextAddArcToPoint(context, maxX, maxY, midX, maxY, radius);
    CGContextAddArcToPoint(context, minX, maxY, minX, midY, radius);
    CGContextClosePath(context);
    
    /* 先ほどのパスをクリップ領域として設定 */
    CGContextClip(context);
    
    /* 描画 */
    [self.image drawAtPoint:CGPointMake(10.0, 10.0)];
    
    /* 描画環境を先ほどの保存時点に戻す */
    CGContextRestoreGState(context);
}



実行。テーブルの「ClippingADrawing」を選択して欲しい。




テーブル




クリッピング


ソースコード

GitHubからどうぞ。

https://github.com/murakami/workbook/tree/master/ios/ImageMasking - GitHub


関連情報
Programming with Quartz: 2D and PDF Graphics in Mac OS X

WWDCでも薦められいた書籍です。

iOS SDK Hacks (吉田 悠一、高山 征大、UICoderz 著)

「HACK #17 クリッピング」を参考にしました。

[iOS]Image Masking

表示したい画像とマスクの画像を用意する。




pict




mask

それを以下のコードでマスクした画像を取得し、描画する。




- (void)awakeFromNib
{
    self.image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"pict.png" ofType:nil]];
    self.mask = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"mask.png" ofType:nil]];
    self.imageMaskedWithImage = [self maskImage:self.image withMask:self.mask];
}
 
- (void)drawRect:(CGRect)rect
{
    [self.imageMaskedWithImage drawAtPoint:CGPointMake(10.0, 10.0)];
}
 
- (UIImage*)maskImage:(UIImage *)image withMask:(UIImage *)maskImage
{   
    CGImageRef maskRef = maskImage.CGImage; 
    CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
                                                                    CGImageGetHeight(maskRef),
                                                                    CGImageGetBitsPerComponent(maskRef),
                                                                    CGImageGetBitsPerPixel(maskRef),
                                                                    CGImageGetBytesPerRow(maskRef),
                                                                    CGImageGetDataProvider(maskRef),
                                                                    NULL,
                                                                    false);
    CGImageRef masked = CGImageCreateWithMask([image CGImage], mask);
    CGImageRelease(mask);
    return [UIImage imageWithCGImage:masked];
}


実行。




run




画像のマスクについては、まだまだ、ほんの一部だと思っているので、機会があったら続きを試してみたい。




ソースコード
GitHubからどうぞ。

https://github.com/murakami/workbook/tree/master/ios/ImageMasking - GitHub


関連情報
Programming with Quartz: 2D and PDF Graphics in Mac OS X

WWDCでも薦められていた書籍です。

How to Mask an Image

[iOS]項目を選択する

複数の表示項目(テキストのラベルや画像etc)がある場合、そのどれが有効で、どれが無効なのかを選択するUIはどう実現できるのだろうか?

iPhoneらしいのは、別の設定画面があって、そこで項目をスイッチでOn/Offする。

画面遷移が煩わしくないか。

表示画面そのもので、On/Offを選択する。

良さそうだ。だが、チェック的なモノがあるのも煩わしくないか。

今回は、項目の表示状態からOn/Offが選択できるUIを実験してみた。




項目が選択されたら表示を変更したいのなら、ユーザー操作、つまり、イベントを取得できるようにしないといけない。

今回は、画像とラベルの選択を実践してみたが、こららのビューを管理するビュー・コントローラでイベントを取得する方法と、個々のビューのサブクラスを作成して、そのサブクラスでイベントを取得する方法に二通りが考えられる。

ビュー・コントローラで対応する方法だと、新たにサブクラスを作成する手間が減るが、ユーザーの操作に対して、どのビューが対象なのかを判断しないといけないが、サブクラスを作成する方法は、その判断が不要となる。

そこで、今回の実験では、サブクラスを作成する方法を選択した。




プロジェクトにQuartzCore.frameworkを追加する。


QuartzCore.framework




画面のビュー・コントローラに画像とラベルを追加する(下図の赤丸)。


ViewController




そして、それぞれのクラスをカスタムクラス(MyImageViewとMyLabel)に変更する。


CustomClass




MyImageViewの選択処理のコードは以下のとおり。


- (void)awakeFromNib
{
    self.selected = NO;
     
    self.layer.masksToBounds = YES;
    self.layer.cornerRadius = 4.0f;
     
    self.layer.borderWidth = 3.0f;
    self.layer.borderColor = [[UIColor grayColor] CGColor];
}
 
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self toggleSwitch];
}
 
- (void)toggleSwitch
{
    if (self.selected) {
        self.selected = NO;
        self.layer.borderColor = [[UIColor grayColor] CGColor];
    }
    else {
        self.selected = YES;
        self.layer.borderColor = [[UIColor blueColor] CGColor];
    }
}



MyLabelの選択処理のコードは以下のとおり。


- (void)awakeFromNib
{
    self.selected = NO;
    self.textColor = [UIColor grayColor];
}
 
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self toggleSwitch];
}
 
- (void)toggleSwitch
{
    if (self.selected) {
        self.selected = NO;
        self.textColor = [UIColor grayColor];
    }
    else {
        self.selected = YES;
        self.textColor = [UIColor blueColor];
    }
}



実行。


Run





ソースコード
GitHubからどうぞ。

https://github.com/murakami/workbook/tree/master/ios/SelectItem - GitHub


関連情報
User Experience Coding How-To's

iOS Developer Libraryの情報です。