20220110のiOSに関する記事は8件です。

【SwiftUI】TabViewを使用した簡単なタブの実装方法

コード struct ContentView: View { var body: some View { TabView { FirstView() .tabItem { Image(systemName: "1.square.fill") Text("First") } SecondView() .tabItem { Image(systemName: "2.square.fill") Text("Second") } ThirdView() .badge(5) .tabItem { Image(systemName: "3.square.fill") Text("Third") } } } } TabViewで囲い各タブごとに表示したいViewを追加していきます。.tabItemにImageとTextを追加することでアイコンとテキストを表示できます。 ThirdView() .badge(5) .badgeで簡単にバッジを表示できます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

M1 MacにVSCodeをインストールする方法

はじめに Apple silicon(以下、M1)のMacにエディターのVisual Studio Code(以下、VSCode)をインストールする手順をまとめた 今までのIntel版ではスムーズにインストールできましたが M1版では同じようにいかないため、その追加点を記載した ※ターミナルからコマンドでインストールできるみたいです 手順 1.下記を開く ここで注意して欲しいのが 一番右側のMacにある下記の写真の箇所 「↓ Mac macOS 10.11+」ではなく その下の ".zip"から続く青い3つのオブジェクトの 「Apple Silicon」を選択すること 2.インストールしたzipを解凍 3.解凍して出てきたappをアプリケーションへドラッグ&ドロップ 開くと 英語表示...(>_<) 次回、VSCode日本語化!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Realm accessed from incorrect thread.

はじめに 去年からずっと悩んでいたRealmSwiftのエラーが最近、解決しました。 原因 タイトルにもあるように不正なスレッドからレルムにアクセスしたことが原因です。 Realmのデータベース(以下、DB)では、異なるスレッド間でのアクセスは保証されていますが、スレッドを跨いでアクセスするのは禁止されています。 let realm = Realm() // メインスレッドで生成 DispatchQueue.global().async { // メインスレッドとは異なるスレッド realm.write {} // Realm accessed from incorrect thread. } 沼った部分 自分はずっとRealmインスタンスだけが、スレッド間を跨いでアクセスしなければOKだと思っていて、その思い込みのせいで沼にハマってしまいました。 解決の糸口は、Realm入門という書籍にありました。 Realmの制約 Realmインスタンスは初期化した際にスレッドIDを内部で保持し、DBのアクセス時に初期化の時のスレッドIDと現在のスレッドIDを比較して異なるスレッドIDならエラーを吐く仕様になっています。 でこれはなにもRealmインスタンスに限った話ではありませんでした。 Realmではマネージドオブジェクト・アンマネージドオブジェクトがあって、Realmインスタンスやモデル定義(テーブル定義)する際に継承するObject、クエリが実行された時に返ってくるResults等がマネージドオブジェクトに該当します。 このマネージドオブジェクトでは必ず、内部でRealmインスタンスを持っており、値にアクセスする時はスレッドチェックが行われます。 そして、注意点としてObjectを継承したモデルオブジェクトは元々、アンマネージドオブジェクトでRealm.add()する事によってマネージドオブジェクトになります。 // モデル定義 class HogeObject: Object { // 何らかの値 } let realm = try! Realm() // メインスレッドで生成 let hogeObj = HogeObject() // この時点ではアンマネージドオブジェクト try! realm.write { // メインスレッドのRealmに保存している realm.add(hogeObj) // add()することによってマネージドオブジェクトになる } let results = realm.objects(HogeObject.self) // メインスレッドのrealmから生成 DispatchQueue.global().async { // メインスレッドとは異なるスレッド // 各インスタンスはメインスレッドで生成しているので // ここでアクセスするとエラーが発生する print(hogeObj) // Realm accessed from incorrect thread. print(results) // Realm accessed from incorrect thread. realm.write { } // Realm accessed from incorrect thread. } 以上を踏まえて自分のコードを読んでみるとマネージドオブジェクトになったモデルオブジェクトを別スレッドで使っていたことが判明しました。 だからずっとエラーが消えなかったんですね。 因みにアンマネージドオブジェクトはスレッドを跨いでもOKです。 解決方法① Realmでは、異なるスレッド間でオブジェクトを渡せるようにThreadSafeReferenceが用意されていて本来であれば、これで解決します。 let realm = try! Realm() // メインスレッドで生成 let hogeObj = HogeObject() try! realm.write { realm.add(hogeObj) } // マネージドオブジェクトのhogeObjを渡し、 // ThreadSafeReferenceインスタンスを生成する let ref = ThreadSafeReference(to: hogeObj) // hogeObjへのスレッドセーフな参照を持つ DispatchQueue.global().async { // メインスレッドとは異なるスレッド let realm = try! Realm() // サブスレッドで生成 // 定数refはhogeObjへのスレッドセーフな参照を持つ // resolve()によって、サブスレッドのrealmインスタンスからhogeObjへの参照を解決(取り出す)ことが出来る guard let resolved = realm.resolve(ref) else { return } // マネージドオブジェクトにアクセスできるようになる print(resolved) // HogeObject } ただ今回は特殊ケースで、保存後・削除後のモデルオブジェクトを一時的に保持してそれを別スレッドで使います。 保存後のモデルオブジェクトであれば、ThreadSafeReferenceでどうにかなりましたが、削除後のモデルオブジェクトはDBから削除されて無効なものになり、モデル定義したプロパティにアクセスするとエラーになります。 なのでThreadSafeReferenceは参照できません。 let realm = try! Realm() let hogeObj = HogeObject() try! realm.write { realm.delete(hogeObj) } // 無効なオブジェクトで参照できないのでエラーになる let ref = ThreadSafeReference(to: hogeObj) // Cannot construct reference to invalidated object しかも、無効になった後もマネージドオブジェクトのままです。 解決方法② ではどうしたのかというと、会社の先輩を頼りました。 どうやって解決されたかというと、元々あるモデルオブジェクトとは別にObjectを継承していないモデルオブジェクトを定義してこの問題を回避していました。 // Objectを継承していないモデルオブジェクト struct HogeObject { var hoge: String init() { let obj = HogeRealmObject() self.hoge = obj.hoge } // Realmオブジェクトに変換する func toRealmObject() -> HogeRealmObject { let obj = HogeRealmObject() obj.hoge = self.hoge return obj } // Objectを継承しているモデルオブジェクト class HogeRealmObject: Object { @objc dynamic var hoge: String = "" } } let realm = try! Realm() var hogeObj = HogeObject() hogeObj.hoge = "hogehoge" // Realmのオブジェクトに変換してDBに追加する let realmObj = hogeObj.toRealmObject() try! realm.write { realm.add(realmObj) } DispatchQueue.global().async { // マネージドオブジェクトではないので別スレッドでもアクセス可能 print(hogeObj.hoge) // "hogehoge" } 以上のやり方だと、保存後・削除後とか関係なく値にアクセスできるし、かつスレッド間を跨ぐことも出来ます。 終わりに とりあえず、年明け前の悩みが消えて良かったです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUI の NavigationLink(destination:,isActive:) を活用する

