SpriteKitで作ったアプリにAdmobを実装する

環境

主なファイル構成

  • GameViewController
  • GameScene.swift

Admobの登録

  • 省略

SDKのインストール

  • ここの説明に従いCocoaPodsからSDKをインストールした
  • Podfileは以下のようにした
  • 適切なバージョンがわからなかったので、バージョン指定はなしになっている (参考)
source 'http://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!

pod 'Google-Mobile-Ads-SDK' 

target 'プロジェクト名' do

end

ビルドするとBitcodeのエラーが出た

  • エラー内容
GoogleMobileAds.framework/GoogleMobileAds(GADDefaultInAppPurchaseFlow.o)' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. for architecture arm64
  • Bitcodeを無効にして対応(参考
  • TARGETS > プロジェクト名 > Build Setting > Build Options > Enable Bitcode を「No」に変更した (最初、表示をBasicにしていて検索しても出てこなかった)

(Bitcodeって?)

  • よくわかっていなかったので調べてみた
  • 「Bitcodeを含めてビルドアップロードすると、将来CPUアーキテクチャが変わっても自動で対応してくれる」ってことだろうか
  • 参考

フレームワークの追加

  • TARGETS > プロジェクト名 > General > Linked Frameworks and Libraries から必要なフレームワークを追加

必要なフレームワーク

  • AdSupport
  • AudioToolbox
  • AVFoundation
  • CoreGraphics
  • CoreMedia
  • CoreTelephony
  • EventKit
  • EventKitUI
  • MessageUI
  • StoreKit
  • SystemConfiguration

バナー広告の追加

  • ここにはStoryboardを利用したやり方しかのっていなかったので、こちらを参考にさせてもらい作業をすすめる
  • GameViewController.swiftに参考記事のコードを記述したら表示された (以下をviewDidLoadから呼び出す)
func showAd() {
    let banner:GADBannerView = GADBannerView(adSize: kGADAdSizeSmartBannerPortrait)
    banner.adUnitID = "ca-app-pub-xxxxxxxxxxxxxxxxxxxxxxxx"
    banner.delegate = self
    banner.rootViewController = self
    let gadRequest:GADRequest = GADRequest()
    banner.loadRequest(gadRequest)
    self.view.addSubview(banner)
}
  • GameScene.swift上でバナーの表示非表示をコントロールしたかったので、var banner: GADBannerView!グローバル変数とし、banner.hidden = true/falseでコントロールした

インタースティシャル広告の追加

var interstitialAD: GADInterstitial! //interstitialという名前は使えなかった

class GameViewController: UIViewController, GADBannerViewDelegate, GADInterstitialDelegate {

    override func viewDidLoad() {
         super.viewDidLoad()
         interstitialAD = createAndLoadInterstitial()
    }

    func createAndLoadInterstitial()->GADInterstitial {
        let interstitial = GADInterstitial(adUnitID: "ca-app-pub-xxxxxxxxxxxxxxxxxxxxxx")
        interstitial?.delegate = self
        let gadRequest:GADRequest = GADRequest()
        interstitial?.loadRequest(gadRequest)
    
        return interstitial!
    }

    func interstitialDidDismissScreen(ad: GADInterstitial!) {
        interstitialAD = createAndLoadInterstitial()
    }
}
  • 広告の呼び出しは、こちらを参考にさせていただき、以下のように行っている
func showInterstitial() {
    let currentViewController: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController!
    interstitialAD?.presentFromRootViewController(currentViewController)
}

Nifty Mobile Backendでプッシュ通知を実装してみた(Swift)

環境

  • Xcode: 7.1.1
  • NCMB: 2.2.3

背景

プッシュ通知にはfelloのサービスを利用していたが、Xcode7になってから実装方法がわからなくなってしまったのと、mBaaSに興味があったのでmobile backendの実装を試すことにした。

証明書準備

公式のチュートリアルがわかりやすく、そのまま進めたら準備が完了した。felloの場合、pemの作成が必要だったので、その手間がないのは楽だった。(pem作成自体は、ドキュメント通りすすめれば難しいことはないが)

SDKインストール

クイックスタートをみて、CocoaPodsでSDKをインストールするも、その後の説明Objective-Cでの例しか見つからなかった。検索してみるとSwiftを用いた記事があったので、こちらを参考にする。記事内にも説明があるが、CocoaPodsからのインストールだとエラーが出てしまったので、GitHubからSDKをダウンロードしてインストールした。(SDKのダウンロードはこちら参考にした。)

実装

Bridging-Header.hの追加

こちらの記事を参考にBridgingHeaderを追加した。Build Settings > Objective-C Bridging Headerには$(SRCROOT)/$(PRODUCT_NAME)/$(PRODUCT_NAME)-Bridging-Header.hと指定してみた。$(PRODUCT_NAME)-Bridging-Header.hの中身は以下の通り。

// for Nifty Mobile Backend
#import "NCMB/NCMB.h"

AppDelegate.swiftの更新

Swiftでの記述が調べられなかったが、こちらの記事通りに進めたら実装が完了しプッシュ通知が確認できた。

エラー(ワーニングだったかな?)対応

You've implemented -[ application:didReceiveRemoteNotification:fetchCompletionHandler:], but you still need to add "remote-notification" to the list of your supported UIBackgroundModes in your Info.plist.

といったエラーが出たので、こちらの記事を参考にTARGETS > Capabilities > Background Modes をオンにして、Remote notificationsにチェックを入れた。念のためPush Notificationsもオンにした。エラーも出なくなり、プッシュ通知の受け取りに成功した。

開封通知(正常に実装できていなそう)

プッシュ通知の開封通知の記述を追加しようとドキュメントを見るも、やはりObjective-Cでの説明のため、変換サービスなどを利用し、以下のように記述してみた。アプリ起動時の通知は取得できたが、アプリ終了時での開封通知が取得できない場合がある。どこか実装をミスっていそう。

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // 省略
    NCMB.setApplicationKey(Const.NcmbApplicationKey, clientKey: Const.NcmbClientKey)
    
    // 以下の行を追加
    NCMBAnalytics.trackAppOpenedWithLaunchOptions(launchOptions)
    
    // 省略
}
    
    func application(application: UIApplication, didReceiveRemoteNotification userInfo:[NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void){
        
        // 以下の行を追加
        if application.applicationState == .Active {
            NCMBAnalytics.trackAppOpenedWithRemoteNotificationPayload(userInfo)
        }
    }

バッジ表示のコントロール

このままだとバッジが消えないので、AppDelegate.swiftにバッジをクリアする処理を入れた。Felloの時のものをそのままだが以下の通り記述した。

func applicationWillEnterForeground(application: UIApplication) {
        
    UIApplication.sharedApplication().applicationIconBadgeNumber = -1

}

アプリ起動時にバッジを消すことは可能になったが、mobile backendにはバッジ件数に「インクリメントする」といった設定がある。こちらのissueを見ると「開封通知登録APIを実行する」ことで「データストア上の端末情報にあるバッジ数のみリセット」となっている。前述通り、開封通知の部分が正常に実装できていなそうなので、こちらの機能は使えていない。(数字がクリアされない。)

iOS7対応(保留)

このままで対応OSをiOS7とするとUIUserNotificationSettingsがiOS7に対応していないためエラーが出てしまう。公式ドキュメントでは以下のように説明がある。

if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_7_1){

    //iOS8以上での、DeviceToken要求方法

    //通知のタイプを設定したsettingを用意
    UIUserNotificationType type = UIUserNotificationTypeAlert |
    UIUserNotificationTypeBadge |
    UIUserNotificationTypeSound;
    UIUserNotificationSettings *setting;
    setting = [UIUserNotificationSettings settingsForTypes:type
                                                categories:nil];

    //通知のタイプを設定
    [[UIApplication sharedApplication] registerUserNotificationSettings:setting];

    //DevoceTokenを要求
    [[UIApplication sharedApplication] registerForRemoteNotifications];
} else {

    //iOS8未満での、DeviceToken要求方法
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
     (UIRemoteNotificationTypeAlert |
      UIRemoteNotificationTypeBadge |
      UIRemoteNotificationTypeSound)];
}

