20201018のSwiftに関する記事は13件です。

【Swift】FirebaseのdocumentIDを取得する方法

var documentId : String?

self.firestore.collection("users").document("samplesample")
            .getDocuments() { (querySnapshot, err) in
                if let err = err {
                    print("Error getting documents: \(err)")
                } else {
                     for document in querySnapshot!.documents {

                         self.documentId = document.documentID
                     }
                  }
              }

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

SwiftでFirebaseのデータを上書きする方法

import Firebase


Firestore.firestore()
                .collection("コレクション名")
                .document(ドキュメントID)
                .setData([
                    "Value": 1,
                    "UserId" : Auth.auth().currentUser!.uid
                ], merge: true)

ポイントはmerge: trueです。
import Firebaseも忘れずに

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

SwiftでFirebaseで認証したユーザーのIDを取得する方法

import Firebase

let userID = Auth.auth().currentUser!.uid

import Firebaseも忘れずに。

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

完全独学 IOSアプリ開発 日記

本日から完全未経験者が独学(参考書1冊)でIOSアプリ開発までの記事を落として行きたいと思います。
※学習内容の整理、定着を目標にしているため更新は夜型になると思います。
※レベル感的には、Railsでサーバーサイドの実装が可能。

なぜ 

3つの効果を期待している。

 1,アウトプットを意識することで学習効率をあげること。
 2,要点だけをまとめて整理すること。
 3,宣言することで退路を断つ!

それでは、Let's Go!

本日の学習内容。

 1,Swiftの特徴
 2,重要単語
 3,個人的Topic

1,Swiftの特徴

 
・まずSwiftとはどんな言語なの? → Mac,Ios系のアプリケーションを開発できる...

もっと詳しく...
静的型付き言語で、コンパイル時の実行段階で変数や定数の型の値の情報を決定してくれる。(間違えた情報をコンパイルエラーとして表示してくれるため安全性が保証された言語である。大規模開発にも適しているそうです。)

2,重要単語

※ここからは学習を進めていく上で特に重要だなと感じた概念や言語を選定して行きます!

型推論...文字列には(String)、数値には(Int)など型を推測してくれること。

ジェネリクス...型を明確に宣言してから変数を定義してくこと。(型を具体的に宣言して引数として渡すからなんか記述量が多い。)←このこと。

ライブラリ
標準ライブラリ-Swiftに標準的備わっているライブラリのこと。

コアライブラリ-非同期や通信、ファイル操作の際に使用するライブラリ

 ・Foundation
 多くのアプリに必要となる機能を提供してくれるライブラリ

 ・libdispatch
 ハードウェアの並列処理を行ってくれるライブラリ

開発ツール...LLDB(バグを見つけやすくしてくれるツールのこと/binding.pry的な...)

命名規則
・変数、関数、定数-camelCaseを利用。

単語選びのコツ
・あいまい×
・略語×
・一般的○ ← そりゃそう。

3,個人Topic

ここまでで個人的に感じたことがあります。(正直...内容うっす。)

これから毎日、更新できるように気ままにやっていきます!
・・・ここに書き落としていく事ってかなり辛いですわ。

本当に初心者の初心者の方のお助けになればいいと思っております。

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

SwiftでコードだけでUICollectionViewを実装する方法

class SampleCollectionView: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    lazy var collectionView : UICollectionView = {
        let collectionView = UICollectionView(

            frame: CGRect(x: 0, y: statusBarHeight, width: self.view.frame.width, height: self.view.frame.size.height - statusBarHeight),
            collectionViewLayout: UICollectionViewFlowLayout())
            collectionView.delegate = self
            collectionView.dataSource = self
            collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "cellId")

        return collectionView
    }()

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        //セルのアイテムの数
    }
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        //アイテムの大きさ
    }
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        //アイテム表示領域全体の上下左右の余白
    }
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        //アイテムの上下の余白の最小値
    }
    // アイテムの表示内容(UICollectionViewDataSource が必要)
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! CollectionViewCell
        //セルの表示内容
        return cell
    }
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        //セルをクリックされた時のアクション
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ARKit : Reality Composerでオブジェクトに名前をつける、Swiftで名前を得る

Reality Composerで名前をつける

