- 投稿日:2020-12-23T23:15:01+09:00
iOS標準のショートカットアプリで、色々なアクションを効率化してみよう
この記事について
この記事は、aslead Agileの開発チーム「オキザリス」にて行っているアウトプット企画である
「チーム「オキザリス」Advent Calendar」の24日目の記事です。あるハッカソンでのできごと
先日、同期と一緒にあるハッカソンに出ました。
開発テーマはなんでもOK!の自由度高いハッカソンだったので
「食欲旺盛な同期 Pくん が彼女の外出中に冷蔵庫を開けると、その場で彼女のお叱りの音声が響き渡り、
彼女に『あいつ冷蔵庫開けてんぞ!』と告げ口LINEが飛ぶ つまみぐい防止のための冷蔵庫監視システム 」なるものを作りました。
システム構成を超ざっくり説明すると、Raspberry Piでセンサデータを取得して、冷蔵庫の開閉を検知する、というものです。
検知した時に彼女のお叱り音声を流したり、LINEのAPIをたたいて、LINE Botでメッセージを送信したりします。で、今回お話ししたいのはこの監視システムではなく、
彼女の在宅状況次第で、システムをオン・オフできるリモコンを作った方法 です。
このリモコンを実現する時に、 iOSのショートカット機能 を利用しました。ショートカットとは
iOS13以降で標準アプリとなった、iPhoneのアプリです。
このアプリ、単にメールや電話などのアプリの起動を楽にするアクションが選べますよ〜、とかそういう機能だけではなく、色々なアクションのフローを構築できます。今回のハッカソンでは、「起動用のアプリをわざわざ構築したり、物理的なスイッチを用意するより手軽にやりたいよね」ということで、このショートカットの利用を決めました。
何ができるの?
ショートカットを使ったことがなくても、
「〇〇さんにLINE」「アラームを設定」をみたことはあるような... という方も多いと思います。
ですが、実はただ特定の条件で一つのアプリを実行するための機能ではないんです。複数のアクションを繋げられる
iPhoneの標準アプリ、自分でインストールしたアプリの色んなアプリのアクションが用意されています。
例えば撮った写真をすぐにインスタに投稿できるショートカット。このショートカットを実行すると
インスタのアプリが開いて、この画像をストーリーにあげるか、フィードにあげるか選べるようになります。
アクションを繋げる時に条件分岐もできる
ただ順番に繋げるだけではないんです。
アクションの結果次第で条件分岐することもできます。例えば、前述のハッカソンの監視システム起動ショートカットはこんな条件分岐を設定してました。
最初のスクリプト実行で、今監視用システムのプロセスが開始しているか確認します。
開始してなかった場合(=シェルスクリプトの結果が値がない、のとき)は起動用のスクリプトを実行し、
開始していた場合(=その他の場合、のとき)は何もしない、という仕組みでした。アクションがすごく充実してる
先程のSSH経由のスクリプト実行のように、
iPhoneにインストールされているアプリの操作以外にも様々なアクションがあります。
- URLエンコード
- Base64エンコード
- ハッシュ生成
- 変数設定
- WebページでのJavaScript実行
などなど、実は色々なアクションが用意されています。
おわりに
iOSの標準アプリ、ショートカットのご紹介でした。
あまり標準アプリを使わない方も多いかもしれませんが、ショートカットは実は色々な組み合わせを楽しめるアプリだったりします。
この機会にぜひつかってみてはいかがでしょうか?
- 投稿日:2020-12-23T22:10:03+09:00
実務未経験からiOSエンジニアへ
2020年10月からフラー株式会社に入社して約3ヶ月になります。
念願だったiOSアプリ開発に携わっています。iOS開発に関するチャレンジングな知見はまだ持ち合わせていないこともあり、
今回は、個人開発と時を振り返ってみて、iOS開発に興味を持っている方や、iOSアプリを仕事としてやっていきたいという方向けに意識して書きました。本記事は技術的観点というより、就業するまでどんな行動をしたのかという点に重きを置いています。
最初は身の上話が続くので、アプリ開発については「取り組んだこと」に飛んでいただければと思います。わたし
情報系専門学校卒
約10年、COBOLやJavaで業務システムを開発・保守とか、医療システムの保守をしたり。きっかけ
4,5年前に、Swift、モダン。お、かっこよさそう。アプリ作れたら楽しいだろうな。
きっかけはこんな感じでした。
ずっとAndroidユーザーでしたが、Appleの統合された美しさによろめいていた時期でした。何事も形から入りたかったので、
まずは、無印Macbook(スペック盛り盛り)を購入しました。仕事の傍ら作る → 中断
仕事の傍らだけど、どうせ作るなら、「自分が使ってみたい」「世に出ていない」アプリを作ろう。
宇宙・ロケットが好きなので、思いついたのがISS(国際宇宙ステーション)の現在位置とライブ映像を閲覧できるというアプリでした。作る
そして、次の機能なら簡単に作れるやろ精神でいました。
・画面上半分にマップを表示
・ISSの現在位置を「どこか」から取得してマップに表示
・ISSが現在位置に近づいたら通知
・画面下半分にUSTREAM(今はもうない)から引っ張ってきたISSのライブストリーミングを表示
画面は一つだけなのでシンプルです。ただ、「ISSの現在位置をどこかから取得」が曲者でした。
現在位置が取得できるライブラリはあるにはあったのですが、
http://www.zeptomoby.com/satellites/
C++で書かれており、swiftから呼ぶ方法も分からずちんぷんかんぷんです。
そもそもISSの現在位置を知って喜ぶ物好きなんていないよなと調べていくと、国際宇宙ステーション(ISS)の座標を取得するAPIを作ろう
http://tsujimotter.info/2013/01/02/iss-api/>宇宙に対する興味は専門家だけのものではないはずです!!
本当にその通りです。救いの神が現れました。ISSの座標位置をJSONで出力してくれていたのです。
早速作者に連絡を取り、念の為使用許可を得ます。中断
マップにピンを立ててリアルタイムに動かせた!ライブ配信も見れる!
次はマップにISSの軌道を描いてISSが半径約500km以内に接近したら通知実装。
難しい…、あれ、うん。分からん。難しい。
仕事の忙しさからISSアプリの開発を中断し忘れてしまいます。再燃
アプリ開発を忘れて数年後、ある本に出会ってから、人生観が劇的に変わってしまいました。
(あるんですねこういう事って)
情熱が湧き上がる。iOSエンジニアへの道
同じIT業界とは言え、三十代で畑違いも甚だしいし、厳しい事を覚悟で、どうすれば転職できるかと考えた時に真っ先に浮かんだことは、
「アプリを自作してリリースしてアピールするしかない」
時間はかかるが、これが一番の方法だと思いました。私はズボラなので、前述の中断の経験から、仕事の傍らでやっていたらアプリは絶対完成しないと悟り、
仕事を辞めてアプリ開発に集中することにしました。この頃のテンションは上がりっぱなしでした。
(ただ、仕事を辞めるという方法はあまりオススメしません…)スクール等で教わりながら作る方法もありますが、あまりお金を使いたくなかったのと、
一応エンジニアで開発経験もあるので、これは一人でいけるやろ精神でいきました。
なので、退職して早速取り掛かる。開発期間はSwiftの学習を含めると約1年程。
場所
家にいると色々誘惑があり作れないと悟っていたので、無印Macbookを片手にほぼ毎日スタバに通う。
時には図書館、コメダ珈琲。
月〜金は仕事するかのごとく朝家を出て10:00〜17:00くらいやってました。(ある程度メリハリをつける)取り組んだこと
どんなアプリを作るか。
まずは、自分が使ってみたいと思うアプリを目指しました。
以前中断したISSアプリを再開しようと思いましたが、初心者には難易度が高いと感じたため眠らせました。
先述のように、私は宇宙・ロケットには目がないので、「ロケットの打ち上げを通知してくれるアプリ」を作ることにしました。青写真
- 全世界の打ち上げ予定のロケット情報を表示
- 打ち上げ前にiPhoneに通知
- Youtubeにリンクさせて打ち上げライブを表示
- 過去の打ち上げ情報が確認できる(50年代~現在までの打ち上げ情報)ただ、機能が単純だと、ストアリリースの審査でアップル様にリジェクトされてしまうことを事前に知っていたので、
ストアに出せない単純アプリはあまり有効ではないと考え、転職ということを見据えながら、
以下のポイントを踏まえて作りました。
- 画面は最低でも4~6個作る
- タブを利用して画面遷移させる。
- 画面は、「打ち上げ予定画面(メイン)」 「過去の打ち上げ一覧画面(検索機能)」 「お気に入り一覧画面」 「設定画面」 「ロケット詳細画面(通知のオン・オフ、Youtubeへのリンク)」 「課金画面」
- サーバーを利用する(通信)
- サーバーを立てる力はありません。幸いにも無類のロケット好きの同志たちが公開しているAPIがあるので、打ち上げ情報をJSON形式でいただきました。
- Launch Library https://launchlibrary.net/
- 検索機能
- 受け取った情報をただ表示するのは味気ないので、過去の打ち上げ情報の条件(打ち上げ年月・宇宙機関・ロケットの種類)を指定して検索できるようにする。
- データ保存機能(お気に入り登録)
- Realm・UserDefaultsを利用して過去の打ち上げ情報や設定情報を保存。
- 通知機能
- 打ち上げ直前にiPhoneに通知。
- 設定機能
- 通知時間の設定ができるようにする。(打ち上げ5分前〜60分前の間)
- 課金機能
- 収益目的は全くないが、実装したかった。(SwiftyStoreKit)
- ウィジェット機能
- 打ち上げ情報をカウントダウン表示。これはクール。
- デザインを作る(アプリアイコン、アプリ内アイコン、アプリストア用画像など)
- ド素人なりに作る(ProCreateというアプリを使ってデザインを作る。)
- 一人だけどGithubを使ってみる
- ソース管理をほぼ使ったことがなかったが、リポジトリを作ってPUSHするくらいはやってみる
- 全部オレ一人で作ったんだぞアピール
開発の大まかな流れ (あくまでアプリ素人が作った方法です)
モックを作る
POP
アプリの全体像を見るために、画面をノートに手書きして、「POP」というアプリを使って画面遷移してイメージを掴みました。
https://apps.apple.com/jp/app/pop-prototyping-on-paper/id555647796Keynote
ペンでノートに画面を書いてPOPで確認するのはよかったのですが、
色までは付けられなかったので いい方法がないか調べてました。
そこで、Keynoteで画面を作成してPOPのように画面を遷移する方法を取りました。
この方法はアップルストアのToday at Appleで教えていただきました。(アップル店員さんに感謝感謝です)デザイン
実は開発で一番楽しかったかもしれません。
デザインは何十種類と作り、あーでもないこーでもないと、自分で取捨選択していきました。
モックを作り、画面のデザイン・配色・アプリアイコン・アプリ内のアイコンを作る。
当時、Figmaというすごいツールの存在を知らなかったので、Procreateというアプリを使ってデザインを作りました。
Procreate https://apps.apple.com/jp/app/procreate/id425073498
(もちろんフリー素材を利用したりプロに発注することも全然ありだと思います。)ネット・技術本をフル活用して開発を進める
設計パターンを利用する方法が保守性・可読性の観点から最適だと思いますが、すぐに理解できる能力もなく、
時間に限りがあり、学習に時間を取られ挫折する恐れがあったので、「設計パターンとかそういうのは無視」と、割り切って開発を進めました。とにかく調べまくる
大抵の情報はネットに載っているので、つまずいた所や分からない所は、とにかく検索しまくりました。
(ネットがなかったら完成しなかった…)
とりわけ私が大変大変お世話になった本は、「絶対に挫折しない iPhoneアプリ開発「超」入門」という本です。
分かりやすく、タイトルに偽りなしでした。神です。
プログラムとは~Swiftの基本的な考え方から、xcodeの各項目の説明、テーブルビューなどの基本的な作り方が手順で示されています。「絶対に挫折しない iPhoneアプリ開発「超」入門」
www.amazon.co.jp/dp/4815604622
(余談:あまりにも参考になったので、著者に連絡をとり、他にもiOS開発でオススメの本や情報はないかとお聞きして、
「try! Swift」というコミュニティがあることを知り、2019年に参加しました。正直講義の内容はちんぷんかんぷんでしたが、海外の方と知り合いになれたので良い経験になりました。)Swiftの文法でお世話になった本
「Swift実践入門」
www.amazon.co.jp/dp/4297112132モチベーションの継続と気合い
ありきたりですが、作ることの「面白さ」「楽しさ」がモチベーション維持に繋がりました。
コードで書いたものが画面になって表示されて動作したときの達成感はひとしおです。
特に個人開発はこれに尽きると思っています。アプリ申請 → 審査 → リジェクト → 審査 → リリース
アプリ申請では主に次の記事を参考にさせていただきました。
「iPhoneアプリ申請やAppleの審査に関するメモ」
https://qiita.com/koogawa/items/5e2ff5a0312cb1f657f8
「2019年最新版、iOSアプリ申請時に必要な情報まとめ」
https://www.bravesoft.co.jp/blog/archives/4840ストアのスクリーンショットの作成
ここが地味に苦労しました。
スクリーンショットのサイズの制約や、デザイン周り。
この面倒なスクリーンショットを自動作成してくれるサービスがありますが、なぜか私はペイントで地道に作りました。
あのwindowsのペイントです。スクリーンショット作成サービス
「APPLAUNCHPAD」
https://theapplaunchpad.com/アプリ申請 → リジェクト → リリース
夕方に申請して、日本時間の明け方にメールが届きます。
一発で通る自信がありましたが、早朝に眠気まなこでドキドキしながらメールを見ると「Reject」の文字が。
リジェクトの内容を見ると、アプリ起動後にインジケーターがクルクル回ったまま情報が表示されていないというのです。
私の拙い英語で、もう一回確認しください。少し待てば表示できるはずです。と返信。
そして、数日後の早朝に審査が通ってました。(しっかりと審査してくれていれば一発で行っていたはず…)
晴れてアップストアに公開されました。
自分の名前がアップストアに表示されたときは何とも言えない達成感でした。
(今思うとリジェクトを経験できたのはよかった)転職活動 → フラーに入社
Wantedlyを利用して転職先を探していました。
実務未経験でありながら、採用していただいて感謝しています。3ヶ月が経ち
入社後1ヶ月は、フラーの文化や業務の事を重点に教えていただきました。
若い人が多く、私のように年上は少数派で、不安で不安で一杯でしたが手厚く迎えていただきました。
(年齢の差なんて些細な事です)
現在はクライアントワークでiOSアプリ開発に関わっています。一番成長を実感した瞬間
レビューでコードを見てもらい、指摘を受けてコードを改善していけることです。
もちろん独学では得られないことでした。
また、ペアプロしていただいてコードを書いている瞬間が成長していると感じます。フラーに入社して驚いたこと
- 働きやすさ
- 今までは、ザ・会社って感じの場所で働いていたので、リモートワークやコアタイム以外は比較的自由など、最初は慣れなくてビビりました。
- 皆がプロダクトに真剣に向き合って、より良いものを作っている!
- デザイナーさんが身近にいて新鮮
- 業務系システムしか関わっていなかったので、プロのデザイナーの方たちが身近にいること、デザインが目の前で見れることは本当にワクワクするし楽しい!
- Slackでのコミュニケーション
- 今までほぼメール(毎回、「お疲れさまです。XXです。」から始まり、大量の返信引用文が蓄積)でしたが、短い文章でのやり取り、リアクションが付けられるという機能にカルチャーショックのようなものを受けましたが、盛り上がり凄くて楽しいし、即座に反応があって嬉しい。
- 勤怠管理・進捗管理やドキュメント管理が新鮮
- JIRAやコンフルといったツールで管理する方法が効率が良く新鮮。
- 書籍購入
- 月3冊まで技術本やビジネス書が購入できるのはすごくありがたい。
- 端末
- 今まではWindows7で最低限動作すればいいだろうな環境だったので、Macbook Pro 13インチ i7 16G は感動ものでした。
まとめ
振り返ると、一人で作りきったのは良い経験になりました。
入社から手厚く歓迎していただいたり、根気よく教えてもらえているので、すごく恵まれていると感じています。
早く一人前になってバリバリやっていきたい!!
私のようなケースはあまりないかもしれませんが、今後どうしようか悩んでいる方がいたら、
この記事を少しでも参考にしていただければ幸いです。
(今後は技術的な事を書きたい…)
- 投稿日:2020-12-23T21:06:37+09:00
iOSアプリ開発でぶち当たるエラー処世術〜Xcode編〜
エラー処世術
アプリケーション開発を行なっていると必ずぶち当たるエラー.検索しても解決策が得られない.開発が滞ってしまい,やる気が削がれる.といったことは誰しもが経験することだと思います.
私自身もその一人で,毎日技術力不足を痛感させられます.この記事では,Xcodeで開発する際に効率よくエラーの解明を行い,圧倒的成長を遂げるための処世術を少し紹介したいと思います.この記事の対象者
- iOSアプリ開発を始めたての方(Xcodeでの)
- LLDBって何?デバッガって?という方
- 追加したUI部品(button, viwe...)が表示されない!どこいったの?という方
処世術1 〜LLDB〜
LLDBとはXcodeのデフォルトデバッガです.皆さんお馴染みの画面の下の方にいます.デバッガとは不具合の原因を探すお手伝いをしてくれる機能やソフトを指します.基本的にはプログラムを一時停止させたりしてエラーを見つけます.
上は,お馴染みのXcodeの画面ですが.画面下の部分がデバッガです.ここでは,14行目にブレークポイントを設定して,プログラムを一時停止させています.行番号をクリックするとブレークポイントを設定できます.解除は,ブレークポイントを掴んでコード上にドラッグするとできます.
デバッガの右の画面の(lldb)と書かれている部分にコマンドを入力することでエラーの原因を探ることができます.
po コマンド
いちばん使うことになるであろうコマンドです.poは変数を出力してくれるものです.
例えば,以下のように,19行目にブレークポイントを設定した場合.
po lemonと入力すると,その時点での変数の値を確認することができます.
この例だとパッとしませんがいつか役に立つと思います.処世術2 〜UI部品どこ??〜
追加したはずのbuttonがシミュレーションを起動すると消えている..どこいったの?コードミスってる?なんて経験あると思います.これは案外簡単なミスであることがほとんどです.ただ慣れないうちは,なぜ無いのか原因を解明するのに時間がかかってしまいがちです.(その時間も成長に繋がる気もしますが,無いに越したことは無いでしょう.)
この問題の対処法のうちの1つを紹介します.Xcodeの隠れた機能 〜ビューデバッガ〜
ビューデバッガとは,現在のビュー階層を3Dで確認することができる機能です.
シミュレーターを起動しているときに,
ここをクリックすることで,確認することができます.
こんな感じで.
この例だと,あまりビューデバッガに恩恵を感じませんが,大規模なアプリになる程利用する機会は多くなると思います.
この機能を使えば,追加したUI部品がどこにあるのか,他のUI部品の下に隠れていないかなどを確認することができます.
「ビューデバッガのおかげで迷子になっていたUI部品を探し出し,救出することができた!」ということが結構あります.おわりに
「アドベントカレンダーかぁ、何書こう.」
ギリギリまでテーマが決まらず投稿が遅れてしまいました.個人的に辛いこともあり,記事を書かずに逃げ出そうかという考えもよぎりました.最初は義務感で書き始めましたが,良い気分転換になり,悩んでいたことも少し気が楽になった気がします.定期的なアウトプットって大事だなあと実感しました.(Qiitaさん,パソナテックさんありがとう!)今回,iOSアプリ開発を始めた時に知っておきたかったことを書きました.私もまだまだ初心者なので,間違いがあれば指摘してください.よろしくお願いします.自分の成長に合わせて内容もどんどん更新していくつもりです.そして,この記事が悩みを抱えていた方の助けになれば幸いです.
- 投稿日:2020-12-23T18:41:25+09:00
[iPhone12まで]ステータスバー・セーフエリアトップ・ナビゲーションバーのサイズ一覧
ナビゲーションバー高さ ステータスバー高さ safeareaInsets.top iPhone12mini 44 44 50 iPhone12 44 47 47 iPhone12Pro 44 47 47 iPhone12ProMAX 44 47 47 iPhone11 44 48 48 iPhone11 Pro 44 44 44 iPhone11 Pro Max 44 44 44 iPhoneX 44 44 44 iPhoneXR 44 44 44 iPhoneXS 44 44 44 iPhoneXS Max 44 44 44 iPhoneSE 44 20 20 iPhone8 44 20 20 iPhone8 Plus 44 20 20 要約
ナビゲーションバーの高さは全て同じで44ptになっています。
ステータスバーは、ノッチ付きの端末(iPhone X以降)だと、それ未満の端末より大きく、40pt台となっています。特にiPhone12系で変化が不規則なので注意が必要です
iPhone 12 miniでのみ、余白があることによりステータスバーの高さでは正しい距離が取れなくなってしまいました。
UIViewのsafeAreaInsets.topを使うと正しい距離が取れます。
- ただし親である
UIViewControllerのviewWillLayoutSubviewsが呼ばれるまでは値が0になっているので待つ必要があります。またviewWillLayoutSubviewsは何度でも呼ばれる可能性があるので注意してください。UIWindowのsafeAreaInsets.topを使ってもいいと思います(UIWindowはUIViewのサブクラス)。これを使って高さを取得する一例は以下になります。var safeAreaTopInsets: CGFloat if #available(iOS 11.0, *) { if let topInsets = UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.safeAreaInsets.top, topInsets > 0 { safeAreaTopInsets = topInsets } } else { safeAreaTopInsets = UIApplication.shared.statusBarFrame.size.height }参考
[iOS] iPhone12 miniでStatusBarの高さ取得が期待した動作をしないのと対策
【iOS】safeAreaInsetsの値が取得できるタイミング
How to resolve: 'keyWindow' was deprecated in iOS 13.0
- 投稿日:2020-12-23T17:58:36+09:00
CoreImageフレームワークを使ってSwiftでしみ消し、くま消し機能を実装した話
バイトル履歴書アプリに画像の補正機能を追加するエンハンスを担当したのでやったことまとめておきます。
今回のエンハンス内容としましては画像補正機能として撮影した写真全体に対して独自で定義したフィルター処理を行うのと、目の下のくまの部分や頬のあたりのしみの部分を部分的に画像補正処理を行う機能をCoreImageフレームワークのみを使い実装しました。
写真全体に対してフィルター処理を行うケースはそんなに難しくないのでサンプルコードだけ載せておきます。
import CoreImage ... // フィルター処理をかけたいSampleイメージを準備 var image = UIImage(named: "sample.jpg") // UIImageからCIImageへの変換 let ciImage: CIImage? = CIImage(image: image) // Sepiaフィルターの準備 var filter = CIFilter(name: "CISepiaTone") // フィルターに画像を渡す filter.setValue(ciImage, forKey: kCIInputImageKey) // Filterをかけた画像を取得する var filteredImage: CIImage? = filter.outputImageセピア補正のフィルターを適用すると結果はこんな感じです。
実際にプロダクトには色々な画像補正フィルターを掛け合わせて独自フィルターを作り実装しました。
オリジナル セピア補正 ※写真はフリー素材を利用:https://www.pakutaso.com/model.html
画像の部分補正処理の流れ
続いて、画像の部分補正をする場合の処理の流れをまとめていきます。
しみ消し機能にしろ、くま消し機能にしろ画像を部分的に補正するという点においては同じなのでどちらも上記の処理フローとなります。
マスク画像の作成
マスク画像の作成にはCoreImageのCIDetectionを使い顔のパーツの位置関係を取得することから始めます。顔検出において取得できる情報ですが、口の位置や目の位置、目が開いているかどうかや顔の輪郭を取得でき、それらの情報を元にマスク画像の作成をしていきます。
顔検出もCoreImageがサポートしていて下記のコードより取得できます。
let options = [CIDetectorAccuracy: CIDetectorAccuracyHigh] var faces: [CIFeature] = [] if let image = CIImage(image: faceImage), let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: options) { faces = faceDetector.features(in: image) }マスク画像の作成には検出した顔のパーツの位置情報を使い、数値モデル化したしみの位置を計算して
CIRadialGradientというCIFilterを使います。こちらのFilterですが、ガウシアンを位置情報を入力することで生成できるフィルターとなっており、しみ消し用のマスクに関しては計6点のガウシアン波形を重ね合わせマスク画像を作成しております。ガウシアン波形の合成にはCISourceOverCompositingと呼ばれるCIFilterを使用しています。let maskCircles: [MaskCircle] = [ /// ガウシアンの位置情報など... ] for (index, circle) in maskCircles.enumerated() { guard let gaussian = CIFilter(name: "CIRadialGradient") else { return nil } gaussian.setValue(CIVector(cgPoint: circle.center), forKey: kCIInputCenterKey) gaussian.setValue(circle.innerRadius, forKey: "inputRadius0") gaussian.setValue(circle.outerRadius, forKey: "inputRadius1") gaussian.setValue(CIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0), forKey: "inputColor0") gaussian.setValue(CIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.0), forKey: "inputColor1") if index == 0 { prev = gaussian } else { shimi = CIFilter(name: "CISourceOverCompositing") if let prevMask = prev?.outputImage, let gaussianMask = gaussian.outputImage { shimi?.setValue(gaussianMask, forKey: kCIInputImageKey) shimi?.setValue(prevMask, forKey: kCIInputBackgroundImageKey) } prev = shimi } }下記の画像がオリジナル画像とマスク画像をオリジナル画像に重ね合わせている図です。顔のしみがある部分を狙ってガウス波形の重ね合わせでマスク画像を作成しています。
オリジナル画像 しみ消し用のマスク画像 画像の合成
続きまして作成したしみ消し用のマスク画像を元に画像合成をしてしみ消し機能を実装していきます。マスク画像を使った部分的な合成には
CIBlendWithMaskフィルターを使います。
CIBlendWithMaskフィルターではマスク画像とオリジナル画像とマスク部分だけ適応したい画像補正をオリジナル画像にかけた画像を用意して合成処理を行います。let blend = CIFilter(name: "CIBlendWithMask") else { return nil } /// ブラー効果をかけた画像の設定 blend.setValue(blurImage, forKey: kCIInputImageKey) /// オリジナル画像の設定 blend.setValue(originalImage, forKey: kCIInputBackgroundImageKey) /// マスク画像の設定 blend.setValue(mask, forKey: kCIInputMaskImageKey) /// 合成した画像の取得 blend.outputImage
オリジナル画像 しみ消し用のマスク画像 Blur画像 結果
部分的に画像補正を行ってしみ消し機能を実装してみた結果はこんな感じです!しみの部分だけ部分的にぼかし効果が適用されてしみが目立たなくなっているのがわかるかと思います。
オリジナル画像 しみ消し処理後の画像
- 投稿日:2020-12-23T17:17:18+09:00
iOS: SFTPでファイルアップロード
- Swift 5
- Xcode Version 12.3
- NMSSH
iOSアプリ開発にて、SFTPでサーバーにファイルをアップロードする仕様でした。
今回使用したライブラリはNMSSH。これ以外、見つけられませんでした。
- https://github.com/NMSSH/NMSSHCocoaPodのインストール
まずはここでつまづきました。
参照)https://qiita.com/spring_i/items/181bc3c05142d1f80d93$ sudo gem install -v1.8.4 cocoapods -n /usr/local/bin $ pod setupMNSSHのインストール
Xcodeのプロジェクトは作成済みとしています。
$ cd [プロジェクト名] $ pod initPodfileが生成されますので、それを編集。
Podfile# Uncomment the next line to define a global platform for your project # platform :ios, '9.0' target 'SampleProject' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! # Pods for GSApp pod 'NMSSH' endインストールコマンド
$ pod installライブラリインポート
プロジェクトを、生成された[プロジェクト名].xcworkspaceをダブルクリックして起動する。
対象のファイルにて、ライブラリインポート
ViewController.swiftimprt NMSSHアップロードするダミーファイル作成
アップロードするためのダミーファイル作成
ViewController.swiftdo { let fileManager = FileManager.default let doc = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) let path = doc.appendingPathComponent("muyFile.txt") let data = "Hello, world".data(using: .utf8) fileManager.createFile(atPath: path.path, contents: data, attributes: nil) } catch { print(error) }アップロード
ViewController.swiftlet host = "[IPアドレス]" let username = "[ユーザー名]" let password = "[パスワード]" let remotePath = "[送信先のファイルパス]" let session = NMSSHSession.connect(toHost: host, withUsername: username) if (session.isConnected) { session.authenticate(byPassword: password) if (session.isAuthorized) { session.channel.uploadFile(path.path, to: remotePath) } } session.disconnect()サンプルソースコード
onClickをボタンと結びつけています。
ViewController.swift// // ViewController.swift // SampleProject // // Created by SankoSC on 2020/12/21. // import UIKit import NMSSH class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } @IBAction func onClick(_ sender: Any) { sftp() } func sftp() { do { let fileManager = FileManager.default let doc = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) let path = doc.appendingPathComponent("muyFile.txt") let data = "Hello, world".data(using: .utf8) fileManager.createFile(atPath: path.path, contents: data, attributes: nil) let host = "[IPアドレス]" let username = "[ユーザー名]" let password = "[パスワード]" let remotePath = "[送信先のファイルパス]" let session = NMSSHSession.connect(toHost: host, withUsername: username) if (session.isConnected) { session.authenticate(byPassword: password) if (session.isAuthorized) { session.channel.uploadFile(path.path, to: remotePath) } } session.disconnect() } catch { print(error) } } }
- 投稿日:2020-12-23T16:02:07+09:00
Swift CombineにてFutureで実行している非同期処理をsinkしているのに途中でキャンセルされる
Combineを使用する際にハマってしまったので、備忘録として残します。
問題
Futureのインスタンスにsinkしてクロージャ(サブスクライバ)を設定し、非同期処理が完了した際にsinkにて設定したクロージャの処理を行いたいのだが、完了する前にSubscribeがキャンセルされてしまう。
func start() { Future<Void, Error>{ promise in DispatchQueue.main.asyncAfter(deadline: .now() + 5.0){ promise(.success(())) } } .handleEvents(receiveSubscription: { _ in print("receiveSubscription") }, receiveOutput: { print("receiveOutput") }, receiveCompletion: { _ in print("receiveCompletion") }, receiveCancel: { print("receiveCancel") }, receiveRequest: { _ in print("receiveRequest") }) // クロージャが実行されない .sink(receiveCompletion: { completion in print("completion:\(completion)") }, receiveValue: { }) }デバッグコンソールreceiveSubscription receiveRequest receiveCancel解決方法
sinkメソッドの返り値を変数に保持するか、storeメソッドを使用して保持する必要がある。
func start() { cancellable = Future<Void, Error>{ promise in DispatchQueue.main.asyncAfter(deadline: .now() + 5.0){ promise(.success(())) } } .handleEvents(receiveSubscription: { _ in print("receiveSubscription") }, receiveOutput: { print("receiveoutput") }, receiveCompletion: { _ in print("receiveCompletion") }, receiveCancel: { print("receiveCancel") }, receiveRequest: { _ in print("receiveRequest") }) .sink(receiveCompletion: { completion in print("completion:\(completion)") }, receiveValue: { }) }デバッグコンソールreceiveSubscription receiveRequest receiveoutput receiveCompletion completion:finished原因
Future内に実装した処理が完了(promise()がコールされる)前にsinkから返却されたAnyCancellableが破棄されてしまうとキャンセルされてしまうみたい。
公式のリファレンスを読むと「Return Value」の部分にひっそりと書かれていた。https://developer.apple.com/documentation/combine/future/sink(receivecompletion:receivevalue:)
- 投稿日:2020-12-23T14:32:17+09:00
iOSショートカットアプリを作って鍵をNFCで素早く開ける
スマートロックをもっとスマートに解錠したい。
玄関の前まで帰ってきたときに
携帯を取り出す → (マスクを外す) → 顔認証を解除 → アプリを探す → アプリを開く → 鍵を開けるというプロセスを踏む必要があるのですが、「アプリを探す → アプリを開く」の2工程をNFCタグを使って削減するアプリを作ります。
特にこのマスク環境下では1工程でも減らしたい。
スマートロックにはGPSで近づくと自動で鍵を開ける設定などもあるのですが、常にGPSをチェックするため電池を激しく消耗し、チェック間隔より手動で開けた方が早いことも多く、
そもそも内廊下のマンションでは電波なんて届かないので使えません。公式アプリではiOSショートカットに対応していないので、自分で対応するアプリを作り、
そこから公式アプリへリダイレクトさせてしまいます。NFCタグ
10枚 NFCステッカー NTAG 213 NFCタグ/ 25 mm(1インチ)黒 円形
シールタイプで黒の目立たないものを選びました。
これを玄関部分に貼り付けるのですが、大抵の玄関の扉は金属製だと思うのですが、金属に貼り付けると反応が鈍くなるので注意!
私はインターホンの近くの壁部分に貼り付けました。一応、マンションの玄関や廊下部分ですが普通は共用部という扱いですのでご注意ください。
iOSアプリ作成
特にView部分は必要ないのですがSwift UIで作っています。
別アプリを開く
まず公式アプリへリダイレクトさせるアプリを作成していきます。
私の場合bitlockを使用しているためlunchBitlockというアプリ名にしました。
画面を表示した瞬間に公式アプリを開くようにしています。import SwiftUI @main struct lunchBitlockApp: App { @Environment(\.scenePhase) private var scenePhase var body: some Scene { WindowGroup { ContentView() .onAppear { lunchApp() } .onChange(of: scenePhase) { _ in lunchApp() } } } func lunchApp() -> Void { if let url = URL(string: "app.jp.co.bitkey.bitlock:") { UIApplication.shared.open(url) } } }アプリ個別のURLスキームは公開されていないため調べるのが難しいのですが、
Androidアプリも同名を使っていることが多いので、Google PlayストアのURLに書かれているパッケージ名から推察できたりします。ショートカット対応させる
iOSショートカットXcodeのメニューから
File → New → File → SiriKit Intent Definition Fileでショートカット対応させるためのファイルを作成します。
作ったIntent DefinitionファイルにCustom Intentsを追加します。
特に設定は不要ですが、わかりやすいようにタイトルを設定しておきましょう。このファイルを追加するだけでiOSショートカットへの対応は完了です。
ショートカットアプリのアクション部分にアプリが表示されるようになります。オートメーション設定
ショートカットアプリのオートメーション設定から新しく追加します。
「いつ」に反応させたいNFCタグを登録し、「行う(アクション)」に作ったアプリを登録するとこんな感じのオートメーションができあがります。
この状態でNFCタグにiPhoneをかざすと自動でアプリが開かれるようになって便利。
おわり
疲れてヘトヘトになって帰ってきたときに「アプリを探す」という工程が面倒だったり、別アプリを開いてるときにサクッとアプリが切り替えられるので割と便利です。
私の場合自宅WiFiに接続したときにこのアプリを開くオートメーションも登録しています。
たまにこっちの方が早く反応することもありますが、WiFi切り替え時などに誤爆します。公式さん対応してください。。。
- 投稿日:2020-12-23T13:47:43+09:00
DateFormatter(short, medium, long)
DateFormatter
.dateStyle
.timeStyleそれぞれの出力結果
short.swiftlet dateFormatter = DateFormatter() dateFormatter.dateStyle = .short dateFormatter.timeStyle = .short print(dateFormatter.string(from: Date())) // 12/23/20, 1:42 PMmedium.swiftdateFormatter.dateStyle = .medium dateFormatter.timeStyle = .medium print(dateFormatter.string(from: Date())) // Dec 23, 2020 at 1:42:57 PMshort.swiftdateFormatter.dateStyle = .short dateFormatter.timeStyle = .short print(dateFormatter.string(from: Date())) // December 23, 2020 at 1:42:57 PM GMT+9
- 投稿日:2020-12-23T11:45:11+09:00
ARKit 4のLiDAR Depth API
iOSデバイスで初めてLiDARスキャナを搭載したiPad Proの発売と同時にリリースされたARKit 3.5では、LiDARを利用してreconstractionした3Dメッシュは得られたが、その計算に用いられているはずのデプス(深度)データにはアクセスできなかった。
その後発表されたARKit 4 / iOS 14(/ iPadOS 14)でようやく、LiDARで計測したデプスを取得できるようになった。従来のデプスと区別するため、「シーンデプス(Scene Depth)」とも呼ばれているようだ。
LiDARデプスの取得方法
LiDAR由来のデプスデータは、
ARWorldTrackingConfiguration利用時、frameSemanticsプロパティに.sceneDepthオプションを指定することで取得可能になる。let session = ARSession() let configuration = ARWorldTrackingConfiguration() // sceneDepthオプションが利用可能かをチェック if type(of: configuration).supportsFrameSemantics(.sceneDepth) { // Activate sceneDepth configuration.frameSemantics = .sceneDepth } session.run(configuration)ARFrameに新たに追加された
sceneDepthプロパティより、LiDARで計測したデプスデータを取得できる。func session(_ session: ARSession, didUpdate frame: ARFrame) { guard let depthData = frame.sceneDepth else { return } // Use depth data }利用可能なコンフィギュレーション
ARConfigurationプロトコルに定義されているframeSemanticsプロパティにsceneDepthを設定するので、API的には利用可能ではあるが、APIリファレンスのsceneDepthの項にある記述を読む限り、ワールドトラッキング以外のコンフィギュレーションでは利用できないっぽい。https://developer.apple.com/documentation/arkit/arconfiguration/framesemantics/3516902-scenedepth
If you enable this option on a world-tracking configuration's frameSemantics, ARKit includes depth information for regions of the current frame's capturedImage. The world-tracking configuration exposes depth information in the sceneDepth property that it updates every frame.
ハードウェア的な制約
同じく
sceneDepthのリファレンスページより。ARKit supports scene depth only on LiDAR-capable devices
チップがA12以上とかの条件はないようだ。(LiDARが載る→必然的に高性能チップとなるからだろうか)
従来のデプスデータとの違い
これまでも(特定の条件下で)ARKit利用時にデプスデータにアクセスすることは可能だった。
ARFrameのcapturedDepthDataプロパティ、estimatedDepthが従来からあったデプスAPI。今回追加されたデプスAPIと何が違うのか。デプスデータの種類と取得条件
まずもちろん、デプスデータの種類が違う。そしてそれに伴い、取得できる条件も違う。
capturedDepthDataはTrue-Depthカメラ由来のデプスデータで、フェイストラッキング利用時のみ取得できる。
estimatedDepthはframeSemanticsにpersonSegmentationWithDepthオプションを指定した場合に取得できるもので、デュアルカメラやTrue-Depthカメラ由来のデプスではなく、そのプロパティ名からわかるとおり推定デプスデータである。要A12チップ以上。
sceneDepthは上述した通り、frameSemanticsにsceneDepthを指定した場合に取得でき、ARコンフィギュレーションとしてはワールドトラッキングのみ、デバイスとしてLiDARを搭載している必要がある。フレームレート/生成アルゴリズムの違い
capturedDepthDataはTrue-Depthカメラ由来のデプスデータであるため、ARFrameのカラーデータ(capturedImageプロパティ)よりも更新頻度が低く、デプスを用いたエフェクトなどをかけていると、被写体(フェイストラッキングなので自分)が速く動いた場合に追従できないことがあった。一方で
estimatedDepthは機械学習ベースで推定されるもので、A12以降の高性能なチップを条件としているため、カメラフレームと同等のフレームレートで更新される。
sceneDepthもLiDARで取得した深度データと広角カメラから取得したカラーデータをベースに機械学習アルゴリズムを利用して生成される。60fpsで動作する、つまりこちらもARFrameが得られるたびに更新されている。(WWDC 2020の"Explore ARKit 4"セッションより)
The colored RGB image from the wide-angle camera and the depth ratings from the LiDAR scanner are fused together using advanced machine learning algorithms to create a dense depth map that is exposed through the API.
(広角カメラからのカラーRGB画像とLiDARスキャナからの深度定格が、高度な機械学習アルゴリズムを使用して融合され、APIを通じて公開される高密度の深度マップが作成されます。)This operation runs at 60 times per second with the depth map available on every AR frame.
(この操作は毎秒60回実行され、ARフレームごとに深度マップが利用可能になります。)型の違い
capturedDepthDataはAVDepthData型、estimatedDepthはCVPixelBuffer型、そしてsceneDepthはARDepthData型で得られる。ARDepthData
上述したとおり
sceneDepthプロパティから得られるLiDAR由来のデプスデータはARDepthData型で得られる。こちらはiOS 14で追加された新クラス。https://developer.apple.com/documentation/arkit/ardepthdata?changes=latest_major
これをうまく利用した公式サンプルが出ているので、詳しい解説はそのサンプルを読みつつ別記事で書く。
LiDARの精度
WWDC 2020ではないが、ARKit 3.5リリース時に、"Advanced Scene Understanding in AR"というTech Talkが公開されており、その中でLiDARの精度について言及があった。
The new iPad Pro comes equipped with a LiDAR Scanner. This is used to determine distance by measuring at nanosecond speeds how long it takes for light to reach an object in front of you and reflect back. This is effective up to five meters away and operates both indoors and outdoors.
(新しいiPad ProにはLiDARスキャナーが搭載されています。これは、目の前の物体に光が到達して反射して戻ってくるまでの時間をナノ秒の速度で測定することで距離を判断するために使用されます。これは5メートル先まで有効で、屋内でも屋外でも使用できます。)LiDARで何メートル先まで計測できるのか、という質問は何度かされたが、こういうのは公式には言及されない印象だったので、「サンプルで実際に試してみるといいですよ」という回答をしていた。目安にしろ貴重な公式情報ではあるのでメモ。
personSegmentationWithDepthとシーンデプス
ARConfigurationのframeSemanticsプロパティにpersonSegmentationWithDepthオプションを指定している場合、シーンデプスを取得可能なデバイスであれば、自動的にシーンデプスを得られるらしい。let session = ARSession() let configuration = ARWorldTrackingConfiguration() // Set required frame semantics let semantics: ARConfiguration.FrameSemantics = .personSegmentationWithDepth // Check if configuration and device supports the required semantics if type(of: configuration).supportsFrameSemantics(semantics) { // Activate .personSegmentationWithDepth configuration.frameSemantics = semantics } session.run(configuration)しかも、追加の電力コストもかからないとのこと。
Additionally if you have an AR app that uses people occlusion feature, and then search the personSegmentationWithDepth frameSemantic, then you will automatically get sceneDepth on devices that support the sceneDepth frameSemantic with no additional power cost to your application.
smoothedSceneDepth
上述した
sceneDepthはWWDC 2020開催時点で(iOS 14 beta 1時点で)既に公開されていたが、iOS 14 beta 5で突如smoothedSceneDepthなるAPIが追加された。https://twitter.com/shu223/status/1295968108352479232
このとき(2020年8月)ドキュメントをすぐに見に行ったが詳細は何も書かれてなかった。
今見に行くとちゃんと説明が書かれており、
sceneDepthとの違いも明記されている。DeepL利用の日本語訳を併記してここにまとめておく。smoothedSceneDepth: ARConfiguration.FrameSemantics
ARConfiguration.FrameSemanticsの型プロパティAn option that provides the distance from the device to real-world objects, averaged across several frames.
(デバイスから実世界のオブジェクトまでの距離を提供するオプションで、複数のフレームで平均化されます。)Declaration
static var smoothedSceneDepth: ARConfiguration.FrameSemantics { get }Discussion
Enable this option on a world-tracking configuration (ARWorldTrackingConfiguration) to instruct ARKit to provide your app with the distance between the user’s device and the real-world objects pictured in the frame's capturedImage. ARKit samples this distance using the LiDAR scanner and provides the results through the smoothedSceneDepth property on the session’s currentFrame.
(ワールドトラッキング設定(ARWorldTrackingConfiguration)でこのオプションを有効にすると、ユーザーのデバイスとフレームのキャプチャ画像に写っている現実世界のオブジェクトとの距離をアプリに提供するようにARKitに指示します。ARKit は、LiDAR スキャナーを使用してこの距離をサンプリングし、セッションの currentFrame の smoothedSceneDepth プロパティを通して結果を提供します。)LiDARスキャナーを使用することがここで明記されている。
To minimize the difference in LiDAR readings across frames, ARKit processes the data as an average. The averaged readings reduce flickering to create a smoother motion effect when depicting objects with depth, as demonstrated in Creating a Fog Effect Using Scene Depth. Alternatively, to access a discrete LiDAR reading at the instant the framework creates the current frame, use sceneDepth.
(フレーム間の LiDAR 読み取り値の差を最小化するために、ARKit はデータを平均として処理します。平均化された読み取り値は、「シーン深度を使用したフォグ効果の作成」で実証されているように、奥行きのあるオブジェクトを描写する際にフリッカーを低減し、より滑らかなモーション効果を生み出します。また、フレームワークが現在のフレームを作成した瞬間に個別の LiDAR 読み値にアクセスするには、sceneDepth を使用します。)ここが一番重要。
sceneDepthとの違いが明記されており、メリットや使い分けについても書かれている。ARKit supports scene depth only on LiDAR-capable devices, so call supportsFrameSemantics(:) to ensure device support before attempting to enable scene depth.
(ARKit は、LiDAR 対応デバイスでのみシーン深度をサポートしているので、シーン深度を有効にする前に supportsFrameSemantics(:) を呼び出してデバイスのサポートを確認してください。)smoothedSceneDepth: ARDepthData
ARFrameの
smoothedSceneDepthプロパティhttps://developer.apple.com/documentation/arkit/arframe/3674209-smoothedscenedepth
An average of distance measurements between a device's rear camera and real-world objects that creates smoother visuals in an AR experience.
(デバイスの背面カメラと実世界のオブジェクトの間の距離測定の平均値で、AR体験でよりスムーズなビジュアルを作成します。)Declaration
var smoothedSceneDepth: ARDepthData? { get }Discussion
This property describes the distance between a device's camera and objects or areas in the real world, including ARKit’s confidence in the estimated distance. This is similar to sceneDepth except that the framework smoothes the depth data over time to lessen its frame-to-frame delta.
(このプロパティは、デバイスのカメラと実世界のオブジェクトやエリアとの距離を記述します。これは sceneDepth と似ていますが、フレームワークがフレーム間の差分を減らすために深度データを時間の経過とともに滑らかにします。)This property is nil by default. Add the smoothedSceneDepth frame semantic to your configuration’s frameSemantics to instruct the framework to populate this value with ARDepthData captured by the LiDAR scanner.
(このプロパティはデフォルトでは nil です。フレームワークに、LiDAR スキャナによってキャプチャされた ARDepthData でこの値を入力するように指示するために、設定の frameSemantics に smoothedSceneDepth フレームセマンティックを追加します。)Call supportsFrameSemantics(:) on your app’s configuration to support smoothed scene depth on select devices and configurations.
(アプリの設定で supportsFrameSemantics(:) を呼び出して、選択したデバイスと設定で平滑化されたシーン深度をサポートします。)関連
ARKitの本、iOSデプスの本を書いています。
実践ARKit - BOOTH
Depth in Depth - iOSデプス詳解 - BOOTHアドベントカレンダー
本記事はiOSアドベントカレンダー20日目の記事です。2020年12月23日現在、エントリーされていた方が記事をアップされていなかったので、その方に確認の上記事を代理投稿させていただきました。
- 投稿日:2020-12-23T09:47:12+09:00
Flutterアプリ開発高速化Tips
この記事は NTTテクノクロス Advent Calendar 2020の25日目(最終日)です。
はじめに
こんにちは。NTTテクノクロスで、エバンジェリストとして活動している神原です。弊社では、高度専門人材向けのキャリアパスが準備されており、私はその中のエバンジェリスト領域でキャリアを積んでいます。
普段は主にモバイル/ウェアラブル/IoT関連の開発や技術支援、国内外の講演、各種執筆、ソフト道場研修講師、技術ブログ、CSR活動(学生向けIT/キャリア教育)などを行なっています。プライベートでは、ランニングと英会話の楽しさに目覚め、日々取り組んでいます。
クロスプラットフォームの開発技術のトレンドの1つであるFlutterの開発テクニックをご紹介します。Flutterは登場してから一定の期間も経ち、商用事例も見かけるようになってきました。
今回は、Flutterを使ってアプリを開発するときに、 知っておくと開発高速化にきっとつながるTips をお伝えします。開発環境にも色々ありますが、今回は統合開発環境(IDE)の1つである Android Studio を対象とします。また、開発高速化と言っても、様々なアプローチがありますが、今回は以下の観点で紹介していきます。
- Tips(1)ボイラープレートの排除
- Tips(2)コーディング支援機能の活用
- Tips(3)その他タスクの高速化
Tips(1)ボイラープレートの排除
Flutterでアプリを開発していると、こんなコードをよく見かけると思います。ちなみに、Stateful Widgetは現場では使わないよという別の議論はあろうかと思います。その通りですが、話がそれてしまうので、今回は触れないでおきます。
それぞれ、10行ほどのコードを書く必要があります。さらにアニメーションを実現しようと思ったら、内容によっては、AnimationControllerのボイラープレート的な処理など、さらに長いコードを書くことになります。IDEが持つコード補完機能を使うとはしても、それなりに負担ではないでしょうか。このようなボイラープレートは、実は簡単に排除することができます。最初にご紹介するのは、標準で提供されているテンプレート機能です。
↑のような感じです。例えば、Stateless Widgetを作りたいときは、 stlessを入力後、Tabキー押下 だけです。同様に、Stateful Widgetを作りたいときは、 stfulを入力後、Tabキー押下です。では、これら呪文のような stlessやstful はどこで定義されているのでしょうか?Android Studioの設定画面を開くと、 Editor->Live Templates に、これらテンプレートを確認できるはずです(Android Studio向けの公式Flutterプラグインを導入している場合)。
現時点で8個のテンプレートが提供されています。ただ、これでは物足りないと感じる方もいると思います:-P Flutter Snipets (開発者:George Herbertさん)というプラグインを導入することで、より多くのテンプレートを利用できます。ちなみにプラグインは、同設定画面のPluginsからプラグインの名前を検索することで、インストールできます。
導入すると、このように、多くのテンプレートを利用できるようになります。
Tips(2)コーディング支援機能の活用
Flutterでアプリ開発の醍醐味は何と言っても、 Hot Reload でしょう。コードを編集しては、リアルタイムに画面を確認してと、開発サイクルを高速に回していくことができます。コードをいじりながら、アプリの画面を少しずつ改造していくことも頻繁にあると思います。そんなときに便利なTipsを紹介します。例えば、このような改造を行うときには、 TextFieldウィジェット(テキストボックス) の周囲に Paddingウィジェット(パディング)を挿入することになります。
コードを自分で逐一入力していっても良いのですが、地味に面倒ではないでしょうか。そんなときは、エディタのコーディング支援機能を活用すると便利です。
起点とするTextFieldウィジェットにカーソルを当て、支援機能を呼び出すと、いくつかの候補がポップアップで表示されます。今回は、 Add Padding を選択すると追加できます。もし間違えてウィジェットを追加してしまった場合に、該当ウィジェットだけを削除する支援機能も便利です。ぜひ色々と試してみてください。
Tips(3)その他タスクの高速化
Flutterでアプリを作っていると、Dartのコーディング以外にも実施すべき作業があります。これらの高速化につながる便利プラグインを幾つかご紹介していきます。
a.パッケージの導入支援
開発中に、pub.dev(Flutter向けパッケージが公開されているサイト)を頻繁に使うと思います。このページ確認しつつ、 pubspec.yamlファイル(パッケージの導入時などに編集) を編集していくはずです。ただ、開発に慣れてくると、パッケージ名の名前は何となく覚えているということもあると思います。そんなときに便利なのが、Flutter Enhancement Suite (開発者:Marius Höflerさん)です。
例えば、blocライブラリを使いたいとしましょう。このプラグインを導入しておくと、
のように数文字入力だけで、pub.devを逐次検索して該当するパッケージをリストアップしてくれます。あとは、候補から選ぶだけです。よく使うパッケージは多少なり名前が頭に入っているはずなので、こちらを使うと効率化できるケースもあると思います。
b.パッケージのバージョンアップ支援
次に、パッケージのバージョンアップのときに役立つプラグインです。利用していたパッケージの開発が進み、知らないうちに新しいバージョンがリリースされていることもよくあると思います。そんなときに便利なのが、Flutter PubVersion Checker (開発者:Paulina Szklarskaさんです。例えば、shared_prefereceパッケージ(AndroidのSharedPreference機能のFlutter版)を使っていたとしましょう。このプラグインを導入しておくと、
のように、 簡単に最新バージョンに更新 することができます。ただし、当然ながら、パッケージのバージョンアップに伴い、アプリの挙動が変わることもよくあります。そのため、動作確認は確実に行いましょう。
c.パッケージのバージョンアップ支援
最後に、画像を利用するアプリで役立つプラグインです。アプリ内で画像を利用するとき、その定義をpubspec.yamlファイルで行います。例えば、画像を追加するたびに、このファイルを編集するのは、あまりに大変です。
そんなときに便利なのが、flutter-img-sync (開発者:Lihahaさんです。プラグインを導入すると、Android Studioの[Tools]配下に、本ツール実行用のメニューが追加されます。そこで、 [Tools]→[FlutterImageSync] と選択することで、 assetsフォルダ配下にある画像のパスを認識し、pubspec.yamlファイルに反映 してくれます。
また、うれしいのが、画像ファイルをDartのリソース管理クラス経由でアクセスできるようになります。 r.dart(画像ファイルへの参照に必要) をインポート後、
このクラス経由でアクセスできるようになります。クラス経由でアクセスできるため、画像のファイル名を誤って記述するなどのミスを軽減できるはずです。
以上、3つの観点で、Flutterアプリを開発するときに知っておくと役立つかもしれないTipsをご紹介しました。実は他にも色々とあるのですが、長くなってきたので、また、別の機会にご紹介できればと思います。
おまけ
最後に余談として、Flutterと私のつながりについて、ご紹介したいと思います。
私は新しい技術が大好きなこともあり、Flutterについても世の中に登場した頃から触っています。コロナ禍に見舞われてしまった1年ではありましたが、社外やコミュニティの皆様とのご縁もあり、幾つかのセミナー/カンファレンスなどでオンライン登壇させていただきました。こちらは、日本で開催された【Online】Flutter Meetup(主催:GDG Tokyo, Flutter Meetup Tokyo, WTM Tokyo)と、欧州・中東・アフリカで企画されたDroidcon EMEA 2020でお話しさせていただいたときの様子です。
オンラインゆえに世界中どこにいても参加できるメリットを強く感じました。海外カンファレンスは9回目の海外講演にして、初めてのオンライン登壇でした。聞いてくださっている方々の顔を直接見ることができない状況で進行していくため、いつもとは異なる感覚がありました。ただ、終わった後には、個別に話しかけていただいたり、メッセージいただいたりとフィードバックをいただけたのはよかったです。
オンラインカンファレンスの良さも多く感じた一方で、2021年は世界に平穏な日常が戻り、現地開催されるイベントも一部戻ってきて、開発者同士で直接お互いの顔を見ながら、語り合える世界も復活してほしいなとも祈っています(オンライン・現地とそれぞれ良いところありますよね)。
これからもFlutterに限らず、さまざまな分野の面白そうな技術に着目していこうと考えています。エバンジェリスト活動も広げていきたいと考えておりまして、何かありましたら、海外・国内限らず、お声がけいただけるとうれしいです!
おわりに
今回は「Flutterアプリ開発高速化Tips」について、ご紹介しました。 Advent Calendar 2020に最後までお付き合いいただき、ありがとうございました!
当社は様々な分野で、多くのエンジニアが活躍しています。興味を持ってくださった方は、NTTテクノクロス Advent Calendar 2020から、気になる記事を読んでいただけると幸いです。まだまだ大変な状況が続いている世の中ですが、皆様にとって良い年末年始となりますように!
- 投稿日:2020-12-23T00:06:53+09:00
アイディアのタネになる面白い技術の活用法【iOS】【ハッカソン】
はじめに
はじめまして、じんむです。DeNAでiOSエンジニアをしております。
プライベートでは個人開発やハッカソンが好きで、楽しいを届ける系ものづくりをしています。プロダクトのアイディアを出す時、需要のあることや実現したいことから考える人間ですが、技術の引き出しがあると、技術の意外な組み合わせ・使い道でアイディアがドンドンふくらんだり、実現できるかの判断が即座にできたりします。
私事ですが、4年連続、通算8回出場しているアイディア当日型のハッカソンSPAJAMの本戦で優勝しました、わあい。そこで、これまで触れてきた面白い技術を、約24時間ノーアイディアからチームメンバーと共に実装したプロダクトでの活用例と共に紹介し、技術の引き出しをざざざッと整理して共有したいと思います?
この記事は技術の楽しい活用例メインで紹介するため、プロダクトの目的や内容の説明はあまりしません。気になる方はリンク先の動画やじんむのLPをチェックしてみてください。また画像は当時の発表スライドの一部をぺたりと貼り付けています…若干恥ずかしい?
あくまで私たちの活用法ですが、みなさんが楽しいを生み出す時の、アイディアのタネや組み合わせのヒラメキになると嬉しいです??
AR
アンカーに追従してARを表示
iOSのARKitなどを活用すれば、アンカーに追従するオブジェクトを簡単に表示することができます。
プロダクト例
マンガの表紙でAR試し読み
マンガの表紙をアンカーとして設定することで、マンガの中身を試し読み表示できるアプリを実装しました。
YouTube ※このプロダクトのみ個人開発ARコンテンツが現実世界に実在する人の前や背後を通り過ぎるといったことを実現できるピープルオクルージョンもリアリティが増すので活用しました。詳しい実装方法はQiitaで紹介しています。
場所にARを表示
アンカーがなくとも、床や壁、空間にオブジェクトを配置できます。ARKit 4ではロケーションアンカーを用いて、緯度、経度、高度を指定してARオブジェクトを配置できます。街中や人気スポットの周辺など、特定の場所にAR体験を配置しましょう。
プロダクト例
観光ポスターを自動生成してARで表示
地元観光ポスターを、入力された写真に何が写っているのかを識別することで自動生成するアプリ。観光地に足を運ぶことで、生成されたポスターをARで一望できます。
YouTubeポスターをアプリで自動生成することがメインのアプリで、生成するのはいいけれど、実際のポスターは大きいし、印刷は面倒、渋谷の駅を歩いてみると観光ポスター剥がされてる…。じゃあ位置情報と相性も良いARが良いねとアイディアが発散して追加した機能です。ARは現実を拡張してくれるのでプロダクトの+αの要素によくなってくれます。
深度
グリーンバックを実現
iOSのARKitで取得できる深度情報を元に、前景と後景を分離できます。リアルタイムに処理可能なので、グリーンスクリーンいらず系プロダクトが作れます。
プロダクト例
離れている人と仲良しっぽい自撮りが撮れる
一人で自撮りをすると、同時に撮影した人とあたかも一緒に旅行に行ったようなツーショット写真が生成されるアプリを実装しました。コロナ渦で旅行にいけない今にぴったり。
YouTube思っているよりものすごく綺麗にくりぬけるので感動します。尊敬する堤さんがDepthを活用するサンプルコードを公開してくださっているので覗いてみることをおすすめします。自分も実装してみましたが、汚い…?
ML
写真を学習して分類モデルを生成
Cloud AutoML Visionなどを活用すれば、分類したいものが写っている画像を10枚~ほど撮影してアップロードするだけでリアルタイム分類モデルを生成してくれます。(100枚以上推奨ではあるが)。組み込みもCoreMLを活用すれば簡単です。
プロダクト例
スマホを家具にかざすことで取り扱い説明書が取り出せる
スマホを家具にかざすと、どの家具であるかリアルタイムに識別して、該当する取扱説明書を表示してくれるアプリを実装しました。
お家にある家具家電は個数が限られているかつ、家庭によって様々なので、自前モデルが適してると判断しました。iOSだとCreate MLでも同じようなことができます。ハッカソン中はものすごく時間がなかったので、モデルの生成がクラウドで高速にできるかつ、精度 & 識別スピードが当初早かったGoogle様を活用しました。
人や動物などを検出する既存モデルを活用
iOSのCoreMLのVision Frameworkを活用すると、人や動物の検知など、たくさんの学習済みモデルが用意されていて、活用できます、楽しい。
iOSのMLは毎年アップデートされており、1年前にできるようになったことをまとめたスライド(2019 WWDC)だけでも盛り沢山。ここからさらに進化しているものもあり、最新情報もまとめたい?プロダクト例
360度画像を背景に旅行先で自撮り体験
Thetaなどで撮影できる360度写真を背景に、好きな角度で旅行自撮り写真を撮影できるアプリを作りました。
YouTubeグリーンバック機能を実現するために、先ほど説明した通り、深度を使えたら最高なのですが、深度をインカメで取得できる端末は全てではない(当時はiPhoneXが出たばかりだった)ので、人を識別するモデルを活用して、リアルタイム繰り抜きを実現。どうしても精度は深度と比較して落ちますが、最低限は可能。繰り抜きよりは、お面系表示の方が向いているとは思います。
文字認識で入力不要
Firebase MLKitのOCRや、iOSのVision Framework Textなどを活用して、文字起こしができます。しかし、なかなか日本語の既存モデルはありません?
プロダクト例
型番を映して取扱説明書を自動で登録
前に紹介した取扱説明書アプリでは、文字認識技術を活用して家電の型番を読み取り、Webで検索した結果からPDF(取扱説明書はだいたいPDFが落ちてる)を自動で登録するように実装しました。型番は相性がよく、ほぼ正しく読み取ってくれてました。
音声認識でテロップ
リアルタイム音声認識で声によるアクションをしたり、テロップ表示を実現したりできます。
- Cloud Speech-to-Text (GCP)
- Amazon Transcribe (AWS)
- Speech to Text (Watson) etc...
など、様々なAPIの選択肢があります。その中でもiOSだとお手軽なSFSpeechRecognizerをよく使います。
プロダクト例
インタビュー風動画を簡単に撮影
インタビュー形式で、普段言えない感謝の言葉や謝罪の言葉を伝えるアプリを作りました。リアルタイム音声認識でのテロップ表示や、顔画像からの年齢性別推定を用いて、ニュースのインタビュー風動画を生成します。
YouTubeテロップのような表現をしたいときに地味に困るのは句読点・改行です。SFSpeechRecognizerは認識しているだけなので、どこで改行すべきかは教えてくれません。なので、対応しているAPIを活用するか、形態素解析なんかと組み合わせるとよりそれっぽくなるかと思います。
ジャイロセンサ
シェイクでジャンプ
シェイクってそもそも認識されていない感はありますが、iOS標準動作だと、キーボードの操作を一つ戻せるなど、直前操作を取り消せます。こう…「しゃッかッ」って感じで端末を上下に降る動作です。ジャイロの数値をいじいじするより、コードが数行ですむので、適した動作を検知するときはこちらを活用します。
override func viewDidLoad() { super.viewDidLoad() self.becomeFirstResponder() } override func becomeFirstResponder() -> Bool { return true } override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?){ if motion == .motionShake { //シェイクシェイク〜 } }プロダクト例
自撮りしながらジャンプで画面切り替え
シェイクの認識パターンが、端末をもってジャンプした時と一致することに気づいたので活用してみました。
前に紹介した360度画像を背景に旅行先で自撮り体験と組み合わせて、ただ画面をタップして撮影場所を切り替えるのではなく、「せーのッ」でジャンプして場所が切り替わる、テレビやYouTubeでよくある夢のある表現を実現できました。
照度センサ
複数端末を同時発火
こちらも意識したことある人は少ないのではないかなと思いますが、iPhoneだとインカメ付近にセンサがあり、手をかざしたり外したりすると反応します。手をかざす動作を起点にイベントを発火させると、スマホに触れずまるで魔法を使えるかのような気分になります?
プロダクト例
複数端末で動画を同時再生
音楽に合わせて顔の動きでセッションするiOSアプリ。動画を撮影した端末を複数台並べ手をかざすことで、同期セッションができる。セッションしている端末を自動グルーピングし、合成動画を生成する。送別会の色紙をバージョンアップしたい思いから作った。
YouTubeTikTokでの自撮り表現はすごく広がっている。そしたら、複数端末での映像表現もあっていいんでない?という発想から、複数端末並べて動画を同時再生したいニーズがありました。そこで、照度センサを用いて手をかざすだけで同時再生される不思議な体験を実現、楽しい。
位置情報
マップにコンテンツを表示
ハッカソンでよく活用される位置情報。その中でもマップの表示はiOSのMapKitがいろいろできて便利です。マップはコンテンツの表示方法の一種で、とても直感的なものです。
プロダクト例
観光ポスターを地図上にピン
しかし、マップは一覧性が高い表示とは言えませんそこで観光ポスターを自動生成するアプリではピン表示とリスト表示を組み合わせています。こうすることで、数多くのピンが立った時も重なって見えないことがなかったり、遠くにピンされた観光ポスターを眺めることができたりします。
YouTubeFirebase
このセクションだけ区分がおかしい?
特にアプリのプロトタイプ作りには欠かせない存在となりました。普段業務ではクラッシュレポートやイベントなどアナリティクス、Remote Configなどを使いこなしていると思いますが、サーバーレスサポートも最高なのです。ぜひキャッチアップしておきましょう。複数端末にコンテンツをリアルタイムに共有
ほぼ全てのプロトタイプで、コンテンツをを複数端末に共有するために、Cloud Firestore または Realtime DatabaseとCloud Storageを活用しています。すごく簡単。
クラウド上でAPI処理
インタビュー動画生成アプリでは、動画から写っている人の年齢を推定するために、Cloud Functionを活用してAzure Face APIを叩いてます。
クラウド上でML
ポスター自動生成アプリでは、Firebase ML Kitを活用して、画像に何が写っているのかを識別し、結果を元に観光ポスターらしいエモいキャッチコピーを生成しています。
Firebaseさまさま?♂️
おわりに
面白い技術の予想外な組み合わせで、新しい楽しいを生み出すのが最高です。
そして、ハッカソンはチームメンバーと寝ずにすごす24時間が楽しいので続けていきたいと思います?この記事はDeNAアドベントカレンダー23日目の記事です?
この記事を読んで少しでもウケる、面白い、楽しいと思ってくださった方、「LGTM」をお願いします。Twitter@koooootakeでも気軽に絡んでくださいいい。また、Twitter@DeNAxTech のフォローもよろしくお願いします:Dアドベントカレンダーも残りわずか?♂️楽しんでいきましょおおお??

























































