20200602のiOSに関する記事は7件です。

RealityKitで直方体の6面それぞれに異なるテクスチャを貼る方法

はじめに

先月、「iPad Pro の LiDARスキャナ を試してみた」という記事を書きました。
その時はじめて RealityKit をいじったのですが、その後、ちょっとしたアイディアを実装してみようと思った時、RealityKit上のオブジェクトにコードのみでテクスチャを貼り付ける方法が見つからず、意外と悩んでしまいました。

ようやくその方法がわかったので、備忘録を兼ねて記事とします。

2017年に「 ARKitで立方体の6面それぞれに異なるテクスチャを貼る方法」という記事を書いていますが、つまり同じことを RealityKit でやってみようという話です。

環境環境等

  • MacBook (Retina, 12inch, Early 2015)
  • macOS Catalina Version 10.15.5
  • Xcode Version 11.5 (11E608c)
  • iPad Pro (12.9インチ) (第4世代)/ iPhone X
  • iOS 13.5 (17F75)

本記事に記載した範囲では LiDAR 搭載である必要はありませんので、普通の iPhone で試すことができます。

実装

プロジェクトの作成

Xcodeで、テンプレートとして “Augmented Realit App” を選択し、Content Technology には “Reality Kit” を選択してプロジェクトを作成します。

テクスチャの準備

ARKitで立方体の6面それぞれに異なるテクスチャを貼る方法」同様、6面分の画像を用意します。

画像は Assets に Texture として登録しておきます。
具体的には、Assets.xcassets の "+" ボタンをクリックし、"New Texture Set" を選択し、現れる枠に画像をドラッグ&ドロップします。名前も適当につけておきます。

スクリーンショット 2020-06-02 11.14.48.png

コード

まずは、ViewController.swift の viewDidLoad() に下記を追加します。
シーンにアンカーを追加し、直方体の生成と配置を行なってます。

なお、雛形のままだと、"Experience.rcproject"の読み込みとシーンへの追加なども viewDidLoad() 内に書かれていますが、それは削除してかまいません。

ViewController.swift
        // ----- ARKit3.5 ScenUnderstanding設定
        // いったん空に
        arView.environment.sceneUnderstanding.options = []

        // オクルージョンを有効化
        arView.environment.sceneUnderstanding.options.insert(.occlusion)

        // デバッグオプションの設定
        arView.debugOptions.insert(.showWorldOrigin)
        arView.debugOptions.insert(.showStatistics)

         // シーンにアンカーを追加する
         let anchor = AnchorEntity(world: SIMD3<Float>(0.0, 0.0, 0.0)) // 原点=デバイスの位置に置く
        arView.scene.anchors.append(anchor)

        // 直方体を生成
        let boxEntity = makeBox()

        // 初期位置の指定: anchorに対して、50cm画面奥、10cm上方に配置
        boxEntity.position = SIMD3<Float>(0.0, 0.1, -0.5)
        anchor.addChild(boxEntity)

直方体の生成〜テクスチャの貼り込みは下記のように行います。

ViewController.swift
    func makeBox() -> ModelEntity  {
        // 幅10cm、高さ20cm、奥行き30cmの立方体を作成
        let boxModel = ModelEntity(mesh: .generateBox(width: 0.1, height: 0.2, depth: 0.3, cornerRadius: 0.01, splitFaces: true))

        // 6面、別々のテクスチャを貼る
        var material1 = SimpleMaterial()
        var material2 = SimpleMaterial()
        var material3 = SimpleMaterial()
        var material4 = SimpleMaterial()
        var material5 = SimpleMaterial()
        var material6 = SimpleMaterial()
        material1.baseColor = try! .texture(.load(named: "Texture1"))
        material2.baseColor = try! .texture(.load(named: "Texture2"))
        material3.baseColor = try! .texture(.load(named: "Texture3"))
        material4.baseColor = try! .texture(.load(named: "Texture4"))
        material5.baseColor = try! .texture(.load(named: "Texture5"))
        material6.baseColor = try! .texture(.load(named: "Texture6"))

        boxModel.model?.materials = [material1, material2, material3, material4, material5, material6]

        return boxModel
    }

ポイントは下記の2つ。

6面に別々のテクスチャを貼る

.generateBox() で直方体の形状を定義していますが、この splitFaces: を true とすると、6面別々のテクスチャが貼られます。逆に false だと、どの面にも1番目のテクスチャが貼られます。

なお、この例では立方体の角を丸めていますが、cornerRadius: を 0 にすれば、普通の角ばった直方体になります。

