2012年3月20日火曜日

Xcode4でiOS3(iPhone 3G)向けアプリをビルドする方法

Xcode4でビルドしたアプリを、iPhone 3G(iOS 3.x)で実行させる方法を紹介します。
様々調べた結果、以下で詳しく説明されていました。

iOS 3.x support in Xcode 4 - Stack Overflow

前提として、オーガナイザーでiPhone 3Gが認識されているものとします。



Development Targetを変更


最初の状態では、ターゲットにiPhone 3Gを選ぶことすらできません。


選べるようにするために、Development Targetを3.0にします。プロジェクト設定の[Summary]で設定します。

これで、iPhone 3Gを選べるようになるのですが、まだまだ実行できません。そんなに簡単にはいきません…


armv6サポート


iPhone 3Gはarmv6というCPUアーキテクチャが利用されています(iPhone 3GS以降はarmv7)。
これをサポートする設定をしなければなりません。
プロジェクト設定の[Build Settings]で[Architectures]に"armv6"を追加します。




続いて、[Build Settings]で[Linking]→[Other Linker Flags]に"-weak-lSystem"を追加します。


最後にInfo.plistの"Required device capabilities"という項目から"armv7"を削除します。



ソースコードの修正


ソースコードの修正が必要です。

1つ目はUIDeviceのuserInterfaceIdiomを利用しているコードです。
iPhoneとiPadを見分けるのに利用されているのですが、iOS3.2より前だとこのメソッドが定義されていないためアプリが不正終了してしまいます。
以下のように修正します。
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {

UI_USER_INTERFACE_IDIOMマクロはuserInterfaceIdiomメソッドが利用できるか確認してくれるので安心して使えます。

2つ目はAppDelegateのapplication:didFinishLaunchingWithOptions:内のコードです。

self.window.rootViewController = self.viewController;
[self.window addSubview:self.viewController.view];

以上、これで無事にiPhone 3G/iOS 3でアプリを開発できます。

2012年3月17日土曜日

「漢字ルーペ」リリース!&アップデート!


2年近く前からiPhoneアプリを趣味で作ってきましたが、公開までやってみようと思い立ち、ついに先日リリースできました!

漢字ルーペ   漢字ルーペ - Prunus

「あの漢字はどうやって書くんだっけ?」そんな時に使えるアプリです。
既に1回アップデートしていて、ちょいちょい機能が増えていってます。

  •  文字を拡大表示
  • 選べるフォント(ゴシック/明朝/筆順)
  • 横にすると全画面表示(ピンチイン・アウトによる拡大縮小が可能)

無料なのでぜひ使ってみてください m(_ _)m

 

UIWebViewのエラー処理

UIWebViewをさわっていたのですが、エラー処理で詰まってしまいました。

デリゲートメソッドwebView:didFailLoadWithError:にエラー処理(アラート表示)をいれたのですが、stopLoadingで読み込みをキャンセルした場合もこれが呼ばれてしまう。


さぁ困ったぞ、と1日経って良く見てみたら、NSErrorでエラー内容が取れるではないかと。
このリファレンスの「URL Loading System Error Codes」という項目にNSURLErrorDomainのエラー一覧があります。
NSURLErrorCancelledのときはアラート表示をしないという処理で大丈夫そうです。

以下ソース。
// エラー
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    LOG(@"UIWebView: error");
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    
    // stopLoadingでキャンセルされた場合はアラートを表示しない
    if (!(   [error.domain isEqualToString:NSURLErrorDomain]
          && error.code == NSURLErrorCancelled)) {
        // アラート表示
        [self showAlert];
    }
    
    [self updateButtonStatus];
}
できました。良さそうです。

iAdを組み込む

せっかくなのでiAdを試してみました。やりたかった事は以下の通りです。
  • iAdが表示できなかったらAdMobの広告を表示したい
  • iOS3はiAdが使えないので、 無条件でAdMobを表示したい

登録


