Objective-C」カテゴリーアーカイブ

【Swift4】【Objective-C】UNUserNotificationCenterで通知の許可をもらう。

毎度のことながら自分用の劣化コピー備忘録ブログです。

自分がメインで手を入れているアプリはObjective-Cで作って徐々にSwiftに置き換わっています。
そんな中でlocalNotificationがiOS10以降でdepricatedになりUNUserNotificationCenterを推奨されたので調べてみました。

参考サイト
Xcode|iOS10で新しくなったUNUserNotificationCenterの使い方を調べてみた
ローカルプッシュ通知(iOS10以降) – Qiita
[iOS 10] User Notifications framework を使用してリモート通知を受け取る処理を実装する #wwdc | DevelopersIO

まずUNUserNotificationCenterを使う前提としてUserNotifications.frameworkが必要です。
入れておきましょう。
スクリーンショット 2019-01-27 13.01.20

とりあえず未だ、AppDelegateはObjective-CなんでObjective-Cで書きます。
まずはUserNotificationをインポートしてデリゲートをセットします。

~略~
#import "AppDelegate.h"
@import UserNotifications;

@interface AppDelegate ()<UIApplicationDelegate,UNUserNotificationCenterDelegate> {

~略~

次にUNUserNotificationCenterのユーザーに対する許諾をもらう部分です。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    //バッジ、サウンド、バナーの許可を求める。
    [[UNUserNotificationCenter currentNotificationCenter]
     requestAuthorizationWithOptions:(UNAuthorizationOptionBadge |
                                      UNAuthorizationOptionSound |
                                      UNAuthorizationOptionAlert )
     completionHandler:^(BOOL granted, NSError * _Nullable error) {
      if (granted) {
          // APNSはここで設定するの?
      }
     }];

    [UNUserNotificationCenter currentNotificationCenter].delegate = self;
//    [application registerForRemoteNotifications];//これはリモート通知の場合に要るのかな?

以上でアプリがバックグランドの時はローカル通知を受けれますが、フォアグラウンドでも通知が受けれるようにするには下記のメソッドを追加します。

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
       willPresentNotification:(UNNotification *)notification
         withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
    // アプリがフォアグランドにいた場合の通知動作を指定する。
    completionHandler(UNNotificationPresentationOptionSound |
                      UNNotificationPresentationOptionAlert);
};

viewControllerはほぼSwiftに置き換わってるのでObjective-Cでのテストはしていません。
以下は参考サイトからのコピペですすみません。
多分動くと思います。(-人-)

// UserNotificationsのインポートが必要

- (IBAction)push:(id)sender {

    // 通知を作成
    UNMutableNotificationContent *unMutableNotice = [UNMutableNotificationContent new];
    // title、body、soundを設定
    unMutableNotice.title = @"おはようございます";
    unMutableNotice.body = @"今日も一日頑張ってください!";
    unMutableNotice.sound = [UNNotificationSound defaultSound];

    // 通知タイミングを設定(今回は、実装後5秒後に通知を受信します)
    UNTimeIntervalNotificationTrigger *triger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5 repeats:NO];

    // リクエストの作成
    UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"リクエスト" content:unMutableNotice trigger:triger];

    // NUserNotificationCenterにリクエストを投げる
    [UNUserNotificationCenter.currentNotificationCenter addNotificationRequest:request withCompletionHandler:nil];
}

追記。
swiftで通知の許可を貰う場合。
参考サイト
<Swift>iOS 10 User Notifications Framework実装まとめ – Qiita

UserNotifications.frameworkを入れるのは上記と同じ。

で、インポートします。

import UIKit
import UserNotifications //ここ

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate ,UNUserNotificationCenterDelegate { //フォアグラウンドで受け取るにはデリゲートも。

didFinishLaunchingWithOptionsで許可を求めます。

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        //通知許可のRequest。
        let center = UNUserNotificationCenter.current()
        center.requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in
            if granted {
                print("Allowed")
            } else {
                print("Didn't allowed")
            }        }

        // フォアグラウンドで受け取るため、UNUserNotificationCenter のデリゲートを設定する
        UNUserNotificationCenter.current().delegate = self
        return true
    }

フォアグラウンドでの通知の受け取りは以下のMethodを実装。

    // アプリがフォアグラウンドの時に通知を受け取る
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.sound ,.alert]) // 通知バナー表示、通知音の再生を指定
    }

