2017年9月28日木曜日

OSをアップグレードして、xcodeも最新版を入れたら、色々と良からぬことが…

・プロジェクトを開くと突然落ちる。
 ~/Library/Developer/Xcode/DerivedDataを削除すると治る。
・ビルドのターゲットの選択肢が「My Mac」だけで、iOSのシミュレータが出てこない。
 プロジェクト.xcodeproj/xuserdataを削除すると治る。
・xcodeのライセンス同意がなされていない扱いになってしまい、色々と困ってしまう。
 sudo xcodebuild -license
 で見てみると、パスが違っている。
 sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer
 でパスを再設定し、
 sudo xcodebuild -license
 を再チャレンジ。

2017年9月18日月曜日

xcode ひさびさに新規プロジェクトを作成

ひさびさにxcodeで新規プロジェクトを作成したら、いろいろと忘れていたことがあったので、五月雨式に記録しておきます。

・プロジェクトにファイルを追加する時は、Copy items if needs=チェックした方が無難。
・Bundle display nameがないと、ホーム画面上のアイコンの下の文字列がPRODUCT_NAMEになってしまう。
・ある静的ライブラリを使うため、Other Linker Flagsに-ObjCをセット。
・ヘッダが見つからない。以下を追加。
 Always Search User Paths=YES
 User Header Search Paths=/Users/~/*.* recursive
・Archiveするとエラーが出る。
 ENABLE_BITCODE=NOにしたら治った。
 全てのライブラリがbitcodeを含んでいれば、YESでいいらしい。
・ビルドするとTestsに対して、linker command failed with exit code 1 (use -v to see invocation)が出る。
 iOS Deployment Targetがサポートしていないバージョンになっていた。
 xcode8にしたら、もうiOS7はダメってことか…

xcode とにかく簡単に課金処理したい


1. エッセンスだけ書き下すと、こんな感じです。

こんな感じと言いながら、これでリリースしているので、ちゃんと動くと思います。


- (void)viewDidLoad {
    [super viewDidLoad];
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    switch (indexPath.section) {
        case 0:
            if (indexPath.row == 0) {
                //In-App Purchases機能制限の確認
                NSString *productID = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];
                if ([SKPaymentQueue canMakePayments]) {
                    SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObjects:productID, nil]];
                    request.delegate = self;
                    [request start];
                    [self.indicator startAnimating];
                }
            }
            break;
    }
}

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
    if (response == nil || [response.invalidProductIdentifiers count] > 0) {
        [_indicator stopAnimating];
        return;
    }
    _product = nil;
    for (_product in response.products) {
        break;
    }
    if (_product == nil) {
        [_indicator stopAnimating];
        return;
    }
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"title", nil) message:msg delegate:self cancelButtonTitle:NSLocalizedString(@"Restore", nil) otherButtonTitles:NSLocalizedString(@"Purchase", nil), nil];
    [alert show];
}

- (void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)buttonIndex
{
    SKPayment *payment;
    switch (buttonIndex) {
        case 1:
            payment = [SKPayment paymentWithProduct:_product];
            [[SKPaymentQueue defaultQueue] addPayment:payment];
            break;
        case 0:
            [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
        default:
           break;
    }
}

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            default:
            case SKPaymentTransactionStatePurchasing:
                break;
            case SKPaymentTransactionStateFailed:
                [queue finishTransaction:transaction];
                [_indicator stopAnimating];
                break;
            case SKPaymentTransactionStatePurchased:
            case SKPaymentTransactionStateRestored:
                [queue finishTransaction:transaction];
                [_indicator stopAnimating];
                break;
        }
        break;
    }
}

- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
    [_indicator stopAnimating];
}

- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
    [_indicator stopAnimating];
}

2. 問題は、各stopAnimatingのところに、何を埋め込むかです。


【失敗処理なら】
StoreKitが勝手に出すダイアログもあるので、整理が難しいのですが、ダイアログが足りないと感じた場合には、自前ダイアログを埋め込んでいます。

【成功処理なら】
成功した場合には、成功したことをDocumentに保存しておく必要があります。
次回、起動時の際にはこの情報を読み込んで、例えば購入したアイテムを表示するとか、購入した機能を生かすとか、処理をしなくてはいけません。
一番簡単なのはplistにBooleanで書き込むことですが、App Storeの審査は通りますが、簡単にハッキングされてしまうので、アプリ販売で稼ぎたいなら、暗号化は必須でしょう。

ちなみにstopAnimatingは6箇所ありますが、4箇所は失敗で、2箇所が成功です。

xcode 点滅(ブリンク)させる方法

1. ググれば、以下のコードが出てきます。これを埋め込めば、とりあえずブリンクします。

[UIView animateWithDuration:0.5 delay:0
    options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat
    animations:^{
        (ブリンクさせたいオブジェクト).alpha = 0;
    }
    completion:^(BOOL finished){
        (ブリンクさせたいオブジェクト).alpha = 1;
    }
];

2. 動かしたり、止めたりしたい時は、どうするの?

私は、(ブリンクさせたいオブジェクト).hiddenでコントロールしてます。
1.のブロックは、裏で動きっぱなしになりますが、無理に止めるより放っておいた方が良いという判断です。

3. 困ったこと。

他のアプリに遷移し、戻ってきた時、ブリンクしなくなる点だと思います。
これはTimerに1.を埋め込んで、回すことで回避している人が、いらっしゃるようですが、
私は、enterForegroundに、1.を埋め込んで回避しています。

enterForegroundとは勝手命名のハンドラですが、UIApplicationWillEnterForegroundNotificationを受けるよう、viewDidLoadでNSNotificationCenterに登録したものです。
他のアプリから戻ってきた時に、呼び出されます。

4. 結局処方箋は何?

・viewWillAppearと、enterForegroundに、1.を埋め込む。
・hiddenで入切する。
かなと思います。

viewWillAppearは、ナビゲーションとかで他のタブに遷移し、戻ってきた時の対策です。
デバッガーでトレースすればわかりますが、
「他のタブに遷移→他のアプリに遷移→他のアプリから戻る→他のタブから戻る」
みたいなイジメ試験をやっても、正しく動作することがわかります。