[iOS] アプリ開発日記 無料アプリに広告を組み込む

2013/02/12

こんにちは。きんくまです。

ふと、アプリのLite版というかFree版を作ってみようと思いました。
機能に制限を設けて、広告をつけたバージョン。
各種オプションと日課を持てる個数(3つ)に制限を設けたもの。

最近はフリーミアムバージョン(基本無料+アプリ内課金)が基本のようですが、すでに有料でアプリは出しているので、別で出すことにしようかと。
フリーミアムは次に別のアプリを出すときはやってみようと思います。
数年前にはこういうFree版が結構出ていたと思うのですが、今は新規アプリではあまり見かけなくなったような。
あんまり効果がなくなっちゃったんでしょうか。

で、いざ広告ってどうやってつけるのかな。いやそもそもどんな広告が今あるのよ?って思って調べたらAppBankさんに良記事がありました。

>> 主なアドネットワークサービス紹介【iPhoneアプリ広告で稼ぐ・番外編2】 – たのしいiPhone! AppBank

いろいろと用語の紹介や、今はどんな配信会社さんがあるかについてよく書かれていました。あとAppBank Networkのバナーを表示させると紹介してもらえることもあるとかないとか。

で、広告は国内と海外で読み込みを分けたかったり、あと表示する優先順位をリモートで変更したいなと思いました。
ググったらやっている方を見つけました。

>> 自前で広告ライブラリを切り替えてリスク分散しよう! » MOKYN

どうやら振り分けのサービス自体すでにあるみたいですが、とりあえず自分としてはこんな感じにしようかなと。

1) 設定ファイル(JSON)を海外用と国内用に切り分けて自分のサーバーに置いておく
2) アプリからLocaleごとに(国内=JP or 海外)設定ファイルを読みにいく
3) 設定ファイルに書かれた優先順位で読み込む広告会社を決定
4) 順番に広告を読んで、もし取得できなかったら次の順位の広告を読む
5) 最終的にどれも読み込めなかったらアプリの有料版のAppStoreへの画像リンクを出す(自社バナー的な)
6) 2のJSON設定ファイルが万が一取得できなかった場合は、アプリにあらかじめ順位を埋め込んでおきその順番とする

一度自前で使いやすいものを作っておけば、別アプリにもすぐに流用できそうですね。

情報もらうばっかりだとわるいので、つくったソースを書いときます

HKAdManager.h

#import <Foundation/Foundation.h>
#import <iAd/iAd.h>
#import "GADBannerView.h"
#import "GADBannerViewDelegate.h"
#import "NADView.h"

@interface HKAdManager : NSObject<NSURLConnectionDataDelegate, ADBannerViewDelegate, GADBannerViewDelegate, NADViewDelegate>
{
    NSURLConnection *_urlConnection;
    NSMutableData *_loadedData;
    NSArray *_adNetworkLoadOrder;
    NSInteger _adNetworkLoadIndex;
    
    //banner
    ADBannerView *_iAdBannerView;
    GADBannerView *_gAdBannerView;
    NADView *_nendAdView;
    NADView *_appBankAdView;
    UIButton *_ownBannerButton;
}
@property (nonatomic, weak) UIViewController *rootViewController;
@property (nonatomic, strong) UIView *adContainerView;
- (id)initWithRootViewController:(UIViewController *)rootViewController;
- (void)setUp;
- (void)reset;
@end

HKAdManager.m

#import "HKAdManager.h"

NSString *const kHKAdManagerWorldSettingsFileURL = @"http://sample.com/world_settings.json";
NSString *const kHKAdManagerJPSettingsFileURL = @"http://sample.com/jp_settings.json";

//ad type
NSString *const kHKAdManagerAdTypeiAd = @"iAd";
NSString *const kHKAdManagerAdTypeAdMob = @"AdMob";
NSString *const kHKAdManagerAdTypeNend = @"Nend";
NSString *const kHKAdManagerAdTypeAppBank = @"AppBank";
NSString *const kHKAdManagerAdTypeOwn = @"Own";

@implementation HKAdManager

- (id)initWithRootViewController:(UIViewController *)rootViewController
{
    self = [super init];
    if(self){
        _rootViewController = rootViewController;
        CGSize screenSize = [UIScreen mainScreen].bounds.size;
        _adContainerView = [[UIView alloc] initWithFrame:CGRectMake(0, 44 + 20, screenSize.width, 50)];
        [self setUpOwnBanner];
    }
    return self;
}

- (void)startNetworkIndicatorAnimate
{
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
}

- (void)stopNetworkIndicatorAnimate
{
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}

- (void)setUp
{
    _adNetworkLoadOrder = nil;
    _adNetworkLoadIndex = 0;
    if(![self loadSettingsJSON]){
        [self loadNextAdNetwork];
    }
}

- (void)reset
{
    [self cleariAd];
    [self clearAdMob];
    [self clearNend];
    [self clearAppBank];
    [self setUp];
}