テスト用に5秒で鳴る通知。

        //ローカル通知の設定
        let content = UNMutableNotificationContent()
        content.title = "Title"
        content.subtitle = "Subtitle" // 新登場!
        content.body = "Body"
        content.sound = UNNotificationSound.default
        
        // 5秒後に発火
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
        let request = UNNotificationRequest(identifier: "FiveSecond",
                                            content: content,
                                            trigger: trigger)
        
        // ローカル通知予約
        UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)

上記をviewWillApperあたりに入れると、開くたびに5秒後に鳴るテストが出来ます。\(^o^)/

【Swift4】【UIDatePicker】のバグふたたび。UIDatePickerMode .countDownTimer

Swiftには、いや、Xcodeにはバグが有る。
2年前にもObjective-Cで泣かされた。
その時はなんとかなったけどそのバグはSwiftになっても直ってなかった。
とゆーか、よりひどくなってた。

そのバグはUIDatePickerのcountDownTimerモードにある。
マイナーすぎて使い所無い機能なのか、それ自体の情報も少ない。

UIDatePickerのcountDownTimerモードというのは、その名の通り、
24時間までのカウントダウンの時間を設定するためのピッカーだ。
ピッカーで10分と設定すると600秒の値が渡される。

さて、そのバグというのはピッカーで数値を決めても一回目のときはスルーされてしまうとゆーものだ。
二回目以降はちゃんとデータが渡されるのだが、一回目はスルーされてしまう。
10分を選んでも全然反映されない。
そのため、例えば10分に設定したい時は、一度11分にしてから10分にするなどというとてもめんどくさいことになる。
そんな状態ではとてもリリースできたものではない。

ネットを調べてみるといくつか情報はある。
古いのはこちら。
ios – UIDatePicker bug? UIControlEventValueChanged after hitting minimum internal – Stack Overflow
どうもiOS7の頃からあるバグらしい。
一応解決策も提示してあり、僕も2年くらい前にお世話になった。
割りと最近の記事ではこちら。
Xcode – DatePickerの値選択時の表示エラーが直らない|teratail
こちらはswift3での解決策が示してある。ま、Objective-cからSwiftに変わっただけでやってることは同じ。

そちらを参考にしてピッカーのモードをカウントダウンタイマーにしたあと、
バグ回避のためDispatchQueue処理を入れてみた。
(下記参照)

//キーボードをピッカーにする
    func keybordSetUp() {
        timeText.delegate = self as? UITextFieldDelegate
        //テキストフィールドのキーボードをdatePickerにする
        timeText.inputView = datePicker
        //ピッカーのモードをカウントダウンタイマーに
        datePicker.datePickerMode = UIDatePickerMode.countDownTimer
 
        //バグ回避のためDispatchQueue
        DispatchQueue.main.async {
//            self.datePicker.countDownDuration = (初期値)
            self.datePicker.countDownDuration = TimeInterval(self.countDownSetValue)
        }
        
        //ピッカーの値の通知先を設定。
        datePicker .addTarget(self,
                              action: #selector(self.datePickerValueChanged),
                              for: UIControlEvents.valueChanged)
    }
    
 //ピッカーの値を反映
    @objc func datePickerValueChanged() {
        
        print("カウントダウンピッカーがまわったよ")
        downCountTime = 0
        countDownSetValue = Int(datePicker.countDownDuration)
        count = Int(datePicker.countDownDuration)
        
        let hh = Int(count/(60*60))
        // doubleで余りを出す計算をするときはfmod
        let mm = Int(fmod(Double(count/60),60))
        let ss = Int(fmod(Double(count),60))
        timeText.text = String(format: "%02d:%02d:%02d", hh, mm, ss)
    }

自分だけなのか、Swift4以降の仕様なのかこの状態ではやはり一回目の設定はスルーされた。
半日ほど試行錯誤したがどうしてもうまくいかない。
そんな中、キーボードを仕舞ってまたキーボードを出したときもまた同じく一回目がスルーされていることに気づく。

そうであれば、キーボードが出たときにバグの回避処置を入れればなんとかなるのでは無いか。
とゆーことで、ノーティフィケーションをつかい、キーボードが出たときに回避処置が入るようにしてみた。