はじめに SwiftUI において画面遷移を行うための簡単な方法は NavigationView と NavigationLink(destination:) を使うものです。 なんらかの条件を満たしたときに強制的に画面遷移を行うなど、より細かい制御を行いたい場合は、NavigationLink(destination:, isActive:) を使います。 オリジナルアプリを個人開発している中で、この NavigationLink(destination:, isActive:) の使い方に関してかなりハマった経験があったので、備忘録がわりに記事としました。 開発環境等 MacBook Air (M1 2020) macOS Monterey Version 12.1 Xcode Version 13.2.1 (13C100) iOS 15.0 基本的な使い方 NavigationLink(destination: ,isActive:) の典型的な使い方は次のとおりです。 ContentView.swift struct ContentView: View { @State private var toFirstView = false // このフラグで画面遷移を制御 var body: some View { NavigationView { VStack { NavigationLink(destination: FirstView(), isActive: $toFirstView) { EmptyView() } Button(action: { // このボタンをタップすると FirstView に遷移する。 toFirstView = true }) { Text("Go To the FirstView") } }.navigationTitle("ROOT") } } } NavigationLink の label: には EmptyView() を入れて隠しておきます。 Button をタップして toFirstView を true にすると、NavigationLink に設定された画面遷移が行われます。 ここで1点補足しておきます。 上記では、toFirstView を NavigationLink による画面遷移を引き起こすスイッチのように使っていますが、逆に画面遷移が生じた場合、ToFirstView は trueになります。 この部分は Toggle(isOn:) の挙動と似ています。 SwiftUI らしい機構だと思いますが、「isActive: フラグを false にしているのに画面遷移し、フラグが true になってしまう。なぜ?」という質問を見かけたりもしたので、念の為書き記しておきます。 画面遷移先から戻る 1階層戻る場合 NavigationView では、前の画面に戻るための "<Back" ボタンが自動的に表示されますが、これに依らずに前の画面に戻ることもできます。 遷移先の画面で isActive: でセットしたフラグを false にすると画面遷移が強制的にキャンセルされて、前の画面に戻ります。 ContentView.swift struct ContentView: View { @State private var toFirstView = false var body: some View { NavigationView { VStack { NavigationLink(destination: FirstView(isActive: $toFirstView), isActive: $toFirstView) { } Button(action: { toFirstView = true }) { Text("Go To the FirstView") } }.navigationTitle("ROOT") } } } struct FirstView: View { @Binding var isActive: Bool var body: some View { VStack { Button(action: { // このボタンをタップすると前画面に戻る。 isActive = false }) { Text("Back To the ROOT") } }.navigationTitle("First View") } } 2階層以上戻る場合 上記方法は、多段階の画面遷移を行った後に2階層以上前の画面にジャンプする場合も有効です。 なんらかの方法で isActive: でセットしたフラグを持ち回して、false にすればよいのです。 上記のように引数で持ち回してもよいですが、コードが分かりにくくなるので、下記のように EnvironmentObject を使う方がスマートかと思います。 まずはこんな感じで EnvironmentObject を定義しておきます。 NavigationFlags.swift class NavigationFlags: ObservableObject { @Published public var toFirstView = false } ROOTに近いViewでそのインスタンスを生成します。App生成時に生成するならこんな感じです。 HogeApp.swift var body: some Scene { WindowGroup { ContentView() .environmentObject(AppSettings()) } } その上で必要な View で呼び出します(下記サンプルコードでは、SecondView で呼び出してます)。 ContentView.swift struct ContentView: View { @EnvironmentObject var navigationFlags: NavigationFlags var body: some View { NavigationView { VStack { NavigationLink(destination: FirstView(), isActive: $navigationFlags.toFirstView) { } Button(action: { navigationFlags.toFirstView = true }) { Text("Go To the FirstView") } }.navigationTitle("ROOT") } } } struct FirstView: View { var body: some View { VStack { NavigationLink(destination: SecondView()) { Text("Go To the SecondView") } }.navigationTitle("First View") } } struct SecondView: View { @EnvironmentObject var navigationFlags: NavigationFlags var body: some View { VStack { Button(action: { navigationFlags.toFirstView = false // 一気に ROOT に戻る。 }) { Text("Back To the ROOT") } } .navigationTitle("Second View") } } 戻りたい画面が複数ある場合 戻りたい画面が複数ある場合は、戻りたい画面遷移ごとに異なるフラグをセットすれば大丈夫です。 ここで重要なのが、NavigationView に、.navigationViewStyle(.stack) というモディファイアをセットすることです。 .stackというのは、「順番に画面遷移を積み重ねる」みたいな意味です。 これを設定していないとまったく想定外の画面遷移をします。 私がハマって2週間ぐらい悩みに悩んでしまったところですので、本稿で最も重要なポイントです! NavigationFlags.swift class NavigationFlags: ObservableObject { @Published public var toFirstView = false @Published public var toSecondView = false } インスタンス作成は上記と同じなので割愛。 必要な View ごとに下記のようにBool値をセットします。 ContentView.swift struct ContentView: View { @EnvironmentObject var navigationFlags: NavigationFlags var body: some View { NavigationView { VStack { NavigationLink(destination: FirstView(), isActive: $navigationFlags.toFirstView) { } Button(action: { navigationFlags.toFirstView = true }) { Text("Go To the FirstView") } }.navigationTitle("ROOT") } .navigationViewStyle(.stack) // 重要!! } } struct FirstView: View { @EnvironmentObject var navigationFlags: NavigationFlags var body: some View { VStack { NavigationLink(destination: SecondView(), isActive: $navigationFlags.toSecondView) { Text("Go To the SecondView") } }.navigationTitle("First View") } } struct SecondView: View { @EnvironmentObject var navigationFlags: NavigationFlags var body: some View { VStack { Button(action: { navigationFlags.toFirstView = false // ROOT に戻る。 }) { Text("Back To the ROOT") } Button(action: { navigationFlags.toSecondView = false // FirstView に戻る。 }) { Text("Back To the First View") } } .navigationTitle("Second View") } } List と組み合わせる List からアイテムを選択し、画面遷移したい、さらに遷移先画面から戻れるようにしたいという場合、上記の組み合わせでついつい下記のようのようなコードを書いてしまうと思います。 ContentView.swift // NG な例です! struct ContentView: View { @State private var toFirstView = false private var items = ["saru", "kiji", "inu"] var body: some View { NavigationView { List { ForEach(items, id: \.self) { item in VStack { NavigationLink(destination: FirstView(item: item), isActive: $toFirstView) { EmptyView() } Button(action: { toFirstView = true }) { Text(item) } } } } .navigationTitle("ROOT") } .navigationViewStyle(.stack) } } しかし、これはいけません。想定外のデタラメな挙動をします。 というのも、ForEachは同じViewを繰り返し生成するので、同一の isActive フラグが複数の NavigationLink に設定されていることになり、どれかひとつのセルを選択するだけで、全セルの画面遷移が発火することになるからです。 「NavigationLink は List の外に追い出せ」が原則です。 選択されたセルを格納する変数を別に設けて(下記ではselectionとしています)、こう書き直しましょう。 ContentView.swift struct ContentView: View { @State private var toFirstView = false private var items = ["saru", "kiji", "inu"] @State private var selection: String? var body: some View { NavigationView { VStack { if let selection = selection { // アイテムが選択されている時だけ有効 NavigationLink(destination: FirstView(item: selection), isActive: $toFirstView) { EmptyView() } } List { ForEach(items, id: \.self) { item in VStack { Button(action: { selection = item toFirstView = true }) { Text(item) } } } } } .navigationTitle("ROOT") } .navigationViewStyle(.stack) // 念のため } } 最後に 約2年前に、iOS(iPadOS)アプリ簡単便利な階層型情報メモアプリ HiMemoを個人開発でリリースしました。 最近、このアプリを SwiftUI で書き直しています。その中ではまさに本稿で述べた、リストからの画面遷移、多階層の画面遷移、階層を一気に戻るUIが必要となるのですが、いちいちハマってしまいました。 せっかく苦労して解決したのでまとめておこうと思いました。 この記事が少しでもお役に立てれば幸いです。 参考 ListでのNavigationLinkとButtonを使った画面遷移 SwiftUIの隠しNavigationLinkを使って画面遷移をプログラムで制御する SwiftUI で複数の画面を一度に戻る方法
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スマホでリアルタイムで物体を認識したいなら、CoreMLモデルにカメラフィードを食わせるだけでOK

iPhoneのカメラでリアルタイムで物体認識をする方法です スマホのカメラで物体認識をするのはかんたんなのか? スマホのカメラにうつるものがリアルタイムで判別できれば、色々なアプリに応用できそう。 しかし、物体認識は機械学習でおこなうが、ちょっとややこしそうな気もする。 しかも、1枚の画像ならまだしも、リアルタイムでカメラの映像を認識するのは設定が多そう。 CoreMLモデルにCaptureOutputを食わせるだけ 実は、それほどたくさんコードが必要なわけではない。 CoreMLモデルさえあれば、AVFoundationで取得したフレームをモデルに与えるだけで、リアルタイム物体認識ができる。 手法 まずはCoreMLの画像認識モデルを入手する。 ネット上から入手できるので、好みのものを使おう。 Appleが公式に配布しているものなど、画像認識モデルにはいろいろな種類があり精度もさまざまだが、 今回は、CoreMLモデル・ズーであるCoreML-Modelsから最新モデルであるRepVGGをダウンロードしてつかってみる。 モデルをSwiftで初期化する。 self.coreMLRequest = VNCoreMLRequest(model: try! VNCoreMLModel(for: try! RepVGG-A0(configuration:MLModelConfiguration()).model)) AVCaptureOutputのフレームをモデルに食わせる。 func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } let handler = VNImageRequestHandler(cvPixelBuffer:buffer, orientation: .right, options: [:]) do { try handler.perform([self.coreMLRequest]) guard let result = self.coreMLRequest.results?.first as? VNClassificationObservation else { return } let label = observation.identifier let confidence = observation.confidence } catch let error { print(error) return nil } } これで、リアルタイムで認識結果のラベル(クラス名)と信頼度が得られます。 高速にフレームごとに処理してくれます。 さまざまな用途に このような手法でスマホのカメラに映った任意のものをリアルタイムで判別できれば、コンシューマーだけでなく例えば監視カメラの映像を判別したり、工場のラインで物体認識したり、いろいろな用途に使えます。 ? フリーランスエンジニアです。 お仕事のご相談こちらまで rockyshikoku@gmail.com Core MLやARKitを使ったアプリを作っています。 機械学習/AR関連の情報を発信しています。 Twitter Medium
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Missing Purpose String in Info.plist File.