画像ファイルからテクスチャを読み込む

SimpleMaterial() で、Material を生成し、その色(baseColor)として Assets から読み込んだテクスチャを設定します。

むむ、ここが分かりにくかったところです!

baseColor という属性名からは、赤とか青といった"色"を設定する属性としか想像できませんが、ここにテクスチャを貼れるという点が想像の範囲外。
またそこにテクスチャをセットする際に、引数を取る列挙型を利用するという点もやや技巧的なように感じます(よく考えると合理的なのですが)。

実行結果

まずは、スクリーンショットをご覧ください。

赤がX軸、緑がY軸、青がZ軸で、それらが交差している原点が、アプリを立ち上げたときのデバイスの初期位置で、一歩下がって、Z軸プラス方向からやや見下ろしている画像です。

A64D3A73-A376-493C-B7A1-45C7DC8FA151.jpeg

こちらは反対側から、つまりZ軸マイナス方向から原点方向をやや仰ぎ見る形になっている画像です。

423CB375-CE15-41F3-ABD5-93FC0CB536FC.jpeg

ちょっと予想外の結果です。

ARKitで立方体の6面それぞれに異なるテクスチャを貼る方法」の実行結果と比較してその違いを確認してください。
なんとテクスチャが裏返になっているうえに上下が逆で、各面に割り付ける順番も異なっています。SceneKitでのテクスチャ貼り付けとこんなに違う結果が得られるなんて。

こうならない設定があるか、あるいは何か合理的な理由があってこうなっているのだと思いますが、そのあたりはまだ調べがついていません。

おわりに

RealityKit を使用して、コードだけでAR空間にオブジェクトを生成し、そこにテクスチャを貼り付けてみました。結果的に SceneKit との意外な差異に気がつくことにもなりました。

RealityKit を使うと簡単にARアプリを作成することができる反面、パーティクルが使えないなど、SceneKit に比べまだまだ不足している機能が多くあります。このあたりは今後拡充していくものと期待しています(とりあえず直近のWWWDCでの発表を楽しみにしています。)。

本稿がARアプリ開発の参考になれば幸いです。

参考

個人的な宣伝

先月、iOS(iPad)アプリ「簡単
iOS(iPadOS)アプリ「簡単便利な階層型情報メモアプリ HiMemo」を個人開発でリリースしました。リリース時に悩んだセキュリティポリシーについての記事を近日中にまとめたいと思っています。
ご興味のある方は、こちらをご覧ください → https://apps.apple.com/jp/app/himemo/id1506694081
※ARは関係ないアプリです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

個人開発したアプリが1万ダウンロード突破したので振り返ってみる

開発したアプリ

Shiori web for safari

コンセプト

『webにしおりをはさめるアプリ』というコンセプトで開発した。

機能

1. 記事保存機能

読んでいる最中の記事やwebサイトをスクロール位置とともに保存できる。それによって、次回開いたときに読んでいた場所からすぐに再開することができる。どこまで読んだか、とかいちいちスクロールして探す必要がない。

2. 動画保存機能

觀ている最中の動画を再生位置とともに保存できる。それによって、次回開いたときに觀ていた時間から再開することができる。簡単に共有もできる。動画の面白い部分を何度も觀たり、友達に見せることがワンタップで可能になる。

使い方

safariで任意のページを開き、共有 > Shioriをタップ。

経緯

  • 今まで作ったアプリはポートフォリオ用とかでユーザーがいなかったので、実際に需要がありそうなアプリを作りたかった

  • iOSアプリ開発の勉強

という主に2つの理由で開発した。

実装

期間

  • 実装期間はだいたい一週間くらい(初版をリリースするまで)。

技術

  • swift, Xcode
  • iosで完結している。share extensionで記事のデータを取得し、coreDataに保存している。
  • 分析はfirebase

こだわり

このアプリではiosで提供されているのshare extensionという機能を利用して記事を保存している。

だが標準では記事のタイトルやURLしか取得できないので、共有ボタンを押したときにjavascriptファイルを実行して、スクロール位置や動画の再生位置を保存している。この機能を使っている先人がほぼいなくて苦労した。

この機能についてはqiitaにも書いた。

データ

  • ダウンロード数は現状12650くらい(20.05.27現在)

  • 広告を一応貼ってはいるが、広告は自由にオフにできるのと、アプリの性質上ユーザーの一回あたりの利用時間が短いのもあって、広告収入は月に数百円くらい。

  • 日本appstoreにおけるニュースカテゴリで1位を達成(2020.03.17)

  • 中国appstoreにおけるニュースカテゴリで118位達成(2020.03.06)

  • 韓国appstoreにおけるニュースカテゴリで4位達成
    その他、台湾・香港等においてもランキング入り