こんな感じ。

    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
  (中略)
        //キーボードが出たとき(UIKeyboardDidShow)に通知する。
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(self.showKeyBoard(notification:)),
            name: NSNotification.Name.UIKeyboardDidShow,
            object: nil
        )
    }


    @objc func showKeyBoard(notification: Notification) {
        print("キーボードが出た")
        DispatchQueue.main.async {
            //            self.datePicker.countDownDuration = (初期値)
            self.datePicker.countDownDuration = TimeInterval(self.countDownSetValue)
        }
    }

結果は見事成功!\(^o^)/
なんとかカウントダウンのピッカーが使えるようになった。

時期に忘れてしまうと思うので備忘録として残しておく。/(^o^)\
あと、誰かの参考になったら嬉しい。

【Swift4】【Objective-c】SKStoreReviewController でアプリ内レビュー

最近流行りのアプリの中でレビュー(アプリの評価)をつけてもらうやつを調べてみた。

参考サイト
SKStoreReviewController を単純な条件で出すだけでも好意的なレビューが増えて良かった
SKStoreReviewController で App Store の評価数が急増した話 – wootan’s diary

機能としてはiOS10.3以降が対応のようです。
表示されるのは年に3回まで。
有意の時点で呼び出して、年に3回に達していないと表示されるらしい。
つまり、何回起動したときとか、そういう条件をつけて、レビューの請求をされてもおかしくない、
できれば好レビューが貰えそうな時に表示されるようにするのが良いっぽい。

コードとしてはStoreキットをインポートして使う。

Swiftの場合。

import StoreKit
SKStoreReviewController.requestReview()

objective-cの場合。

#import <StoreKit/StoreKit.h>
[SKStoreReviewController requestReview];

一度この表示が出て評価を送信すると、その後は出なくなるのか、それとも、直後でも呼び出されるとまた表示されてしまうのか、そのへんの詳しい挙動がわからんのでそれなりの対応が必要そうです。

と、ゆーことで、いま手入れしてるobjective-c用にコードを書いてみた。

- (void)countToReview{
    // NSUserDefaultsの取得
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    //初期値設定用のインスタンス。
    NSMutableDictionary *setDefaults = [NSMutableDictionary dictionary];
    //レビュー表示までのカウント数の初期値。
    [setDefaults setObject:@"90" forKey:@"countToReview"];
    //初期値を保存。すでに値がある場合は無視される。
    [defaults registerDefaults:setDefaults];
    
    //countToReviewの値を1足す。
    [defaults setInteger:((int)[defaults integerForKey:@"countToReview"] + 1 )  forKey:@"countToReview"];
    [defaults synchronize];

    NSInteger count = (int)[defaults integerForKey:@"countToReview"];
    NSLog(@"このページの表示%ld回目",(long)count);
    
    if (count == 100) {
        if (@available(iOS 10.3, *)) {
            //レビューの請求
            [SKStoreReviewController requestReview];
            NSLog(@"iOS10.3以上だよ");
        } else {
            NSLog(@"iOS10.3未満だよ");
        }
        //カウントを0にリセット
        [defaults setInteger:0 forKey:@"countToReview"];
    }
}

最初は10回呼ばれたら、その後は100回呼ばれる毎にレビューが請求されます。

あ、バージョンでの切り分けはこちらを参考にしました。
Objective-C で OS バージョン判定を簡潔に行う方法

【Swift4】objective-cからSwiftのクラスを呼び出す。

おぶしーからすいふとに切り替えて暫く経ちますが、古いアプリなど丸っとスイフトに書き換えるのは面倒なので、
objective-cはそのままで、新規クラスはSwiftでと混在させてます。

んで、objective-cからSwiftのクラスを呼び出す時にちょっと詰まったんで備忘録。

参考サイト
Objective-CコードからSwiftのクラス/プロトコルを使う方法(フレームワーク開発編)
Objective-CベースのプロジェクトからSwiftのクラスを呼び出す
SwiftとObjective-Cの相互利用する際の注意

例えば、
hogehogeというアプリがあって、
higehige.h higehige.m というobjective-cのクラスが有って、
hagehage.swift というswiftのクラスがあるとする。

higehige.m で hagehage.swiftクラスを使いたいので、適当にインポートしてみた。

#import "hagehage.swift"  //'hagehage.swift'file not found

エラーが出た。(´・ω・`)ショボーン

ネットで調べて、-Swift.hを着けたら良いのね!(`・ω・´)シャキーン