で、まずはiAdを利用するために、様々申請が必要です。全てiTunes Connectで行います。
以下のページに詳しく解説されていました。ほとんどこの通りにやりました。

 Contracts, Tax, and Banking 銀行と税金の契約 | iPhone使いへの道

 あ、1点だけ違う点が。「Account Holder Name(口座名義)」をカタカナで入力しました。入力時に脇のヘルプにそのように書いてあったので。

コーディング


申請が無事完了したら、次は一番大事なコーディングです。
これもまた、以下のページを参考にさせていただきました。

管理人の部屋: [続] iAdをメインにしてAdmobをバックアップとして設置

大枠はそのままなのですが、若干手を加えたので、ソースを掲載します。
あとで使い回しをしやすいように、カテゴリでソースを分けてあります。

ViewController+Ad.h
#import "ViewController.h"
#import <iAd/iAd.h>
#import "GADBannerView.h"

@interface ViewController()
{
    BOOL _iAdIsVisible;
    ADBannerView *_iAdView;
    BOOL _isEnableAdMob;
    GADBannerView *_adMobView;
}
@end

@interface ViewController(Ad) <ADBannerViewDelegate, GADBannerViewDelegate>

// 広告を開始する
- (void)startAd;

// 広告を解放する
- (void)releaseAd;

// private methods
- (void)startiAd;
- (void)startAdMob;
- (void)stopAdMob;

@end

ViewController+Ad.m
#import "ViewController+Ad.h"

#define ADMOB_PUBLISHER_ID @"???????????????"

@implementation ViewController(Ad)

- (void)startAd
{
    // iAd (iOS4.0以上)
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 4.0) {
        [self startiAd];
    }
    else {
        [self startAdMob];
    }
    
    return;
}

- (void)releaseAd
{
    // AdMob
    if (_isEnableAdMob) {
        _adMobView.delegate = nil;
        [_adMobView release];
    }
    
    //iAd
    _iAdView.delegate = nil;
    [_iAdView release];
    
    return;
}

#pragma mark - iAd

- (void)startiAd
{
    LOG(@"iAd:startiAd");
    
    // iAd初期化
    _iAdView = [[ADBannerView alloc] initWithFrame:CGRectZero];
    
    // サイズ設定
    NSString *identifier;
    if (&ADBannerContentSizeIdentifierPortrait != nil) {
        identifier = ADBannerContentSizeIdentifierPortrait;
    }
    else {
        identifier = ADBannerContentSizeIdentifier320x50;
    }
    _iAdView.requiredContentSizeIdentifiers = [NSSet setWithObject:identifier];
    _iAdView.currentContentSizeIdentifier = identifier;
    
    // 下に配置
    CGSize bannerSize = [ADBannerView sizeFromBannerContentSizeIdentifier:identifier];
    _iAdView.frame = CGRectOffset(_iAdView.frame, 0, self.view.frame.size.height - bannerSize.height);
    
    // 隠しておく
    _iAdView.hidden = YES;
    _iAdView.alpha = 0.0f;
    
    // delegate
    _iAdView.delegate = self;
    
    [self.view addSubview:_iAdView];
}

// iAdによって広告がloadされた時に実行される
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
    LOG(@"iAd:bannerViewDidLoadAd");
    
    // AdMob非表示
    [self stopAdMob];
    
    if (_iAdIsVisible == NO) {
        // iAdを表示
        [UIView beginAnimations:@"iAdViewShow" context:NULL];
        _iAdView.alpha = 1.0f;
        [UIView commitAnimations];
        _iAdView.hidden = NO;
        _iAdIsVisible = YES;
    }
}

// iAdが広告の読み込みに失敗したときに実行される
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
    LOG(@"iAd:didFailToReceiveAdWithError %@", [error localizedDescription]);
    
    if (_iAdIsVisible) {
        // iADを隠す
        _iAdView.hidden = YES;
        _iAdView.alpha = 0.0f;
        _iAdIsVisible = NO;
    }
    
    // Admob開始
    [self startAdMob];
}