- (BOOL)loadSettingsJSON
{
    [_urlConnection cancel];
    [self startNetworkIndicatorAnimate];
    NSString *jsonURL = nil;
    if([[[NSLocale currentLocale] localeIdentifier] isEqualToString:@"ja_JP"]){
        jsonURL = kHKAdManagerJPSettingsFileURL;
    }else{
        jsonURL = kHKAdManagerWorldSettingsFileURL;
    }
    NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:jsonURL]];
    _urlConnection = [[NSURLConnection alloc] initWithRequest:req delegate:self];
    if(!_urlConnection){
        NSLog(@"ad settings JSON file url initialize failed");
        [self stopNetworkIndicatorAnimate];
        return NO;
    }
    return YES;
}

- (void)storeDefaultAdOrder
{
    if([[[NSLocale currentLocale] localeIdentifier] isEqualToString:@"ja_JP"]){
        _adNetworkLoadOrder = @[
                                kHKAdManagerAdTypeiAd,
                                kHKAdManagerAdTypeNend,
                                kHKAdManagerAdTypeAppBank,
                                kHKAdManagerAdTypeAdMob,
                                kHKAdManagerAdTypeOwn
                                ];
    }else{
        _adNetworkLoadOrder = @[
                                kHKAdManagerAdTypeiAd,
                                kHKAdManagerAdTypeAdMob,
                                kHKAdManagerAdTypeOwn
                                ];
    }
}

- (void)loadNextAdNetwork
{
    if(_adNetworkLoadOrder == nil){
        [self storeDefaultAdOrder];
        [self loadNextAdNetwork];
    }else{
        if(_adNetworkLoadIndex > [_adNetworkLoadOrder count] - 1){
            return;
        }
        NSString *adNetworkName = [_adNetworkLoadOrder[_adNetworkLoadIndex] lowercaseString];
        _adNetworkLoadIndex++;
        if([adNetworkName isEqualToString:[kHKAdManagerAdTypeiAd lowercaseString]]){
            [self setUpiAd];
        }else if([adNetworkName isEqualToString:[kHKAdManagerAdTypeAdMob lowercaseString]]){
            [self setUpAdMob];
        }else if([adNetworkName isEqualToString:[kHKAdManagerAdTypeNend lowercaseString]]){
            [self setUpNend];
        }else if([adNetworkName isEqualToString:[kHKAdManagerAdTypeAppBank lowercaseString]]){
            [self setUpAppBank];
        }else{
            [self loadNextAdNetwork];
            //[self setUpOwnBanner];
        }
    }
}

- (void)setUpOwnBanner
{
    if(_ownBannerButton == nil){
        //自社バナーの_ownBannerButtonの初期化処理をここにかく
    }
    [_adContainerView addSubview:_ownBannerButton];
}

- (void)clearOwnBanner
{
    if(_ownBannerButton != nil){
        [_ownBannerButton removeFromSuperview];
    }
}

- (void)ownBannerButtonTapped
{
    NSURL *url = @"リンクさせたいURL";
    [[UIApplication sharedApplication] openURL:url];
}

- (void)setUpiAd
{
    if ([ADBannerView instancesRespondToSelector:@selector(initWithAdType:)]) {
        _iAdBannerView = [[ADBannerView alloc] initWithAdType:ADAdTypeBanner];
    } else {
        _iAdBannerView = [[ADBannerView alloc] init];
    }
    _iAdBannerView.delegate = self;
}

- (void)cleariAd
{
    if(_iAdBannerView != nil){
        [_iAdBannerView removeFromSuperview];
        _iAdBannerView.delegate = nil;
        _iAdBannerView = nil;
    }
}

- (void)setUpAdMob
{
    [self startNetworkIndicatorAnimate];
    _gAdBannerView = [[GADBannerView alloc] initWithAdSize:kGADAdSizeBanner origin:CGPointMake(0, 0)];
    _gAdBannerView.adUnitID = @"xxxxx";
    _gAdBannerView.rootViewController = _rootViewController;
    _gAdBannerView.delegate = self;
    [_gAdBannerView loadRequest:[GADRequest request]];
}

- (void)clearAdMob
{
    if(_gAdBannerView != nil){
        [_gAdBannerView removeFromSuperview];
        _gAdBannerView.rootViewController = nil;
        _gAdBannerView.delegate = nil;
        _gAdBannerView = nil;
    }
}

- (void)setUpNend
{
    CGRect rect = CGRectMake(0,0,
                             NAD_ADVIEW_SIZE_320x50.width,
                             NAD_ADVIEW_SIZE_320x50.height);
    _nendAdView = [[NADView alloc] initWithFrame:rect];
    [_nendAdView setNendID:@"xxxxx" spotID:@"xxxxx"];
    [_nendAdView setRootViewController:_rootViewController];
    [_nendAdView setDelegate:self];
    [_nendAdView load];
    [self startNetworkIndicatorAnimate];
}

- (void)clearNend
{
    if(_nendAdView != nil){
        [_nendAdView pause];
        [_nendAdView removeFromSuperview];
        _nendAdView.rootViewController = nil;
        _nendAdView.delegate = nil;
        _nendAdView = nil;
    }
}