#import "hagehage-swift.h"  //'hagehage-swift.h'file not found

あれ?(´・ω・`)

#import "hagehage.swift"  //'hagehage.swift'file not found
#import "hagehage-swift.h"  //'hagehage-swift.h'file not found
#import "hagehage-Swift.h"  //'hagehage-Swift.h'file not found
#import "<hagehage>-Swift.h"  //'<hagehage>-Swift.h'file not found
#import <hagehage-swift.h>  //'hagehage-swift.h'file not found
#import <hagehage-Swift.h>  //'hagehage-Swift.h'file not found

ムキー!なんで通らんのや!/(^o^)\

じっくり調べなおすと、、、「プロダクト名」-Swift.h なのか…。orz

#import "hogehoge-Swift.h"

これでよかったようです。/(^o^)\
クラス呼び出すんやからクラス名だと思うやん…。(´・ω・`)

ちなみに呼ばれる側は

@objcMembers
class hagehage : NSObject {

のように@objcMembersをつけるか、メソッドの前に @objc をつけておくようです。

他にもハマりどころはありそうなので他サイトも参考に。
うじゃうじゃ。

2018/07/10追記
上記のようにswiftファイルをインポート出来るようになりました。
で、次の記事にあるSEManager.swiftで音を鳴らしたいのですが、
objective-cからSEManager.swiftのメソッドを呼び出そうとするとエラーが!!

[[SEManager sharedInstance]playSoundWithSoundName:@"hit.mp3"];    // No visible @interface for 'SEManager' declares the selector 'playSoundWithSoundName'

SEManager.swiftのplaySoundメソッドは引数があるのでobjective-c側からはplaySoundWithSoundNameで呼び出します。
xcodeの変換サポートでも候補に出るし、問題なく認識していそうなのですが、どーやってもNo visibleのままです。

よくわからないまま試行錯誤を繰り返すこと丸一日、SEManager.swiftのplaySoundメソッドの中にある’throws’を消すと認識されました。
throws,do,try,catchというのはよく分かってないですがAVAudioPlayerを使う時にXcodeからつけろーと言われます。
このthrowsを消すと、別のswiftファイルからplaySoundメソッドを呼び出す所で黄色三角のエラーが出ますがビルドは出来ます。
なんとも明確な解決策が見いだせませんでしたが、とりあえず動いたので良しとしつつ、自分の無能を呪っときましょう。/(^o^)\

could not build module googlemobileads というエラー

Xcodeを7から8にアップデートして、
環境を2009のmacminiから2016のMacBook Proに移行して、
何が原因だかわからんが、ちょっと前に作ったアプリをエミュレーションで起動しようとしたらエラーが出た。
Objective-cで作ったアプリだ。

エラーメッセージはこんな感じ。
could not build module googlemobileads

どうもGoogleMobileAds.hが見つからん的なエラーが幾つか出る。
ぐぐったところimportの書き方を変えてみる的な記事が出てきたがそれでは治らんかった。

cocoa podでインストールしたものを削除して再インストールしてみたらどーかなとやってみた。

参考サイト
CocoaPodsのアンインストール方法 · Yukim Log

podファイルで
pod ‘Async’
だそうだ。

↓podファイル

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target ‘アプリ名’ do


pod ‘Async’


  end

end

んでもってターミナルから実行。

ディレクトリは移動しておこうね。ヽ(^。^)ノ

$ pod install
Analyzing dependencies
Downloading dependencies
Using Google (2.0.4)
Using Google-Mobile-Ads-SDK (7.7.1)
Using GoogleAnalytics (3.14.0)
Using GoogleInterchangeUtilities (1.2.0)
Using GoogleNetworkingUtilities (1.2.0)
Using GoogleSymbolUtilities (1.1.0)
Using GoogleUtilities (1.3.0)
Generating Pods project
Integrating client project
Sending stats
Pod installation complete! There are 2 dependencies from the Podfile and 7 total pods installed.

んで、アプリ.xcworkspaceも削除。

再度podファイルを編集

# Uncomment the next line to define a global platform for your project
 platform :ios, '9.0'

target 'アプリ名' do
  # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
   use_frameworks!

pod 'Google-Mobile-Ads-SDK', '~> 7.0'
pod 'Google/Analytics'

  # Pods for アプリ名

