/* */ /* */

2012年11月26日月曜日

【iPhoe】価格の多言語対応について【StoreKit】

またもやiPhoneの話題です。
いい加減Androidに触りたいです。Rubyでも遊びたいです。


さて、今回の話題はiPhoneでの多言語対応、特に課金処理部分で私が躓いた点です。

課金処理の入ったiPhoneアプリで多言語対応にする際、
問題となるのは金額の表記だと思います。

日本の人には円表記で、海外の人には米ドル表記にしたい!となった時にどうするかなのですが、
公式のリファレンスそのままでオーケーです。


- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)responseの中で


NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setLocale:product.priceLocale];
NSString *formattedString = [numberFormatter stringFromNumber:product.price];


この様にするだけで、自動で価格の表記が変わってくれます。

さて、ここで問題なのが”何に合わせて”表記が変化するのかです。
自分は最初、他の部分のローカライズと同じで、iPhone本体の言語設定から拾ってくれるのだろうと考えておりました。
実際は、”最後にログインしたAppleアカウントに紐付いている国”の表記がされます。
多分、請求とかでややこしくなるのを避ける措置なのかなと思います。

なので、本体設定を英語にして、表記をUSに設定しても、円単位の数字が返って来る事があります。
テストする時は他の国に設定したアカウントでログインする様にしてください。
ホント面倒です・・・


【iPhone】課金処理が勝手に走る状態の防止【StoreKit】

※この記事の内容はどう考えても推奨されません。その場しのぎです。
 実装しちゃって課金処理で不利益出ても何も出来ません。

※iOS7、iOS8で再度組み直してみたのですが、今コレ使えないみたいです。
 ダイアログ出る前にSKPaymentTransactionStatePurchasingが
 飛んできませんでした。

何やらiPhoneの課金処理に関する掲示板などを見ていると、
「アプリを起動すると何故か課金処理が走ってしまう」
というのを時々見かけます。

取りあえずその場しのぎの策に行き着いたので書き残します。

iPhoneアプリを起動して、特に操作をしていないのに、
課金のダイアログが出て、IDとパスワードを求められる事があります。
これは、前回何かしらのアプリを起動して、課金処理に入り、
課金処理が正常に終了しないままアプリが落ちるとなります。
次回起動した後、特定のタイミング(StoreKitさんが要らん親切で通知してくれます。)でダイアログが呼び出されます。

本当に厄介!!

この問題に突き当たるという事は、公式のプログラミングガイド通りに、
アプリ起動直後にオブザーバー(何か課金処理を管理してるっぽい奴)の登録をしているのだと思われます。
そすると、最初に登録したオブザーバーさんが律儀に途中で終わっちゃってるトランザクションを拾って来てくれるみたいです。(しかもストアキットさんの任意のタイミングで・・・)

なので。。。

これ破棄すればオーケー

「ちゃん再処理しろよ」という白い林檎からのプレッシャーを感じますが取り合えず無視
 だっていきなりダイアログ出てくるとか不審過ぎるし、タイミング取得できる様な仕組みも見当たらないし・・・


本題です。
ここではNonCondensableな課金を例として挙げております。


AppDelegate.h

@interface AppDelegate : UIResponder 


AppDelegate.m
//未完トランザクション用

-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{

    for (SKPaymentTransaction *transaction in transactions) {

        switch (transaction.transactionState) {

    

            case SKPaymentTransactionStatePurchasing: {// 購入中

                [queue finishTransaction:transaction];

                //アイテムID取得したいならこの部分

                break;

            }

            case SKPaymentTransactionStatePurchased: {// 購入成功

                [queue finishTransaction:transaction];

                break;

            }

            case SKPaymentTransactionStateFailed: { //購入失敗/中断

                [queue finishTransaction:transaction];

                break;

            }

            case SKPaymentTransactionStateRestored: {// 購入履歴復元

                [queue finishTransaction:transaction];

                break;

            }

        }

    }

}


//オブザーバーの削除(NSNotificationCenterで呼ばれる)


-(void)removeTransaction {

    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

}

//オブザーバーの登録(NSNotificationCenterで呼ばれる)

-(void)addTransaction {

    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

}


