- 投稿日:2020-09-22T21:58:45+09:00
SwiftでRealmを使用する方法について
Realmをインストールする
Realmを使用するために、CocoaPodsを使用してインストールを行います。
PodFileにpod 'RealmSwift'
を追記し、pod install
でインストールします。pod 'RealmSwift'Swiftでは
import RealmSwift
でインポートします。import RealmSwiftモデル定義
class モデル名: Object { }
でモデル定義を行います。
@objc dynamic var
で、プロパティ定義を行います。import UIKit import RealmSwift class モデル名: Object { @objc dynamic var プロパティ名1 : 型名 = 初期値 @objc dynamic var プロパティ名2 : 型名 = 初期値 }データの追加
realm.add(モデル名(value: 値))
で、データを追加できます。
事前に、プロパティに対する値を入れている必要があります。let realm = try! Realm() let object = モデル名() object.プロパティ名1 = 値1 object.プロパティ名2 = 値2 try! realm.write { realm.add(モデル名(value: object)) }データの更新
.filter
等で対象のObjectを取得し、write()
でデータの更新を行います。let realm = try! Realm() let results = realm.objects(dayList.self).filter("プロパティ名1 == 値1 AND プロパティ名2 == 値2").first try! realm.write { results!.プロパティ名1 = 値3 }データの削除
.filter
等で対象のObjectを取得し、write()
でデータを削除を行います。
.delete()
で対象のみdeleteAll()
で全て削除します。let realm = try! Realm() let results = realm.objects(toDoList.self).filter("name == 'Michael Jeffrey Jordan'") // try! realm.write { realm.delete(results) }データが全くない場合初期値を追加する
データがない場合、データを読み込みに失敗します。
そのため、以下のようにして、データがない場合のみ初期値の追加をする必要があります。let realm = try! Realm() // データが1つもない場合 if realm.objects(モデル名().self).first == nil { let object = モデル名() object.プロパティ名1 = 値1 object.プロパティ名2 = 値2 try! realm.write { realm.add(モデル名(value: object)) } }モデルの変更をした場合
プロパティ名の追加等を行った場合、Realmの呼び出しでエラーになります。
そのため、変更があった場合スキーマの情報を更新させエラーが発生しないようにする必要があります。// プロパティ名の変更があった場合、schemaVersionの値を変更する let config = Realm.Configuration( schemaVersion: 1, migrationBlock: { migration, oldSchemaVersion in}) Realm.Configuration.defaultConfiguration = config config = Realm.Configuration() config.deleteRealmIfMigrationNeeded = true参考
- 投稿日:2020-09-22T21:21:10+09:00
iOSDC「あなたの知らない連絡先の世界」「詳解Storyboard」補足&参加した感想
iOSDC Japan 2020で発表してきた内容でいくつか受けた質問の答えや補足&参加した感想になります
あなたの知らない連絡先の世界
はてなブックマークで紹介されたらしく、えらい数見ていただいてます。
https://speakerdeck.com/coe/anatafalsezhi-ranailian-luo-xian-falseshi-jie
vCard 3.0(iOS)と vCard 2.1(Android)
Androidで取得したvCardは、Quoted-printableがかかっています。
iOS
BEGIN:VCARD VERSION:3.0 PRODID:-//Apple Inc.//iPhone OS 13.6//EN N:日向;強;搩;ドクター;三世 FN:ドクター 強 搩 日向 三世 END:VCARDAndroid
BEGIN:VCARD VERSION:2.1 N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E6=97=A5=E5=90=91=E7=81=98;=E5=BC=B7;=46;=44=72;=4A=72 FN;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=44=72=20=E6=97=A5=E5=90=91=E7=81=98=20=46=20=E5=BC=B7=20=4A=72 END:VCARDPersonNameComponentsFormatterはサーバーサイドSwiftで使える?
使えませんでした。NSUnsupportedでした。
https://github.com/apple/swift-corelibs-foundation/blob/2a5bc4d8a0b073532e60410682f5eb8f00144870/Sources/Foundation/PersonNameComponentsFormatter.swiftObjective-Cでしか使えないメソッド
本編で紹介した
enumeratorForChangeHistoryFetchRequest:error:
の他にも、
連絡先を取得するenumeratorForContactFetchRequest:error:
もObjective-Cでしか使えません。
SwiftUI? Combine? 甘
https://developer.apple.com/documentation/contacts/cncontactstore/3294191-enumeratorforcontactfetchrequest?changes=_3&language=objc詳解storyboard
https://speakerdeck.com/coe/xiang-jie-storyboard
SceneDelegateのリストア
今回発表されていた
iPadOSDC: Multiple Windows
で解説されています。
https://speakerdeck.com/hironytic/ipadosdc-multiple-windowsAppleのサンプルドキュメントもあります。
https://developer.apple.com/documentation/uikit/uiscenedelegate/restoring_your_app_s_stateStoryboardの分割
WWDC 19の
Great Developer Habits
にてStoryboardの分割に触れています。
その他にもエンジニアにとって重要な話をしているので、このセッションは非常におすすめです。
https://developer.apple.com/videos/play/wwdc2019/239/まばたきの検出
本編では省略したまばたきの検出コードですが、例えば
AVCaptureVideoDataOutputSampleBufferDelegate
でCMSampleBufferから顔を検出し、目を閉じている、開いているを検出して判定するとまばたきの検出が行えます。
以下のコードは、両目を閉じている人を検出してアクションを起こす例です。まばたき検出はもうちょっとコードが必要になります。// MARK: - AVCaptureVideoDataOutputSampleBufferDelegate func captureOutput(_ output: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) { let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)! let ciImage: CIImage = CIImage(cvPixelBuffer: pixelBuffer) guard let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: [CIDetectorAccuracy:CIDetectorAccuracyHigh]) else { return } let faceFeatures = faceDetector.features(in: ciImage, options: [ CIDetectorSmile:true, CIDetectorEyeBlink:true ]) as! [CIFaceFeature] let closedEyes = faceFeatures.filter { (faceFeature) -> Bool in return faceFeature.leftEyeClosed && faceFeature.rightEyeClosed } if !closedEyes.isEmpty { self.sendActions(for: .primaryActionTriggered) } }個人的な感想
よかったら二つやりませんか?との打診を受けて、Storyboardだけやろうかなと思ったんですが、せっかくだから俺はどちらとも選ぶぜという勢いのみでやってみました。
結果的には本命のStoryboardではなく連絡先が好評?だったので、フェイルセーフが働いて良かったです。
セッションは20分コースと40分コースがあって、40分喋り続けるのきついなと思ってチキって20分にしたんですが、「持ち時間が40分もらえる」という考えであれば、次出られるのであれば長い時間はありかな、と思いました。Storyboardは説明がいっぱいいっぱいだったので小粋なトークが挟めませんでした。
なんにせよ、見ていただいた方、ご清聴ありがとうございました!ご清聴?
- 投稿日:2020-09-22T19:54:42+09:00
Cordova開発環境をWindows10 + AndroidとMac + iOSでそれぞれ構築
Cordovaのインストール(共通)
Windows、Macともに共通で実施します。
npmが必要なので事前に入れておきます。
私はWindowsならnodist、MacならnodebrewでNode.js環境を作りました。Cordova公式サイトの「Get Started」に沿って進めます。
> npm install -g cordova > cordova --version 10.0.0 > cordova create MyApp Creating a new cordova project. > cd MyAppCordovaアプリをブラウザで動かす
プラットフォーム(Cordovaの実行環境)を追加して動かしてみます。
まずはブラウザで動かします。> cordova platform add browser Using cordova-fetch for cordova-browser@^6.0.0 Adding browser project... Creating Cordova project for cordova-browser: Path: C:\Users\hoge\Desktop\MyApp\platforms\browser Name: HelloCordova Installing "cordova-plugin-whitelist" for browser 6.0.0 > cordova run browserブラウザが開いて「Hello World」アプリが実行されれば成功です。
Androidアプリの実行環境構築
こちらはWindowsで実施します。
事前準備
JDKのインストール
JDK(Java Development Kit)8をインストールします。
https://www.oracle.com/jp/java/technologies/javase/javase-jdk8-downloads.html> javac -version javac 1.8.0_261環境変数「JAVA_HOME」の設定
JDK8をインストールしたファルダ「
C:\Program Files\Java\jdk1.8.0_261
」を環境変数「JAVA_HOME」としてOSに設定します。Gradleをインストール
ビルドツールのGradleをインストールします。
https://gradle.org/install/
ダウンロードしたら解凍し任意のフォルダに置いてパスを通します。> gradle -v Welcome to Gradle 6.6.1! Here are the highlights of this release: - Experimental build configuration caching - Built-in conventions for handling credentials - Java compilation supports --release flag For more details see https://docs.gradle.org/6.6.1/release-notes.html ------------------------------------------------------------ Gradle 6.6.1 ------------------------------------------------------------ Build time: 2020-08-25 16:29:12 UTC Revision: f2d1fb54a951d8b11d25748e4711bec8d128d7e3 Kotlin: 1.3.72 Groovy: 2.5.12 Ant: Apache Ant(TM) version 1.10.8 compiled on May 10 2020 JVM: 1.8.0_261 (Oracle Corporation 25.261-b12) OS: Windows 10 10.0 amd64Android Studioをインストール
Android SDKとエミュレーターを使うためにAndroid Studioをインストールします。
https://developer.android.com/studio環境変数「ANDROID_SDK_ROOT」の設定
Android Studioの「Tools > SDK Manager」から↓のような画面を開きSDKのパスを取得します。
環境変数「ANDROID_SDK_ROOT」に設定します。
ライセンスの同意
> cd C:\Users\hoge\AppData\Local\Android\Sdk\tools\bin > sdkmanager --licenses上記コマンドを実行し、全て「y」を入力します。
プラットフォーム追加
> cordova platform add android Using cordova-fetch for cordova-android@^9.0.0 Adding android project... Creating Cordova project for the Android platform: Path: platforms\android Package: io.cordova.hellocordova Name: HelloCordova Activity: MainActivity Android target: android-29 Subproject Path: CordovaLib Subproject Path: app Android project created with cordova-android@9.0.0 Discovered plugin "cordova-plugin-whitelist". Adding it to the project Installing "cordova-plugin-whitelist" for android Adding cordova-plugin-whitelist to package.jsonエミュレーターで実行
Android Studioの「Tools > AVD Manager」からAVDを構成します。
エミュレーターを実行し、エミュレーターが立ちあがったら下記コマンドを実行。> cordova emulate androidビルドが成功すれば、↓のようにエミュレーターでアプリの実行が確認できます。
ビルドログを見るとAPKファイルもできているのが確認できます。
実機で実行
Androidの実機端末をUSBで接続します。
端末側で開発者向けオプションを有効化し、USBデバッグをオンにしておきます。> cordova run androidビルドが成功すれば、実機にアプリがインストールされます。
iOSアプリの実行環境構築
こちらはMacで実施します。
事前準備
Xcode
入ってなければインストールしておきましょう。
プラットフォーム追加
$ cordova platform add ios Using cordova-fetch for cordova-ios@^6.1.0 Adding ios project... Creating Cordova project for the iOS platform: Path: platforms/ios Package: io.cordova.hellocordova Name: HelloCordova iOS project created with cordova-ios@6.1.1 Installing "cordova-plugin-whitelist" for iosエミュレーターで実行
$ cordova emulate iosビルドが成功すれば、↓のようにエミュレーターでアプリの実行が確認できます。
実機で実行
Xcodeにアカウントを登録しておきます。
実機にデプロイするために
ios-deploy
が必要なのでこれもインストールしておきます。$ npm install ios-deploy-gUSBでiPhoneを接続してコンピューターを信頼します。
MyApp/platforms/ios/HelloCordova.xcodeproj
をXcodeで開きます。
アプリに署名し、左上のデバイス選択から接続したiPhoneを選択します。$ cordova run iosビルドが成功すれば、実機にアプリがインストールされます。
IPAファイルも作成されます。
- 投稿日:2020-09-22T14:24:12+09:00
部品を画面の中央に配置する方法
(1)AutoLayoutの部分の左から2番目のアイコンをクリックします。
(2)以下2つにチェックをいれる
Horizontally in Container (cf. Horizontal 水平な)
Vertically in Container (cf. Vertical 垂直な)
(3) 「Add 2 Constraints」をクリック
→これで自動的に部品が画面の中央に配置されます
- 投稿日:2020-09-22T13:55:20+09:00
[WIP][iOS] [Xcode] IntirinsicContentSizeがない部品とScrollViewの付き合い方
はじめに
WIPです。
この記事ではUIScrollView
をStoryboardで使用する際に、スクロールのコンテンツにUIStackView
が含まれる場合の実装例を紹介します。抽象的には
UIScrollView
のコンテンツにIntrinsicContentSize
が無効なビューを含んでいる場合の解決策を紹介します。環境
Xcode: 12
Swift: 5.3
MacOS: Catalina結論
UIStackView
は固有サイズ(intirinsicContentSize
)が設定されていないためスクロールビューのコンテンツの大きさが自動で決定されません。
なので、ScollViewがスクロールできる高さの制約を設定する必要があります。この記事で分かること
UIScrollView
って何?UIStackView
って何?- 固有サイズって何?
UIScrollView
って何?コンテンツをスクロールやズームができるビューです。
スクロールビュー自体の大きさの他に、コンテントサイズ(contentSize
)という表示するコンテンツの大きさが存在します。なお、コンテンツサイズがスクロールビュー自体のサイズよりも小さい場合はスクロールできません。
余談ですが、
UITableView
やUICollectionView
はUIScrollView
を継承しているため、スクロール可能なビューとなっています。
UIStackView
って何?1列または1行に部品を並べることに特化した部品です。StackViewの中には子ビューを入れることができます。
子ビューの大きさによってサイズを変えたり、子ビューの並び方や揃え方を統一することができます。詳細なStackViewについては当記事では説明しないのでこちらの記事などを別の記事を参考にしてください。
固有サイズって何?
固有サイズとはビュー自体が持つコンテンツの大きさのことです。
例えば、UILabelを例に出します。
下の画像のように画面の中央に配置されるように設定したUILabelがあります。
このUILabelには
こんにちは
という文字列が表示されています。
ここで、こんにちは
をこんにちはでしょうか、こんばんはでしょうか?
に変えてみます。コンテンツの横幅が大きくなったため、UILabelの横幅もコンテンツに合わせて自動で大きくなりました。
これが固有サイズ(intrinsicContentSize
)です。また、固有サイズを有効にするにはAutoLayoutを縦・横それぞれ1つずつ設定しておく必要があります。
一方で、UITableViewCell
UICollectionViewCell
のコンテンツを可変サイズにしたい場合は縦・横それぞれ2つの制約が固有サイズの他に必要です。固有サイズを持つ/持たないについて
intirinsicContentSize
はUIView
のプロパティですが、固有サイズを持つものと持たないものが存在します。
例えば、先ほどの例であげたUILabel
は固有サイズを持ちますが、UIScrollView
は固有サイズを持ちません。下に固有サイズを持つものと持たないものを比較したテーブルを用意しました。
固有サイズを持つ 固有サイズを持たない UILabel( numberOfLines = 1
)UILabel( numberOfLines ≠ 1
)UIButton UIView UIPickerView, UIDatePicker UIStackView UIImageView(image ≠ nil) UIImageView(image = nil) UITextField UIScrollView(UITableView, UICollectionViewも同様) UITextView(Scroll不可) UITextView(Scroll可能)
UILabel
やUITextView
など、プロパティの状態によってIntirinsicContentSizeを持つか持たないかが変わることがあります。標準実装だと、上のようにクラスごとに決まった固有サイズの種類を持っていますが、クラスを継承することによって、固有サイズを応用することができます。
固有サイズをカスタマイズする方法
固有サイズはそのビュー自身の
intirinsicContentSize
をオーバーライドすることでカスタマイズすることができます。例えば、固有サイズを持たせないビューに対しては、
UIView. noIntrinsicMetric
をwidth/heightに指定します。class NoIntrinsicLabel: UILabel { override var intrinsicContentSize: CGSize { CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric) } }一方で、あまり有効な場面が考えられないですが、固有サイズをハードコーディングすることもできます。ただ、この場合は固有サイズを使用せずにHeight/Widthの制約として
NSLayoutConstraint
を使用したほうが良いと考えます。class StaticIntrinsicLabel: UILabel { override var intrinsicContentSize: CGSize { CGSize(width: 120, height:120) } }最後に
- 投稿日:2020-09-22T12:28:05+09:00
iOSDC2020に参加しました
はじめに
iOSDC2020が昨日無事終了しました。
今回の開催はニコニコ生放送での初のオンラインということで、どういう感じになるのだろうと思っていたのですが、グリーンバックを使っていたり、ニコ生のコメント機能でリアルとはまた違った感じで盛り上がる、家でリラックスしながら見れるなど、オンラインならではのいいところがたくさんあり、とても良かったです。以下自分の気になったセッションを簡単にまとめていきます
気になったセッション
サムネイルをクリックすることでスピーカーの皆様が上げてくださったSpeakerDeck/SlideShareへ移動できます。
今日から分かるAVAudioEngineの全て
Day0/TrackA
スピーカー:meteorさんmeteorさんのトークでは
・iOSのAudio周りをあまりしらない方のためのAudioUnit/AVFoundationの解説
・実際のコードを例に挙げながらAVAudioEngineなどの基本的な使い方
・RMSの取得
・WORLDを使ってボイスチェンジャーを実装して(ラッパーであるWorldInAppleのポイントを解説)みるという高度な話をされていました。最後の方は完全にはついていけていなかったのですが、自分も現在音声処理のアプリの案件に携わっているので、とても復習になり、またmeteorさんのトークは録画感を感じさせず実際に生放送で話されているみたいで、いいセッションでした。
iOSリジェクト戦記 ~リジェクトされないための課金ページ~
Day1/TrackA
スピーカー:HiromuTsurutaさんHiromuTsurutaさんのトークでは
・課金ページで守らなければいけないこと
・課金ページにはどういうタイプがあるのか
・注意文言/金額表記/無料表記で実際にリジェクトされたときの問題点/対策など
・いろいろなアプリの過去と今の課金ページの比較
・リジェクトを切り抜けるための回避策(グレーゾーン)
をわかりやすく説明されていました。個人的にはAppleの課金ページでは無料を大きくして、値段を小さくしているのでガイドラインを守っていないというオチが面白かったです。
課金(サブスクリプション)ページのリジェクト理由など、深く観察されている+スライドの作り込みすごい+トークの上手さもあり、とても完成度が高いセッションだと思いました。スピーカーのHiromuTsurutaさんが以前書かれたアプリアイコンについての記事もとても完成度が高くおすすめなのでぜひ興味のある方は読んでみてください
エラーアーキテクチャ設計について考えてみる
Day1/TrackD
スピーカー:きちえもんさんきちえもんさんのトークでは
・iOSアプリでエラーハンドリングの改善
・Androidアプリでエラーハンドリングの改善
・エラーアーキテクチャ設計について
を話されていました。iOSでのエラー処理では共感できるところが多く、またその解決法もenumを使うなどなるほどな〜と勉強になるところが多かったです。エラーアーキテクチャについてどのような姿を目指すかの中でユースケースについての説明の時に、ついてユーザーが異なる操作を行った/何か悪いことが起こったに焦点を当てた事を雨の日のシナリオという表現/喩えがとてもわかりやすく、スライドも綺麗に纏まっていて、とても見易かったです。
ユースケース駆動については初めて知ったので、自分でも調べていこうと思いました。Github ActionsでiOSアプリをCIする個人的ベストプラクティス
Day1/TrackB
スピーカー:uhooiさんuhooiさんのトークでは
前半に
・CI
・Make
・GitHub Actions
の説明を行い、後半に実際にHANDSON形式で行ってみるという形でした。CI,Make,GitHub Actionsなどをuhooiさんの言葉/イラストでわかりやすく説明してくれており、HANDSONではGitHubActionsの導入を実際にやってみるという形で見せながらわかりやすく解説してくれていました。実際にトークをみながら導入を試していた方もいらっしゃったようです。
個人的にはスライドの途中で出てきた手書きイラストが味があって好きでした。Swiftで始める静的解析
Day2/TrackD
スピーカー:まつじさんまつじさんのセッションでは
・そもそも静的解析とは
・静的解析はどのように行われているのか
・実際にSwiftSyntaxを用いて、構文木を解析してみる。
についてお話しされていました。自分はこのセッションを聞くまではソフトウェア工学をあまり知らず、静的解析についてもなんとなくSwiftLintをがあるということしかわかっていなかったのですが、このセッションで静的解析がどのような仕組みで行われているのかというトークを聞いていて、これはどうなんだ?と思った所を次の話でまつじさんが解説してくれて、静的解析への理解が深まるセッションでした。
テストコードが増えるとバグは減るのだろうか? - 「0% → 60.3%」で見えた世界の話
Day2/TrackD
スピーカー:ahirustarrrさんahirustarrrさんのセッションでは
・テスト実装に至る背景
・テストコードがなかったプロダクトにテストが実装されて、感じたメリット/デメリット
・組織開発体制に適したテスト手法
・テストが増えるとバグは減るのか
についてお話しされていました。トークの背景、内容などがとても共感できる内容であるあると思いながらトークを聞いていました。
テスト導入に近い話は普段、他の会社の話はあまり聞くことがなかったので、とてもいい機会でした
テストを導入するメリット/デメリットを詳しく解説してくれていて、これからテスト導入しようかなという人にもみて欲しいなというセッションです。
Twitterでテストコードを書いた経験がないとどうテストを書けばいいのかわからない的な意見もみられ、自分もまだ浅い方なのでなるほどなと思いました。イベントを通しての感想
他にも良かったセッションやまだ見れてないセッションはいくつもありますのでこの後視聴しようと思います。
気になったかたは後日YouTubeに動画が上がるそうなので視聴して、来年参加してみてはどうでしょうか?運営スタッフの皆様、スピーカーの皆様ありがとうございました。
参考リンク
- 投稿日:2020-09-22T02:18:22+09:00
enumを使ってリテラルをまとめる
こんにちは。
最近自分が書いているCodeの品質を上げるために日々模索中です。
最近よく思うのは stringの文言とか、identifierを直書きしたりとか、そういうのしたくないなと思うようになり、
良い書き方ないかなと考えてCode読みあさって考えてみたら良さげな実装を思いついたので、記事にしようと思いました。結論
enumで定義してしまえばまとまっててわかりやすいんじゃね?
よくある実装1
- UITableViewのidentifierとか
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell /// ... return cellよくある実装2
private let cellId = "hogeCell" /// ... func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! TableViewCell /// ... return cell上記のよな感じで実装するとなんかいけてないし、個人的に直書きしたくないので(よきせぬ不具合を出したくない)ので以下の実装にしてみました。
実装
enum IdentifierType { static let segueId = "hogeSegue" static let cellId = "fugaCell" static let nibId = "HogeFugaTableViewCell" }こんな感じで定義して、
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: IdentifierType.fugaCell, for: indexPath) as! TableViewCell /// ... return cellとか
let nib = UINib(nibName: IdentifierType.nibId, bundle: nil) hogeTableView.register(nib, forCellReuseIdentifier: IdentifierType.cellId)みたいにして定義してあげれば、どこの何が定義してあるのかとてもわかりやすいし、直書きしなくてより安全なのではと思いこのような実装にしてみました。
まとめ
今回は、さくっとできてそれなりにまとめられて、すっきりできて、より安全に、より可読性も上がるなと個人的に思い上記の実装をしてみました。
しかし、より大規模アプリとかになると上記のような実装だけだといろいろ足りないし、より広範囲でまるっとサポートしてくれたらと思うので、より安全性や品質を高めて行ったり、楽したかったりしたらR.swiftがとても優秀なのでそれ使った方が良いと思います。
あくまで上記は小規模アプリとかで、ライブラリ入れなくても良いくらいの規模感のアプリなら、上記で対応しても良さそうに思いました。読んでいただきありがとうございました!
もっと良い実装とか他何かありましたらコメントいただけたら涙が出るくらい喜びます!!