  target 'アプリ名Tests' do
    inherit! :search_paths
    # Pods for testing
  end

end

んで、ターミナルから実行。

$ pod install
Analyzing dependencies
Removing Google
Removing Google-Mobile-Ads-SDK
Removing GoogleAnalytics
Removing GoogleInterchangeUtilities
Removing GoogleNetworkingUtilities
Removing GoogleSymbolUtilities
Removing GoogleUtilities
Downloading dependencies
Installing Async (0.2.0)
Generating Pods project
Integrating client project
Sending stats
Pod installation complete! There is 1 dependency from the Podfile and 1 total pod installed.

[!] Your Podfile has had smart quotes sanitised. To avoid issues in the future, you should not use TextEdit for editing it. If you are not using TextEdit, you should turn off smart quotes in your editor of choice.
kurodakouseinoMacBook-Pro:DaimokuCounter kurodakousei$ pod install
Analyzing dependencies
Removing Async
Downloading dependencies
Installing FirebaseAnalytics (3.6.0)
Installing FirebaseCore (3.4.6)
Installing FirebaseInstanceID (1.0.8)
Installing Google (3.0.3)
Installing Google-Mobile-Ads-SDK (7.16.0)
Installing GoogleAnalytics (3.17.0)
Installing GoogleInterchangeUtilities (1.2.2)
Installing GoogleSymbolUtilities (1.1.2)
Installing GoogleToolboxForMac (2.1.0)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `DaimokuCounter.xcworkspace` for this project from now on.
Sending stats
Pod installation complete! There are 2 dependencies from the Podfile and 9 total pods installed.

新しく.xcworkspaceファイルも作られて、エラーが消えました。\(^o^)/

【Localizable.strings】多言語化した後からとか、コードで多言語化。

参考サイトを見てもらったほうが速い。(^_^;)

参考サイト
【Swift】Xcode6でiOSアプリの多言語・ローカライズ対応のまとめ – mzgkworks

、、、だけではよろしく無いので簡単に画像つきで書いてみる。

File -> New -> File…で、Strings Fileを選択する
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-07-06-19-05-40
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-07-06-19-06-25

ファイル名:Localizable.stringsとして、Supporting FilesのGroupに追加
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-07-06-19-07-15

Localizable.stringsを選択して、ユーティリティエリアのShow the File inspectorで、Localize…をクリック
表示されたダイアログでBaseを選択し、Localizeをクリック
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-07-06-19-07-47

Localizable.stringsを選択して、ユーティリティエリアのLocalizationでJapaneseにチェックをつける
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-07-06-19-08-15

Localizable.stringsにLocalizable.strings(Base)とLocalizable.strings(Japanese)の2つが追加される
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-07-06-19-08-21

多言語化したい項目(タイトルやメッセージなど)のキー項目を値を定義する
Baseファイル
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-07-06-19-13-23

*/

"reviewButton" = "Write Review   >";

Japaneseファイル
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-07-06-19-13-23

 */

"reviewButton" = "アプリを評価する。   >";

多言語化したい部分のコードを変更。
NSLocalizedString(@”reviewButton”, nil)

