20211202のiOSに関する記事は7件です。

Swift Concurrency で遅延実行

Swift Concurrency な環境で DispatchQueue を書きたくないので コード Before DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { print("遅延処理") } After Task { try await Task.sleep(nanoseconds: 1_000_000_000) print("遅延処理") } 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FirebaseCrashlyticsのiOSで「dSYMが不足している」というエラーが発生した場合のトラブルシューティング

概要 FirebaseCrashlyticsと連携していて、管理コンソール上で「dSYMが不足している」という文言が表記された場合のトラブルシューティングです。 ↓参考画像 結論 XCodeで/ios/Runner.xcworkspaceを開き、debug information format を DWARF から DWARF with dSYM File に変更します。 参考 dSYMが必要な理由 Firebaseのドキュメントより以下引用。 Firebase Crashlytics はデバッグ シンボル(dSYM)ファイルを自動的に処理して、難読化解除された(人が読める形式の)クラッシュ レポートを生成します。 わずかながらも、dSYM のアップロードが失敗する状況がいくつかあります。 多くの場合、dSYM ファイルが欠落する理由は Xcode がファイルを生成していないことにあります。アップロードが失敗すると、Crashlytics は Firebase コンソールに「不足している dSYM」というアラートを表示します。 ref: https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?hl=ja&platform=ios 上記のようにビルド設定を変更しても「dSYMが不足しているエラー」が発生した場合 手動でdSYMファイルをfirebaseにアップロードすることで解決出来るらしく、App Store ConnectからdSYMファイルをダウンロードしてアップロードできるそうです。 ref: https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?hl=ja&platform=ios https://dev.classmethod.jp/articles/ios-firebase-crashlytics-upload-dsym/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IDE上でiOSのアプリ起動ができなくなった場合のtips

この場合、IDEの実行ボタンではなくコンソールでflutter runすると起動できます。 ※こんなエラーが出ます Launching lib/main.dart on iPhone 11 Pro Max in debug mode... Warning: CocoaPods is installed but broken. Skipping pod install. You appear to have CocoaPods installed but it is not working. This can happen if the version of Ruby that CocoaPods was installed with is different from the one being used to invoke it. This can usually be fixed by re-installing CocoaPods. For more info, see https://github.com/flutter/flutter/issues/14293. To re-install: sudo gem install cocoapods CocoaPods not installed or not in valid state. 結論 IDEをコンソールから起動してください。(open /Applications/Android\ Studio.app) 原因はあまり深く追っていないので正確にはわからないのですが、 GUI経由で起動した場合とTerminal経由で起動した場合でPATHの読み込み結果が変わってしまうらしく、以降名前解決が上手く出来なくなってしまうことに寄るらしいです。 奮闘記録 IDEを変えてみる IntelliJ IDEA → Android Studio → VSCode 事象全く変わらず。 XCodeのビルドが通るのか試してみる 通らない。同じエラーが出る。 rbenvではなくシステムのrubyに切り替える m1 mac × rbenv × cocoapodsの組み合わせが悪いらしい。 rbenvをアンインストールしてシステムのrubyに切り替え、homebrewでcocoapodsをインストールし直す。 事象全く変わらず。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UIKitでたまに遭遇するけど忘れがちなTipsメモ

