20201018のiOSに関する記事は6件です。

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」
こんな感じにしてみました。

そしたらこれは通った様子。
しかしこの後に他にもいくつか指摘が来て、大変だった。
特に前回指摘なかったところでも、後から気がついて指摘してきてリジェクトしてくるからつらい。
そこは前できてたじゃんと思いつつ直す。

参考までに。

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

大学の研究室配属選考での自己アピールのために製作物をGitHubで公開してみた話

経緯

大学の情報系学科に通っている大学3年生です!
私の通っている大学・学科では、3年生の後半(10~11月)に研究室配属があります。

この研究室配属では、各研究室が成績+面接等によって希望者の中から配属者を選考するのですが、面接では研究への興味・プログラミング能力・継続力・学習意欲などの自己アピールを求められます。そして、ここで重要なのは自己アピールにはGitHub等にあげた製作物も利用することができるという点です。

今回、私も研究室配属選考に備え、自己アピールに使うために過去の製作物をGitHubで公開し、「せっかく公開するんだったら記事でも書いてみようかな」と思って記事を書いて見ました。

製作物

Qiita_for_iOS

iOS開発の学習のために作成した俺得Qiita閲覧アプリです。

image1.png image2.png image3.png image4.png image5.png
  • 記事の閲覧・検索やLGTM・ストック、ストック記事の確認などができます。
  • Qiitaの公開APIを使用
  • iOS13のキャッチアップを兼ねて作成したので、UICollectionViewCompositionalLayoutProperty Wrapperなどを盛り込んでいます。

discord_clone_firebase

React開発の学習のために作成したチャットサービスDiscordのクローンアプリです。

デモ: https://discord-clone-36c89.web.app/
※ ユーザー名の入力が求められますが、「test」等を入力していただければ大丈夫です。

image1.png

  • メッセージ送信, 画像・ファイル送信, AddReactionなどができます
  • バックエンドにFirebaseを利用
  • React + Typescript + React Hooks

大学2年生以下の開発者様へ

製作物があると自己アピールに使える武器が手に入るので、作っておいて損はないと思いますし、研究室配属選考に限らず、その自己アピールが使えるケースも多いのではないでしょうか。

私の場合は製作物がiOS, Reactアプリケーションなので研究内容に直結しにくいですが、それでもプログラミング能力・継続力・学習意欲などのアピールになると思いますし、更に近年は機械学習・画像処理・音声認識などの比較的研究に関連しやすい分野も個人開発で手を出せるので、製作物で研究への興味や経験をアピールすることも可能だと思います。

もし、大学2年生以下で開発をしているのであれば、1つで良いのである程度形になった・公開できる製作物を作っておくと研究室配属で役に立つかもしれません!!

まとめ

(大学の先輩から聞いた話なのですが)
研究室配属選考の面接だと、「〇〇に興味があります。」 「プログラミングは得意です。」等の口頭での自己アピールのみの学生も多いらしく、その中で「〇〇に興味があります。〇〇を作りました。ソースコードはGitHubに上げてあります。」 「プログラミングは得意です。〇〇を作りました。△△にリリースしてあります。」と言った様に 製作物という証拠と一緒にアピールをすると信憑性が高く評価されやすい みたいです。
もちろん今までの成績も評価されますが、成績のみで全てを決める場合はむしろ少ない様です(?)。
やはり、自分がやってきたことのアウトプットは大事だなと思いました。

今回、自分の過去のリポジトリで公開できる物を探したところ、とりあえずできそうだったのは2つでしたが、これを機にこれからは製作物をよりPublicに発信して行こうと思います。
私も研究室配属選考で勝ち残れる様に頑張ります!!!

客観的評価がついているとより強いと思うので、製作物が良いと思ったらリポジトリにスターください(小声)(願望)(切実)

おまけ

GitHubのプロフィールも作成し、それっぽくしてみました!

https://github.com/kntkymt

スクリーンショット 2020-10-17 12.21.39.png

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

Kerasで作った簡単なモデルをCoreMLを使ってiOSで動作させる

CoreML Toolsを理解するために、自分で作ったシンプルなモデルを使えば、自分で好きなようにモデルを変更して試せるので良いのではないかと思い試してみました。その手順を説明します。

作るもの

数字2組を与えたら、それらを足した結果を予測するモデルを作ります。

<図1>

手順

今回のコードの完全版はこちらに保管してあります。
https://gist.github.com/TokyoYoshida/bab3d0396c05afce445852d2ae224cf4

1.Google Colabを起動する

Google Colaboratoryのサイトにアクセスします。
Google Colaboratory

2. 必要なものをインストール & インポートする

coremltoolsのバージョンにあわせてtensorflow, kerasもインストールしています。

notebook
!pip install tensorflow==1.14.0
!pip install -U coremltools
!pip install keras==2.2.4

そして必要なモジュールをインポートします。

notebook
from keras.models import Sequential
from keras.layers import Dense, Activation
import numpy as np
import math
from keras.utils import np_utils
import keras

3. 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表現と呼びます。

<図2>
image.png