表示するオブジェクトをダブルクリックして設定画面を表示し名前をつける。下図の場合「hako」という名前をつけている。
スクリーンショット 2020-10-18 20.37.29.png

Swiftで名前を得る

オブジェクトをタップするとデバッグ画面に「hako」と表示される。

import UIKit
import RealityKit

class ViewController: UIViewController {

    @IBOutlet var arView: ARView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Load the "Box" scene from the "Experience" Reality File
        let boxAnchor = try! Experience.loadBox()
        boxAnchor.actions.tapped.onAction=clickEvent(_:)
        // Add the box anchor to the scene
        arView.scene.anchors.append(boxAnchor)
    }

    func clickEvent(_ entity: Entity?) {
        guard let entity = entity else { return }

        print(entity.name)
    }
}

「tapped」はReality Composerで付けたビヘイビア。

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

[Tips]初心者向け XCodeやSwiftで困ったときの解消法

環境

macOS:Catalina 10.15.6
Xcode:12.0
Swift:5.3

筆者について

趣味でswiftを勉強中の只のエンジニアです。仕事では全く使っていません。
swiftは2015年頃に一度勉強していて、最近再燃して勉強し直しています。

1.Stack Viewの制約がうまくつけられない

spacingの値に0以外の値が入っていると制約に影響が出てしまうようなので、
spacingには0を入力した方が良いです(制約に影響が出ない箇所なら可)。
スクリーンショット 2020-10-18 19.50.23.png

2.新しいクラスを作成しプロトコルを継承すると、警告が出る。

Cannot declare conformance to 'NSObjectProtocol' in Swift;
と言って怒られます。
この場合、import Foundationした上で、NSObjectを継承すれば解決です。
私の場合、import Foundationを削除した上で試行錯誤していたので、大変でした。

3.ビルドエラー

Multiple commands produce XXXが発生。
詳しく見るとTarget [アプリ名] has copy command from XXXTarget [アプリ名] has link command with output XXXが発生。
少し調べてみると何かが衝突しているようだった。.swiftファイルをXCode上で手動コピーしたのがまずかったのかと思う。
よく分からなかったので、プロジェクトを作り直すことで解決した。
XXX.swiftMain.storyboardFinder上で、旧プロジェクトフォルダから新プロジェクトフォルダへコピーすることで解決した。
Main.storyboardの場所が一瞬分からなかったが、Finder上では.../[アプリ名]/[アプリ名]/Base.Iproj/の中にある。

4.オブジェクトを比率で上手いこと調整したい(高さ40%のTableViewなど)

こちらのサイトが参考になります。

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

カスタムUIViewにて"loadNibNamed"のところで"EXC_BAD_ACCESS "

ちょっとだけ沼ったんで書いておきます。

起こった事件スクリーンショット 2020-10-18 17.34.29.png

カスタムUIViewを読み込もうとしたときに、loadNibNamedEXC_BAD_ACCESSのエラー...

解決した方法

簡単に解決できました。

xib上のViewのCustom Classにクラスを指定していたことが間違いでした。
これをFile'sOwnerのCustom Classに指定し直しましょう。

スクリーンショット 2020-10-18 17.37.57.png

Outlet接続等はViewのCustom Classにクラスを指定していてもうまくいくので気付きにくいなぁと思いました。

参考

xibファイルのカスタムViewクラスが File's Owners に設定すること

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

俺的RxSwiftまとめ①

RxSwiftとは

RxSwiftはReactive Extensions Swiftの略のこと。Reactive Extensionsは非同期処理をいい感じに時系列に沿って処理してくれるC#の最強ライブラリ。

つまり、RxSwiftはSwiftに従来備わっている非同期処理の書き方

の仲間で、非同期処理をいい感じに行うための方法の一種です。

RxSwiftの非同期処理方法

では、RxSwiftはどうやって非同期処理を行っているのか?
RxSwiftでは、様々なイベント/処理を、時間とともに流れてくる一連のデータ(データストリーミング)に対して実行します。(リアクティブプログラミングと呼ばれるもの)

上記の利点として、

  • プログラム内で発生する全てのイベントを時間軸で捉えることができる
  • コールバック地獄に陥らない
  • MVVMを導入することで、FatViewControllerからの脱却/テストしやすいコードを書ける(具体例1,具体例2)
  • 関数型プログラミングで書ける

というものがあります。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む