//なんか、スプラッシュ画面で呼ばれてる部分


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

    〜〜〜〜〜〜〜

    //何か色んな処理 

    〜〜〜〜〜〜〜


    //オブザーバーの登録

    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

    

    // 通知センターに登録

    //オブザーバーの破棄をするメソッド

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeTransaction) name:@"remove" object:nil];

    //オブザーバーの登録をするメソッド

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(addTransaction) name:@"addtran" object:nil];

}
という具合に、アプリ起動時に未完トランザクション用のオブザーバーの登録を済ませてしまいます。
未完トランザクションがあった場合、-(void)paymentQueueホニャララが呼ばれますので取りあえずfinishTransactionを呼んじゃいます。
ダイアログが表示されるのは、1回目に
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
に入った後(1回目は購入中に入ります)なので、そこで終了させてしまえば
ダイアログは出ないみたいです。
「任意のタイミングで処理したい」ならば、「//購入中」の部分で
アイテムIDを適当なArrayにでも突っ込んで、
お好きなタイミングで課金処理を走らせると良いかと思います。
取りあえず破棄完了!!
でもこのままだと、未完じゃない新規のトランザクションまで破棄されちゃいます。
なので、別に実際の課金処理を用意してやります。
MyStoreObserver.h
#import 
@interface MyStoreObserver : NSObject
@end
MyStoreObserver.m
@implementation MyStoreObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    NSLog(@"paymentQueue:updatedTransactions");
    for (SKPaymentTransaction *transaction in transactions)
    {
        switch (transaction.transactionState)
        {
            case SKPaymentTransactionStatePurchasing:
            {
                // 購入処理中
                break;
            }
            case SKPaymentTransactionStatePurchased:
            {
                [queue finishTransaction:transaction];
                // 購入処理成功時の処理
                break;
            }
            case SKPaymentTransactionStateFailed:
            {
                [queue finishTransaction:transaction];
                // 購入処理エラー。ユーザが購入処理をキャンセルした場合もここにくる
                // エラーが発生したことをユーザに知らせる
                // [self failedTransaction:transaction];
                if (transaction.error.code != SKErrorPaymentCancelled){
                    //エラーの場合
                } else {
                    //ユーザーがキャンセルした場合
                }
                break;
            }
            case SKPaymentTransactionStateRestored:
            {
                [queue finishTransaction:transaction];
                // リストア処理
                [self restoreTransaction:transaction];
                break;
            }
        }    
    }
}
// 全てのリストア処理が終了(その後、「購入処理の終了」が呼ばれる)
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue 
{
     //何かの処理   
}
// リストアの失敗(cancel時も呼ばれる)(その後、「購入処理の終了」は呼ばれない)
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
    //何かの処理
}
// 購入処理の終了
- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions 
{    
    //南下の処理
}
@end

んでもって、後は実際課金走らせる部分で
○○○.m
//上の方で宣言しておく
MyStoreObserver *mySKObserver;
~~~~~
~~~~~
//ここでダミー破棄の通知
NSNotification *n = [NSNotification notificationWithName:@"remove" object:self];
[[NSNotificationCenter defaultCenter] postNotification:n];
//重複すると怖いので一応破棄。多分無くても大丈夫
[[SKPaymentQueue defaultQueue] removeTransactionObserver:mySKObserver];
//実際に走らせるオブザーバーを登録
[[SKPaymentQueue defaultQueue] addTransactionObserver:mySKObserver];
~~~~~~~~~~
~~~~~~~~~~
-(void)viewWillDisappear:(BOOL)animated{
    //使い終わったオブザーバーをここで削除
    [[SKPaymentQueue defaultQueue]removeTransactionObserver:mySKObserver];
    //ノーティフィケーションも削除
    [[NSNotificationCenter defaultCenter]removeObserver:self];
    
    //一応もう一回ダミーを立てるべきだろうか・・・
    //NSNotification *n = [NSNotification notificationWithName:@"remove" object:self];
}

という感じで、ダミーのオブザーバーと実際に課金走らせるオブザーバーを交換して上げる。
取りあえずはこれで動いてる感じ!!
参考サイト様一覧
【なんてこったいブログ】
http://nantekottai.com/2011/10/28/storekit/
【アルデンテ】
http://d.hatena.ne.jp/kozy_twt/20110401/1301680959
In-App Purchaseプログラミングガイド
https://developer.apple.com/jp/devcenter/ios/library/documentation/StoreKitGuide.pdf



2012年11月14日水曜日

【iPhone】iPhoneでデバッグ時にだけ走るコード【デバッグ】

今回はiPhoneアプリのお話。(Javaが打ちたい。。。ブログ書く時間ない。。。)

取りあえず私のやらかしたお話・・・
iPhoneアプリのデバッグコード有効のままリリースしちゃった☆

こんなミスを二度と繰り返さない様に、デバッグの時だけ有効になる
コードの書き方を残しておきます。

#define DEBUG 1
とか書いて
if(DEBUG){〜〜}
とか書いていたのですが、この"DEBUG"を書き間違えるというミスを今回侵しました。
なので、ビルドコンフィギュレーション(BuildConfiguration)によって自動で
読み込む部分を変える様にしてみます。

Xcode左側のファイル一覧からProjectを選択
Project->BuildSettings->Apple LLVM compiler 4.1 - Preprocessing










夫々で対応したコンフィグに対する定数を入れて行きます。
自分はデバッグ環境の時のみ1になるようにしております。
これで、#define定義した時と同じ動きをする模様。
しかも、リリースとかデバッグとかアドホックとかのビルド環境によって値を変えられます!

後はコード内で
if(DEBUG){
    //デバッグ時にのみ走らせたいコード
}else{
    //本番時にのみ走らせたいコード
}
という書き方をすればオーケー。

なんか、Xcodeのバージョンによっては最初からDEBUG=1が
設定されている事もあるようです。

序でに、こちらのサイトさんでは、デバッグ構成時にだけ走るLogの書き方が載っておりました。
http://goo.gl/OFDqY


最近Rubyのお勉強始めました。