こちらを参考に

if #available(iOS 8.0, *) {
    let settings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil)
    application.registerUserNotificationSettings(settings)
    application.registerForRemoteNotifications()
} else {
     UIApplication.sharedApplication().registerForRemoteNotificationTypes(.Alert | .Badge | .Sound);
}

このように記述してみるも、Type of expression is ambiguous without more contextとエラーが出てしまい。実装方法がわからなかった。

結果

  • Xcode: 7.1.1
  • NCMB: 2.2.3
  • iOS8以上は実装完了
  • iOS7以下は実装未完了
  • 開封通知の挙動が不安定(インクリメントがリセットできない)

WatchKitの画面リロードやインジケータがわからない

環境

  • Xcode: 7.1.1
  • watchOS: 2

画面リロードはどうやるのか?

WatchKitにて、Memu ItemのタップをきっかけにWKInterfaceLabelを更新するコードを書いてみた。

@IBAction func nextPost() {
    title.setText("タイトル更新")
    body.setText("本文更新")
}

更新後にスクロール位置を最上段に持って行きたかったのだが、ただsetTextしただけではスクロール位置が変わらなかった。setHiddenで一度WKInterfaceLabelを消してから、再度表示させるようにしたら上段に移動した。

@IBAction func nextPost() {
    title.setHidden(true)
    body.setHidden(true)

    title.setText("タイトル更新")
    body.setText("本文更新")
    
    title.setHidden(false)
    body.setHidden(false)
}

インジケータがない?

上記のようにLabelを消す形にしたら、消えているあいだ画面が真っ黒になるのが気になった。Indicatorが見つからなかったので、消えている間は"Loding..."とLabelを表示するようにした。

@IBAction func nextPost() {
    title.setHidden(true)
    body.setHidden(true)
    loding.setHidden(false)

    title.setText("タイトル更新")
    body.setText("本文更新")
    
    title.setHidden(false)
    body.setHidden(false)
    loding.setHidden(true)
}

もっと良い方法はないだろうか。