tech.sinayaka.com

アプリを更新せずに、旧アプリから新アプリへユーザーを送客した話

2026-06-01
2026-06-11
9分
1683語
iOS iOSAdMob広告ASO

数独パズルアプリを作り直した。旧アプリはキーチェーン周りの問題でアップデートできなくなり、ストアから削除。後継アプリを新規で出した。

ところが、機能は後継のほうが上なのにまったくダウンロードされない。新規ユーザーはほぼゼロ。一方で削除したはずの旧アプリは、既存ユーザーが今も毎日遊んでいて広告も回っている。

この「旧アプリの現役ユーザーを新アプリへ送りたい」を、アプリの更新なしで実現した話を書く。

なぜ新アプリは埋もれるのか

データを見て原因がはっきりした。新アプリの新規ユーザーは、アナリティクス上**全て (direct)**だった。

(direct) はApp Store検索経由ではなく、URLを直接知っている人(=自分や既存ファン)からの流入を意味する。つまりApp Store検索結果に一切出てきていない

理由は単純で、新アプリは別アプリ扱いだから:

  • レビュー・評価がゼロからの再スタート
  • ランキング・検索順位の蓄積がない
  • 「数独」「ナンプレ」のような激戦キーワードで新規アプリが上位に出るのはほぼ不可能

旧アプリが積み上げた資産は、新アプリには1ミリも引き継がれない。

旧アプリには「現役ユーザー」がいる

ここで効くのが、削除済みの旧アプリだ。

ストアから消えても、すでにインストール済みのユーザーのアプリは動き続ける。広告レポートを見ると、旧アプリは月に数万インプレッションを叩き出していた。数百人規模が今も遊んでいる。

この人たちに「後継アプリがあるよ」と伝えられれば、検索流入ゼロの問題を一気に飛び越えられる。一番確実な見込み客が、すでに旧アプリの中にいる。

問題は、旧アプリが更新できないこと。普通なら「アプリ内に新アプリへのバナーを追加してアップデート」だが、それができない。

自社広告サーバーという逃げ道

幸い、旧アプリ(Objective-C世代)には自前の広告配信の仕組みが入っていた。

起動時に自社サーバーへ問い合わせ、JSONで「どの広告を出すか」を受け取る方式だ。

https://example.com/ad.php?code=<アプリ識別コード>

レスポンスはこんな形:

{
  "ad_type": 10002,
  "text1": "ca-app-pub-xxxxx/yyyyy",
  "per": 100
}

ad_type で広告の種類を切り替える。値はDB(media / tag テーブル)で管理されていて、サーバー側のレコードを書き換えるだけで配信内容が変わる。アプリ側のコードには一切触らない。

つまり、アプリを更新できなくても、広告枠の中身は今からでも差し替えられる

HTML広告でバナーを差し込む

ad_type にはAdMob以外にHTML広告のタイプがあった。

10001 = HTML
10002 = AdMob

HTMLタイプを返すと、アプリ側は受け取った文字列を UIWebView に流し込む:

NSString *html = [NSString stringWithFormat:
    @"<body style='margin:0;text-align:center;'>%@</body>", adText];
[adWeb loadHTMLString:html baseURL:nil];

ここに新アプリへのリンク付きHTMLを入れればいい。広告枠に自前のバナーを描画できる。

タップでApp Storeが開くか

懸念は「UIWebView 内のリンクをタップしたとき、ちゃんとApp Storeが開くか」だった。小さなWebView内でApp StoreのURLを開こうとすると詰む。

旧アプリの実装を確認したら、ちゃんとデリゲートで処理していた:

- (BOOL)webView:(UIWebView *)webView
  shouldStartLoadWithRequest:(NSURLRequest *)request
  navigationType:(UIWebViewNavigationType)navigationType
{
    if (navigationType == UIWebViewNavigationTypeLinkClicked) {
        if ([[UIApplication sharedApplication] canOpenURL:[request URL]]) {
            [[UIApplication sharedApplication] openURL:[request URL]];
            return NO;  // WebView内では読み込まない
        }
    }
    return YES;  // 初期表示(loadHTMLString)はそのまま許可
}

リンククリック(UIWebViewNavigationTypeLinkClicked)を検知したら openURL: で外部に投げ、WebView内ロードは return NO で止める。初回の loadHTMLString はクリック扱いではないので return YES で正常に表示される。

App StoreのURL(https://apps.apple.com/app/idXXXX)を渡せば、iOSがApp Storeアプリに引き渡してくれる。狙い通りだった。

古いWebViewでもCSSアニメは動く

UIWebView は古いAPIだが、中身はWebKitだ。CSSアニメーションもトランジションも動く。

せっかくなので、ただの静止バナーではなく、目に留まる演出をつけた:

  • アプリアイコンが左右にゆらゆら揺れる(花のモチーフに合わせて)
  • バナー全体を斜めの光が定期的に横切る(shine)
  • CTAボタンがぷるぷる拡縮する
@keyframes shine {
  0%   { left: -60%; }
  60%, 100% { left: 120%; }
}
.banner::after {
  content: '';
  position: absolute; top: 0; left: -60%;
  width: 40%; height: 100%;
  background: linear-gradient(120deg,
    transparent, rgba(255,255,255,.35), transparent);
  animation: shine 3s ease-in-out infinite;
}

広告枠は 320×50(iPhone)。アイコンを height:100% で枠にフィットさせ、右にテキストとCTAを並べる横長レイアウトにした。

配信割合とA/Bテスト

DBの per カラムは配信割合(重み付け抽選)だ。AdMobと新アプリ誘導を半々にしたいなら:

-- AdMob枠
UPDATE tag SET per=50 WHERE id=<AdMobのタグ>;
-- 新アプリ誘導枠
UPDATE tag SET ad_type=10001, per=50, text1='<バナーHTML>'
  WHERE id=<誘導タグ>;

per の合計に対する割合で抽選されるので、50:50 なら半々で配信される。実際に何度もリクエストして比率を確認した。

さらに面白いのは、誘導バナーを複数登録すればA/Bテストになること。表示・クリックはタグ単位でDBに記録されるので、どのコピーが一番刺さるかを後から数字で比較できる。

タグA: 「神ヒントで解き方が学べる」    per=17
タグB: 「もう詰まらない」              per=17
タグC: 「新作・解法ヒントが神レベル」  per=16

3案を均等に配信し、クリック率の高いコピーに寄せていく。広告枠ひとつで小さなマーケティング実験ができる。

やってみた結果(途中経過)

設定して半日で、誘導バナーは800回以上表示された。旧アプリの現役ユーザーがちゃんと見ている。

ただ、クリックは数回どまり。新アプリのダウンロードにはまだ繋がっていない。表示はされるが、コピーがまだ弱いのだと思う。だからこそA/Bテストで「どう言えば刺さるか」を探っている最中だ。

まとめ

やったことポイント
旧アプリの自社広告サーバーを使うアプリ更新なしでDB書き換えだけ
HTML広告タイプで自前バナーを描画loadHTMLString
タップでApp Storeを開くshouldStartLoadWithRequestopenURL:
CSSアニメで目立たせる古いUIWebViewでもWebKitなので動く
per で配信割合を制御AdMobと混在配信・A/Bテスト

「アプリが更新できない」は詰みに見えて、配信の仕組みを自前で持っていれば抜け道がある。旧アプリの現役ユーザーは、新アプリにとって一番確実な見込み客だ。検索流入をゼロから積み上げるより、すでにいる人に届けるほうがずっと早い。




Copyright 2026
サイトマップ