はじめに 本業のアプリをAppStoreConnectにアップロードした際にリジェクトを喰らいました。 Missing Purpose String in Info.plist File. Your app's code references one or more APIs that access sensitive user data. The app's Info.plist file should contain a NSLocationWhenInUseUsageDescription key with a user-facing purpose string explaining clearly and completely why your app needs the data. 簡単に訳すと、Info.plistにNSLocationWhenInUseUsageDescription keyを追加してくれ!と書いてます。 NSLocationWhenInUseUsageDescriptionはフォアグラウンドで位置情報を必要とするアプリではInfo.plistに追加しないといけませんが、自分がアップロードしようとしたアプリではそもそも位置情報サービスを必要としていなかったので非常に困りました。 何が原因かを調べていくうちに導入していたOneSignal iOS SDKが悪さをしてました。 原因 OneSignal iOS SDKを使用するとジオフェンス機能を用いたプッシュ通知を使用することができるそうで、この部分がアプリが実際に位置情報サービスを使用していない場合でも位置情報サービスを使用するアプリと認識してしまうそうです。 ジオフェンス機能とは、任意の場所を指定し、その圏内を出入りするとあらかじめ決めていた作業を自動で行なってくれるというものです。 ジオフェンス機能を用いたプッシュ通知では、例えばコンビニの近くを通ったりすると、プッシュ通知でクーポンを受け取ったり出来ます。 解決方法 info.plistにNSLocationWhenInUseUsageDescriptionを追加し、OneSignal.setLocationShared(false)を呼び出すだけです。 OneSignal.setLocationSharedは位置情報の収集を無効または有効にするAPIでfalseを指定している為、位置情報を使用されることはありません。 最後に 同じようにリジェクトになった方の中には、「Info.plistにNSLocationWhenInUseUsageDescriptionとNSLocationAlwaysUsageDescriptionを追加して下さい 」と言われてました。 なのでもうリジェクトを喰らいたくなかった自分は、念には念という事でNSLocationAlwaysUsageDescriptionも追加してアップロードした所、リジェクトが消えました! めでたし。 参考にしたもの Your app "infomobi" (Apple ID: XXX) has one or more issues - on deploy
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[iOS初心者向け]SnapKitとは