    //ボタンのローカライズ
    [_reviewButton setTitle:NSLocalizedString(@"reviewButton", nil) forState:UIControlStateNormal];

これでiPhoneの言語設定によってローカライズされるはずです。

kCTLeftTextAlignment is deprecated in IOS 9

ちょっと前に作ったアプリを久しぶりに開いて実行したら黄色い三角がいっぱい。

よくよく見ると、
kCTLeftTextAlignment is deprecated in IOS 9
となっている。

kCTLeftTextAlignmentといったコマンドが非推奨になってるようだ。

日本語ページで検索したが、
コマンドがkCTLeftTextAlignment「に」変更されたとゆーページしか無かった。
参考サイト
arudenteのアプリ

再変更されたとゆーことか。
で、英語サイトですぐに答えは見つかった。
参考サイト
kCTLeftTextAlignment is deprecated in IOS 9 · Issue #594 · TTTAttributedLabel/TTTAttributedLabel · GitHub

kCTLeftTextAlignment
kCTCenterTextAlignment
kCTRightTextAlignment
kCTNaturalTextAlignment

これらが、

kCTTextAlignmentLeft
kCTTextAlignmentCenter
kCTTextAlignmentRight
kCTTextAlignmentNatural

に変わったようだ。

割りとビミョーんな変化だがそうせにゃならん必要性がなんかあったんかね。
うじゃうじゃ。

【UIActivityIndicatorView】インジケーターの表示とか。

何かの処理中にインジケーターを表示して他の操作をさせなくしよう。

参考サイト
[iOS]アプリ内課金の金額をあらかじめ表示する – Qiita

まずUIviewを全画面に配置しautolayoutで全画面に入れます。
それに載せるようにUIActivityindicetorViewを配置します。
これはViewに対しX軸Y軸の中心になるようにautoLayoutします。

uiViewはBackGroundをBlackにし、Alpha値を0にしておきます。
UIActivityindicetorViewは、StyleをLarge Whiteにしておきます。

それらをoutlet接続します。

@property (weak, nonatomic) IBOutlet UIView *indicatorView;
@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *indicator;

それらを実行するメソッドを作成。
そのまんまコピーですみません。(−人−)

// indicator
- (void)indicator:(BOOL)start
{
    if (start) {
        // indicator start
        // 全面を覆うviewを表示
        [self.indicatorView setAlpha:0.6];
        // インジケータを回す
        [self.indicator startAnimating];
        // ステータスバーのインジケータも表示
        [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
        // (必要なら)バックボタンを消す
        [self.navigationItem setHidesBackButton:YES animated:YES];
    } else {
        // indicator stop
        // 全面を覆うviewを非表示にする
        [self.indicatorView setAlpha:0.0];
        // インジケータを止める
        [self.indicator stopAnimating];
        // ステータスバーのインジケータも消す
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
        // (必要なら)バックボタンを表示する
        [self.navigationItem setHidesBackButton:NO animated:YES];
    }
}

インジケーターを表示したいところで

    // インジケータ表示
    [self indicator:YES];

インジケーターを終了したいところで

    // インジケータ非表示
    [self indicator:NO];

とゆー感じで使えるようになると思います。

【In-App Purchase】自分なりにアプリ内課金備忘録の2 リストア

自分用備忘録。
自分なりに整頓。

アプリ内課金のリストアについてはこちらが参考になった。
AT-Sphere: In-App Purchaseにハマる (リストア編)

冗長な感じだがまずはリストアボタンを押されてAlertで確認。

#pragma mark リストア
- (IBAction)restoreButton:(id)sender
{
    NSLog(@"リストアボタン押した");
    // コントローラを生成
    UIAlertController * ac =
    [UIAlertController alertControllerWithTitle:@"Restore"
                                        message:@"Do you want to start the restoration ?"
                                 preferredStyle:UIAlertControllerStyleAlert];
    
    // Cancel用のアクションを生成
    UIAlertAction * cancelAction =
    [UIAlertAction actionWithTitle:@"Cancel"
                             style:UIAlertActionStyleCancel
                           handler:^(UIAlertAction * action) {
                               // ボタンタップ時の処理
                               NSLog(@"Cancel button tapped.");
                           }];
    
    // OK用のアクションを生成
    UIAlertAction * okAction =
    [UIAlertAction actionWithTitle:@"OK"
                             style:UIAlertActionStyleDefault
                           handler:^(UIAlertAction * action) {
                               // ボタンタップ時の処理
                               [self startRestore];
                               NSLog(@"OK button tapped.");
                           }];
    
    // コントローラにアクションを追加
    [ac addAction:cancelAction];
    [ac addAction:okAction];
    
    // アラート表示処理
    [self presentViewController:ac animated:YES completion:nil];   
}

OKを押すと[self startRestore]でstartRestoreメソッドへ。
ココではプロダクトIDをセットして購入の確認に投げる。

- (void)startRestore
{
    NSLog(@"リストアスタート");
    
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    
    //プロダクトIDをセット
    NSSet *set = [NSSet setWithObjects:@"プロダクトID2",@"プロダクトID3",@"プロダクトID4", nil];
    SKProductsRequest *prductsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
    prductsRequest.delegate = self;
    // 購入履歴チェック
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}

次のメソッドで購入済みプロダクトIDを確認してそれぞれ処理。

- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
    NSLog(@"ペイメントキューりすとあ");
    BOOL restore = NO;
    
    for (SKPaymentTransaction *transaction in queue.transactions)
    {
        // プロダクトIDが一致した場合
        if ([transaction.payment.productIdentifier isEqualToString:@"プロダクトID2"]) {
            restore = YES;
            NSLog(@"Restore1 OK!");
            
            [queue finishTransaction:transaction];
        }
        
        if ([transaction.payment.productIdentifier isEqualToString:@"プロダクトID3"]) {
            restore = YES;
            NSLog(@"Restore2 OK!");
            
            [queue finishTransaction:transaction];
        }
        
        if ([transaction.payment.productIdentifier isEqualToString:@"プロダクトID4"]) {
            restore = YES;
            NSLog(@"Restore3 OK!");
            
            [queue finishTransaction:transaction];
        }
    }
    
    // 一致するものがなかった場合
    if (restore == NO)
    {
        for (SKPaymentTransaction *transaction in queue.transactions) {
            [queue finishTransaction:transaction];
        }
        // コントローラを生成
        UIAlertController * ac =
        [UIAlertController alertControllerWithTitle:@"Error"
                                            message:@"Restre failed."//リストア失敗。
                                     preferredStyle:UIAlertControllerStyleAlert];
        
        // OK用のアクションを生成
        UIAlertAction * okAction =
        [UIAlertAction actionWithTitle:@"OK"
                                 style:UIAlertActionStyleDefault
                               handler:^(UIAlertAction * action) {
                                   // ボタンタップ時の処理
                                   NSLog(@"OK button tapped.");
                               }];
        
        // コントローラにアクションを追加
        [ac addAction:okAction];
        // アラート表示処理
        [self presentViewController:ac animated:YES completion:nil];
    }else{
        [self restoreSuccessAlart];
    }
}

最後にユーザーにリストア成功を伝えて完了。
あ、これはメソッド分けなくても良かったか。

-(void)restoreSuccessAlart
{
    UIAlertController * ac =
    [UIAlertController alertControllerWithTitle:@"Success"
                                        message:@"It succeeded to restore."
                                 preferredStyle:UIAlertControllerStyleAlert];
    
    // OK用のアクションを生成
    UIAlertAction * okAction =
    [UIAlertAction actionWithTitle:@"OK"
                             style:UIAlertActionStyleDefault
                           handler:^(UIAlertAction * action) {
                               // ボタンタップ時の処理
                               NSLog(@"OK button tapped.");
                           }];
    
    // コントローラにアクションを追加
    [ac addAction:okAction];
    // アラート表示処理
    [self presentViewController:ac animated:YES completion:nil];

}

あ、あと何らかの理由で失敗したら飛ぶらしいメソッド。よくわからん。

// リストアに失敗した場合
- (void) paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
    