// ユーザが広告をタップしたときに実行される
- (BOOL)bannerViewActionShouldBegin:(ADBannerView *)banner willLeaveApplication:(BOOL)willLeave
{
    LOG(@"Banner view is beginning an ad action");
    BOOL shouldExecuteAction = YES;
    if (!willLeave && shouldExecuteAction)
    {
        // ここにコードを挿入して、広告と競合する可能性のあるサービスを一時停止する
    }
    return shouldExecuteAction;
}

#pragma mark - AdMob

- (void)startAdMob
{
    LOG(@"AdMob:startAdMob isEnableAdMob: %d", _isEnableAdMob);
    
    if (_isEnableAdMob) return;
    
    // 確保
    _adMobView = [[GADBannerView alloc]
                  initWithFrame:CGRectMake(0.0,
                                           self.view.frame.size.height - GAD_SIZE_320x50.height,
                                           GAD_SIZE_320x50.width,
                                           GAD_SIZE_320x50.height)];
    
    // 隠しておく
    _adMobView.hidden = YES;
    _adMobView.alpha = 0.0f;
    
    // 配置
    _adMobView.adUnitID = ADMOB_PUBLISHER_ID;
    _adMobView.delegate = self;
    _adMobView.rootViewController = self;
    [self.view addSubview:_adMobView];
    
    // リクエスト
    GADRequest *request = [GADRequest request];
    [_adMobView loadRequest:request];
    
    _isEnableAdMob = YES;
    
    return;
}

- (void)stopAdMob
{
    LOG(@"AdMob:stopAdMob isEnableAdMob: %d", _isEnableAdMob);
    
    if (_isEnableAdMob == NO) return;
    
    // 解放
    [_adMobView removeFromSuperview];
    _adMobView.delegate = nil;
    [_adMobView release];
    _isEnableAdMob = NO;
    
    return;
}

- (void)adViewDidReceiveAd:(GADBannerView *)adView
{
    LOG(@"AdMob:adViewDidReceiveAd");
    
    // AdMobを表示
    [UIView beginAnimations:@"AdMobViewShow" context:NULL];
    adView.alpha = 1.0f;
    [UIView commitAnimations];
    adView.hidden = NO;
    
    return;
}

- (void)adView:(GADBannerView *)view didFailToReceiveAdWithError:(GADRequestError *)error
{
    LOG(@"AdMob:didFailToReceiveAdWithError %@", [error localizedFailureReason]);
    [self stopAdMob];
}

@end

アプリ申請


残る注意点としては、アプリ申請後にiTunes Connectの[Manage Your Applications]でiAdを有効にするのをお忘れなく、といったところでしょうか。


iAdを組み込んだアプリを申請しました。どうなるか楽しみです。

2012年3月11日日曜日

効果音をループ再生する

iPhoneで効果音(短いサウンド)を再生するには、System Audio Servicesを利用します。
AVFoundationと違って扱いは楽なのですが、その分機能が制限されています。
ループ再生の方法も提供されていないので、再生が終わったら再び再生するという方法で実現してみました。

ボタンが選択状態の間、効果音をループ再生します。

ボタンに結びつけるアクション。
- (IBAction)switchPlayingSE:(id)sender
{
    UIButton *button = sender;
    
    // 再生終了
    if (button.selected) {
        AudioServicesRemoveSystemSoundCompletion(_seID);
        AudioServicesDisposeSystemSoundID(_seID);
    }
    // 再生開始
    else {
        NSURL *url = [NSURLfileURLWithPath:[[NSBundlemainBundle] pathForResource:@"se"ofType:@"aif"]];
        AudioServicesCreateSystemSoundID((CFURLRef)url, &_seID);
        
        AudioServicesAddSystemSoundCompletion(_seID, NULL, NULL, soundCompletionProc, NULL);
        AudioServicesPlaySystemSound(_seID);
    }
    
    // ボタンの状態を切り替える
    button.selected = !button.selected;
}
AudioServicesAddSystemSoundCompletion()で再生が終わったあとに実行するハンドラを設定しています。
ハンドラは以下です。再び再生するのみ。
void soundCompletionProc(SystemSoundID ssID, void *clientData)
{
    // 再生完了したらもう一度再生する
    AudioServicesPlaySystemSound(ssID);
    
    return;
}