リリース後

  • リリース後しばらくはいわゆるゾンビアプリだった

    →ダウンロード数50、とか。ユーザーも自分を含めて2~3人、という状況が数ヶ月続いた。

  • しばらくしてwechatのおすすめかなんかに乗った(たぶん)

    →中国圏からのダウンロードが爆伸びした

  • その後アプリ紹介系のサイトに何度か紹介してもらって、増えていった。

学び

  • 9割のアプリはゾンビアプリ

    • ゾンビアプリというのは、検索順位が低すぎて誰にも認知されないアプリのこと。appstore等で自分たちが見聞きしているアプリは上位1%の勝ち組アプリ。実際自分で作っても基本見向きもされない。
  • これは反省も込めてだが、アプリ名とスクリーンショットですべての情報を伝えるべき。それ以外の情報は伝わらないと思って良い

  • アプリ出すなら多言語対応して損はない

    • アプリの種類によっては不可能なものもあるだろうが、可能ならば複数の言語に対応しておくと良いんじゃないかと思う。どの国で使われるかはリリースしてみないとわからない。ちなみにこのアプリも中国と韓国のユーザーさんが大半を占めている。
  • 多言語対応するなら最初からしておいたほうがいい

    • 前のとの関連で、リリースした後に新しく外国語に対応しようとすると、スクリーンショットを作り直したり専用のファイルを作成しないといけなくて意外と大変。
  • 完成からリリースまで時間かかる

    • Appleの審査は厳しくて、謎の理由でリジェクトされることが結構ある。
    • このアプリもなかなか使い方を理解してもらえなくて、提出からリリースまで一ヶ月くらいかかった。
  • 人に使ってもらえるアプリを作るのはむずかしいけど、最高に楽しい

役に立ちそうなツールとかリソースとか

  • 教材

    • これをやれば簡単なアプリは作れる。実務で使うには足りないと思う(知らない)。このサイトはかなり頻繁にセールをやっているので安いときに買うのがおすすめ。
  • アイコン

    • アプリアイコンの編集はcanvaというサイトが便利だった。
    • ↑で作成したアイコンはappiconというサイトでいろんなサイズ用に自動生成できる。
  • スクリーンショット作成

    • スクリーンショット(appstoreのアプリ紹介画像)の作成方法については、良い方法(無料で簡単)が見つかっていない。現状はLaunchKitAppLaunchpadを組み合わせて作っている。もっと良いツールを知っている人がいたら教えてほしい。Illustratorを勉強するしかないのか...?

感想

  • 家族でも友達でもない人に自分の作ったものを使ってもらう、という感覚が不思議で、嬉しい。
  • ユーザーからフィードバックがくるのが嬉しい。良い評価だったらもちろん嬉しいし、批判もなんならうれしい。開発やっててよかったと思う瞬間。
  • 外国の方が日本語でほめてくれたことがあってめっちゃうれしかった。

その他

githubリポジトリはこちら: https://github.com/MasatoraAtarashi/Shiori

このアプリに関するお問い合わせ・フィードバックは以下のアドレスで受け付けています。
shiori.web.forsafari@gmail.com

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

設定したフォントが実機で変わってしまう

現象

XibでSystemフォントを設定しているのに、
実機で動かすと別のフォントで表示されている。
そんなこと変えた覚えないんだが…

環境

Xcode 11.5
Objective-C
iOS 13.5

調査

フォントが変わってしまっているのは、
UITextViewを使っている所だった。

対応

フォントが変わっていないUITextViewもあったので違いを確認したら、
コード上でフォントサイズを指定していたので、
フォントが変わってしまった所も指定する様に変更したらSystemフォントで表示された!!

修正例:textView.font = [UIFont systemFontOfSize:16]

あとがき

よくよく調べてみとiOS13のバグらしい…
今回は簡単に自己解決出来たから良かったけど、
こっそり変わってるから半年ほど気づかなかったよ…

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

R.swiftのスクリプトが意外に遅かったので使用箇所を1モジュールにすることでビルド速度改善

概要

R.swiftを複数のモジュールで使用していたのですが、差分ビルドの遅い箇所を分析するとR.swiftの毎回走るコード生成のスクリプトがリソースの量に関わらず

1箇所毎8秒程 ※差分ビルド・フルビルド関係なく

掛かっていて、何も修正していなくてもビルドする度に時間が掛かっていたのをリソースを1つのモジュールにまとめて、R.swiftのスクリプト実行を1回にすることでビルド速度改善しました。