- (void)setUpAppBank
{
    CGRect rect = CGRectMake(0,0,
                             NAD_ADVIEW_SIZE_320x50.width,
                             NAD_ADVIEW_SIZE_320x50.height);
    _appBankAdView = [[NADView alloc] initWithFrame:rect];
    [_appBankAdView setNendID:@"xxxxx" spotID:@"xxxxx"];
    [_appBankAdView setRootViewController:_rootViewController];
    [_appBankAdView setDelegate:self];
    [_appBankAdView load];
    [self startNetworkIndicatorAnimate];
}

- (void)clearAppBank
{
    if(_appBankAdView != nil){
        [_appBankAdView pause];
        [_appBankAdView removeFromSuperview];
        _appBankAdView.rootViewController = nil;
        _appBankAdView.delegate = nil;
        _appBankAdView = nil;
    }
}

- (void)layoutiAdBannerView:(ADBannerView *)bannerView
{
    CGRect bannerFrame = CGRectZero;
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0
    NSString *contentSizeIdentifier = ADBannerContentSizeIdentifierPortrait;
    bannerFrame.size = [ADBannerView sizeFromBannerContentSizeIdentifier:contentSizeIdentifier];
#else
    bannerFrame.size = [bannerView sizeThatFits:contentFrame.size];
#endif
    
    bannerFrame.origin.y = 0;
    bannerView.frame = bannerFrame;
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0
    bannerView.currentContentSizeIdentifier = contentSizeIdentifier;
#endif
}

#pragma mark NSURLConnectionDataDelegate

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    _loadedData = [[NSMutableData alloc] init];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [_loadedData appendData:data];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    [self stopNetworkIndicatorAnimate];
    NSLog(@"ad settings JSON file connection error %@", error);
    [self loadNextAdNetwork];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    [self stopNetworkIndicatorAnimate];
    NSError *error = nil;
    id JSON = [NSJSONSerialization JSONObjectWithData:_loadedData options:NSJSONReadingMutableContainers error:&error];
    if(error){
        NSLog(@"ad settings JSON file parse error %@", error);
        [self loadNextAdNetwork];
        
    }else{
        NSMutableArray *jsonArr = JSON;
        _adNetworkLoadOrder = [NSArray arrayWithArray:jsonArr];
        [self loadNextAdNetwork];
    }
}

#pragma mark iAdDelegate

- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
    [self stopNetworkIndicatorAnimate];
    [self layoutiAdBannerView:banner];
    [_adContainerView addSubview:banner];
}

- (void)bannerViewWillLoadAd:(ADBannerView *)banner
{
    [self startNetworkIndicatorAnimate];
//    NSLog(@"will load ad");
}

- (void)bannerViewActionDidFinish:(ADBannerView *)banner
{
//    NSLog(@"action");
}

- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
    [self stopNetworkIndicatorAnimate];
    [self cleariAd];
    [self loadNextAdNetwork];
}

#pragma mark gAdDelegate

- (void)adView:(GADBannerView *)view didFailToReceiveAdWithError:(GADRequestError *)error
{
    [self stopNetworkIndicatorAnimate];
    [self clearAdMob];
    [self loadNextAdNetwork];
}

- (void)adViewWillPresentScreen:(GADBannerView *)adView
{
    
}

- (void)adViewDidReceiveAd:(GADBannerView *)view
{
    [self stopNetworkIndicatorAnimate];
    [_adContainerView addSubview:view];
}

#pragma mark - NADView delegate

// NADViewのロードが成功した時に呼ばれる
- (void)nadViewDidFinishLoad:(NADView *)adView
{
//    NSLog(@"delegate nadViewDidFinishLoad:");
}

// 広告受信成功
-(void)nadViewDidReceiveAd:(NADView *)adView
{
    [self stopNetworkIndicatorAnimate];
    [_adContainerView addSubview:adView];
//    NSLog(@"delegate nadViewDidReceiveAd:");
}

// 広告受信エラー
-(void)nadViewDidFailToReceiveAd:(NADView *)adView
{
    [self stopNetworkIndicatorAnimate];
    if([adView isEqual:_nendAdView]){
        [self clearNend];
    }else if([adView isEqual:_appBankAdView]){
        [self clearAppBank];
    }
    [self loadNextAdNetwork];
//    NSLog(@"delegate nadViewDidFailToLoad:");
}
@end

使い方としては、initWithなんたらでインスタンス作ったらadContainerViewを任意の場所に置いて、setUpを呼ぶ。好きなタイミングでresetを呼ぶと、また1から設定ファイルを読み込みにいくようになってます。

設定jsonファイルはこんな感じ。上から優先的に呼ばれていきますです。

[
    "iAd",
    "AppBank",
    "Nend",
    "AdMob",
    "Own"
]

そうそう。各広告会社に登録し始めて気づいたのですが、申請してからそれなりに審査に日数(2〜7日?)がかかるみたいです。
もし広告つきアプリを作る場合は早めに申請しておくと吉と出てますぞ。

LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

ページトップへ戻る