モデルは、シンプルな全結合レイヤーを3つ、活性化関数はsoftmaxを使用します。

参考:
Kerasで1桁の足し算

notebook
x = 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))

上のコードを実行すると学習が始まります。

notebook
7000/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ではレイヤーが下から上に向かっていましたが、こちらは上から下に向かっていますね。

notebook
model.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: 0

6. Notebook上で予想してみる

iOSで動作させる前に、Notebook上でうまく動作しているか確認します。
適当に2つの数字の組み合わせを与えると、正しく計算できていることがわかります。

notebook
np.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に変換することもできます。)

notebook
model.save('my_model.h5')

読み込んで変換します。

notebook
from 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から変数名としてアクセスできるようになる
)

保存します。

notebook
coreml_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内でモデルを選択すると、プレビューを見ることができます。
image.png

入力が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.swift
class 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/tokyoyoshida

Twitterでは簡単なtipsを発信しています。
https://twitter.com/jugemjugemjugem

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

iOS14 Widget機能を実装してみる

今回の記事では、iOS14のWidget機能について、紹介、実装していこうと思います。

環境
OS:Catlina 10.15.4
Xcode:12.2 beta2

1、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がデフォルトで表示されます。

スクリーンショット 2020-10-17 14.42.47.png
スクリーンショット 2020-10-17 14.45.41.png

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の存在もあり得るため)
  • descriptionWidgetの説明
  • supportedFamiliesはサーポットサイズでデフォルトでは大、中、小を全てサーポット。

providerは、Widgetを更新するためのタイムラインを決定するオブジェクトです。 Widgetを更新するための将来の時間を提供することで、システムは更新プロセスを最適化できます。
widgetDemoWEntryViewのEntryViewは実際に表示するViewです。

2020-10-17 16.39.39.png2020-10-17 16.39.39.png2020-10-17 16.39.39.png
Widgetをクリックしたら本アプリを起動できます。Widgetを長押すと編集に入ります。

3、Widgetの表示内容をカスタマイズする

Widgetの更新はWidgetCenterによって完全に制御されます。 開発者は、APIを介してWidgetビューをアクティブに更新することはできません。タイムラインを更新する必要があることをWidgetCenterに通知することしかできません。
システム上は、タイムラインのリロードを駆動する2つの方法を提供します。 System ReloadsApp-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()
    }
}

実際の表示

スクリーンショット 2020-10-17 23.51.37.png

実装例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分単位で更新します。

実際の表示

2020-10-17 16.39.39.png2020-10-17 16.39.39.png2020-10-17 16.39.39.png

4、終わり

WidgetKitの発想はすごく良いですが、OSのパフォーマンスと電力消費の考慮で、Widgetはビデオと動的画像の表示ができません。但し、うまく利用すればアプリの品質向上に繋がるには間違いないと思いので、もっと優秀なWidgetが出るのを期待したいです!

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

AVCaptureVideoPreviewLayerのプレビューサイズについて

AVFoundationのカメラ映像とプレビューのサイズが思ったように合わない時ってありますよね。
そんな時は、videoGravityで調整します。

【準備】AVCaptureVideoPreviewLayerの設定

let previewLayer = AVCaptureVideoPreviewLayer(session: avCaptureSession) // avCaptureSessionは任意のAVCaptureSessionとします
previewLayer.frame = previewView.bounds // previewViewは任意のUIViewとします
previewLayer.connection.videoOrientation = .portrait // 向きの設定
previewView.addSubLayer(previewLayer)

【本題】プレビューサイズの設定

カメラ映像全体が以下とします。
pexels-fotografierende-2397448.jpg
わかりやすいようにプレビューレイヤーは横長にします。

1、縦横比を保ったまま、レイヤーサイズ内に収める

pexels-tuấn-kiệt-jr-3830482.jpg

previewLayer.videoGravity = .resizeAspect

2、縦横比を変えて、レイヤーサイズを満たす

pexels-fotografierende-2397448.jpg

previewLayer.videoGravity = .resize

3、縦横比を保ったまま、レイヤーサイズを満たす

pexels-tuấn-kiệt-jr-3830482.jpg
映像の一部しか映りません。

previewLayer.videoGravity = .resizeAspectFill

4、おまけ:映像の真ん中だけプレビューする

11.png
カメラ映像のサイズが 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.com

Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。

Twitter
Medium

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

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.git
    
  • flutterへパスを通す

    # bash_profileへ設定追加
    export PATH="\$PATH:`pwd`/flutter/bin" >> ~/.bash_profile
    # 設定を即時反映させる
    source ~/.bash_profile
    
  • 開発者ツールをダウンロード(任意)

    flutter precache
    
  • 環境構築状況の確認

    flutter doctor
    
  • flutter doctorの結果に応じて必要なツールをインストール

    • 表示されたメッセージの内容に合わせて設定していけばOK
    • 解決しない場合は個別に調査

私の環境で足りなかった設定の追加

  • android studio のセットアップ
  • cocoapods のインストール

    • インストールコマンドを実行
      sudo gem install cocoapods
    
      ERROR:  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として動作することを確認

参考

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