- 投稿日:2020-10-18T14:29:47+09:00
iosのアプリ審査でリジェクト(Guideline 5.1.1 - Legal - Privacy - Data Collection and Storage)
アプリをアップデートしたらリジェクトされたのでメモ。
カメラあたりはアップルはいろいろ煩いです。前にもカメラあたりで指摘をもらってました。
審査はipadでもしっかり確認するようで、ipadだとカメラがずれてるとか、そういう指摘もくる。キャプチャーももらえてテスターとしては優秀。だけどメッセージしても返事に数時間かかるから厄介。
もうipadも確認して来られるとandroidと変わらなくなってきてるぐらいiosも面倒です。Hello, Thank you for providing this information. Guideline 5.1.1 - Legal - Privacy - Data Collection and Storage We noticed that your app requests the user’s consent to access the camera but does not clarify the use of the camera in the applicable purpose string. To help users understand why your app is requesting access to their personal data, all permission request alerts in your app should specify how your app will use the requested feature. Next Steps Please revise the relevant purpose string in your app’s Info.plist file to specify why the app is requesting access to the camera. Make sure the purpose string includes an example of how the user’s data will be used. You can modify your app’s Info.plist file using the property list editor in Xcode. Resources For additional information and instructions on requesting permission, please review the Requesting Permission section of the iOS Human Interface Guidelines and the Information Property List Key Reference. You may also want to review the Technical Q&A QA1937: Resolving the Privacy-Sensitive Data App Rejection page for details on how to provide a usage description for permission request alerts. We hope that you will make the appropriate changes to your app to bring it into compliance with the App Store Review Guidelines and resubmit your app for review. We look forward to reviewing your resubmitted app. Best regards, App Store Reviewアプリでカメラを使うときに、カメラ利用の目的を小さなダイアログでユーザに伝えますが、その文言の指摘でした。それじゃーわからんということのよう。
元々出力してたのは「take your camera」でした。
そっか、これじゃーだめか。UI的にここでカメラならわかるだろって発想で適当に設定してました。
そして変更後
「Use the camera to change the background image」
こんな感じにしてみました。そしたらこれは通った様子。
しかしこの後に他にもいくつか指摘が来て、大変だった。
特に前回指摘なかったところでも、後から気がついて指摘してきてリジェクトしてくるからつらい。
そこは前できてたじゃんと思いつつ直す。参考までに。
- 投稿日:2020-10-18T10:33:47+09:00
大学の研究室配属選考での自己アピールのために製作物をGitHubで公開してみた話
経緯
大学の情報系学科に通っている大学3年生です!
私の通っている大学・学科では、3年生の後半(10~11月)に研究室配属があります。この研究室配属では、各研究室が成績+面接等によって希望者の中から配属者を選考するのですが、面接では研究への興味・プログラミング能力・継続力・学習意欲などの自己アピールを求められます。そして、ここで重要なのは自己アピールにはGitHub等にあげた製作物も利用することができるという点です。
今回、私も研究室配属選考に備え、自己アピールに使うために過去の製作物をGitHubで公開し、「せっかく公開するんだったら記事でも書いてみようかな」と思って記事を書いて見ました。
製作物
Qiita_for_iOS
iOS開発の学習のために作成した
俺得Qiita閲覧アプリです。
- 記事の閲覧・検索やLGTM・ストック、ストック記事の確認などができます。
- Qiitaの公開APIを使用
- iOS13のキャッチアップを兼ねて作成したので、
UICollectionViewCompositionalLayout
やProperty Wrapper
などを盛り込んでいます。discord_clone_firebase
React開発の学習のために作成したチャットサービスDiscordのクローンアプリです。
デモ: https://discord-clone-36c89.web.app/
※ ユーザー名の入力が求められますが、「test」等を入力していただければ大丈夫です。
- メッセージ送信, 画像・ファイル送信, AddReactionなどができます
- バックエンドにFirebaseを利用
- React + Typescript + React Hooks
大学2年生以下の開発者様へ
製作物があると自己アピールに使える武器が手に入るので、作っておいて損はないと思いますし、研究室配属選考に限らず、その自己アピールが使えるケースも多いのではないでしょうか。
私の場合は製作物がiOS, Reactアプリケーションなので研究内容に直結しにくいですが、それでもプログラミング能力・継続力・学習意欲などのアピールになると思いますし、更に近年は機械学習・画像処理・音声認識などの比較的研究に関連しやすい分野も個人開発で手を出せるので、製作物で研究への興味や経験をアピールすることも可能だと思います。
もし、大学2年生以下で開発をしているのであれば、1つで良いのである程度形になった・公開できる製作物を作っておくと研究室配属で役に立つかもしれません!!
まとめ
(大学の先輩から聞いた話なのですが)
研究室配属選考の面接だと、「〇〇に興味があります。」 「プログラミングは得意です。」等の口頭での自己アピールのみの学生も多いらしく、その中で「〇〇に興味があります。〇〇を作りました。ソースコードはGitHubに上げてあります。」 「プログラミングは得意です。〇〇を作りました。△△にリリースしてあります。」と言った様に 製作物という証拠と一緒にアピールをすると信憑性が高く評価されやすい みたいです。
もちろん今までの成績も評価されますが、成績のみで全てを決める場合はむしろ少ない様です(?)。
やはり、自分がやってきたことのアウトプットは大事だなと思いました。今回、自分の過去のリポジトリで公開できる物を探したところ、とりあえずできそうだったのは2つでしたが、これを機にこれからは製作物をよりPublicに発信して行こうと思います。
私も研究室配属選考で勝ち残れる様に頑張ります!!!客観的評価がついているとより強いと思うので、製作物が良いと思ったらリポジトリにスターください(小声)(願望)(切実)
おまけ
GitHubのプロフィールも作成し、それっぽくしてみました!
- 投稿日:2020-10-18T06:07:59+09:00
Kerasで作った簡単なモデルをCoreMLを使ってiOSで動作させる
CoreML Toolsを理解するために、自分で作ったシンプルなモデルを使えば、自分で好きなようにモデルを変更して試せるので良いのではないかと思い試してみました。その手順を説明します。
作るもの
数字2組を与えたら、それらを足した結果を予測するモデルを作ります。
手順
今回のコードの完全版はこちらに保管してあります。
https://gist.github.com/TokyoYoshida/bab3d0396c05afce445852d2ae224cf41.Google Colabを起動する
Google Colaboratoryのサイトにアクセスします。
Google Colaboratory2. 必要なものをインストール & インポートする
coremltoolsのバージョンにあわせてtensorflow, kerasもインストールしています。
notebook!pip install tensorflow==1.14.0 !pip install -U coremltools !pip install keras==2.2.4そして必要なモジュールをインポートします。
notebookfrom keras.models import Sequential from keras.layers import Dense, Activation import numpy as np import math from keras.utils import np_utils import keras3. Tensorboardの準備をする
CoreML Toolsで変換をするとき、モデルの情報が必要になることがあります。
そのためにはある程度モデルを理解する必要がありますが、Tensorboardで可視化しておくと、モデルの情報を理解する助けになるため、その準備をしておきます。
(今回は自分でモデルを作っているので、モデルの情報は要りませんが)参考:
[TF]KerasからTensorboardを使用する方法notebook!mkdir logs tb_cb = keras.callbacks.TensorBoard(log_dir='./logs', histogram_freq=0, batch_size=32, write_graph=True, write_grads=False, write_images=False, embeddings_freq=0, embeddings_layer_names=None, embeddings_metadata=None) cbks = [tb_cb]4. モデルを作り学習させる
入力データして、2つの整数の組み合わせを1万組作成します。
教師データは今回は分類問題として扱うため、[0,1,2,3,4・・17,18]という18個の配列になります。(答えの組み合わせとして9 + 9 = 18が最大値のため)
この配列には、答えとなる部分だけに1が入り、それ以外は0が入ります。このような表現方法をOne-hot表現と呼びます。モデルは、シンプルな全結合レイヤーを3つ、活性化関数はsoftmaxを使用します。
参考:
Kerasで1桁の足し算notebookx = np.random.randint(0, 10, (10000,2)) y = np_utils.to_categorical(np.sum(x, axis=1)) model = Sequential() model.add(Dense(512, activation='relu', input_dim=2)) model.add(Dense(256, activation='relu')) model.add(Dense(y.shape[1])) model.add(Activation("softmax")) model.compile('rmsprop', 'categorical_crossentropy', metrics=['accuracy']) train_rate = 0.7 train_len = math.floor(len(x) * train_rate) trainx = x[0:train_len] trainy = y[0:train_len] testx = x[train_len:] testy = y[train_len:] history = model.fit(trainx, trainy, batch_size=128, epochs=100, verbose=1, callbacks=cbks, validation_data=(testx, testy))上のコードを実行すると学習が始まります。
notebook7000/7000 [==============================] - 0s 54us/step - loss: 0.1705 - acc: 0.9677 - val_loss: 0.0172 - val_acc: 1.0000 Epoch 99/100 7000/7000 [==============================] - 0s 53us/step - loss: 0.0804 - acc: 0.9804 - val_loss: 0.0069 - val_acc: 1.0000 Epoch 100/100 7000/7000 [==============================] - 0s 57us/step - loss: 0.0745 - acc: 0.9806 - val_loss: 0.0062 - val_acc: 1.0000今回はトレーニングデータとテストデータは分割しているものの、実際にはかぶりがあるので、テストデータによる検証結果(val_acc)はあまり当てにならないです。
5. Tensorboardで可視化してみる
Tensroboardを読み込んで、実行します。
なぜかtensorboard-plugin-wit
をアンインストールしないとエラーがでるので、アンインストールしておきます。notebook%load_ext tensorboard !pip uninstall tensorboard-plugin-wit %tensorboard --logdir ./logs学習の状況
グラフの情報
レイヤーは下に入力(dense_1)、上に出力(activation_1)というようにレイヤーが下から上に向かっています。
入力側(dense_1)を見てみます。
OperationがPlaceholderとなっており、ここにデータが入ることがわかります。
dtypeはDT_FLOATとなっています。今回のデータは整数で作っていますが、小数以下のデータも扱えます。
shapeは、{"dim":{"size":-1},"size":2]}となっています。つまり(-1,2)のshapeということですね。
-1は任意の値という意味になります。2は、2つの文字の組み合わせを入力しているためです。
<図.2>の入力データのところで、2つの数字を組み合わせxテストデータ数(任意)ということと一致します。出力側(activation_1)を見てみます。
activation_1はsoftmax関数なので、1つ前のdense_3が出力した19個の数字に対してsoftmax関数の計算結果を出し、loss、metrics、trainingに出力しています。モデルの情報は、kerasのsummaryメソッドでも出力できます。
Tensorboardではレイヤーが下から上に向かっていましたが、こちらは上から下に向かっていますね。notebookmodel.summary() _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_1 (Dense) (None, 512) 1536 _________________________________________________________________ dense_2 (Dense) (None, 256) 131328 _________________________________________________________________ dense_3 (Dense) (None, 19) 4883 _________________________________________________________________ activation_1 (Activation) (None, 19) 0 ================================================================= Total params: 137,747 Trainable params: 137,747 Non-trainable params: 06. Notebook上で予想してみる
iOSで動作させる前に、Notebook上でうまく動作しているか確認します。
適当に2つの数字の組み合わせを与えると、正しく計算できていることがわかります。notebooknp.argmax(model.predict(np.array([[7,6]])),axis=1) // array([13]) np.argmax(model.predict(np.array([[1,3]])),axis=1) // array([4])7. Core MLに変換する
上で作ったモデルを保存しておきます。
(これをしなくても既にモデルは得られているのでそのままCore MLに変換することもできます。)notebookmodel.save('my_model.h5')読み込んで変換します。
notebookfrom keras.models import load_model keras_model = load_model('my_model.h5') from coremltools.converters import keras as converter # 予想結果の数字の分類ラベルを作る["0","1","2"..."18"] class_labels = np.arange(0, 19).astype('unicode').tolist() # 変換 mlmodel = converter.convert(keras_model, # 変換対象のモデル output_names=['digitProbabilities'], # 予想の出力に名前をつける。swiftから変数名としてアクセスできるようになる class_labels=class_labels, # 予想結果の分類ラベル predicted_feature_name='digit' # 分類の出力に名前をつける。swiftから変数名としてアクセスできるようになる )保存します。
notebookcoreml_model_path = 'my_model.mlmodel' mlmodel.save(coreml_model_path)このあたりのコードは、こちらの本に書いてあるものをそのまま使っています。
Core ML Tools実践入門 - iOS × DEEP LEARNING
8. Core MLモデル(.mlmodelファイル)をダウンロードする
Notebookから下のように選択して「ダウンロード」を選択してダウンロードします。
9. XcodeのプロジェクトにDrag & Dropする
Xcodeを起動して、プロジェクトの作成から「Single View App」を作成します。
今回作ったプロジェクトはgithubにあるので、こちらを使っても良いです。
TokyoYoshida/CoreMLSimpleTestプロジェクトの、任意の位置に.mlmodelファイルをDrag & Dropします。
Xcode内でモデルを選択すると、プレビューを見ることができます。
入力がMultiArray型で2つのDoubeを取ります。これは例えば、2+3を予想させたいなら、[2,3]を与えます。
出力は、digitProbabilitiesは、Dictionaryで文字列がKey、DoubleがValueになっています。この項目は予想結果で、数字のラベルごとの確率が出力されます。
digitは、予想結果を数字のラベルに当てはめた結果です。なお、MultiArray型は、Core ML内に定義されているモデルの入力や出力として使用する多次元配列です。
10. Core MLを使った推論のコードを書く
今回のモデルは、画像認識ではないのでVisionFrameworkなどは用いず、Core MLを直接操作します。
ViewControllerのviewDidLoadにコードを書いていきます。ViewController.swiftclass ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let model = my_model() // 予想したい数字のペアをつくる let inputArray = try! MLMultiArray([2,3]) let inputToModel: my_modelInput = my_modelInput(input1: inputArray) // 推論する if let prediction = try? model.prediction(input: inputToModel) { // 結果の出力 print(prediction.digit) print(prediction.digitProbabilities) } } }11. 実行する
アプリを実行すると無駄にアプリの画面が出ますが、気にせずXcodeのOutput欄を見ます。
結果出力5 ["13": 1.401298464324817e-45, "7": 4.403268860642129e-08, "16": 0.0, "12": 1.401298464324817e-45, "10": 1.401298464324817e-45, "4": 2.876720373024e-06, "11": 1.401298464324817e-45, "1": 1.2956196287086532e-23, "6": 6.624156412726734e-06, "8": 6.452452973902557e-18, "15": 1.401298464324817e-45, "2": 7.265933324842114e-14, "0": 1.0373160919090815e-33, "18": 0.0, "9": 1.7125880512063084e-34, "17": 0.0, "3": 1.129986526746086e-15, "14": 1.401298464324817e-45, "5": 0.9999904632568359]2+3を与えているので、5が推論されていますね。
digitProbabilitiesの出力では、各ラベルをキーとして確率が出力されています。
5である確率は0.9999904632568359なので、ほぼ1になっています。
それ以外の数字、例えば13である確率は、1.401298464324817e-45となっていますがこれは浮動小数の表現で1.401298464324817✕10の-45乗なのでほぼゼロという結果になっています。最後に
NoteではiOS開発について定期的に発信していますので、フォローしていただけますと幸いです。
https://note.com/tokyoyoshidaTwitterでは簡単なtipsを発信しています。
https://twitter.com/jugemjugemjugem
- 投稿日:2020-10-18T03:53:23+09:00
iOS14 Widget機能を実装してみる
今回の記事では、iOS14のWidget機能について、紹介、実装していこうと思います。
環境
OS:Catlina 10.15.4
Xcode:12.2 beta21、Widgetとは
Appleは
iOS8
からホーム画面から左にスワイプすると出てくる通知センターにカレンダーやタイマーといったようなシンプルな機能を持ったアプリをアクセス出来るようなAPIを提供してきたが、iOS14
ではホーム画面にもその表示ができるようになりました。機能がさらに増加され、表示内容もタイムラインに沿って更新できます。!重要:
Widget
機能はSwiftUI
でしか実装できないので、Widget
の本質はタイムラインによって変わるSwiftUI
ビューと言っても良い。2、実装順番
Widget Extension追加
SwiftUI
でマルチプラットホームのプロジェクトを新たに作成したら、Widget Extension
を追加します。
ちなみに、1つのアプリで複数のWidget Extension
を追加することができます。Xcode →File →New →Target →Widget Extension
widgetDemoW target
の追加に成功したら、タイムviewがデフォルトで表示されます。Widgetの入り口
widgetDemoW.swiftクラスの中、
@main
マークはWidget
の入り口です。struct widgetDemoWEntryView : View { var entry: Provider.Entry var body: some View { Text(entry.date, style: .time) } } @main struct widgetDemoW: Widget { let kind: String = "widgetDemoW" var body: some WidgetConfiguration { IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in widgetDemoWEntryView(entry: entry) } .configurationDisplayName("My Widget") .description("This is an example widget.") .supportedFamilies([.systemMedium]) } }
configurationDisplayName
はユーザがアプリのWidget
を追加、編集する時のWidget
の表示名(1つのアプリに複数のWidget
の存在もあり得るため)description
はWidget
の説明supportedFamilies
はサーポットサイズでデフォルトでは大、中、小を全てサーポット。providerは、
Widget
を更新するためのタイムラインを決定するオブジェクトです。Widget
を更新するための将来の時間を提供することで、システムは更新プロセスを最適化できます。
widgetDemoWEntryViewのEntryViewは実際に表示するViewです。
Widget
をクリックしたら本アプリを起動できます。Widget
を長押すと編集に入ります。3、Widgetの表示内容をカスタマイズする
Widget
の更新はWidgetCenter
によって完全に制御されます。 開発者は、APIを介してWidget
ビューをアクティブに更新することはできません。タイムラインを更新する必要があることをWidgetCenterに通知することしかできません。
システム上は、タイムラインのリロードを駆動する2つの方法を提供します。System Reloads
とApp-Driven Reloads
。注意:タイムラインのリロードは
Widget
を直接更新しませんが、WidgetCenter
はデータの次のステージのためにWidget
を再要求します。 タイムラインのProvider
は、このデータを提供するオブジェクトです。タイムラインの
Provider
によって提供されるデータには2つの部分があります。1つはTimelineEntry
で、もう1つはReloadPolicy
です。
TimelineEntry
は、Widget
が特定のタイムノードの下に表示する必要があるビュー情報と時点です。
ReloadPolicy
は、次の期間中のタイムラインの更新ポリシーです。3つのタイプがあります。atEnd:最後のタイムスライスに達したときにタイムラインが更新されることを示します。
atAfter:一定時間後の定期的な更新を指します。
never:それは将来更新する必要がないことを意味します。 最後のEntry
が表示されたら、更新せず最後のEntry
に止まります。アプリの起動だけでなく、
Widget
には異なるページへ遷移する複数のボタンが含まれることも可能です(中/大サイズのWidget
のみ)。実装例1
静的表示
struct widgetDemoWEntryView : View { var entry: Provider.Entry var body: some View { HStack { Text("今回の記事では、iOS14のWidget機能について、紹介、実装していこうと思います。 -20201017") .foregroundColor(.black) VStack { Image("widgetPic") .overlay(RoundedRectangle(cornerRadius: 10) .stroke(Color.purple, lineWidth: 3)) .shadow(radius: 10) .cornerRadius(10.0) Text(entry.date, style: .time) } .padding() } .padding() } }実際の表示
実装例2
タイムラインに沿って表示する
import WidgetKit import SwiftUI import Intents struct Provider: IntentTimelineProvider { func placeholder(in context: Context) -> SimpleEntry { SimpleEntry(date: Date(), text: "朋遠方より来たるあり。また楽しからずや。", configuration: ConfigurationIntent()) } func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) { let entry = SimpleEntry(date: Date(), text: "朋遠方より来たるあり。また楽しからずや。", configuration: configuration) completion(entry) } func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { var entries: [SimpleEntry] = [] // 現在の日付から開始して、1時間間隔で60エントリで構成されるタイムラインを生成する let currentDate = Date() var textArray = [String]() for i in 0..<60 { textArray.append("朋遠方より来たるあり。また楽しからずや。 timeline: \(i)") } for minOffset in 0 ..< 60 { let entryDate = Calendar.current.date(byAdding: .minute, value: minOffset, to: currentDate)! let entryText = textArray[minOffset] let entry = SimpleEntry(date: entryDate, text: entryText, configuration: configuration) entries.append(entry) } let timeline = Timeline(entries: entries, policy: .atEnd) completion(timeline) } } struct SimpleEntry: TimelineEntry { let date: Date let text: String let configuration: ConfigurationIntent } struct widgetDemoWEntryView : View { var entry: Provider.Entry var body: some View { HStack { Text(entry.text) .foregroundColor(.black) VStack { Image("widgetPic") .overlay(RoundedRectangle(cornerRadius: 10) .stroke(Color.purple, lineWidth: 3)) .shadow(radius: 10) .cornerRadius(10.0) Text(entry.date, style: .time) } .padding() } //アプリ側ではonOpenURLを通じてURLをハンドリングし、Widgetをタップすれば特定の画面へ遷移するアクションを実現 .widgetURL(URL(string: "widgetdemo://go2secondview")) .padding() } } @main struct widgetDemoW: Widget { let kind: String = "widgetDemoW" var body: some WidgetConfiguration { IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in widgetDemoWEntryView(entry: entry) } .configurationDisplayName("My Widget") .description("This is an example widget.") .supportedFamilies([.systemMedium]) } } struct widgetDemoW_Previews: PreviewProvider { static var previews: some View { widgetDemoWEntryView(entry: SimpleEntry(date: Date(), text: "朋遠方より来たるあり。また楽しからずや。", configuration: ConfigurationIntent())) .previewContext(WidgetPreviewContext(family: .systemMedium)) } }悟空アイコンが静的に表示し、論語のコンテンツとタイムを1分単位で更新します。
実際の表示
4、終わり
WidgetKit
の発想はすごく良いですが、OSのパフォーマンスと電力消費の考慮で、Widget
はビデオと動的画像の表示ができません。但し、うまく利用すればアプリの品質向上に繋がるには間違いないと思いので、もっと優秀なWidget
が出るのを期待したいです!
- 投稿日:2020-10-18T01:12:35+09:00
AVCaptureVideoPreviewLayerのプレビューサイズについて
AVFoundationのカメラ映像とプレビューのサイズが思ったように合わない時ってありますよね。
そんな時は、videoGravityで調整します。【準備】AVCaptureVideoPreviewLayerの設定
let previewLayer = AVCaptureVideoPreviewLayer(session: avCaptureSession) // avCaptureSessionは任意のAVCaptureSessionとします previewLayer.frame = previewView.bounds // previewViewは任意のUIViewとします previewLayer.connection.videoOrientation = .portrait // 向きの設定 previewView.addSubLayer(previewLayer)【本題】プレビューサイズの設定
カメラ映像全体が以下とします。
わかりやすいようにプレビューレイヤーは横長にします。1、縦横比を保ったまま、レイヤーサイズ内に収める
previewLayer.videoGravity = .resizeAspect2、縦横比を変えて、レイヤーサイズを満たす
previewLayer.videoGravity = .resize3、縦横比を保ったまま、レイヤーサイズを満たす
previewLayer.videoGravity = .resizeAspectFill4、おまけ:映像の真ん中だけプレビューする
カメラ映像のサイズが 1920 / 1080 で 上記画像のように真ん中の 810 / 1080 をプレビューしたいとします。let previewViewSize = previewView.bounds.size // previewViewは任意のUIViewとします let widthAspect = previewViewSize.width / 1080 let heightAspect = previewViewSize.height / 810 print(widthAspect,heightAspect) previewLayer?.frame = CGRect(x: -(1080 / 2) * widthAspect, y: -(810/ 2) * heightAspect, width: 1080 * widthAspect, height: 1920 * heightAspect) previewView.layer.addSublayer(previewLayer!) previewLayer.videoGravity = .resizeAspectプレビューレイヤーをビューより大きくして、はみ出させて真ん中だけビューから見えています。
?
お仕事のご相談こちらまで
rockyshikoku@gmail.comCore MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-10-18T01:11:12+09:00
futterのインストールからサンプルプログラムを動かすまで
目的
- モバイルアプリの開発を1つのコードで実装することを目的とし、そのためのツールとしてFlutterを使用することとした
- Flutterのインストールを行い、インストール後の確認としてサンプルプログラムを作成してiOSとAndroidの各シミューレタで動かす
環境
- OS:macOS Mojave(10.14.6)
- iOS向けのコマンドラインツール等はインストール済み(iOSの開発をした端末を使用)
- 動作確認はVSCodeを使用(インストール済み)
flutterのインストール
gitからclone
# 開発用フォルダへ移動 cd /Users/xxxx/develop # gitから取得 git clone https://github.com/flutter/flutter.gitflutterへパスを通す
# bash_profileへ設定追加 export PATH="\$PATH:`pwd`/flutter/bin" >> ~/.bash_profile # 設定を即時反映させる source ~/.bash_profile開発者ツールをダウンロード(任意)
flutter precache環境構築状況の確認
flutter doctorflutter doctorの結果に応じて必要なツールをインストール
- 表示されたメッセージの内容に合わせて設定していけばOK
- 解決しない場合は個別に調査
私の環境で足りなかった設定の追加
- android studio のセットアップ
- android studio のページからインストーラをダウンロードして実行
cocoapods のインストール
- インストールコマンドを実行
sudo gem install cocoapodsERROR: While executing gem ... (NoMethodError) undefined method `invoke_with_build_args' for nil:NilClass
- エラーになったので、エラーメッセージを調べてた結果rubyを入れ直し
rbenv uninstall 2.6.1 rbenv install 2.6.1
- インストールコマンドを再実行して問題なく終了することを確認
flutter doctor
を実行 → android studio のtool chainのエラーが消えない[!] Android toolchain - develop for Android devices: is partially installed; more components are available. (Android SDK version 30.0.2)
- android studio からコマンドラインツールを追加インストール
- 次の
flutter doctor
の実行でandroidのライセンス確認?のエラーが出ていたので、メッセージに合わせてコマンドを実行flutter doctor --android-licenses
デバイスが見つからない
[!] Connected device ! No devices available
- android studio から仮想端末を起動
- android studio を起動
- 「Configure → AVD manager」
- 設定済みの端末があったので、そのまま再生ボタンのアイコンをクリックして実行
flutter doctor
ですべての問題が解決していることを確認Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel master, 1.23.0-19.0.pre.83, on Mac OS X 10.14.6 18G6020 darwin-x64, locale ja-JP) [✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2) [✓] Xcode - develop for iOS and macOS (Xcode 11.2.1) [✓] Android Studio (version 4.1) [✓] VS Code (version 1.48.0) [✓] Connected device (1 available)サンプルプロジェクトの作成、シミュレータでの動作確認
- 必要に応じてVSCodeの拡張機能を入れる、これらは入れておきましょう
- Flutter
- Dart
プロジェクトを作成
flutter create {project name}VSCodeで
lib/main.dart
を開くiOSでの動作確認
- F5もしくはrun→start debuggingで実行
- シミュレータの選択肢が表示されるのでOSを選択
- iOSシミュレータ起動後、しばらく(数分程度)待つと起動します
- iOSとして動作することを確認
androidでの動作確認
- コマンドパレットから「Flutter: Select Device」を選択、androidのエミュレータを選択
- 選択したエミュレータ起動後、androidとして動作することを確認
参考
- 公式サイトのflutterのインストール手順(macOS)(https://flutter.dev/docs/get-started/install/macos)
- github(https://github.com/flutter/flutter)
- FlutterでHello worldを動かすまでの環境構築手順(iOS, Android)(https://qiita.com/unsoluble_sugar/items/deaa129fb289922c8634)