Unityで2Dゲーム系アプリを作るときのメモ

iOSで60FPS出す方法

Is it possible to get above 30 FPS on an iOS device? – Unity Answers

Androidだとデフォルトで60FPSが出ているんですが、iOSで試すとFPSが30しかでません。
これだと、アニメーションの速度などに差異が出てしまうので、同じにしたいもんです。

Application.targetFrameRate = 60;

これだけでOK!

UnityアプリをiOSで書きだしてビルドするとdSYMのところですんごい時間かかる

  • XCodeのプロジェクト設定ページからBuild Settingsタブを選択
  • dSYMでBuild Options項目を検索
  • DWARF with dSYM fileからDWARFに変更
  • via: UnityアプリをiOSに書き出す時間をdSYMを無効化して早くする – The jonki

    これで解決!
    1分以上かかっていたビルドが数秒になります。

    ただ、XCode上から設定するので、これ以降はUnityはappendでビルドしないといけないかもです。

    prime31のEtceteraTwoの在処が分からない

    LocalNotificationのスケジューリングをしようとすると、EtceteraプラグインだけではダメでTwoのほうが必要になります。
    AndroidのほうはなぜかEtceteraだけでいけるんですが、iOSは2個必要みたい。

    このtwoプラグインはAssetStoreにありません。
    なので、以下のURLから購入することができます。
    prime[31] – the best Unity native code plugins

    ただ、LocalNotificationをただ通知したいだけなら、iOS・Android含めたコードは、

    #if UNITY_IPHONE
            LocalNotification l = new LocalNotification();
            l.applicationIconBadgeNumber = 1;
            l.fireDate = System.DateTime.Now.AddSeconds(10);
            l.alertBody = "hello world";
            NotificationServices.ScheduleLocalNotification(l);
    #elif UNITY_ANDROID
            EtceteraAndroid.scheduleNotification( 10, "Notiifcation Title", "The subtitle of the notification", "Ticker text gets ticked", "my-other-special-data" );
    #endif
    

    こんな感じでもいいかなーと思います。
    LocalNotificationのためだけにtwoで$65払うのもちょっとね。

    PlayerPrefs

    ちょいと値を保存したいとき用に使える。

    Unity – Scripting API: PlayerPrefs

    複数のシーンをマージしてロードする

    テラシュールウェア [Unity3D]ロードの待ち時間を短くする(その4/Application.LoadLevelAdditive)
    このあたりを読むとApplication.LoadLevelAdditiveはかなりオーバーヘッドがあるようで、あまりシーンの分割をしすぎるのは危険のよう。

    開発する人数が多いとシーンの分割はしたくなるが、ある程度最小限のほうがよさそう。

    今回はUnity3D – Unity3Dで、体感ロード時間を減らす – Qiitaを参考にApplication.LoadLevelAdditiveAsyncを採用してみた。

    ただし、非同期で読み込むので、読み込みが完了したかが分からなかったので、以下のようにあるオブジェクトがFindできたらLoadしたとみなす処理を書いた。

    Application.LoadLevelAdditiveAsync ("Game");
    
    StartCoroutine (ObjectChecker (0.1f, () => {
        Debug.Log ("OnLoad---------------------------------");
    }));
    
    IEnumerator ObjectChecker (float secs, Action callback)
    {
        while (GameObject.Find(ConstantsUI.GAME_CAMERA) == null) {
            yield return new WaitForSeconds (secs);
        }
    
        callback ();
    }
    

    このあたりもっと良い方法がないかなー

    NGUIについて

    2Dのゲームアプリを作るため、NGUIを使ってみました。
    はじめの数日はハマることが多いですが、手に馴染んでくるとこれなしでは2Dアプリは作れないという印象。

    NGUIのレンダリングコントロール

    少しやっかいだったのが、レンダリングの順番をどうコントロールするか。

    UIPanelのRenderQでStartAtにしてここでコントロールした。
    NGUIはどうもRenderQueueが3000番から始まるようで、ここを知っていればレンダリングのコントロールは用意です。

    NGUIのコントロールとUnityの3Dコントロール、たとえばパーティクルとかを一緒に使う場合は、以下のようなコードをパーティクルにつけて、3001とかになるようにすればパーティクルが手前にレンダリングされます。

    ■参考リンク

    Webデザイナーが覚えるUnity その2 NGUIの基礎知識 | GREE Creators Blog
    【Unity】NGUIを使ってボタンを実装する。 | albatrus.com
    ゲームは初心者にやさしく: NGUI 描画順について
    NGUIではないオブジェクトをNGUIの手前に描画する方法 – 太郎Work
    ウダサンコウボウ: [Unity]NGUIのDepth設定
    ウダサンコウボウ: [Unity]NGUIのDepth設定その2(複数Atlas)

    NGUIのアニメーションをプログラムから指定する

    TweenPosition tp = TweenPosition.Begin (gameObject, 0.5f, new Vector3 (0, 0, 0));
    tp.tweenGroup = 0;
    tp.from = new Vector3 (transform.localPosition.x, transform.localPosition.y, transform.localPosition.z);
    tp.method = UITweener.Method.EaseInOut;
    tp.style = UITweener.Style.Once;
    tp.AddOnFinished (new EventDelegate (OnShowOverlayEnd));
    tp.Play (true);
    

    ■参考リンク
    開発メモ 「NGUIのまとめ(スクリプトから動的動作)」 – シヴァのブログ
    NGUI TweenPostion.. – Unity Answers
    NGUI: Next-Gen UI kit: TweenPosition Class Reference
    【Unity】NGUIのTweenを利用してみる | albatrus.com

    NGUIのイベントたち

  • void OnHover (bool isOver) – マウスポインタが重なったときに呼ばれます。タップは×。
  • void OnPress (bool isDown) – マウスによりクリック(押下時と離脱時の2回)が発生したときによばれます。タップも○。
  • void OnClick() — 上と同じですが、離脱時の1回だけ呼ばれるパターン。
  • void OnDoubleClick () — ダブルクリックされたとき。
  • void OnSelect (bool selected) –選択メニューで便利?OnClickと同じだけど、こっちは他のものが選択されるまでは2度目の選択時には呼ばれません。
  • void OnDrag (Vector2 delta) – OnPress(true) と OnPress(false)の間に起きた移動を取得。タップ○。
  • void OnDrop (GameObject drag) – ドロップ時(OnPress(false)のタイミング)にドロップされた(下にある)オブジェクト側で呼ばれる。引数には、ドロップした(上にある)オブジェクトが渡される。
  • void OnTooltip (bool show) – Sent after the mouse hovers over a collider without moving for longer thantooltipDelay, and when the tooltip should be hidden. Not sent on touch-based devices.
  • void OnScroll (float delta) マウスのホイールが動いた時によばれる
  • void OnKey (KeyCode key) キーボードが押されたときによばれる
  • via: NGUIでイベントを取得する | Unity3Dのプラグインによる開発

    こちらのサイトにはほんと助けられました。
    どうゆうイベントがいつ呼ばれるのかまったく分かりませんでした。

    動画をネイティブのプレイヤーでフルスクリーンで再生する

    Handheld.PlayFullScreenMovie ("main_movie.mp4", Color.black, FullScreenMovieControlMode.Full);
    

    たったのこれだけ、ただUnity上で実行するのではなくあくまでもネイティブのプレイヤーを表示するので、
    使いどころとしては、何かチュートリアル的な動画などを再生するときかなー

    Unity – Scripting API: Handheld.PlayFullScreenMovie
    Handheld.PlayFullScreenMovie on Android | Unity Community

    Unity4入門   最新開発環境による簡単3Dゲーム製作
    浅野 祐一 荒川 巧也 森 信虎
    ソフトバンククリエイティブ
    売り上げランキング: 1,332

    Unityでgree/unity-webviewを使ってLandscapeでWebViewを表示するときの注意点

    UnityでWebViewを表示しようと思ってググるとgreeのこのプラグインがよく使われているので、試してみました。
    gree/unity-webview · GitHub

    ざっくりとサンプルを書くとこんな感じです。

    using UnityEngine;
    using System.Collections;
    
    public class WebviewSample : MonoBehaviour {
      public string url;
      WebViewObject webViewObject;
    
      // Use this for initialization
      void Start () {
        webViewObject = (new GameObject("WebViewObject")).AddComponent<WebViewObject>();
        webViewObject.Init((msg) => {
          Debug.Log(msg);
        });
        webViewObject.LoadURL("http://google.com/");
        webViewObject.SetMargins(0, 0, 0, 100);
        webViewObject.SetVisibility(true);
      }
    
      // Update is called once per frame
      void Update () {
    
      }
    }
    

    あとは、AndroidManifest.xmlに以下を追加し、変更点を加える。

    インターネットを使えるようにする。

    <uses-permission android:name=”android.permission.INTERNET”/>
    

    これをしないとWebView内のタッチがDOM側に通知されない。

    <meta-data android:name=”unityplayer.ForwardNativeEventsToDalvik” android:value=”true” />
    

    iOSでlandscape時に横幅いっぱいにWebViewが表示されない

    この問題を修正したプルリクがあり、あれちゃんと対応されているなーと思っていたんですが、
    Merge pull request #13 from endavid/fix/iOSLandscape · 3343201 · gree/unity-webview
    どうもunity-webview.unitypackageがこのコードを含んだ状態になっていないようで、これをいくらimportしてもうまくいかなくてハマりました。

    以下のふたつのファイルをimport後に上書きするとこの問題は解消されます。

    1. unity-webview/plugins/WebViewObject.cs at master · gree/unity-webview
    2. unity-webview/plugins/iOS/WebView.mm at master · gree/unity-webview

    参考リンク

    unity-webviewを使ってサイトを表示してみました!! 〜i-tai.net〜
    Unity上でWebViewを表示する | 代打、俺
    Unity で WebView を表示してみた。 | Lonely Mobiler

    Unity4ゲームコーディング 本当にゲームが作れるスクリプトの書き方
    浅野 祐一 荒川 巧也
    SBクリエイティブ
    売り上げランキング: 9,521

    UnityでiOSのPushNotification(APNS)を実装する方法

    UnityでAndroidのPushNotification(GCM)を実装する方法 | ひささん日記のとき同様、Apple Push Notifications (PNS for iOS) – Unity | Games2win Developersこちらのサイトのとおりやってみました。

    iOS版の場合、NotificationServicesを使って自前で書く方法がちょいちょいググると出てくるんですが、この方法はremoteNotificationCountが1以上の場合かどうかで判断していて、ポーリングしないとダメみたいであんまりやりたくありませんでした。
    たとえOnFixedUpdate内でやったとしても常にPushNotificationがあるかどうかのために回すのはあんまりイケていないです。
    このあたりの議論は、Unity 3.5 and Push Notificationsここに載っています。

    その点、prime[31] – Unity plugin documentationはイベントハンドラを登録しておくことができるので、ポーリングする必要がありません。
    今回はこのAssetの使い方がメインになります。

    事前にPushNotification用の.p12(証明書)を作成する

    このあたりはググるとすぐに出てくるので割愛。
    .pemファイルを作る方法が多いのですが、キーチェーンアクセスから.p12ファイルを書き出してそれを使っても動くので、今回は.p12ファイルを使っています。
    (どうゆう環境でPushNotificationするかによるのかもしれません)

    PushNotification用のunitypackageをimportする

    Asset Storeから prime31 Etcetera Plugin などで検索し、購入後にimportしておきます。

    demoのシーンを使ってPushNotificationを試してみる

    demoにシーンがあるので、そちらを表示しておく。

    EtceteraEventListener.cs

    StringBuilderを使うので、以下を追加。

    using System.Text;
    

    アプリ起動時にPushNotification用のdeviceTokenを取得するAPIを実行します。(詳細は別クラスで)
    ここのremoteRegistrationSucceededの中でdeviceTokenが取れるので、PushNotificationを配信するサーバーに登録しましょう。

    private string regURL = null;
    
    void remoteRegistrationSucceeded( string deviceToken )
    {
      Debug.Log( "remoteRegistrationSucceeded with deviceToken: " + deviceToken );
    
      //string apns_env = "production";
      string apns_env = "develop";
    
      StringBuilder urlString = new StringBuilder ();
    
      urlString.AppendFormat ("&appName={0}", WWW.EscapeURL ("app_name")); // provided by G2W
      urlString.AppendFormat ("&app_id={0}", WWW.EscapeURL ("PNS_ID")); // provided by G2W
      urlString.AppendFormat ("&appversion={0}", WWW.EscapeURL ("APP_Version")); // provided by G2W
      urlString.AppendFormat ("&deviceuid={0}", WWW.EscapeURL ("00"));
      urlString.AppendFormat ("&devicetoken={0}", WWW.EscapeURL (deviceToken));
      urlString.AppendFormat ("&devicename={0}", WWW.EscapeURL (SystemInfo.deviceName));
      urlString.AppendFormat ("&devicemodel={0}", WWW.EscapeURL (SystemInfo.deviceModel));
      urlString.AppendFormat ("&deviceversion={0}", WWW.EscapeURL (SystemInfo.graphicsDeviceVersion));
      urlString.AppendFormat ("&apns_env={0}", WWW.EscapeURL (apns_env));
    
      Debug.Log ("http://" + ("hoge.com/" + urlString));
      regURL = urlString.ToString ();
    
      StartCoroutine ("RegDeviceForPush");
    }
    
    IEnumerator RegDeviceForPush ()
    {
      WWW w = new WWW ("http://" + ("hoge.com/" + regURL), UTF8Encoding.UTF8.GetBytes (regURL));
      yield return w;
    
      Debug.Log ("EEEEE: " + w.error);
    }
    

    アプリがフォアグラウンドでPushNotificationを受けた場合。

    void remoteNotificationReceived( IDictionary notification )
    {
      Debug.Log( "remoteNotificationReceived" );
      Prime31.Utils.logObject( notification );
    }
    

    アプリがバックグラウンドでPushNotificationを受け、さらにその通知自体をタップした場合。

    void remoteNotificationReceivedAtLaunch( IDictionary notification )
    {
      Debug.Log( "remoteNotificationReceivedAtLaunch" );
      Prime31.Utils.logObject( notification );
    }
    

    EtceteraGUIManagerTwo.cs

    アプリ起動時にEtceteraBinding.registerForRemoteNotifcationsを呼んで、deviceTokenを登録します。

    if( GUILayout.Button( "Register for Push" ) )
    {
      EtceteraBinding.registerForRemoteNotifcations( P31RemoteNotificationType.Alert | P31RemoteNotificationType.Badge | P31RemoteNotificationType.Sound );
    }
    

    タイミングの話し

    PushNotificationするときに必ずこの話しが出てきちゃう(ぼくだけかな?)んですが、ネイティブの場合は、

    アプリ側でPushが来るパターンは以下の3つ

    1. フォアグラウンドでプッシュ通知を受け取ったとき
    2. アプリのプロセスがバックグラウンドで生きているときにプッシュ通知を受け取り、ユーザーがアクションボタンをタップしたとき
    3. アプリのプロセスがバックグラウンドで生きていないときにプッシュ通知を受け取り、ユーザーがアクションボタンをタップしたとき

    ただ、2の項目は、アプリ内にバッジのようなものを表示する場合は、 ここではやらずapplicationDidBecomeActiveでやるようにしている。
    これの理由は、バックグラウンドにいてアクションボタンではなく、ホーム画面からアプリアイコンをタップされた場合の処理が必要なので、
    あえて、applicationDidBecomeActiveでやっているのです。
    ネイティブの実装例は、PushNotification-iOSandAndroid/iOS/AppDelegate.m at master · hisasann/PushNotification-iOSandAndroidこちらをご覧ください。

    問題はこの2のapplicationDidBecomeActiveでやるような処理をUnityでどうやるかですね。

    ここでNotificationServicesを使ってもできそうなんですが、
    せっかくなので、Etceteraを使ってみましょう。

    if( EtceteraBinding.getBadgeCount() > 0)
    {
      // 何か処理をゴニョゴニョ
    
      // バッジの数値をクリア
      EtceteraBinding.setBadgeCount( -1 );
    }
    

    アプリケーションがOnResumeしたタイミングでこんな感じでPushNotificationが来ていたかどうかを確認できます。

    参考リンク

    prime[31] – Unity plugin documentation
    Unity and IOS push notifications – Google Docs

    詳細! Objective-C iPhoneアプリ開発 入門ノート Xcode5+iOS7対応
    大重 美幸
    ソーテック社
    売り上げランキング: 2,016

    UnityでiOS StoreKit In App Purchase Pluginを使ってアプリ内課金をためす方法

    UnityでAndroid In App Billing Pluginを使ってアプリ内課金をためす方法 | ひささん日記でAndroid版を書いています。

    引き続き、prime31Asset Store – iOS StoreKit In App Purchase Pluginのプラグインを使って、iOSの課金のテストをしていきます。

    iOSで課金のテストをするには、iTunesConnectにてアプリを作成し口座登録をし課金アイテムを作成する必要があります。
    このときに、iOS Developer Programでやる必要がありEnterpriseではiTunesConnectにログインができません。

    以下、書くまでもない内容は項目だけ書いています。

    iOS Developer Programに登録する

    Certificates、AppID、Devices登録をしてProvisioning Profilesを事前に登録する

    iTunesConnectにアプリを登録

    Androidでは、実際に仮のapkを登録する必要がありましたが、ここでは必要ではないようで、
    単にアプリを作ればOKです。
    (申請するときと同じ項目いれないといけないのですんごくめんどうですが、、、)

    こちらのまとめでいけた!
    iTunesConnect アプリの登録方法 – 散歩しながら 〜〜アプリ開発〜〜

    口座情報を登録する

    結構面倒なんですが、これをやらないとアイテム作成のところで課金アイテムの登録ができませんでした。

    こちらのまとめでいけた!
    iTunesConnect 銀行口座の登録 – 散歩しながら 〜〜アプリ開発〜〜

    テストユーザーを登録する

    課金するときに使うテストユーザーを作成します。
    AppleIDに紐付いていないメアドでも大丈夫でした。

    こちらのまとめでいけた!
    iTunesConnect テストユーザーの作成 – 散歩しながら 〜〜アプリ開発〜〜

    課金用のアイテムを作成する

    iTunesConnectのアプリページの右側にManage In-App Purchasesボタンがあるのでクリックする。
    左上にあるCreate Newボタンをクリックする。
    講座情報の設定が済んでいれば、ここにいろんなアイテムの種類が並んでいると思います。

    それぞれの違いは以下のとおり。

    Consumable

        消費型

      Non-Consumable

         非消費型

      Auto-Renewable Subscriptions

        自動更新購読

      Free Subscription

        無料購読

      Non-Renewing Subscription

         非更新購読

    via: iTunesConnect アプリ内課金 プロダクト作成 手順 – 散歩しながら 〜〜アプリ開発〜〜

    今回は魔法石的なアイテムを作るので、Consumableを選択します。
    アイテムを作成するときにProduct IDはのちのち使うので、覚えておきましょう。

    via iTunesConnect アプリ内課金 プロダクト作成 手順 – 散歩しながら 〜〜アプリ開発〜〜

    UnityでiOS StoreKit In App Purchase Pluginを使ってみる

    StoreKitEventListener.cs

    そもそもHierarchyに追加したgameObjectが破棄されることがあるのか調査できていないのですが、これを追加。

    void Start ()
    {
        // このオブジェクトが破棄されないようにする
        DontDestroyOnLoad (gameObject);
    }
    

    StoreKitGUIManager.cs

    まずはStoreKitBinding.canMakePayments()でアプリ内の購入制限(子供に触らせる方とか)が掛かっているか確認する。
    (掛かっている場合の挙動はいろんなアプリを見て、促す仕組みが必要なのか要調査)

    if( GUILayout.Button( "Request Product Data" ) )
    {
      // array of product ID's from iTunesConnect. MUST match exactly what you have there!
      var productIdentifiers = new string[] { "magic_stone" };
      StoreKitBinding.requestProductData( productIdentifiers );
    }
    

    ここのstring配列にアイテムのProduct Idを入れる。

    購入が完了すると、StoreKitEventListener.purchaseSuccessful()が呼ばれるのでレシート情報をここで取得しAppサーバーに投げるなど。
    Androidの場合は、購入後に消費しないと何度も同じアイテムが買えなかったですが、iOSのほうは消費型アイテムなので、
    その処理は必要なさそうですね。

    今回もこちらのブログにすごく助けられました。
    [Unity][iOS] prime31 iOS StoreKit Pluginを使用したアプリ内課金の実装 : West Hill 開発メモ

    詳細! Objective-C iPhoneアプリ開発 入門ノート Xcode5+iOS7対応
    大重 美幸
    ソーテック社
    売り上げランキング: 752

    スマホサイト作っててハマったことメモ(2013/06/13時点)

    window.scrollTo(0, 1);でアドレスバーが隠れない

    以下のようにJavaScriptからスクロール位置をY軸方向に1px動かすことで、アドレスバーを引っ込めることができますが、
    これが動くことは動くんですが、すぐに隠れずに、4〜5秒ぐらいしてからヒョコッと隠れる。

    自分で作った他のサイトでは、すぐに引っ込むのになんでだろう、、、と

    (function () {
      $(window).bind("load", function (e) {
        setTimeout(doScroll, 100);
      });
    
      $(window).bind("orientationchange", function (e) {
        // e.orientation === "portrait" ? "縦" : "横"
        setTimeout(doScroll, 100);
      });
    
      function doScroll() {
        if (window.pageYOffset === 0) {
          window.scrollTo(0, 1);
        }
      }
    
      window.doScroll = doScroll;
    })();
    

    そもそもjQueryMobileを使っていると内部的に↑のようなことはしてくれるので、自分で実装する必要はないのですが、
    jQueryMobileを使ってもアドレスバーが引っ込むタイミングが遅い!

    早く引っ込むサイトとそうでないサイトを見比べると、Mobile Safariのアドレスのところに「リーダー」というのが出てる。
    (あやしい、、、)

    そこで、このリーダーを消す作業をしました。

    1. JavaScript疑う
    2. CSSを疑う
    3. 心が折れる
    4. htmlを削り始める

    この最後でbodyの中身をスッカラカンにすると、リーダーがなくなり、最終的にarticleタグが全体を囲っているとどうもリーダーが出ちゃう可能性が高い
    という結論に到達しました。
    とりあえずdivにしたんですが、それでもダメな場合があるらしく、そのあたりは以下の記事がわかり易かったです。

    リーダーのせいでmobile safariのアドレスバー隠しが遅れる – < /gecko >

    jQueryMobileでTransitionが効かない場合がある

    jQueryMobileの特徴として、次の画面をAjaxで取得しDomに追加し、アニメーションしながら遷移できる。
    というのがありますが、ここでの醍醐味は、アニメーションで遷移できる点かなーと思っています。

    このアニメーション効果、つまりTransitionがつかずに遷移する場合があったので、ここにメモ。

    Transitionがつかないと、Ajax遷移には間違いないんですが、一瞬で次の画面に行くので、
    普通に画面遷移しちゃったのかな?と思うぐらいよくわからない。

    そしてこれが起きる原因は、サイトの全体の高さがある一定以上を超える場合に起きる可能性が高い。

    以下にjQueryMobileのコードから抜粋してみました。

    Line: 3652

    Math.max( $.mobile.window.scrollTop(), toScroll ) &gt; $.mobile.getMaxScrollForTransition()
    

    Line: 3771

    defaultGetMaxScrollForTransition = function() {
      return $.mobile.getScreenHeight() * 3;
    };
    

    Line: 3797

    // Set the getMaxScrollForTransition to default if no implementation was set by user
    $.mobile.getMaxScrollForTransition = $.mobile.getMaxScrollForTransition || defaultGetMaxScrollForTransition;
    

    $.mobile.getScreenHeight() * 3;ここの部分が該当の箇所だと思うのですが、
    スクロール位置が、画面のScreenの高さを3倍したものを超えた状態でAjax遷移をするとTransitionが効かないという感じです。

    実際にconsole.logで出力しながらやってみたら、まさしくそのとおりでした。

    ここは割り切るか、画面の高さを縮めるか、という感じですかね。
    個人的にはこの配慮はパフォーマンスを意識して書かれていると思うので、Transitionが効かないときがあってもいいのかなーと思っています。
    普通の画面遷移よりはどのみち速いので。

    jQueryMobileのローディングにアニメーションgifが表示されない

    CoreなCSSだけあればよかったので、「jquery.mobile.structure-1.3.1.css」を読み込んでいたのですが、
    どうもアニメーションgifの部分のCSSクラスが定義されていなく、ローディングが一瞬出るのですが中身のグルグルがない状態でした。

    なので、本体のCSSから以下をコピーしてきました。

    /* loading icon */
    .ui-icon-loading {
      background: url(images/ajax-loader.gif);
      background-size: 46px 46px;
    }
    

    これで動いたけど、みんなどうしているのかな?

    jQueryMobileとanimate.cssの相性が悪い

    animate.cssに書かれている以下の内容がどうもダメっぽく、Transitionでアニメーションしたあとに少しチラつきがでちゃいます。

    body { /* Addresses a small issue in webkit: http://bit.ly/NEdoDq */
      -webkit-backface-visibility: hidden;
    }
    

    なので、animate.cssはインクルードせずにSassを使って以下のように組み込みました。

    @include keyframes(bounceIn) {
      0% {
        @include transform(scale(.3));
        @include opacity(0);
      }
      60% {
        @include transform(scale(1.05));
        @include opacity(1);
      }
      80% {
        @include transform(scale(.9));
      }
      100% {
        @include transform(scale(1));
      }
    }
    

    Sass楽ちん!

    iOSアプリからカレンダーに予定を追加する方法

    メモメモ。

    #import <EventKit/EventKit.h>
    #import "ViewController.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    
        EKEventStore *eventStore = [[EKEventStore alloc] init];
        // weakつけるとブロックの中で参照がなくなってたので、ダメみたい。でもselfは大丈夫
        // でもつけないとリークしちゃうよね?
    //    __weak EKEventStore *__eventStore = eventStore;
        __weak ViewController *__self = self;
        [eventStore requestAccessToEntityType:EKEntityTypeEvent
          completion:^(BOOL granted, NSError *error) {
              // なので、ブロックの中で再生成してみた
              EKEventStore *__eventStore = [[EKEventStore alloc] init];
    
              NSLog(@"__eventStore = %@", __eventStore.description);
              NSLog(@"__self = %@", __self.description);
    
              // カレンダーへのアクセスが許可された場合
              // 一度拒否を選んでしまうと二度とアクセスできないっぽい
              // ユーザーに設定からリセットしてもらう必要がある
              // via http://d.hatena.ne.jp/appbakery/20130327/p2
              // via http://program.station.ez-net.jp/special/handbook/objective-c/iphone/grant/reset.asp
              if (granted == NO) {
                  // ここはmainキューでないので、UIの操作はdispatch_get_main_queueを使ってmainキューの中でやる必要がある
                  // via http://d.hatena.ne.jp/zentoo/20121028/1351443185
                  dispatch_async(dispatch_get_main_queue(), ^{
                      UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"お知らせ" message:@"カレンダー操作の許可をくださいちょんまげ"
                                            delegate:self cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
                      [alert show];
                  });
                  return;
              }
    
              //アクセス許可後の処理はここで
              EKEvent *event = [EKEvent eventWithEventStore:__eventStore];
              event.title = @"ひささんピースDay";
              event.timeZone = [NSTimeZone defaultTimeZone];
              event.notes = @"ひささんを楽しませる会";
              event.startDate = [[NSDate alloc] init];
              event.endDate = [[NSDate alloc] initWithTimeInterval:6000 sinceDate:event.startDate];
              event.calendar = __eventStore.defaultCalendarForNewEvents;
              [event setCalendar:[__eventStore defaultCalendarForNewEvents]];
    
              NSError *err;
              BOOL result = [__eventStore saveEvent:event span:EKSpanThisEvent error:&err];
              NSString *message = nil;
              if (result) {
                  message = [NSString stringWithFormat:@"イベントの保存が完了しました。"];
              } else {
                  message = [NSString stringWithFormat:@"イベントの保存ができませんでした。 : %@", error];
              }
              __weak NSString *__message = message;
              dispatch_async(dispatch_get_main_queue(), ^{
                  UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:__message
                                        delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
                  [alert show];
              });
          }];
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    

    iOSのWebViewで外部リンクをネイティブブラウザで開く場合の注意点

    以下のメソッド内で外部リンクかどうか判断し、外部リンクの場合はネイティブブラウザを開くようにできるのですが、iOSのWebViewはややこしいことにiframeのリクエスト時もここに入ってきてしまいます。

      - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:  (NSURLRequest *)request navigationType:  (UIWebViewNavigationType)navigationType {-
    

    これだと計測用のiframeのリクエストもこのメソッド内に入るので、外部URLだった場合にネイティブブラウザで開くという挙動を入れると、このiframeのリクエスト時にも反応してしまいます。

    そこで方法としては、クリックした場合にのみ外部リンクかどうかを判断するようにするという方法があります。

    シンプルなサンプルですが、以下のような雰囲気。

      NSString *absoluteUrl = request.URL.absoluteString;
    
      // 外部リンクをクリックした場合には、デバイスのデフォルトブラウザを起動する
      NSRange range = [absoluteUrl rangeOfString:@'domain'];
      if (range.location == NSNotFound) {
          if (navigationType == UIWebViewNavigationTypeLinkClicked) {
              [[UIApplication sharedApplication] openURL:request.URL];
              return NO;
          }
      }
    

    そして、WebView側のコードに以下のようにクリックイベントをfireするコードで置き換えます。
    もしもアンカーリンクなどをクリックする場合は以下のコードは不要ですが、何かしらJavaScriptでURLを作ってlocation.hrefに入れている場合には置き換えが必要になります。

      $("#hoge")[0].dispatchEvent(click());
    
      function click(){
        var event = document.createEvent("HTMLEvents");
        event.initEvent('click', true, false);
        return event;
      };
    

    AndroidのWebViewはiframeなどはshouldOverrideUrlLoadingメソッドの中に入ってこないので、楽なのですが、
    iOSは結構ややこしいですね。

    iPhoneアプリの申請方法、iTunesConnectからXCodeへ(2013/03/18時点)

    だいたいの手順はアプリ申請まとめ(2012/3現在)|iPhoneアプリ開発日誌 -ZERO-が参考になりました。
    また、iTunesConnectでの作業はiTunes ConnectでiPhone/iPadアプリの新規登録を行う手順(2013年1月現在)が良い感じ。
    ただ、やはりiTunesConnectでの入力項目はしょっちゅう変わるようなので、以下に2013/03/18時点の内容を書いておきます。

    iTunesConnect

    iTunesConnectでアプリ情報を入力する。

    iTunes Connect

    アプリケーション情報の入力

    Default Language
    App Name
    SKU Number(UTF-8の英数字で表されたアプリケーションの一意の識別子)
    Bundle ID(AppId)

    地域、価格、公開の設定

    Availability Date
    公開日は先にしておかないとリリース日順検索で上位に表示されないらしい。
    リジェクトされた場合に、公開日がすぎると過去日になってしまうので新着で引っかからない可能性がある。
    なので、少し先にしておいて公開後に調整すればよいみたい。
    [via]
    iPhoneアプリ申請時に知っておくと良いこと – されど空の青さを知る
    【開発】利用開始日(Availability Date)の設定は戦略的に!申請したiPhoneアプリ「e-スペル」がReady for Sale(販売中)に – creativi.tea

    Price Tier
    Discount for Educational Institutions
    Custom B2B App
    App Store Worldwide

    バージョン情報の入力

    Version Number
    Copyright
    Primary and Secondary Category(カテゴリは選んだらあとで修正できないと聞いたことがある)
    Subcategory
    Rating

    メタデータ

    Description
    Keywords
    Support URL(ティザーサイトでなくてもよい)
    Marketing URL(非必須)
    Privacy Policy URL(非必須)

    App Review Information

    レビュー後のAppleからの連絡用。

    First Name
    Last Name
    Email Address
    Phone Number
    Review Notes(非必須)

    画像のアップロード

    Large App Icon
    1024 x 1024

    3.5-Inch Retina Display Screenshots(iPhone4)
    960×640, 960×600, 640×960 or 640×920
    3枚設定した

    4-Inch Retina Display Screenshots(iPhone5)
    1136×640, 1136×600, 640×1136 or 640×1096
    3枚設定した

    iPad Screenshots
    1024×768, 1024×748, 768×1024, 768×1004, 2048×1536, 2048×1496, 1536×2048 or 1536×2008
    3枚設定した

    Routing App Coverage File(非必須)

    Done!!

    iTunesConnectでView Detailをクリック。

    Ready to Upload Binaryをクリック。

    暗号化についての回答

    ここで、ややこしい英語が出てきた。
    ググりながら回答した。

    「Is your product designed to use cryptography or does it contain or incorporate cryptography?」

    httpsを使っている場合はYESを選択。

    「Does your product qualify for any of the exemptions provided in Category 5 part 2?

    You are responsible for the proper classification of your product; make certain that it meets the criteria of the exemption (listed here). Otherwise you may be in violation of the US export laws and could be subjected to penalties including delisting of your app from App Store. Please go through the FAQ page thoroughly before attempting to answer the question.

    You can answer “YES” to question #2, if the encryption in your app is: (a) is specially designed for medical end-use; (b) is limited to intellectual property or copyright protection; (c) is limited to authentication, digital signature or the decryption of data or files; (d) is specially designed and limited for banking use or ‘money transactions’; (e) is limited to “fixed” data compression or coding techniques; or (f) if your app meets the descriptions provided in Note 4 to Category 5 Part 2.

    Please visit the FAQ for additional guidance on the exemptions.」

    YESを選択。

    [via]
    HTTPS を使ってるアプリを AppStore や Android Market で配信するときの輸出手続きについて(その2) – 規制対象になるかどうかの判断 – むらかみの雑記帳

    Saveをクリック。

    アプリのStatusがWaiting For Uplaodになっているのを確認する。

    XCodeからアプリをアップロードする

    Archiveでビルドする
    Organizerが立ち上がるので、Distributeをクリック。

    「Submit to the iOS App Store」をチェック。

    iTunesConnectでログインしたアカウントを入力する。

    アプリのDistribution用のプロビジョニングプロファイルを選択する。
    (注意:ここでAdHoc用のプロビジョニングプロファイルも選べるので間違えないように、というかAppStoreへの申請なのになんでAdHocのほうが出てくるんだ!)

    Nextをクリック。

    15分ほど掛かる。

    No issues were found in “アプリ名”がでたらOK!

    Finishをクリック。

    オーガナイザ画面のアプリのStatusが、Submittedになっていることを確認します。

    iTunesConnectを見てみると「Waiting For Upload」から「Waiting For Review」に変わります。

    やったーーーー!

    おつかれさまでした!

    .dSYMファイルを控えておく

    あとあとクラッシュログを読めるようにしておくため、
    「~/Library/Developer/Xcode/DerivedData」の下の該当のアプリディレクトリで、
    .dSYMファイルを検索し、リリースビルドしたファイルをバックアップしておく。

    via iOSデバイスのクラッシュログを読むには – Awaresoft

    余談 – お客さんのiTunesConnectアカウントにログインして申請を代行する方法

    iPhoneアプリの申請を自分のAppleアカウントからやる場合は、ごくごく普通にiTunesConnectから行えばいいのですが、
    お客さんのiOS Developer Centerに登録されたアカウントからやる場合はちょっとややこしい。

    まず、iOS Developer Programに登録されていないAppleアカウントを作る必要がある。
    つまり普通にiTunesを使う用途としてAppleアカウントを作成して、そのアカウントをお客さんのiTunesConnectにAddしてもらう。
    これで、お客さんのiTunesConnectにログインできて申請作業が行える。

    こんな方法があるなんて初めて知りました!

    よくわかるiPhoneアプリ開発の教科書【iOS 5&Xcode 4.2対応版】
    森 巧尚
    マイナビ
    売り上げランキング: 7,831

    Push通知をまとめてみた(PushNotification) – iOS and Android

    hisasann/PushNotification-iOSandAndroid · GitHubにあげておきました。
    iOSとAndroidはディレクトリを分けて各ファイルを置いておきました。

    プロジェクト自体は重いのでpushしていないです。
    README.mdに基本的なことを記載。

    iOS

    PushNotification-iOSandAndroid/iOS at master · hisasann/PushNotification-iOSandAndroid · GitHub

    release用のPush通知証明書ファイルでためす場合は、AdHocとして入れればOK!

    Android

    PushNotification-iOSandAndroid/Android at master · hisasann/PushNotification-iOSandAndroid · GitHub

    はじめてのAndroidアプリ開発―Android4対応版 (TECHNICAL MASTER)
    山田 祥寛
    秀和システム
    売り上げランキング: 10,811

    UIViewのbackgroundImageをrepeatする方法

    すんごく簡単なんだけど、忘れそうなのでメモメモ。

    self.view.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:@"hoge.png"]];
    

    [via]
    cocoa touch – Repeating background image in native iPhone app – Stack Overflow