UIKitで、たまに遭遇するけど忘れがちな部分をメモっておきます。 ViewやCell等でControlイベントが取れない場合のチェックリスト イベントを取りたいViewの isInteractionEnabled がtrueになってるか? イベントを取りたいViewの alpha が0になっていないか? backgroundColor = .clearにして、UIView.alphaは0より大きく設定する イベントを取りたいViewの上に、透明系のViewが被さってて、それがイベントを奪っていないか? storyboardでUIViewを作成すると、デフォルトで isInteractionEnabled = true になっているので、注意! storyboardでなくても、コードで追加したUIViewも同様なので、注意! イベントを透過させたいUIViewは、isInteractionEnabledをfalseにする そもそもoverride func hitTest()は取れてるか? pointInside() 参考: https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/using_responders_and_the_responder_chain_to_handle_events UICollectionViewのselection管理 UICollectionViewやUITableViewには、セルの選択状態を管理する機能が備わっています。 isSelectedは、collectionView側で管理されている スクロールでCellが見えなくなり、(Cellが再利用され)、スクロールで再度表示した際にはcell.isSelectedが復元されている collectionView.indexPathsForSelectedItems()で、選択状態のindexPathの配列が取れる Cellは画面外にスクロールすると再利用されるのに、選択状態は見えない範囲も全て管理してるのが、意外といえば意外でした。 参考 https://developer.apple.com/documentation/uikit/uicollectionviewdelegate/changing_the_appearance_of_selected_and_highlighted_cells https://developer.apple.com/documentation/uikit/uitableviewdelegate/selecting_multiple_items_with_a_two-finger_pan_gesture UITextFieldのshouldChangeCharactersInをRangeで扱う NSRangeはNSStringに対応している。(UTF-16) RangeはStringに対応する。(Unicode Scalar + 正規化) 無理に使うと、絵文字を入れた時等にクラッシュする事がある。(特に絵文字バリエーションシーケンスの扱いが違う) 参考 https://qiita.com/doraTeX/items/4feea8d9221419b19954 https://moji-memo.hatenablog.jp/entry/20120802/1343876603 (なぜここだけNSRangeのまま残ってしまってるのか…) public func textField(_ textField: UITextField, shouldChangeCharactersIn nsRange: NSRange, replacementString string: String) -> Bool { guard let text = textField.text, let range = Range(nsRange, in: text) else { return false } /* ... */ } NavigationBarをスクロールの方向で隠す 比較的簡単でTwitterっぽい自然な感じの動きにしてみました。 バウンスに反応しないようisTrackingを見ています。 class MyViewController { @IBOutlet private var collectionView: UICollectionView! private var lastContentOffset: CGPoint = .zero private var lastTracking = false override func viewDidLoad() { super.viewDidLoad() collectionView.delegate = self } // 画面遷移前には表示に戻しておく override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) navigationController?.setNavigationBarHidden(false, animated: true) } } extension MyViewController: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { let dy = scrollView.contentOffset.y - lastContentOffset.y lastContentOffset = scrollView.contentOffset let didTouchUp = lastTracking && !scrollView.isTracking lastTracking = scrollView.isTracking // 下にスクロールしたら隠す if scrollView.isTracking && dy > 5 { navigationController?.setNavigationBarHidden(true, animated: true) } // 指を離したタイミングで上にスクロールしていたら表示 if didTouchUp && dy < -10 { navigationController?.setNavigationBarHidden(false, animated: true) } } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【キュンです】指ハートを機械学習してエフェクトを出す