2012年3月9日金曜日

iPhone開発で使えそうなベクターアイコン

以下のサイトで、photoshopのシェイプ形式で様々なアイコンが公開されています。

Shapes4FREE

ライセンスが「個人利用、商用利用可」などかなり緩めに設定されています。
ありがたし。重宝しそうです。


xcode4でのローカライズ

xcode4.2でのローカライズ方法をまとめてみます。

文字列のローカライズ


これはもっと良い方法があるような気がするのですが(ソースコードを解析するツールがあるみたい)、私は以下の方法でやっています。
  1. プロジェクトのツリーを右クリックし、[New File]を選択する
  2. [Other]の[Empty]で、Localizable.stringsという名前のファイルを作成する

  3. 作成したファイルを選択し、右側のファイルインスペクターを表示する
  4. Localizationの+を押すと、Englishが追加される
  5. もう一回+を押し、Japaneseを追加する

  6. これでen.lprojフォルダとja.lprojフォルダにLocalizable.stringsが作成されます。

  7. [Open as] → [Property List]でLocalizable.stringsを開きます。適当に何か書き込んでおかないと開けないみたいです。
  8. あとは、Keyにキーワードを、Valueに文字列を追加していきます。

ローカライズ文字列をプログラム内で参照するには以下のようにします。
NSString *string = NSLocalizedString(@"keyword", @"説明");
第1引数にキーワードを入れます。


アプリケーション名のローカライズ


上で、Localizable.stringsのLocalizationでEnglishとJapaneseを追加したのと同じ手順で、InfoPlist.stringsをローカライズします。
あとは、CFBundleDisplayNameというキー名でアプリケーション名を設定します。



画像のローカライズ


上で、Localizable.stringsのLocalizationでEnglishとJapaneseを追加したのと同じ手順で画像をローカライズします。


ちなみに、ファイルをローカライズすると、Englishのリソースはen.lprojディレクトリに、Japaneseのリソースはja.lprojディレクトリに、自動で振り分けられます。


2012年3月7日水曜日

iPhoneでバイブレーション

iPhoneでバイブレーションを鳴らす方法は、効果音を鳴らす方法と同じです。

まずは、AudioToolBox.frameworkをリンクする必要があります。

プロジェクト設定から[Build Phases]を選択し、[Link Binary With Libraries]のプラスボタンを押し、AudioToolBox.frameworkを追加します。


あとは、ヘッダをimportした上で、
#import <AudioToolbox/AudioServices.h>
AudioServicesPlaySystemSoundを呼ぶだけです。
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
kSystemSoundID_VibrateがバイブレーションのサウンドIDです。

iOS5で明朝を表示する

開発側の話です。
iOS5で明朝フォントを使って文字を描画するには、drawRect:中で
[nsstring drawAtPoint:CGPointMake(left, top) withFont:[UIFontfontWithName:@"HiraMinProN-W3"size:fontSize]];
とやります。

でもなんかsizeWithFontしたときの高さがおかしいです・・・。

ちなみに、全フォントのファミリー名を表示するには以下のようにします。
NSArray *families = [UIFont familyNames];
id aFamily;
for (aFamily in families) {
    NSLog(@"\nfamilyName=%@\n  fontname=%@", aFamily, [UIFont fontNamesForFamilyName:aFamily]);
}

再開

「日誌」といいつつ2年近く間が空くとかありがちすぎて・・・
熱が出て苦しい今、これを機会に再開したいと思います。
ここ最近やったことをつらつら書いていきます。
m(_ _)m