26秒(3箇所) -> 9秒(1箇所) (-17秒)

【R.swift】
https://github.com/mac-cain13/R.swift

環境

  • R.swiftバージョン:5.1.0
  • Xcode 11.x
  • iMac (Quad-Core Intel Core i7 4.2 GHz, メモリ32 GB)

実施した施策

R.swiftを使用するリソースを1つの共通モジュールに集めそこでR.swiftを使用する

他のモジュールでも使用する場合は こちら のように --accessLevel public をスクリプトに付けることで public でアクセスできます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コード量を減らさずSwiftファイル数を減らしてビルド速度改善

概要

私の担当するアプリはSwiftファイル数がかなり多い(1モジュール内のSwiftファイル数も結構多い)のですが、
必要なコード量はそのままにSwiftファイル数を減らすことでビルド速度改善ができないかと言う話になりました。

環境

  • Xcode 11.x
  • iMac (Quad-Core Intel Core i7 4.2 GHz, メモリ32 GB)

先に結論

ビルドするマシンの環境依存の影響もあると思いますが、以下のような結果になりました。
可読性もあるのでそこまでファイル数を減らせていないですが、Swiftファイル数減少に応じて多少ビルド速度改善されました。
(一応複数回計測して平均と取っています)

[削減したSwiftファイル数]
 対応前
  4325
 対応後
  3943 (-382)

[ビルド時間計測結果(フルビルド)]
 対応前
  282秒
 対応後
  276秒 (-6秒程)

やった施策

関連性の強いコードを一つのファイルにする

例えば下記のように特定のファイルに強く依存しているenumを定義しているファイルを

〜Type.swift

enum 〜Type {
  ...
}

〜Processor.swift

class 〜Processor {
  ...
}

強く依存している方のファイル内で定義する

〜Processor.swift

enum 〜Type {
  ...
}

class 〜Processor {
  ...
}

短く冗長的なコードを一つにまとめる

短く冗長的なSwiftファイルが複数ある場合

〜Executable.swift

public protocol 〜Executable {
  ...
}

〜HogeExecuter.swift

struct 〜HogeExecuter: 〜Executable {
 (短いコード量)
}

〜FugaExecuter.swift

struct 〜FugaExecuter: 〜Executable {
 (短いコード量)
}

1つのファイルにまとめて定義する

〜Executable.swift

public protocol 〜Executable {
  ...
}

struct 〜HogeExecuter: 〜Executable {
 (短いコード量)
}

struct 〜FugaExecuter: 〜Executable {
 (短いコード量)
}

サイズなどが異なるだけの定義を1つのファイルにまとめる

サイズ毎に定義があるがSwiftファイルが分かれているのを

Small〜Entity.swift

public class Small〜Entity {
  ...
}

Medium〜Entity.swift

public class Medium〜Entity: Small〜Entity {
  ...
}

Large〜Entity.swift

public class Large〜Entity: Medium〜Entity {
  ...
}

1つのファイルにまとめました。

〜Entity.swift

public class Small〜Entity {
  ...
}

public class Medium〜Entity: Small〜Entity {
  ...
}

public class Large〜Entity: Medium〜Entity {
  ...
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS13】PDFをUIWebViewで開くとクラッシュする

TL; DR

WKWebViewを使う

環境

iOS 13.4.1
Xcode 11.5
iOS Deployment Target iOS 8.0

落ちる

古ーいアプリでPDFを開くとクラッシュするとご連絡が。
10ページほどのPDFをUIWebViewで開いて、スクロールしていくとEXC_BAD_ACCESS発生。
CGContextDrawPDFPageWithDrawingCallbacksという聞き慣れない場所で落ちてる模様…
スタックには

CGContextDrawPDFPageWithDrawingCallbacks
CGContextDrawPDFPageWithProgressCallback
[UIPDFPageContentDelegate drawLayer:inContext:]
[UIPDFPageRenderJob renderImage]

などなど。
ググってみるも特に解決策は見つからず。

WKWebView

試しにWKWebViewに変えてみたら問題なく開けた。
早いとこUIWebViewは捨てないとですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

こんなソースコードはイヤだ-JSONのパースでforは要らない

プログラムのソースコードのより良い書き方をまとめていこうと思います。

JSONのパースでforは要らない

sample.swift
 for (_, subJson) : (String, JSON) in json["users"]{
    if(subJson.isEmpty){
      break
    }
    self.users.append(User(json: subJson)!)
  }

どのようにリファクタリングできるのか

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む