Macで機械学習でHand Pose Classification(手のポーズ分類)ができます。 今回はデモとして、CreateMLでポーズ認識モデルを作成して、「指ハート」と「ピースサイン」を認識するスマホアプリを作ってみます。 【キュンです】 動画解説:Youtube 1、データを集める 学習には画像データが必要です。 集めてフォルダ分けします。 必要な画像: ・識別したいポーズの画像(今回は"fingerHeart"と"peace") ・識別する必要のないポーズの画像("background") を、それぞれのクラスごとにフォルダ分けします。 "background"には、識別する必要のないさまざまなポーズと、分類したいポーズに移行するときの中途半端な手の画像を含めます。 さまざまな肌の色、年齢、性別、照明条件の画像を用意します。 今回はそれぞれのクラスで200枚前後を用意しました。 2、CreateMLでモデルをトレーニングする CreateMLをひらきます。 (Xcodeをコントロールクリックして、OpenDeveloperToolsから開く。) HandPoseClassificationを選択します。 (HandPoseClassificationは、macOS Monterey / Xcode13以上の環境で使えます。) データセット(Training Data)を選択。 検証データは指定しなければ、自動で生成されます。 データセット拡張(Augmentations:画像を回転させたりして水増し)を適宜設定します。今回は手の左右は問わないので、水平反転と回転を入れてみました。 Trainを押して学習を開始します。 数分で学習が終わります。 トレーニングが終わると、Previewタブから手持ちの画像でテストができます。 macのカメラでライブプレビューもできます。 Outputタブからモデルを入手します。 3、アプリでモデルを使用する モデルをXcodeプロジェクトにドロップして初期化します。 import CoreML import Vision ... let model = try? MyHandPoseClassifier_1(configuration: MLModelConfiguration()) Visionフレームワークで手のポイント(指先、関節などのキーポイントの位置:上記CreateMLプレビューの点の位置)を検出してからモデルに入力します。 func session(_ session: ARSession, didUpdate frame: ARFrame) { // 今回はARSessionからカメラフレームを取得します let pixelBuffer = frame.capturedImage // 手のポーズの検出リクエストを作成 let handPoseRequest = VNDetectHumanHandPoseRequest() // 取得する手の数 handPoseRequest.maximumHandCount = 1 // カメラフレームで検出リクエストを実行 // カメラから取得したフレームは90度回転していて、 // そのまま推論にかけるとポーズを正しく認識しなかったりするので、 // orientationを確認する let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, orientation: .right, options: [:]) do { try handler.perform([handPoseRequest]) } catch { assertionFailure("HandPoseRequest failed: \(error)") } guard let handPoses = handPoseRequest.results, !handPoses.isEmpty else { return } // 取得した手のデータ guard let observation = handPoses.first else { return } // 毎フレーム、モデルの推論を実行すると処理が重くなり、 // ARのレンダリングをブロックする可能性があるので、インターバルをあけて推論実行する frameCounter += 1 if frameCounter % handPosePredictionInterval == 0 { makePrediction(handPoseObservation: observation) frameCounter = 0 } } (注)カメラから取得したフレームは90度回転していて、そのまま推論にかけるとポーズを正しく認識しなかったりします。フレームの向き(orientation)を確認してから後続の作業をした方がいいと思います 取得した手のポイントのデータをMultiArray(多次元配列)に変換しCoreMLでモデルに入力・推論実行します。 func makePrediction(handPoseObservation: VNHumanHandPoseObservation) { // 手のポイントの検出結果を多次元配列に変換 guard let keypointsMultiArray = try? handPoseObservation.keypointsMultiArray() else { fatalError() } do { // モデルに入力して推論実行 let prediction = try model!.prediction(poses: keypointsMultiArray) let label = prediction.label // 最も信頼度の高いラベル guard let confidence = prediction.labelProbabilities[label] else { return } // labelの信頼度 print("label:\(prediction.label)\nconfidence:\(confidence)") } catch { print("Prediction error") } } label:fingerHeart confidence:0.9999963045120239 4、手のポーズに応じてARをつける 取得した分類ラベルに応じて処理をスイッチします。 if confidence > 0.9 { // 信頼度が90%以上で実行 switch label { case "fingerHeart":displayFingerHeartEffect() case "peace":displayPeaceEffect() default : break } } エフェクトを指の位置に出現させるために、 Visionで取得した指の位置をカメラから20cm奥にマッピングします。 func getHandPosition(handPoseObservation: VNHumanHandPoseObservation) -> SCNVector3? { // 人差し指の第二関節の位置に出現させる guard let indexFingerPip = try? handPoseObservation.recognizedPoints(.all)[.indexPIP], indexFingerTip.confidence > 0.3 else {return nil} // Visionの指の位置の検出結果は0~1に正規化されているので、 // view.boundsのサイズに直す。これにはVisionの関数が使える。 // また、Visionの座標原点は左下なので、Yを反転させてviewの座標システムに合わせる let deNormalizedIndexPoint = VNImagePointForNormalizedPoint(CGPoint(x: indexFingerTip.location.x, y:1-indexFingerTip.location.y), viewWidth, viewHeight) // 指はカメラから20cm奥にあると想定する let infrontOfCamera = SCNVector3(x: 0, y: 0, z: -0.2) guard let cameraNode = arScnView.pointOfView else { return nil} // カメラのワールド座標位置から20cm奥の位置を求める let pointInWorld = cameraNode.convertPosition(infrontOfCamera, to: nil) // view平面内の2次元の人差し指の位置と上記20cm奥の位置をスクリーン平面に定義する var screenPos = arScnView.projectPoint(pointInWorld) screenPos.x = Float(deNormalizedIndexPoint.x) screenPos.y = Float(deNormalizedIndexPoint.y) // スクリーン平面の指の位置を3次元座標にマッピングする let finalPosition = arScnView.unprojectPoint(screenPos) return finalPosition } SceneKitをつかって、3Dオブジェクトをアニメーションさせます。 func displayFingerHeartEffect(){ guard !isEffectAppearing else { return } // エフェクトが起動中かチェック isEffectAppearing = true // 人差し指の第二関節の位置を取得 guard let handPoseObservation = currentHandPoseObservation,let indexFingerPosition = getHandPosition(handPoseObservation: handPoseObservation) else {return} // エフェクトを指の位置に移動 heartNode.position = indexFingerPosition // アニメーションを定義 let fadeIn = SCNAction.fadeIn(duration: 0.2) let up = SCNAction.move(by: SCNVector3(x: 0, y: 0.1, z: 0), duration: 0.1) let shakeHalfRight = SCNAction.rotate(by: -0.3, around: SCNVector3(x: 0, y: 0, z: 1), duration: 0.025) let shakeLeft = SCNAction.rotate(by: 0.6, around: SCNVector3(x: 0, y: 0, z: 1), duration: 0.05) let shakeRight = SCNAction.rotate(by: -0.6, around: SCNVector3(x: 0, y: 0, z: 1), duration: 0.05) let shakeHalfLeft = SCNAction.rotate(by: 0.3, around: SCNVector3(x: 0, y: 0, z: 1), duration: 0.025) let shake = SCNAction.sequence([shakeLeft,shakeRight]) let fadeOut = SCNAction.fadeOut(duration: 1) let shakeRepeat = SCNAction.sequence([shakeHalfRight,shake,shake,shake,shake,shakeHalfLeft]) let switchEffectAppearing = SCNAction.run { node in // エフェクトのフラグをoffにしておく self.isEffectAppearing = false } // アニメーションを実行 heartNode.runAction(.sequence([fadeIn,up,shakeRepeat,fadeOut,switchEffectAppearing])) } 【キュンです】 このデモのGitHubリポジトリ(Xcodeプロジェクト): HandPoseClassificationAR 引用/参考文献: 手のポイントの取得について: VisionでHand Pose Detection 手のトラッキング: MLBoyだいすけ 2次元座標の3次元へのマッピングはKBoy様のコードを使わせていただきました: ARKitのための3D数学: Kei Fujikawa ? フリーランスエンジニアです。 お仕事のご相談こちらまでお気軽に? rockyshikoku@gmail.com Core MLやARKitを使ったアプリを作っています。 機械学習/AR関連の情報を発信しています。 GitHub Twitter Medium
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

動画からスライドを自動生成するアプリを作りました

この記事は クソアプリアドベントカレンダー2 の2日目の記事です。今まで傍から見ているだけでしたがついに初参加できて嬉しいです。 課題: 動画よりもスライドでサクサク見たい WWDCセッションの内容をチェックしては発信する、ということを毎年やっている 1 のですが、なぜか昨年(2020)あたりからセッションのスライドPDFが公開されなくなりました。 セッションの動画は例年通り公開されているのですが、 動画よりもスライドのほうがシーク効率が圧倒的に良いので、やっぱりスライドが欲しい。2 で、つくったのが、 動画からスライドを抽出するiOS/macOSアプリ「Slidify」 ↑この動画をSlidifyに食わせて全自動で生成したスライドが、 ↑こちら。 いかがでしょう? 荒削りな部分(後述)はあるものの、私は自分用ツールとしては十分実用に耐えうると思いました。 しくみ スライド抽出のしくみはざっくり以下のとおりです: 動画のフレームを一定間隔ごとに解析 以下の条件を満たすフレームだけを抽出 文字がある 文字領域が前フレームから一定以上変化している これだけ。 上のロジックに該当する実装を抜粋すると、こんな感じです: 一定間隔ごとの動画解析 let processingOption = VNVideoProcessor.RequestProcessingOptions() processingOption.cadence = VNVideoProcessor.TimeIntervalCadence(processingTimeInterval) let videoProcessor = VNVideoProcessor(url: url) // request は VNDetectTextRectanglesRequest try! videoProcessor.addRequest(request, processingOptions: processingOption) let timeRange = CMTimeRange(start: CMTime.zero, end: CMTime.indefinite) try! videoProcessor.analyze(timeRange) 文字があるか? // observations は VNTextObservation の配列 guard observations.count > 0 else { return false } 文字領域が前フレームから一定以上変化しているか? let intersection = bbox1.intersection(bbox2) if intersection.area / bbox1.area < threshold { return true } 文字認識も、抽出したフレーム画像同士の類似度判定も行っていません。 こういう簡素な抽出ロジックのため、 メガネに映り込んだアイコン画像の中の文字を拾ってしまっていたり 同じようなページが連続してしまっていたり といった問題も散見されますが、 「スライドをざーっと見て概要を把握したい」という自分の用途としては重大な問題ではありませんでした。 実装はほぼ使いまわし ほとんどの実装は、過去の個人開発アプリからの流用です。 動画選択UI Chopper からの流用 PHPickerViewControllerとUIDocumentPickerViewControllerを利用 文字領域検出 Somato からの流用 文字領域検出だけでなく、Visionラッパーとして自分が使いやすいようにモジュール化してある TOTOCでも同パッケージを使っている 動画からのフレーム抽出 Somato, TOTOCからの流用 今回Slidify用に独自実装した部分は文字領域の差分判定のところぐらい。 使い回しやすいパーツはSwift Package化しておいて、別プロジェクトにサクッと横展開できるようにしてあります。 今後の展望 以下対応しておくともっと便利に使えそう: 類似画像の除去 コマンドラインツール化 コンテキストメニュー対応 動画ファイル選択 → 右クリックで実行できるようにしたい おわりに 作り終えて記事を書き始めてから思いましたが、クソアプリカレンダーには真面目すぎる題材だったかもしれません... 「作りたい!という衝動にまかせて、有用性や需要のことは考えずとにかく手を動かす」 というものづくりの姿勢から生まれたものをクソアプリと呼ぶ、と拡大解釈して投稿させていただきました。もし場違いでしたらすみません??‍♂️こういうネタ要素の少ないものでもよろしければぜひまた参加させてください。 OSSを作ったり、本を書いたり、noteマガジンを書いたり。 ↩ しっかり調査する際には結局動画を見るのだとしても、どの動画を見るかどうか決めるにあたって、まずはスライドをパラパラと見てざっと概要を把握したい。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOSアプリをApp Storeに公開するまでの流れ

はじめに こんにちは、HCB Advent Calendar 2021の3日目を担当する、kudokaiです。 今年は個人開発のアプリをApp Storeにリリースしたこともあり、ストアに申請を出す手順を備忘録的にまとめようと思います。 リリースしてから4ヶ月ほど経つので思い出す作業も兼ねて書きます。 ここではApple Developerへの登録手順の説明は省きます。 ※記事内に添付しているスクリーンショットは2021/12/2時点のものです。 申請までの大まかな流れ Certificate作成 App IDs登録 Provisioning Profile作成 アプリをApp Store Connectに登録 Xcodeを用いてアプリをアップロード App Store Connectでアプリの情報を編集 スクリーンショット アプリアイコン プロモーション用テキスト 概要 キーワード サポートURL ビルド情報 申請 やること多いんですよね... Certificate作成 ここからはApple Developerサイトにアクセスして作業するのですが、公式で使用するブラウザはSafariを推奨しています。 Apple DeveloperサイトのCertificates Identifiers & Profiles にアクセス Certificatesを選択し、+ボタンをクリック iOS Distribution (App Store and Ad Hoc)をクリック キーチェーンを使って作成したファイルを追加 App IDs登録 Identifiersを選択し、+ボタンをクリック App IDsをクリック 次の画面で「App」を選択し、Continueをクリック DescriptionにAppIDの説明、Bundle IDに開発中のアプリのBundle identifierを入力し、Explicitを選択 Provisioning Profile作成 僕はこの辺りがよく分からずにAppleに何度も問い合わせメール送り、Xcode上でAutomatically manage signingにチェックを入れるとProvisioning Profileを自動で管理してくれるので特別な操作は不要になることが分かりました。 Automatically manage signing便利ですね。 Automatically manage signingにチェックを入れていない場合の流れを以下に記載します。 Profilesを選択し、+ボタンをクリック App Storeを選択し、Continueをクリック 「App ID:」の欄から先ほど作成したApp IDを選択し、Continueをクリック 該当するCertificateを選択し、Continueをクリック Provisioning Profile Nameに任意の名前を入力し、Ganereteをクリック アプリをApp Store Connectに登録 Apple DeveloperサイトのApp Store Connect にアクセスし、マイAppをクリック。 次の画面で+ボタンをクリックし、「新規App」を選択 ポップアップが表示されたら以下を入力 プラットフォーム: iOS 名前: アプリ名 プライマリ言語: App情報がローカライズされていない国または地域で使用する言語 バンドルID: 該当するバンドルID SKU: Appに割り当てる固有ID Xcodeを用いてアプリをアップロード Xcodeを立ち上げ、Products→Archiveを選択。 Archiveが完了するまでしばらく待つ。 Validate Appをクリック その後、画面の指示通り進んでいき、Validateまでクリック Distibute Appをクリック その後、App Store Connect→Uploadの順に選択。 Upload可能になったら「Upload」ボタンをクリック。 App Store Connectでアプリの情報を編集 ここではリリース申請に必要なアプリ情報を編集する。 Apple DeveloperサイトのApp Store Connect にアクセスする。 申請には6.5インチ・5.5インチ・12.9インチのスクリーンショットが必要になります。 それぞれ最低1枚ずつが必要です。 プロモーション用テキスト: アプリの説明欄の上部に表示される文章 概要: アプリの概要、機能の説明やアプリの特徴など キーワード: App Storeで検索する際のキーワード サポートURL: 問い合わせ用のURL マーケティングURL: 別に空欄でも可 Xcodeからアップロードしたビルドバージョンを選択する 右側のメニューから「App情報」を選択する。 ここではアプリの名前・サブタイトル・カテゴリを編集する。 名前: アプリのタイトル サブタイトル: 検索時にアプリタイトルの下に表示される カテゴリ: アプリが分類されるカテゴリをプルダウンから選択 プライバシーポリシーのURLを記載する。 入力し終えたら保存ボタンをクリック。 申請 必要なデータを全て入力したら「審査へ提出」ボタンをクリックします。 提出し終えたら確認メールが届くので後は、申請が通ることを祈って待ちます。 無事、申請が通るとAppleからメールが届くので、App Storeに開発したアプリが公開されているか確認しましょう。 大体が48時間以内に申請結果が返ってくるそうです。 僕の場合は10時間くらいで申請結果が返ってきて、アプリを公開することができました。 さいごに ここまでとても長かったですね... 僕自身、人生初めてのリリース作業だったのでとても時間がかかりました。 これからiOSアプリのリリース申請をする人へのお役に立てば幸いです。 内容の不備などがあればお知らせいただければ嬉しいです。 次のHCB Advent Calendarは、また僕が書く予定なので、お楽しみに!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む