    // コントローラを生成
    UIAlertController * ac =
    [UIAlertController alertControllerWithTitle:@"Error"
                                        message:@"Restre failed."//リストア失敗。
                                 preferredStyle:UIAlertControllerStyleAlert];
    
    // OK用のアクションを生成
    UIAlertAction * okAction =
    [UIAlertAction actionWithTitle:@"OK"
                             style:UIAlertActionStyleDefault
                           handler:^(UIAlertAction * action) {
                               // ボタンタップ時の処理
                               NSLog(@"OK button tapped.");
                           }];
    
    // コントローラにアクションを追加
    [ac addAction:okAction];
    // アラート表示処理
    [self presentViewController:ac animated:YES completion:nil];

}

こんな感じでどうですかね。
うじゃうじゃ。

【In-App Purchase】自分なりにアプリ内課金備忘録

アプリ内課金。
なんかめんどくさそう。
Appleのサーバとやりとりがどーたら。
ゆーててもしゃないので適当にやってみた。

参考サイト
iTunesConnect アプリ内課金 プロダクト作成 手順 – 散歩しながら 〜〜アプリ開発〜〜
失敗しない iOS In-App Purchase プログラミング – A Day In The Life
頭と尻尾はくれてやる! 初めてのアプリ内課金

課金コンテンツをAppleに預ける – How to implement "Hosting Content with Apple" – おおばログ

前提としてiTunes Connectのアプリで販売する物のプロダクトIDをつくっておく。
とりあえずココでは割愛。

で、まず、前処理としてアプリが課金可能な状態か確認する。

