2019年2月7日木曜日

AR Cake Divider をリリースしました

この度初めてのiOS向けアプリ AR Cake Divider をリリースしました。
ケーキやピザを等分するときに役に立つアプリです。これまで同じ様なアプリはありましたが、ARKitを使ってガイドを付けるアプリは世界初なはずです。




予算の関係上スクリーンショットはピザになってしまいましたが…、今後ケーキを切ってるところをYouTubeにでもアップしたい思ってます。






2019年2月5日火曜日

ARアプリに必要なターゲットカーソルをBlenderで作る

はじめに

ARアプリを作ると、3Dオブジェクトをどこに置くかをユーザにわかりやすく伝えるためターゲットカーソルが必要になることがあります。
Apple公式のメジャーアプリでも出てくるアレです。


このカーソルは切れ目が入っているためSCNTubeやSCNPlaneを組み合わせて作ることが難しいですが、Blenderを使えば簡単に作れるのでその方法を紹介します。

作成環境

  • macOS 10.14.1
  • Blender 2.79

作成手順

1. Blenderを起動する

Blenderを起動するとイニシャルのプロジェクトで上記のような立方体が表示されます。

2. 立方体とカメラ、ライトを削除する

邪魔なので右のペインから全て削除してしまいましょう。

3. カーソルを原点に持ってくる

Shift + S でカーソルを原点に移動させることができます。

4. 円メッシュを2つ作成する

左のペインの「作成」タブ→円を選択して円を作成します。
そのまま「ツール」タブ→拡大縮小を選択すると円の大きさを調整できます。
これを繰り返し大きい円と少し小さな円を2つ作成します。


5. 2つの円メッシュを統合する

このままだとバラバラで面をはると穴なしの円になってしまうため2つを統合して細長いドーナッツを作ります。
「ツール」タブ→統合を選択します。

6. ドーナッツに面を張る


編集モードに変更して、下のペインのメニューから メッシュ > 面 > 面を張るを選択します。

7. ドーナッツ(輪っか)を削る

このままではただの輪っかなのでターゲットカーソルらしく一部分を削ります。
編集モードのままCを押して削りたい部分の頂点を選択します。
そのまま、メニューの削除→頂点を選択します。
同じ作業を繰り返して3箇所削ります。

8. 中央に小さな円メッシュを張る

中央にカーソルを持っていき先ほどの手順で小さな円メッシュを張ります。

9. 両面表示設定をする

このままだと中央の円と周りの輪っかがそれぞれ裏表逆になることがあるので、裏表無しになるように右側のペインを操作して両面表示をONにします。

10. DAE形式のファイルでエクスポート

Xcodeで扱うためDAE(Collada)形式でエクスポートします。

エクスポートが完了すると下記のようにXcodeで扱えるターゲットカーソルが出来上がります。






2019年1月30日水曜日

ARアプリ開発するのにもPlaygroundが便利

はじめに

今ARKitを使ったiOS向けのアプリの開発をしています。ARKitは3Dゲーム開発のためのフレームワークであるSCNKitの延長線にあるため、よく自分の書いたコードが3D空間で思い通りに動いているか確認をします。
これまでは、都度実機で確認を行なっていたのですが、座標変換などの簡単な確認をするのにもいちいち実機で動かすのは面倒に思っていました。しかし、最近PlaygroundでもSCNKitやSpriteKitを動かせることを知り、これは便利だと思ったので紹介します。

手順

1. Xcodeのメニュー > File > New > Playground...を選択する

2. Gameを選択する


画面の左下のexecuteボタンを押すことでコードが実行できますが、この状態では何も表示されません。

3. 右上の Show the Assistant Editor を押す

4. Assistant Editorを開いた状態でコードを実行する


Assistant Editor上に擬似画面が表示され、SpriteKitのコードが実行されます。

ちゃんとマウスカーソルで画面を押すことでインタラクションイベントも確認することができます。


2019年1月27日日曜日

Android 7.0以降だとSAF経由でGoogleDriveへファイル作成ができないことがある

はじめに


最近Androidアプリのメンテナンスをしていなかったので一番の稼ぎ頭であるNudiaの不具合修正を行うことにしたが、結構大変だったので内容を残しておく。

不具合の内容と調査結果


不具合の内容


アプリバージョン 2.3.0でAndroid 7.0以降の端末で作成した画像ファイルをGoogleDrive上に保存しようとすると0 byteの空ファイルができる。

調べてみると、stackoverflowの方に同じ悩みを抱えている人がいたが原因と対策は分かっていない様だった。

調査結果


当初、Android Nougatからアプリ間のファイル共有のポリシーが厳しくなったことにより、プログラム側での処理が足りていないのかと思っていたが、ファイルの保存方法はAndroid公式のSAFに紹介されている手順に従っているため、他に原因があるはずだが全く見当が付かなかった。

そして施行錯誤をする中で、なぜかGoogleDriveを再起動した直後の1回だけ保存が成功することがわかった。

ちなみに公式のSAFのドキュメントでACTION_OPEN_DOCUMENTインテントとACTION_CREATE_DOCUMENTインテントを紹介してCATEGORY_OPENABLEと組み合わせることで次の3パターンのファイルwrite操作が可能になる。


CATEGORY_OPENABLE ありCATEGORY_OPENABLE なし
ACTION_CREATE_DOCUMENT
新規にファイルを作成
空ファイル作成
ACTION_OPEN_DOCUMENT
既存ファイルを上書き