概要 SnapKitの役割を、一言で表すと「調整」です。 調整というのは、ビューレイアウトのパディングや制約を簡単に設定できます。 こちらは、公式のライブラリではなくサードパティとなります。 使い方 READMEのコードをお借りしますと import SnapKit class MyViewController: UIViewController { lazy var box = UIView() override func viewDidLoad() { super.viewDidLoad() self.view.addSubview(box) box.backgroundColor = .green box.snp.makeConstraints { (make) -> Void in make.width.height.equalTo(50) make.center.equalTo(self.view) } } } 見た目すごく使いやすそうですね。 ここでは、サブビューのboxのレイアウトを調整しています。 makeConstraintsの1行目は、幅と高さを50dpに設定しています。 そして、2行目では中心点を親のビューと同じにしています。 まとめ SnapKitとは、簡単にビューを「調整」できるサードーパーティライブラリです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

集中力を維持するためのポモドーロタイマー

学内の学生主体で進めるプロジェクトで「ユーザの集中力を持続させるシステム」を構築する機会がありました。 その際にポモドーロタイマーを用いて休憩時間を管理してくれるアプリを作成致しましたので、その内容について少しだけ触れさせて頂きます。 まず今回使用したのはReact Nativeです。iosとandroidの双方に対応できるクロスプラットフォームフレームワークを使用したいという理由から使用しました。 上の画像が実際のアプリ画面です。 左上にタスクに取り組む時間を、右上に休憩時間を打ち込み、開始ボタンを押します。 指定した時間が過ぎると端末が振動し、テキストが変化します。 簡単かつシンプルですが、作っていて楽しかったのでよし!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む