#pragma mark 購入前処理
-(void)canMakePayment
{
    if (![SKPaymentQueue canMakePayments])//購入制限がかかっている場合
    {
        
        // コントローラを生成
        UIAlertController * ac =
        [UIAlertController alertControllerWithTitle:@"Error"
                                            message:@"App billing are not allowed"
                                     preferredStyle:UIAlertControllerStyleAlert];
        
        // OK用のアクションを生成
        UIAlertAction * okAction =
        [UIAlertAction actionWithTitle:@"OK"
                                 style:UIAlertActionStyleDefault
                               handler:^(UIAlertAction * action) {
                                   // ボタンタップ時の処理
                                   NSLog(@"OK button tapped.");
                                   //購入制限がかかってたらなんかするときはココ
                               }];
        
        // コントローラにアクションを追加
        [ac addAction:okAction];
    }
}

続いて購入ボタンを押してプロダクトIDをセットして購入処理に入る。

#pragma mark 購入処理
- (IBAction)removeAdsButton:(id)sender {
    [self startPayment];
}

-(void)startPayment{
    //プロダクト情報のリクエストを開始。
    NSSet *set = [NSSet setWithObjects:@"ここに買うアイテムのプロダクトID", nil];
    SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
    productsRequest.delegate = self;
    [productsRequest start];

}

プロダクトIDがセットされたらproductRequestに送られる。
よくわからんが、IDが間違ってないか確認してから支払い処理に投げるっぽい。

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    // 無効なアイテムがないかチェック
    if ([response.invalidProductIdentifiers count] > 0)
    {
        // コントローラを生成
        UIAlertController * ac =
        [UIAlertController alertControllerWithTitle:@"Error"
                                            message:@"item ID is not correct."//アイテムIDが不正です。
                                     preferredStyle:UIAlertControllerStyleAlert];
        
        // OK用のアクションを生成
        UIAlertAction * okAction =
        [UIAlertAction actionWithTitle:@"OK"
                                 style:UIAlertActionStyleDefault
                               handler:^(UIAlertAction * action) {
                                   // ボタンタップ時の処理
                                   NSLog(@"OK button tapped.");
                               }];
        
        // コントローラにアクションを追加
        [ac addAction:okAction];
        
    }else
    {
        // 購入処理開始
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
        for (SKProduct *product in response.products)
            {
                SKPayment *payment = [SKPayment paymentWithProduct:product];
                [[SKPaymentQueue defaultQueue] addPayment:payment];
            }
    }
}

上記メソッドから自動で下記paymentQueueに投げられる。

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction *transaction in transactions) {
        if (transaction.transactionState == SKPaymentTransactionStatePurchasing) {
            // 購入処理中
            NSLog(@"購入処理中");
            /*
             * 基本何もしなくてよい。処理中であることがわかるようにインジケータをだすなど。
             */
            
        } else if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
            // 購入処理成功
            //ここでレシートの確認やアイテムの付与を行う。

            NSLog(@"広告の削除 %d",_removeAds);
            NSLog(@"購入処理成功");
            [queue finishTransaction:transaction];
            
        } else if (transaction.transactionState == SKPaymentTransactionStateFailed) {
            // 購入処理エラー。ユーザが購入処理をキャンセルした場合もここにくる
            [queue finishTransaction:transaction];
            NSLog(@"購入処理失敗");
            // エラーが発生したことをユーザに知らせる
            // コントローラを生成
            UIAlertController * ac =
            [UIAlertController alertControllerWithTitle:@"Error"
                                                message:[transaction.error localizedDescription]//アイテムIDが不正です。
                                         preferredStyle:UIAlertControllerStyleAlert];
            
            // OK用のアクションを生成
            UIAlertAction * okAction =
            [UIAlertAction actionWithTitle:@"OK"
                                     style:UIAlertActionStyleDefault
                                   handler:^(UIAlertAction * action) {
                                       // ボタンタップ時の処理
                                       NSLog(@"OK button tapped.");
                                   }];
            
            // コントローラにアクションを追加
            [ac addAction:okAction];
            

        } else {
            // リストア処理完了
            /*
             * アイテムの再付与を行う
             */
            [queue finishTransaction:transaction];
        }
    }		
}

うまく機能すれば支払いの確認のダイアログが出てOKすると支払い処理がなされる。

なんかリストア処理も入ってるっぽいので多分大丈夫じゃないかな。
複数の課金アイテムがあると処理がややこしそう。
自分用メモなので違ってたらゴメンね。
うじゃうじゃ。