アプリ側ではACTION_CREATE_DOCUMENTとCATEGORY_OPENABLEを指定しているのだが、何故か2回目以降だとCATEGORY_OPENABLEが効いていなかった。
またlogcatでGoogleDriveの動作を監視してみると、再起動直後のみアプリでの保存操作後ActivityManagerから何かしらのIntentがGoogleDriveへ飛んでいることが分かった。
しかしこれ以上の原因については分からなかったため、暫定対応としてAndroid Nougat以上ではGoogleDriveへの保存をさせないように制限を付けることにした。

もし今後さらなる原因がわかったら、恒久的な修正をリリースします。


最後に、これまでアプリをご利用くださっていた方にはご不便をおかけして申し訳ありませんでした。



2019年1月26日土曜日

GoogleDirveでのStackEdit解除方法



はじめに


AndroidからiOSアプリへ開発を切り替えたのをきっかけにこのブログではなくQiitaの方へ投稿をしていたのですが、プログラミング以外のことも書きたいのでこっちを再開しようかと考えています。

Qiitaの投稿はMarkdownだったため、そちらで慣れているとBloggerでもMarkdownで書きたくなります。調べてみるとStackEditというWebツールを使えばMarkdown形式で書けるとのことで導入して見ましたが次の理由でやっぱりやめました。

StackEditについて気になった点


  • 画像を投稿に含める場合、先にどこかにアップしてそのURLを指定しなければならない(ドラッグ&ドロップで自動でアップロードしてくれない)

  • StackEditで作ったドキュメントだけとは言え、自分のGoogleDriveへのアクセスを付けるのが嫌

  • そもそも、GoogleDrive上でStackEditドキュメントを作成しようとしたときに確認されるアカウント紐付けで、「Restrict access」を選択しないとGoogleDrive上の全てのアクセス権を要求されるのが物凄く嫌



そのためGoogleDriveに設定したStackEditを解除することにしましたが、やり方が分からず戸惑ったのでやり方を残しておきます。

StackEditの解除方法

  • GoogleDriveを開く

  • 右上の設定アイコンを選択し、設定を開く

  • 左のペインよりアプリの管理を選択し、一覧の中にあるStackEditを選び「ドライブから切断」をクリックする







2017年7月27日木曜日

複数積んだFragmentBackStackを一度にクリアする方法


Androidアプリを作るとき昔はActivityを複数作ってIntentで画面切替をしていた。
今は一つのActivityの中にFragmentを突っ込んでFragmentを切り替えることで画面切替をするようにしている。(一般的にもこの方法が今はメジャーなはず)

FragmentA、FragmentB、FragmentCと3つのFragmentがあったとして、
Fragmentでの画面切替は普通こんな感じで行う。

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.container, FragmentA.newInstance() );
transaction.addToBackStack(null);
transaction.commit();

commit時にBackStackに積んでいるので
[A] -> [B] -> [C] と画面切替をした後バックキーを押す度に

[C] -> [B]

[B] -> [A]

と切り替わる。

しかし、一度に [C] -> [A]に戻りたいときどうすれば良いかを分らなかった。

調べたところ、BackStackはFragmentのインスタンスを保存しているのではなく、transactionで行ったことを記録しているだけらしく、popBackStack()が実行されると記録されたことと逆のことが実行される。

例えば、replace(FragmentB)(中身はremove(FragmentA)してadd(FragmentB))が記録されていた場合
その逆のremove(FragmentB)してadd(FragmentA)が実行されるらしい。


知らなかったーーーー。

それならば、話は簡単で[C] -> [A]に一度のバックキーで戻るにはFragmentBackStackの数が変わったことをイベントトリガとして、BackStackの数が0に成るまでpopBackStackを呼んでやれば良い。

FragmentBackStackの数の変更はFragmentManager.OnBackStackChangedListnerを実現することで検知できる。

以下サンプルコード

public class MainActivity implements FragmentManager.OnBackStackChangedListener{
    private int fragmentBackStackCnt = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        FragmentManager fragmentManager = getFragmentManager();
        fragmentManager.beginTransaction().add(R.id.container, FragmentA.newInstance()).commit();
        fragmentManager.addOnBackStackChangedListener(this);
    }

    @Override
    public void onBackStackChanged() {
        FragmentManager fragmentManager = getFragmentManager();
        int count = fragmentManager.getBackStackEntryCount();

        if (fragmentBackStackCnt > count && count != 0) {
            // backStackが0になる(検索画面になるまで)fragmentをpopする
            fragmentManager.popBackStack();
        }
        fragmentBackStackCnt = count;
    }

}

参考ページ:
Android Fragment トランザクション - バックスタックの落とし穴

2016年12月2日金曜日

iOSアプリのNavigationBarにメニューアイコンをつける

やりたいこと

下のスクリーンショットにあるようにNavigationBarにメニューを示すアイコンを表示したい

やり方

  • IoniconsSwiftを使ってアイコン画像をロード
  • ロードした画像を使ってUIBarButtonItemインスタンスを作成
  • インスタンスをnavigationItemにsetLeftBarButtonItemsしてやるだけ

実際のコード

override func viewDidLoad() {
    super.viewDidLoad()
    
    // NavigationBarの高さ × 0.8にアイコンのサイズを指定する
    let barHeight : Int = Int((self.navigationController?.navigationBar.frame.size.height)! * 0.8)
    
    // Ioniconよりメニューアイコン画像をロード
    let menuIcon = UIImage.imageWithIonicon(ionicon.AndroidMenu, color: UIColor.black, iconSize: CGFloat(barHeight), imageSize: CGSize(width: barHeight, height: barHeight))

    // アイコン画像からUIBarButtonItemを生成
    let leftNavEditButtonItem = UIBarButtonItem(image: menuIcon, style: .plain, target: self, action: #selector(MyViewController.doSomething))

    // UIBarButtonItemをセット
    self.navigationItem.setLeftBarButtonItems([leftNavEditButtonItem], animated: true)
}