- 投稿日:2020-12-23T21:06:37+09:00
iOSアプリ開発でぶち当たるエラー処世術〜Xcode編〜
エラー処世術
アプリケーション開発を行なっていると必ずぶち当たるエラー.検索しても解決策が得られない.開発が滞ってしまい,やる気が削がれる.といったことは誰しもが経験することだと思います.
私自身もその一人で,毎日技術力不足を痛感させられます.この記事では,Xcodeで開発する際に効率よくエラーの解明を行い,圧倒的成長を遂げるための処世術を少し紹介したいと思います.この記事の対象者
- iOSアプリ開発を始めたての方(Xcodeでの)
- LLDBって何?デバッガって?という方
- 追加したUI部品(button, viwe...)が表示されない!どこいったの?という方
処世術1 〜LLDB〜
LLDBとはXcodeのデフォルトデバッガです.皆さんお馴染みの画面の下の方にいます.デバッガとは不具合の原因を探すお手伝いをしてくれる機能やソフトを指します.基本的にはプログラムを一時停止させたりしてエラーを見つけます.
上は,お馴染みのXcodeの画面ですが.画面下の部分がデバッガです.ここでは,14行目にブレークポイントを設定して,プログラムを一時停止させています.行番号をクリックするとブレークポイントを設定できます.解除は,ブレークポイントを掴んでコード上にドラッグするとできます.
デバッガの右の画面の(lldb)と書かれている部分にコマンドを入力することでエラーの原因を探ることができます.
po コマンド
いちばん使うことになるであろうコマンドです.poは変数を出力してくれるものです.
例えば,以下のように,19行目にブレークポイントを設定した場合.
po lemonと入力すると,その時点での変数の値を確認することができます.
この例だとパッとしませんがいつか役に立つと思います.処世術2 〜UI部品どこ??〜
追加したはずのbuttonがシミュレーションを起動すると消えている..どこいったの?コードミスってる?なんて経験あると思います.これは案外簡単なミスであることがほとんどです.ただ慣れないうちは,なぜ無いのか原因を解明するのに時間がかかってしまいがちです.(その時間も成長に繋がる気もしますが,無いに越したことは無いでしょう.)
この問題の対処法のうちの1つを紹介します.Xcodeの隠れた機能 〜ビューデバッガ〜
ビューデバッガとは,現在のビュー階層を3Dで確認することができる機能です.
シミュレーターを起動しているときに,
ここをクリックすることで,確認することができます.
こんな感じで.
この例だと,あまりビューデバッガに恩恵を感じませんが,大規模なアプリになる程利用する機会は多くなると思います.
この機能を使えば,追加したUI部品がどこにあるのか,他のUI部品の下に隠れていないかなどを確認することができます.
「ビューデバッガのおかげで迷子になっていたUI部品を探し出し,救出することができた!」ということが結構あります.おわりに
「アドベントカレンダーかぁ、何書こう.」
ギリギリまでテーマが決まらず投稿が遅れてしまいました.個人的に辛いこともあり,記事を書かずに逃げ出そうかという考えもよぎりました.最初は義務感で書き始めましたが,良い気分転換になり,悩んでいたことも少し気が楽になった気がします.定期的なアウトプットって大事だなあと実感しました.(Qiitaさん,パソナテックさんありがとう!)今回,iOSアプリ開発を始めた時に知っておきたかったことを書きました.私もまだまだ初心者なので,間違いがあれば指摘してください.よろしくお願いします.自分の成長に合わせて内容もどんどん更新していくつもりです.そして,この記事が悩みを抱えていた方の助けになれば幸いです.
- 投稿日:2020-12-23T20:49:02+09:00
[Cocoa][Swift]AirPlay 2 について
HomePod miniは2台でステレオ再生ができるということで購入したのだが、使ってみたところ、それはAirPlay 2を利用した場合だった。MacintoshにレコードプレーヤーをLINE入力で繋いだので、レコードがステレオで再生されることを夢見たのだが、サウンドの入力を出力に繋ぐために利用しているQuickTime PlayerはAirPlay 2に対応していないので、ステレオ再生できない。そもそも、QuickTime Playerを使う必要があるということも面倒なので、レコードプレーヤーの音声を再生するアプリを制作作するため、AirPlay 2 に対応する手順を調べた。
AVAudioSessionのroute-sharing policyに .longForm を設定する
let audioSession = AVAudioSession.sharedInstance() try audioSession.setRouteSharingPolicy(.longForm)let audioSession = AVAudioSession.sharedInstance() try audioSession.setCategory(AVAudioSessionCategoryPlayback, mode: AVAudioSessionModeDefault, routeSharingPolicy: .longForm )AVRouteDetectorを使って出力先があるかどうか調べて、AVRoutePickerView で出力先を選択する。
MPRemoteCommandCenter を使って再生が制御できるようにする。再生中のメディアの情報は MPNowPlayingInfoCente から得る。
AVPlayer 又は AVQueuePlayer を利用するか、 AVSampleBufferAudioRenderer と AVSampleBufferRenderSynchronizer で再生を行う。
情報は少ないが、サンプルコードを見つけた。
【関連情報】
Getting AirPlay 2 into Your App
Integrating AirPlay for Long-Form Video Apps
Cocoa Advent Calendar 2020
Cocoa.swift
Cocoa勉強会 関東
Cocoa練習帳
- 投稿日:2020-12-23T18:54:19+09:00
【Swift】Optional<Wrapped>型でエラー処理を行う
エラー処理には、Optional<Wrapped>型を用いた処理の他にも、
Result<Success, Failure>型を用いた処理やdo-catch文などもあります。エラー処理は、私自身よく理解できておらず正直なところ苦手です。
しかし、コードを書く上では絶対に必要な処理なので
備忘録がてら皆さんに共有できたらなと思います。Optional<Wrapped>型によるエラー処理
ご存知かと思いますがOptional<Wrapped>型は、
値が存在するかどうかを表現することができます。Optional<Wrapped>型はnilを許容するので、
値が存在する時は格納されている値を、存在しない場合はnilを返します。エラー処理で使う場合は
値が存在することを成功・存在しないことを失敗とみなせば、
エラー処理の機構として成り立ちますよね、という話です。これでしたら結構簡単そうです!
実装方法
例えば、エラーが発生し得る関数であれば、
その戻り値をOptional<Wrapped>型にすればエラー処理を行うことができます。下記のサンプルコードでは、
引数のidに該当するインスタンスがあったらそのインスタンスを返しています。
なかった場合はnilが返ります。struct User { var id: Int var name: String } // インスタンス化 let user1 = User(id: 1, name: "Tarou") let user2 = User(id: 2, name: "Zirou") // インスタンスを配列に格納 var users = [user1, user2] // エラーが起きる可能性のある関数 // 引数のidに該当するものがなかったらnilを返す func findUserById(id: Int) -> User? { for user in users { if user.id == id { return user } } return nil } // 値が存在する場合 if let user = findUserById(id: 1) { print(user.name) } else { print("Error: User not found") } // 値が存在しない場合 if let user = findUserById(id: 3) { print(user.name) } else { print("Error: User not found") } 実行結果 Tarou Error: User not foundOptional<Wrapped>型を処理の成否に利用している例としては、
他にも失敗可能イニシャライザなどがあります。次のサンプルコードでは、
引数のメールアドレスを二分割できない場合は
不正なデータとみなしてインスタンス化を失敗させています。
let item = mail.split(separator: "@")のitemは、
mailを二分割した時の2つの値が格納された配列です。struct User { var name: String var mail: String init?(name: String, mail: String) { let item = mail.split(separator: "@") guard item.count == 2 else { return nil } self.name = name self.mail = mail } } let user1 = User(name: "Tarou", mail: "Tarou@gmail.com") let user2 = User(name: "Zirou", mail: "Zirou.com") var users = [user1, user2] for user in users { if let user = user { print("Name: \(user.name)\nMail: \(user.mail)") } else { print("Error: Invalid data") } } 実行結果 Name: Tarou Mail: Tarou@gmail.com Error: Invalid data利用するタイミング
利用するタイミングとしては、値の有無だけで結果を十分に表せる場合です。
先ほどのように、idで一致するものを検索する場合や
メールアドレスの形式がおかしくないかのチェックなどの単純なものは
Optional<Wrapped>型でいいと思います!ただ、id検索する際に、データベースに接続できなかったり
データが重複していたりなどとエラーの種類を多くすることは恐らく難しいですね・・・。なので失敗の原因に応じてメッセージを表示したり、
復旧の処理を行ったりするのであればOptional<Wrapped>型では不十分です!結論として、Optional<Wrapped>型は単純なケースのみにしましょう!
また、冒頭で紹介した、
Result<Success, Failure>型を用いた処理やdo-catch文ですが、
別で記事にする予定なのでぜひご覧ください!・【Swift】Result<Success, Failure>型でエラー処理を行う
・【Swift】do-catch文でエラー処理を行う(その1)最後までご覧いただきありがとうございました。
- 投稿日:2020-12-23T18:30:22+09:00
swiftでクリスマスツリーを作る
はじめに
メリークリスマス!
本日は24日なので、swiftのUIBezierPathを使ってクリスマスツリーを作ります。
Storyboardでクリスマスツリーの画像を貼れば簡単ですが、今回はすべてコードで書いていきたいと思います。
(少しずれている所もありますが許してください。。)ひとまず完成図
こんな感じで作りました。
一気にクリスマスツリー作るのは大変そうだったので、
「星」・「木の部分の三角形」・「枝の部分の長方形」・「鉢の部分の長方形」に分けて作りました。コード
class ViewController: UIViewController { var treeFrame: CGRect = .zero var starFrame: CGRect = .zero var roundRectFrame: CGRect = .zero override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .black starViewSetup() treeViewSetup() branchViewSetup() potViewSetup() } // 星を作る func starViewSetup() { let starLayer = CAShapeLayer.init() let starViewWidth = CGFloat(50) starFrame = CGRect(x: (view.bounds.width / 2) - (starViewWidth / 2) , y: 100, width: starViewWidth, height: 50) starLayer.frame = starFrame starLayer.strokeColor = UIColor.yellow.cgColor starLayer.fillColor = UIColor.yellow.cgColor let starViewHeight = starFrame.size.height let starBezier = UIBezierPath() starBezier.move(to: CGPoint.init(x: 0, y: starViewHeight / 3)) starBezier.addLine(to: CGPoint.init(x: starViewWidth / 3, y: starViewHeight / 3)) starBezier.addLine(to: CGPoint.init(x: starViewWidth / 2, y: 0)) starBezier.addLine(to: CGPoint.init(x: starViewWidth * 0.66, y: starViewHeight / 3)) starBezier.addLine(to: CGPoint.init(x: starViewWidth, y: starViewHeight / 3)) starBezier.addLine(to: CGPoint.init(x: starViewWidth * 0.75, y: starViewHeight * 0.6)) starBezier.addLine(to: CGPoint.init(x: starViewWidth * 0.9 , y: starViewHeight)) starBezier.addLine(to: CGPoint.init(x: starViewWidth / 2, y: starViewHeight * 0.75)) starBezier.addLine(to: CGPoint.init(x: starViewWidth / 10 , y: starViewHeight)) starBezier.addLine(to: CGPoint.init(x: starViewWidth / 4, y: starViewHeight * 0.6)) starBezier.close() starLayer.path = starBezier.cgPath view.layer.insertSublayer(starLayer, at: 1) } // 木を作る func treeViewSetup() { let treeLayer = CAShapeLayer.init() treeFrame = CGRect(x: starFrame.minX - starFrame.size.height / 2, y: starFrame.minY - starFrame.height + 10, width: 100, height: 300) treeLayer.frame = treeFrame treeLayer.strokeColor = UIColor.green.cgColor treeLayer.fillColor = UIColor.green.cgColor let treeViewHeight = treeFrame.height let treeViewWidth = treeFrame.width let treeBezier = UIBezierPath() treeBezier.move(to: CGPoint(x: treeViewWidth / 2, y: treeViewHeight / 5)) treeBezier.addLine(to: CGPoint(x: treeViewWidth * 1.2, y: treeViewHeight)) treeBezier.addLine(to: CGPoint(x: 0, y:treeViewHeight)) treeBezier.close() treeLayer.path = treeBezier.cgPath view.layer.insertSublayer(treeLayer, at: 0) } // 枝を作る func branchViewSetup() { let roundRectLayer = CAShapeLayer.init() roundRectFrame = CGRect.init(x: treeFrame.midX - 5, y: treeFrame.maxY - 10, width: 30, height: 100) roundRectLayer.frame = roundRectFrame roundRectLayer.strokeColor = UIColor.green.cgColor roundRectLayer.fillColor = UIColor.green.cgColor roundRectLayer.path = UIBezierPath.init(roundedRect: CGRect.init(x: 0, y: 0, width: roundRectFrame.size.width, height: roundRectFrame.size.height), cornerRadius: 8).cgPath view.layer.insertSublayer(roundRectLayer, at: 2) } // 鉢を作る func potViewSetup() { let potLayer = CAShapeLayer.init() let potFrame = CGRect.init(x: (roundRectFrame.midX) - (roundRectFrame.size.height / 2), y: roundRectFrame.height + roundRectFrame.minY - 10, width: roundRectFrame.size.height, height: 70) potLayer.frame = potFrame potLayer.strokeColor = UIColor.brown.cgColor potLayer.fillColor = UIColor.brown.cgColor potLayer.path = UIBezierPath.init(roundedRect: CGRect.init(x: 0, y: 0, width: potFrame.size.width, height: potFrame.size.height), cornerRadius: 8).cgPath view.layer.insertSublayer(potLayer, at: 3) } }結論・最後に
星の微調整が結構大変でしたが楽しかったです。
なので、
Storyboardは楽!!!!次回以降は、ツリーを装飾できたりしたらいいな、、、
最後までお読みいただきありがとうございました。
良いクリスマス、年末年始を!
- 投稿日:2020-12-23T17:58:36+09:00
CoreImageフレームワークを使ってSwiftでしみ消し、くま消し機能を実装した話
バイトル履歴書アプリに画像の補正機能を追加するエンハンスを担当したのでやったことまとめておきます。
今回のエンハンス内容としましては画像補正機能として撮影した写真全体に対して独自で定義したフィルター処理を行うのと、目の下のくまの部分や頬のあたりのしみの部分を部分的に画像補正処理を行う機能をCoreImageフレームワークのみを使い実装しました。
写真全体に対してフィルター処理を行うケースはそんなに難しくないのでサンプルコードだけ載せておきます。
import CoreImage ... // フィルター処理をかけたいSampleイメージを準備 var image = UIImage(named: "sample.jpg") // UIImageからCIImageへの変換 let ciImage: CIImage? = CIImage(image: image) // Sepiaフィルターの準備 var filter = CIFilter(name: "CISepiaTone") // フィルターに画像を渡す filter.setValue(ciImage, forKey: kCIInputImageKey) // Filterをかけた画像を取得する var filteredImage: CIImage? = filter.outputImageセピア補正のフィルターを適用すると結果はこんな感じです。
実際にプロダクトには色々な画像補正フィルターを掛け合わせて独自フィルターを作り実装しました。
オリジナル セピア補正 ※写真はフリー素材を利用:https://www.pakutaso.com/model.html
画像の部分補正処理の流れ
続いて、画像の部分補正をする場合の処理の流れをまとめていきます。
しみ消し機能にしろ、くま消し機能にしろ画像を部分的に補正するという点においては同じなのでどちらも上記の処理フローとなります。
マスク画像の作成
マスク画像の作成にはCoreImageのCIDetectionを使い顔のパーツの位置関係を取得することから始めます。顔検出において取得できる情報ですが、口の位置や目の位置、目が開いているかどうかや顔の輪郭を取得でき、それらの情報を元にマスク画像の作成をしていきます。
顔検出もCoreImageがサポートしていて下記のコードより取得できます。
let options = [CIDetectorAccuracy: CIDetectorAccuracyHigh] var faces: [CIFeature] = [] if let image = CIImage(image: faceImage), let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: options) { faces = faceDetector.features(in: image) }マスク画像の作成には検出した顔のパーツの位置情報を使い、数値モデル化したしみの位置を計算して
CIRadialGradientというCIFilterを使います。こちらのFilterですが、ガウシアンを位置情報を入力することで生成できるフィルターとなっており、しみ消し用のマスクに関しては計6点のガウシアン波形を重ね合わせマスク画像を作成しております。ガウシアン波形の合成にはCISourceOverCompositingと呼ばれるCIFilterを使用しています。let maskCircles: [MaskCircle] = [ /// ガウシアンの位置情報など... ] for (index, circle) in maskCircles.enumerated() { guard let gaussian = CIFilter(name: "CIRadialGradient") else { return nil } gaussian.setValue(CIVector(cgPoint: circle.center), forKey: kCIInputCenterKey) gaussian.setValue(circle.innerRadius, forKey: "inputRadius0") gaussian.setValue(circle.outerRadius, forKey: "inputRadius1") gaussian.setValue(CIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0), forKey: "inputColor0") gaussian.setValue(CIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.0), forKey: "inputColor1") if index == 0 { prev = gaussian } else { shimi = CIFilter(name: "CISourceOverCompositing") if let prevMask = prev?.outputImage, let gaussianMask = gaussian.outputImage { shimi?.setValue(gaussianMask, forKey: kCIInputImageKey) shimi?.setValue(prevMask, forKey: kCIInputBackgroundImageKey) } prev = shimi } }下記の画像がオリジナル画像とマスク画像をオリジナル画像に重ね合わせている図です。顔のしみがある部分を狙ってガウス波形の重ね合わせでマスク画像を作成しています。
オリジナル画像 しみ消し用のマスク画像 画像の合成
続きまして作成したしみ消し用のマスク画像を元に画像合成をしてしみ消し機能を実装していきます。マスク画像を使った部分的な合成には
CIBlendWithMaskフィルターを使います。
CIBlendWithMaskフィルターではマスク画像とオリジナル画像とマスク部分だけ適応したい画像補正をオリジナル画像にかけた画像を用意して合成処理を行います。let blend = CIFilter(name: "CIBlendWithMask") else { return nil } /// ブラー効果をかけた画像の設定 blend.setValue(blurImage, forKey: kCIInputImageKey) /// オリジナル画像の設定 blend.setValue(originalImage, forKey: kCIInputBackgroundImageKey) /// マスク画像の設定 blend.setValue(mask, forKey: kCIInputMaskImageKey) /// 合成した画像の取得 blend.outputImage
オリジナル画像 しみ消し用のマスク画像 Blur画像 結果
部分的に画像補正を行ってしみ消し機能を実装してみた結果はこんな感じです!しみの部分だけ部分的にぼかし効果が適用されてしみが目立たなくなっているのがわかるかと思います。
オリジナル画像 しみ消し処理後の画像
- 投稿日:2020-12-23T17:18:15+09:00
【Swift】Qiita 週間LGTM数ランキング【自動更新】
他のタグ
AWSAndroidDockerGitGoiOSJavaJavaScriptLinuxNode.jsPHPPythonRailsReactRubySwiftTypeScriptVimVue.js集計について
- 期間: 2020-12-17 ~ 2020-12-24
- 条件: ストック数が 2 以上の記事
LGTM 数ランキング
1 位: 新卒iOSエンジニアがコードレビューを受けて気づいたポイント4選
89 LGTM
@yayamochi さん ( 2020-12-19 22:59 に投稿 )2 位: PokéAPIを利用したPokedexにUIアニメーションを導入してみた
アニメーションSpriteKitSwiftpokemon20 LGTM
@haguhoms さん ( 2020-12-24 11:16 に投稿 )3 位: ARKit 4のLiDAR Depth API
13 LGTM
@shu223 さん ( 2020-12-23 11:45 に投稿 )4 位: iOS14 で始める CarPlay
iPhoneiOSCarPlaySwiftSwift513 LGTM
@crexista さん ( 2020-12-18 17:30 に投稿 )5 位: SwiftLintの設定ファイルをサーバーに配置して共有する方法
11 LGTM
@uhooi さん ( 2020-12-17 23:02 に投稿 )6 位: iOSアプリ上にストリーミングサーバーを構築する
iOSavfoundationSwiftServerSideSwift10 LGTM
@rb-de0 さん ( 2020-12-20 12:00 に投稿 )7 位: [Swift] SwiftUIの不思議な機能を実現する変数展開"()"について調べた
8 LGTM
@En3_HCl さん ( 2020-12-20 00:15 に投稿 )8 位: 【iOS】CIFilterを駆使して、ネガフィルムの写真をネガポジ変換する
iOSCIFilterMetalSwiftCIKernel7 LGTM
@MYamate_jp さん ( 2020-12-18 01:13 に投稿 )9 位: SpriteKit Particle Fileを使ってクリスマスカード作ってみた
6 LGTM
@Umiii さん ( 2020-12-22 19:36 に投稿 )10 位: Cocoa MVCがおFatになりやすいワケ
6 LGTM
@k_awoki さん ( 2020-12-21 23:46 に投稿 )11 位: 【Swift】MessageKitを解剖する
6 LGTM
@Sossiii さん ( 2020-12-19 16:50 に投稿 )12 位: SwiftUIで作るプログレス
6 LGTM
@Masataka-n さん ( 2020-12-18 15:00 に投稿 )13 位: サブする iOS で取り組んでいる DI(Dependency Injection)可能な実装について
5 LGTM
@branch10480 さん ( 2020-12-24 01:43 に投稿 )14 位: iOS用ライブラリ作成者向けSwift Package Managerのリソース周りTips
5 LGTM
@kazuhiro4949 さん ( 2020-12-17 21:16 に投稿 )15 位: ARKit 4でのFace Trackingの改善
4 LGTM
@shu223 さん ( 2020-12-24 17:23 に投稿 )16 位: SwiftUI 使って Watch Complication を実装してみる!
SwiftAppleWatchwatchOSComplicationSwiftUI3 LGTM
@MilanistaDev さん ( 2020-12-24 01:28 に投稿 )17 位: 【Swift】アプリ内にデータを保存する UserDefaults を使う
3 LGTM
@antk さん ( 2020-12-22 18:25 に投稿 )18 位: 【Swift5】レビュー機能のUIを実装する
iPhoneレビューiPhone開発SwiftSwift53 LGTM
@tomotaka_s さん ( 2020-12-21 23:48 に投稿 )19 位: VisionでHand Pose Detection 手のトラッキング
iOSMachineLearningSwiftVisionFrameworkhandtracking3 LGTM
@john-rocky さん ( 2020-12-19 23:40 に投稿 )20 位: 【Xcode&Swift】ちょっと手こずるエラーまとめ
3 LGTM
@Erica_pon さん ( 2020-12-18 10:22 に投稿 )21 位: ビットボードの紹介~少しのオセロ実装を添えて~
3 LGTM
@watergreat31 さん ( 2020-12-18 09:04 に投稿 )22 位: iOSアプリ開発でぶち当たるエラー処世術〜Xcode編〜
2 LGTM
@okamoto441 さん ( 2020-12-23 21:06 に投稿 )23 位: 【Swift】型の設計指針について
2 LGTM
@onishiApp さん ( 2020-12-20 14:36 に投稿 )24 位: The Composable ArchitectureでPullbackは何をしているのか
iOSSwiftTheComposableArchitecture2 LGTM
@redryerye さん ( 2020-12-19 23:47 に投稿 )25 位: 【Swift】プロトコルの概念と定義方法
2 LGTM
@onishiApp さん ( 2020-12-18 17:16 に投稿 )26 位: WKWebViewでSPAなサイトの遷移を監視する方法
2 LGTM
@yo1106 さん ( 2020-12-17 23:39 に投稿 )27 位: 【Swift】非同期処理「GCD」について
非同期処理GCDSwiftQoSDispatchQueue1 LGTM
@onishiApp さん ( 2020-12-22 16:53 に投稿 )28 位: [iOS] iPhone12 miniでStatusBarの高さ取得が期待した動作をしないのと対策
1 LGTM
@t_osawa_009 さん ( 2020-12-22 12:11 に投稿 )29 位: 【Swift】非同期処理ってなにもの?
非同期処理GCDSwiftThreadOperationQueue1 LGTM
@onishiApp さん ( 2020-12-22 11:02 に投稿 )30 位: Storyboardを使わずにカウントアプリを作ってみる
1 LGTM
@tanaka-tt さん ( 2020-12-21 06:18 に投稿 )31 位: NotificationCenterを用いて、iOSアプリのライフサイクルイベントを検知する
1 LGTM
@yutosa3 さん ( 2020-12-18 19:33 に投稿 )
- 投稿日:2020-12-23T17:18:15+09:00
【Swift】Qiita 週間 LGTM 数ランキング【自動更新】
他のタグ
AWSAndroidDockerGitGoiOSJavaJavaScriptLinuxNode.jsPHPPythonRailsReactRubySwiftTypeScriptVimVue.js集計について
- 期間: 2020-12-17 ~ 2020-12-24
- 条件: ストック数が 2 以上の記事
LGTM 数ランキング
1 位: 新卒iOSエンジニアがコードレビューを受けて気づいたポイント4選
89 LGTM
@yayamochi さん ( 2020-12-19 22:59 に投稿 )2 位: PokéAPIを利用したPokedexにUIアニメーションを導入してみた
アニメーションSpriteKitSwiftpokemon20 LGTM
@haguhoms さん ( 2020-12-24 11:16 に投稿 )3 位: ARKit 4のLiDAR Depth API
13 LGTM
@shu223 さん ( 2020-12-23 11:45 に投稿 )4 位: iOS14 で始める CarPlay
iPhoneiOSCarPlaySwiftSwift513 LGTM
@crexista さん ( 2020-12-18 17:30 に投稿 )5 位: SwiftLintの設定ファイルをサーバーに配置して共有する方法
11 LGTM
@uhooi さん ( 2020-12-17 23:02 に投稿 )6 位: iOSアプリ上にストリーミングサーバーを構築する
iOSavfoundationSwiftServerSideSwift10 LGTM
@rb-de0 さん ( 2020-12-20 12:00 に投稿 )7 位: [Swift] SwiftUIの不思議な機能を実現する変数展開"()"について調べた
8 LGTM
@En3_HCl さん ( 2020-12-20 00:15 に投稿 )8 位: 【iOS】CIFilterを駆使して、ネガフィルムの写真をネガポジ変換する
iOSCIFilterMetalSwiftCIKernel7 LGTM
@MYamate_jp さん ( 2020-12-18 01:13 に投稿 )9 位: SpriteKit Particle Fileを使ってクリスマスカード作ってみた
6 LGTM
@Umiii さん ( 2020-12-22 19:36 に投稿 )10 位: Cocoa MVCがおFatになりやすいワケ
6 LGTM
@k_awoki さん ( 2020-12-21 23:46 に投稿 )11 位: 【Swift】MessageKitを解剖する
6 LGTM
@Sossiii さん ( 2020-12-19 16:50 に投稿 )12 位: SwiftUIで作るプログレス
6 LGTM
@Masataka-n さん ( 2020-12-18 15:00 に投稿 )13 位: ARKit 4でのFace Trackingの改善
5 LGTM
@shu223 さん ( 2020-12-24 17:23 に投稿 )14 位: サブする iOS で取り組んでいる DI(Dependency Injection)可能な実装について
5 LGTM
@branch10480 さん ( 2020-12-24 01:43 に投稿 )15 位: iOS用ライブラリ作成者向けSwift Package Managerのリソース周りTips
5 LGTM
@kazuhiro4949 さん ( 2020-12-17 21:16 に投稿 )16 位: SwiftUI 使って Watch Complication を実装してみる!
SwiftAppleWatchwatchOSComplicationSwiftUI3 LGTM
@MilanistaDev さん ( 2020-12-24 01:28 に投稿 )17 位: 【Swift5】レビュー機能のUIを実装する
iPhoneレビューiPhone開発SwiftSwift53 LGTM
@tomotaka_s さん ( 2020-12-21 23:48 に投稿 )18 位: VisionでHand Pose Detection 手のトラッキング
iOSMachineLearningSwiftVisionFrameworkhandtracking3 LGTM
@john-rocky さん ( 2020-12-19 23:40 に投稿 )19 位: 【Xcode&Swift】ちょっと手こずるエラーまとめ
3 LGTM
@Erica_pon さん ( 2020-12-18 10:22 に投稿 )20 位: ビットボードの紹介~少しのオセロ実装を添えて~
3 LGTM
@watergreat31 さん ( 2020-12-18 09:04 に投稿 )21 位: 【Swift】アプリ内にデータを保存する UserDefaults を使う
2 LGTM
@antk さん ( 2020-12-22 18:25 に投稿 )22 位: 【Swift】型の設計指針について
2 LGTM
@onishiApp さん ( 2020-12-20 14:36 に投稿 )23 位: The Composable ArchitectureでPullbackは何をしているのか
iOSSwiftTheComposableArchitecture2 LGTM
@redryerye さん ( 2020-12-19 23:47 に投稿 )24 位: 【Swift】プロトコルの概念と定義方法
2 LGTM
@onishiApp さん ( 2020-12-18 17:16 に投稿 )25 位: WKWebViewでSPAなサイトの遷移を監視する方法
2 LGTM
@yo1106 さん ( 2020-12-17 23:39 に投稿 )26 位: iOSアプリ開発でぶち当たるエラー処世術〜Xcode編〜
1 LGTM
@okamoto441 さん ( 2020-12-23 21:06 に投稿 )27 位: 【Swift】非同期処理「GCD」について
非同期処理GCDSwiftQoSDispatchQueue1 LGTM
@onishiApp さん ( 2020-12-22 16:53 に投稿 )28 位: [iOS] iPhone12 miniでStatusBarの高さ取得が期待した動作をしないのと対策
1 LGTM
@t_osawa_009 さん ( 2020-12-22 12:11 に投稿 )29 位: 【Swift】非同期処理ってなにもの?
非同期処理GCDSwiftThreadOperationQueue1 LGTM
@onishiApp さん ( 2020-12-22 11:02 に投稿 )30 位: Storyboardを使わずにカウントアプリを作ってみる
1 LGTM
@tanaka-tt さん ( 2020-12-21 06:18 に投稿 )31 位: NotificationCenterを用いて、iOSアプリのライフサイクルイベントを検知する
1 LGTM
@yutosa3 さん ( 2020-12-18 19:33 に投稿 )
- 投稿日:2020-12-23T17:17:18+09:00
iOS: SFTPでファイルアップロード
- Swift 5
- Xcode Version 12.3
- NMSSH
iOSアプリ開発にて、SFTPでサーバーにファイルをアップロードする仕様でした。
今回使用したライブラリはNMSSH。これ以外、見つけられませんでした。
- https://github.com/NMSSH/NMSSHCocoaPodのインストール
まずはここでつまづきました。
参照)https://qiita.com/spring_i/items/181bc3c05142d1f80d93$ sudo gem install -v1.8.4 cocoapods -n /usr/local/bin $ pod setupMNSSHのインストール
Xcodeのプロジェクトは作成済みとしています。
$ cd [プロジェクト名] $ pod initPodfileが生成されますので、それを編集。
Podfile# Uncomment the next line to define a global platform for your project # platform :ios, '9.0' target 'SampleProject' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! # Pods for GSApp pod 'NMSSH' endインストールコマンド
$ pod installライブラリインポート
プロジェクトを、生成された[プロジェクト名].xcworkspaceをダブルクリックして起動する。
対象のファイルにて、ライブラリインポート
ViewController.swiftimprt NMSSHアップロードするダミーファイル作成
アップロードするためのダミーファイル作成
ViewController.swiftdo { let fileManager = FileManager.default let doc = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) let path = doc.appendingPathComponent("muyFile.txt") let data = "Hello, world".data(using: .utf8) fileManager.createFile(atPath: path.path, contents: data, attributes: nil) } catch { print(error) }アップロード
ViewController.swiftlet host = "[IPアドレス]" let username = "[ユーザー名]" let password = "[パスワード]" let remotePath = "[送信先のファイルパス]" let session = NMSSHSession.connect(toHost: host, withUsername: username) if (session.isConnected) { session.authenticate(byPassword: password) if (session.isAuthorized) { session.channel.uploadFile(path.path, to: remotePath) } } session.disconnect()サンプルソースコード
onClickをボタンと結びつけています。
ViewController.swift// // ViewController.swift // SampleProject // // Created by SankoSC on 2020/12/21. // import UIKit import NMSSH class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } @IBAction func onClick(_ sender: Any) { sftp() } func sftp() { do { let fileManager = FileManager.default let doc = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) let path = doc.appendingPathComponent("muyFile.txt") let data = "Hello, world".data(using: .utf8) fileManager.createFile(atPath: path.path, contents: data, attributes: nil) let host = "[IPアドレス]" let username = "[ユーザー名]" let password = "[パスワード]" let remotePath = "[送信先のファイルパス]" let session = NMSSHSession.connect(toHost: host, withUsername: username) if (session.isConnected) { session.authenticate(byPassword: password) if (session.isAuthorized) { session.channel.uploadFile(path.path, to: remotePath) } } session.disconnect() } catch { print(error) } } }
- 投稿日:2020-12-23T16:02:07+09:00
Swift CombineにてFutureで実行している非同期処理をsinkしているのに途中でキャンセルされる
Combineを使用する際にハマってしまったので、備忘録として残します。
問題
Futureのインスタンスにsinkしてクロージャ(サブスクライバ)を設定し、非同期処理が完了した際にsinkにて設定したクロージャの処理を行いたいのだが、完了する前にSubscribeがキャンセルされてしまう。
func start() { Future<Void, Error>{ promise in DispatchQueue.main.asyncAfter(deadline: .now() + 5.0){ promise(.success(())) } } .handleEvents(receiveSubscription: { _ in print("receiveSubscription") }, receiveOutput: { print("receiveOutput") }, receiveCompletion: { _ in print("receiveCompletion") }, receiveCancel: { print("receiveCancel") }, receiveRequest: { _ in print("receiveRequest") }) // クロージャが実行されない .sink(receiveCompletion: { completion in print("completion:\(completion)") }, receiveValue: { }) }デバッグコンソールreceiveSubscription receiveRequest receiveCancel解決方法
sinkメソッドの返り値を変数に保持するか、storeメソッドを使用して保持する必要がある。
func start() { cancellable = Future<Void, Error>{ promise in DispatchQueue.main.asyncAfter(deadline: .now() + 5.0){ promise(.success(())) } } .handleEvents(receiveSubscription: { _ in print("receiveSubscription") }, receiveOutput: { print("receiveoutput") }, receiveCompletion: { _ in print("receiveCompletion") }, receiveCancel: { print("receiveCancel") }, receiveRequest: { _ in print("receiveRequest") }) .sink(receiveCompletion: { completion in print("completion:\(completion)") }, receiveValue: { }) }デバッグコンソールreceiveSubscription receiveRequest receiveoutput receiveCompletion completion:finished原因
Future内に実装した処理が完了(promise()がコールされる)前にsinkから返却されたAnyCancellableが破棄されてしまうとキャンセルされてしまうみたい。
公式のリファレンスを読むと「Return Value」の部分にひっそりと書かれていた。https://developer.apple.com/documentation/combine/future/sink(receivecompletion:receivevalue:)
- 投稿日:2020-12-23T14:32:17+09:00
iOSショートカットアプリを作って鍵をNFCで素早く開ける
スマートロックをもっとスマートに解錠したい。
玄関の前まで帰ってきたときに
携帯を取り出す → (マスクを外す) → 顔認証を解除 → アプリを探す → アプリを開く → 鍵を開けるというプロセスを踏む必要があるのですが、「アプリを探す → アプリを開く」の2工程をNFCタグを使って削減するアプリを作ります。
特にこのマスク環境下では1工程でも減らしたい。
スマートロックにはGPSで近づくと自動で鍵を開ける設定などもあるのですが、常にGPSをチェックするため電池を激しく消耗し、チェック間隔より手動で開けた方が早いことも多く、
そもそも内廊下のマンションでは電波なんて届かないので使えません。公式アプリではiOSショートカットに対応していないので、自分で対応するアプリを作り、
そこから公式アプリへリダイレクトさせてしまいます。NFCタグ
10枚 NFCステッカー NTAG 213 NFCタグ/ 25 mm(1インチ)黒 円形
シールタイプで黒の目立たないものを選びました。
これを玄関部分に貼り付けるのですが、大抵の玄関の扉は金属製だと思うのですが、金属に貼り付けると反応が鈍くなるので注意!
私はインターホンの近くの壁部分に貼り付けました。一応、マンションの玄関や廊下部分ですが普通は共用部という扱いですのでご注意ください。
iOSアプリ作成
特にView部分は必要ないのですがSwift UIで作っています。
別アプリを開く
まず公式アプリへリダイレクトさせるアプリを作成していきます。
私の場合bitlockを使用しているためlunchBitlockというアプリ名にしました。
画面を表示した瞬間に公式アプリを開くようにしています。import SwiftUI @main struct lunchBitlockApp: App { @Environment(\.scenePhase) private var scenePhase var body: some Scene { WindowGroup { ContentView() .onAppear { lunchApp() } .onChange(of: scenePhase) { _ in lunchApp() } } } func lunchApp() -> Void { if let url = URL(string: "app.jp.co.bitkey.bitlock:") { UIApplication.shared.open(url) } } }アプリ個別のURLスキームは公開されていないため調べるのが難しいのですが、
Androidアプリも同名を使っていることが多いので、Google PlayストアのURLに書かれているパッケージ名から推察できたりします。ショートカット対応させる
iOSショートカットXcodeのメニューから
File → New → File → SiriKit Intent Definition Fileでショートカット対応させるためのファイルを作成します。
作ったIntent DefinitionファイルにCustom Intentsを追加します。
特に設定は不要ですが、わかりやすいようにタイトルを設定しておきましょう。このファイルを追加するだけでiOSショートカットへの対応は完了です。
ショートカットアプリのアクション部分にアプリが表示されるようになります。オートメーション設定
ショートカットアプリのオートメーション設定から新しく追加します。
「いつ」に反応させたいNFCタグを登録し、「行う(アクション)」に作ったアプリを登録するとこんな感じのオートメーションができあがります。
この状態でNFCタグにiPhoneをかざすと自動でアプリが開かれるようになって便利。
おわり
疲れてヘトヘトになって帰ってきたときに「アプリを探す」という工程が面倒だったり、別アプリを開いてるときにサクッとアプリが切り替えられるので割と便利です。
私の場合自宅WiFiに接続したときにこのアプリを開くオートメーションも登録しています。
たまにこっちの方が早く反応することもありますが、WiFi切り替え時などに誤爆します。公式さん対応してください。。。
- 投稿日:2020-12-23T14:25:47+09:00
[Xcode 12] [Swift] guard節で複数条件を判定する時の推奨コーディングスタイル
Xcode 12でguard節の自動フォーマットが変わりました。
Xcode 12以降でのコーディングスタイルについての考察です。Xcode 11以前の自動フォーマットと、私個人のコーディングスタイル
func foo() { // Xcode 11の自動フォーマット。letの位置がズレていて読みづらい… guard let hoge = hoge, let fuga = fuga else { print("guarded") return } print("\(hoge) - \(fuga)") } func bar() { // 上だと読みづらいので、Xcode 11ではこう書いてました。 guard let hoge = hoge, let fuga = fuga else { print("guarded") return } print("\(hoge) - \(fuga)") }Xcode 12以降の自動フォーマット
func foo() { // Xcode 12の自動フォーマット。letの位置が合って読みやすくなりました。多分これが推奨のスタイル。 guard let hoge = hoge, let fuga = fuga else { print("guarded") return } print("\(hoge) - \(fuga)") } func bar() { // Xcode 11以前のスタイルだと、elseの位置が変わりました。 // 別にこれでも悪くはないけど、Xcodeが上の自動フォーマットをしてくれるなら、このスタイルで書く必要性はなくなりました。 guard let hoge = hoge, let fuga = fuga else { print("guarded") return } print("\(hoge) - \(fuga)") }Xcode 12のリリースノート(英語)では、前者
func foo()のスタイルが例示されていますので、Xcode 12以降では前者のスタイルで書くのが良さそうです。番外:
func foo() { // returnするだけならこれでも良いかと思われます。 guard let hoge = hoge else { return } guard let fuga = fuga else { return } print("\(hoge) - \(fuga)") }
- 投稿日:2020-12-23T13:47:43+09:00
DateFormatter(short, medium, long)
DateFormatter
.dateStyle
.timeStyleそれぞれの出力結果
short.swiftlet dateFormatter = DateFormatter() dateFormatter.dateStyle = .short dateFormatter.timeStyle = .short print(dateFormatter.string(from: Date())) // 12/23/20, 1:42 PMmedium.swiftdateFormatter.dateStyle = .medium dateFormatter.timeStyle = .medium print(dateFormatter.string(from: Date())) // Dec 23, 2020 at 1:42:57 PMshort.swiftdateFormatter.dateStyle = .short dateFormatter.timeStyle = .short print(dateFormatter.string(from: Date())) // December 23, 2020 at 1:42:57 PM GMT+9
- 投稿日:2020-12-23T11:48:47+09:00
モデルをオプショナル型で宣言する
はじめに
MVCアーキテクチャについて勉強していたらインスタンス変数をオプショナル型で定義しているコードがありました。その理由と挙動について書きます。コメント歓迎してます。
モデルクラスの用意
class Model { func model() { print("model") } }
modelというメソッドを定義しています。想定としては、コントローラークラスから参照され、用いられます。コントローラークラスの用意
インスタンス変数
modelをModel?型で定義しています。
methodOfModelはModelクラスのmodelメソッドを行うメソッドです。class Controller { var model: Model? { didSet { print("変更された") } } func methodOfModel() { model?.model() } }挙動
let controller = Controller() print("modelは\(controller.model)")//modelはnil controller.methodOfModel()//この時点では実行されない controller.model = Model() //変更された print("modelは\(controller.model)")//modelはOptional(Page_Contents.Model) controller.methodOfModel()//model1行目では
Controllerをインスタンス化しています。
2行目ではcontroller.modelがnilであることを確認しています。
3行目ではcontroller.methodが実行されないことを確認しています。
4行目ではcontroller.modelにModel()を入れています。ここでmodelは値が変更されたのでdidSetが実行されます。
5行目では2行目と同じことが書かれています。が、modelがnilでないことが確認できます。
6行目も3行目と同じことが書かれています。が、Modelクラスのmodelが実行されます。なぜモデルをオプショナル型で宣言するのか
ここからは書いている人の予想という色が強いことをはじめに宣言しておきます。
let model = Model()としてしまうとクラスとクラスの結合が強くなってしまいます。クラス同士を疎結合にするために、外からインスタンス化を行わせているというのが狙いの一つではないでしょうか。
- 投稿日:2020-12-23T11:45:11+09:00
ARKit 4のLiDAR Depth API
iOSデバイスで初めてLiDARスキャナを搭載したiPad Proの発売と同時にリリースされたARKit 3.5では、LiDARを利用してreconstractionした3Dメッシュは得られたが、その計算に用いられているはずのデプス(深度)データにはアクセスできなかった。
その後発表されたARKit 4 / iOS 14(/ iPadOS 14)でようやく、LiDARで計測したデプスを取得できるようになった。従来のデプスと区別するため、「シーンデプス(Scene Depth)」とも呼ばれているようだ。
LiDARデプスの取得方法
LiDAR由来のデプスデータは、
ARWorldTrackingConfiguration利用時、frameSemanticsプロパティに.sceneDepthオプションを指定することで取得可能になる。let session = ARSession() let configuration = ARWorldTrackingConfiguration() // sceneDepthオプションが利用可能かをチェック if type(of: configuration).supportsFrameSemantics(.sceneDepth) { // Activate sceneDepth configuration.frameSemantics = .sceneDepth } session.run(configuration)ARFrameに新たに追加された
sceneDepthプロパティより、LiDARで計測したデプスデータを取得できる。func session(_ session: ARSession, didUpdate frame: ARFrame) { guard let depthData = frame.sceneDepth else { return } // Use depth data }利用可能なコンフィギュレーション
ARConfigurationプロトコルに定義されているframeSemanticsプロパティにsceneDepthを設定するので、API的には利用可能ではあるが、APIリファレンスのsceneDepthの項にある記述を読む限り、ワールドトラッキング以外のコンフィギュレーションでは利用できないっぽい。https://developer.apple.com/documentation/arkit/arconfiguration/framesemantics/3516902-scenedepth
If you enable this option on a world-tracking configuration's frameSemantics, ARKit includes depth information for regions of the current frame's capturedImage. The world-tracking configuration exposes depth information in the sceneDepth property that it updates every frame.
ハードウェア的な制約
同じく
sceneDepthのリファレンスページより。ARKit supports scene depth only on LiDAR-capable devices
チップがA12以上とかの条件はないようだ。(LiDARが載る→必然的に高性能チップとなるからだろうか)
従来のデプスデータとの違い
これまでも(特定の条件下で)ARKit利用時にデプスデータにアクセスすることは可能だった。
ARFrameのcapturedDepthDataプロパティ、estimatedDepthが従来からあったデプスAPI。今回追加されたデプスAPIと何が違うのか。デプスデータの種類と取得条件
まずもちろん、デプスデータの種類が違う。そしてそれに伴い、取得できる条件も違う。
capturedDepthDataはTrue-Depthカメラ由来のデプスデータで、フェイストラッキング利用時のみ取得できる。
estimatedDepthはframeSemanticsにpersonSegmentationWithDepthオプションを指定した場合に取得できるもので、デュアルカメラやTrue-Depthカメラ由来のデプスではなく、そのプロパティ名からわかるとおり推定デプスデータである。要A12チップ以上。
sceneDepthは上述した通り、frameSemanticsにsceneDepthを指定した場合に取得でき、ARコンフィギュレーションとしてはワールドトラッキングのみ、デバイスとしてLiDARを搭載している必要がある。フレームレート/生成アルゴリズムの違い
capturedDepthDataはTrue-Depthカメラ由来のデプスデータであるため、ARFrameのカラーデータ(capturedImageプロパティ)よりも更新頻度が低く、デプスを用いたエフェクトなどをかけていると、被写体(フェイストラッキングなので自分)が速く動いた場合に追従できないことがあった。一方で
estimatedDepthは機械学習ベースで推定されるもので、A12以降の高性能なチップを条件としているため、カメラフレームと同等のフレームレートで更新される。
sceneDepthもLiDARで取得した深度データと広角カメラから取得したカラーデータをベースに機械学習アルゴリズムを利用して生成される。60fpsで動作する、つまりこちらもARFrameが得られるたびに更新されている。(WWDC 2020の"Explore ARKit 4"セッションより)
The colored RGB image from the wide-angle camera and the depth ratings from the LiDAR scanner are fused together using advanced machine learning algorithms to create a dense depth map that is exposed through the API.
(広角カメラからのカラーRGB画像とLiDARスキャナからの深度定格が、高度な機械学習アルゴリズムを使用して融合され、APIを通じて公開される高密度の深度マップが作成されます。)This operation runs at 60 times per second with the depth map available on every AR frame.
(この操作は毎秒60回実行され、ARフレームごとに深度マップが利用可能になります。)型の違い
capturedDepthDataはAVDepthData型、estimatedDepthはCVPixelBuffer型、そしてsceneDepthはARDepthData型で得られる。ARDepthData
上述したとおり
sceneDepthプロパティから得られるLiDAR由来のデプスデータはARDepthData型で得られる。こちらはiOS 14で追加された新クラス。https://developer.apple.com/documentation/arkit/ardepthdata?changes=latest_major
これをうまく利用した公式サンプルが出ているので、詳しい解説はそのサンプルを読みつつ別記事で書く。
LiDARの精度
WWDC 2020ではないが、ARKit 3.5リリース時に、"Advanced Scene Understanding in AR"というTech Talkが公開されており、その中でLiDARの精度について言及があった。
The new iPad Pro comes equipped with a LiDAR Scanner. This is used to determine distance by measuring at nanosecond speeds how long it takes for light to reach an object in front of you and reflect back. This is effective up to five meters away and operates both indoors and outdoors.
(新しいiPad ProにはLiDARスキャナーが搭載されています。これは、目の前の物体に光が到達して反射して戻ってくるまでの時間をナノ秒の速度で測定することで距離を判断するために使用されます。これは5メートル先まで有効で、屋内でも屋外でも使用できます。)LiDARで何メートル先まで計測できるのか、という質問は何度かされたが、こういうのは公式には言及されない印象だったので、「サンプルで実際に試してみるといいですよ」という回答をしていた。目安にしろ貴重な公式情報ではあるのでメモ。
personSegmentationWithDepthとシーンデプス
ARConfigurationのframeSemanticsプロパティにpersonSegmentationWithDepthオプションを指定している場合、シーンデプスを取得可能なデバイスであれば、自動的にシーンデプスを得られるらしい。let session = ARSession() let configuration = ARWorldTrackingConfiguration() // Set required frame semantics let semantics: ARConfiguration.FrameSemantics = .personSegmentationWithDepth // Check if configuration and device supports the required semantics if type(of: configuration).supportsFrameSemantics(semantics) { // Activate .personSegmentationWithDepth configuration.frameSemantics = semantics } session.run(configuration)しかも、追加の電力コストもかからないとのこと。
Additionally if you have an AR app that uses people occlusion feature, and then search the personSegmentationWithDepth frameSemantic, then you will automatically get sceneDepth on devices that support the sceneDepth frameSemantic with no additional power cost to your application.
smoothedSceneDepth
上述した
sceneDepthはWWDC 2020開催時点で(iOS 14 beta 1時点で)既に公開されていたが、iOS 14 beta 5で突如smoothedSceneDepthなるAPIが追加された。https://twitter.com/shu223/status/1295968108352479232
このとき(2020年8月)ドキュメントをすぐに見に行ったが詳細は何も書かれてなかった。
今見に行くとちゃんと説明が書かれており、
sceneDepthとの違いも明記されている。DeepL利用の日本語訳を併記してここにまとめておく。smoothedSceneDepth: ARConfiguration.FrameSemantics
ARConfiguration.FrameSemanticsの型プロパティAn option that provides the distance from the device to real-world objects, averaged across several frames.
(デバイスから実世界のオブジェクトまでの距離を提供するオプションで、複数のフレームで平均化されます。)Declaration
static var smoothedSceneDepth: ARConfiguration.FrameSemantics { get }Discussion
Enable this option on a world-tracking configuration (ARWorldTrackingConfiguration) to instruct ARKit to provide your app with the distance between the user’s device and the real-world objects pictured in the frame's capturedImage. ARKit samples this distance using the LiDAR scanner and provides the results through the smoothedSceneDepth property on the session’s currentFrame.
(ワールドトラッキング設定(ARWorldTrackingConfiguration)でこのオプションを有効にすると、ユーザーのデバイスとフレームのキャプチャ画像に写っている現実世界のオブジェクトとの距離をアプリに提供するようにARKitに指示します。ARKit は、LiDAR スキャナーを使用してこの距離をサンプリングし、セッションの currentFrame の smoothedSceneDepth プロパティを通して結果を提供します。)LiDARスキャナーを使用することがここで明記されている。
To minimize the difference in LiDAR readings across frames, ARKit processes the data as an average. The averaged readings reduce flickering to create a smoother motion effect when depicting objects with depth, as demonstrated in Creating a Fog Effect Using Scene Depth. Alternatively, to access a discrete LiDAR reading at the instant the framework creates the current frame, use sceneDepth.
(フレーム間の LiDAR 読み取り値の差を最小化するために、ARKit はデータを平均として処理します。平均化された読み取り値は、「シーン深度を使用したフォグ効果の作成」で実証されているように、奥行きのあるオブジェクトを描写する際にフリッカーを低減し、より滑らかなモーション効果を生み出します。また、フレームワークが現在のフレームを作成した瞬間に個別の LiDAR 読み値にアクセスするには、sceneDepth を使用します。)ここが一番重要。
sceneDepthとの違いが明記されており、メリットや使い分けについても書かれている。ARKit supports scene depth only on LiDAR-capable devices, so call supportsFrameSemantics(:) to ensure device support before attempting to enable scene depth.
(ARKit は、LiDAR 対応デバイスでのみシーン深度をサポートしているので、シーン深度を有効にする前に supportsFrameSemantics(:) を呼び出してデバイスのサポートを確認してください。)smoothedSceneDepth: ARDepthData
ARFrameの
smoothedSceneDepthプロパティhttps://developer.apple.com/documentation/arkit/arframe/3674209-smoothedscenedepth
An average of distance measurements between a device's rear camera and real-world objects that creates smoother visuals in an AR experience.
(デバイスの背面カメラと実世界のオブジェクトの間の距離測定の平均値で、AR体験でよりスムーズなビジュアルを作成します。)Declaration
var smoothedSceneDepth: ARDepthData? { get }Discussion
This property describes the distance between a device's camera and objects or areas in the real world, including ARKit’s confidence in the estimated distance. This is similar to sceneDepth except that the framework smoothes the depth data over time to lessen its frame-to-frame delta.
(このプロパティは、デバイスのカメラと実世界のオブジェクトやエリアとの距離を記述します。これは sceneDepth と似ていますが、フレームワークがフレーム間の差分を減らすために深度データを時間の経過とともに滑らかにします。)This property is nil by default. Add the smoothedSceneDepth frame semantic to your configuration’s frameSemantics to instruct the framework to populate this value with ARDepthData captured by the LiDAR scanner.
(このプロパティはデフォルトでは nil です。フレームワークに、LiDAR スキャナによってキャプチャされた ARDepthData でこの値を入力するように指示するために、設定の frameSemantics に smoothedSceneDepth フレームセマンティックを追加します。)Call supportsFrameSemantics(:) on your app’s configuration to support smoothed scene depth on select devices and configurations.
(アプリの設定で supportsFrameSemantics(:) を呼び出して、選択したデバイスと設定で平滑化されたシーン深度をサポートします。)関連
ARKitの本、iOSデプスの本を書いています。
実践ARKit - BOOTH
Depth in Depth - iOSデプス詳解 - BOOTHアドベントカレンダー
本記事はiOSアドベントカレンダー20日目の記事です。2020年12月23日現在、エントリーされていた方が記事をアップされていなかったので、その方に確認の上記事を代理投稿させていただきました。
- 投稿日:2020-12-23T10:50:53+09:00
【Swift】非同期処理「Operation」について
Operation、OperationQueueクラス
非同期処理に関して、以前GCDの記事を投稿しましたが、
GCDはコアライブラリのlibdispatchで実装されています。同じくコアライブラリのFoundationにも非同期処理を実現するクラスである、
OperationクラスとOperationQueueクラスがあります。Operationクラスは、実行されるタスクとその情報をカプセル化したものです。
このOperationクラスのインスタンスがキューに入れられて順次実行されます。
この時にキューの役割を果たすのがOperationQueueクラスになります。実行方法
タスクの定義
GCDの時はタスクはクロージャ内に書いていましたが、
Operationを使う時は、Operationクラスのサブクラスとして定義します。クラスとして表すことによって少し手間ですがメリットもあり、
扱いやすいインターフェースを提供することができます。なお、処理内容はmain( )メソッドの中に記述します。
import Foundation import PlaygroundSupport PlaygroundPage.current.needsIndefiniteExecution = true class SomeOperation: Operation { let value: Int init(value: Int) { self.value = value } override func main() { // スレッドを1秒止める Thread.sleep(forTimeInterval: 1) print(value) } }これでタスクを定義することができたので、
次にこのタスクをキューに入れる必要があります。ですが、キューに入れる前にキューを作らなければいけません。
キューの生成
タスクを実行するキューとなるOperationQueueクラスのインスタンスを作成します。
OperationQueueクラスは引数なしのイニシャライザでインスタンス化できます。
インスタンス化後にプロパティ経由で各種設定を行います。import Fountation let queue = OperationQueue()・nameプロパティ
nameプロパティを設定するとキューに名前をつけることができます。
この値には逆順DNS形式を使用することが一般的です。・maxConcurrentOperationCountプロパティ
特定のキューが同時に実行できるタスク数はシステムの状況から適切に判断されますが、
maxConcurrentOperationCountプロパティを使うことでその数を指定することができます。当たり前ですが、大きな数を設定しても高速になる訳ではなく、
むしろ遅くなる可能性がありますのでご注意ください。・QualityOfService型
GCDにはQoSで実行優先度を決めることができましたが、
それのOperationQueueクラスバージョンがこれになります。QualityOfService型の値の役割はGCDのQoSと同様です。
import Foundation let queue = OperationQueue() queue.name = "com.example.my_operation_queue" queue.maxConcurrentOperationCount = 2 queue.qualityOfService = .userInitiatedキューとタスクは作ることができたので、
キューにタスクを追加する必要があります。キューへのタスクの追加
タスクの追加方法は、OperationQueueクラスの
addOperation(_:)メソッドを使用します。
引数にはOperationクラスの値を渡します。また、OperationQueueクラスには複数のOperationクラスを渡すためのメソッドである、
addOperations(_:waitUntilFinished:)も用意されています。第一引数の型は[Operation]型になります。
次のサンプルコードでは、
SampleOperation型の値を5個作成しキューに追加しています。第二引数の
waitUntilFinishedにtrueを与えると、
全てのタスクの実行が終わるまで呼び出したスレッドをブロックします。今回はfalseを与えているので、タスクの終了を待たずにそのまま次の処理を実行します。
import Foundation import PlaygroundSupport PlaygroundPage.current.needsIndefiniteExecution = true class SampleOperation: Operation { let value: Int init(value: Int) { self.value = value } override func main() { Thread.sleep(forTimeInterval: 1) print("value: \(value)") } } let queue = OperationQueue() queue.name = "com.example.my_operation_queue" queue.maxConcurrentOperationCount = 2 queue.qualityOfService = .userInitiated var operations: [SampleOperation] = [] for i in 0..<5 { operations.append(SampleOperation(value: i)) } queue.addOperations(operations, waitUntilFinished: false) print("OperationQueueにタスクを追加しました。") 実行結果 OperationQueueにタスクを追加しました。 value: 0 value: 1 value: 2 value: 3 value: 4
maxConcurrentOperationCount = 2のように指定しているため、
1秒おきにタスクを2つずつ実行していきます。
Thread.sleep(forTimeInterval: 1)をコメントアウトした場合は
下記のような実行結果になります。import Foundation import PlaygroundSupport PlaygroundPage.current.needsIndefiniteExecution = true class SampleOperation: Operation { let value: Int init(value: Int) { self.value = value } override func main() { // Thread.sleep(forTimeInterval: 1) print("value: \(value)") } } let queue = OperationQueue() queue.name = "com.example.my_operation_queue" queue.maxConcurrentOperationCount = 2 queue.qualityOfService = .userInitiated var operations: [SampleOperation] = [] for i in 0..<5 { operations.append(SampleOperation(value: i)) } queue.addOperations(operations, waitUntilFinished: false) print("OperationQueueにタスクを追加しました。") 実行結果 value: 0 value: 1 OperationQueueにタスクを追加しました。 value: 2 value: 3 value: 4タスクのキャンセル
Operationクラスにはタスクをキャンセルする機能を持つ
cancel()メソッドがあります。次のサンプルコードでは、先ほどのコードにcansel( )メソッドを追加しています。
operations[2].cancel()により value: 2が存在しないのがわかります。import Foundation import PlaygroundSupport PlaygroundPage.current.needsIndefiniteExecution = true class SampleOperation: Operation { let value: Int init(value: Int) { self.value = value } override func main() { print("value: \(value)") } } let queue = OperationQueue() queue.name = "com.example.my_operation_queue" queue.maxConcurrentOperationCount = 2 queue.qualityOfService = .userInitiated var operations: [SampleOperation] = [] for i in 0..<5 { operations.append(SampleOperation(value: i)) } queue.addOperations(operations, waitUntilFinished: false) operations[2].cancel() 実行結果 value: 0 value: 1 value: 3 value: 4タスクの依存関係の設定
Operationクラスでは複数のタスク間での依存関係を設定することができます。
依存関係を定義することにより、
あるタスクが終了してからでないとこのタスクを実行しないよ!といった処理ができます。あるタスクに対して、それよりも先に実行されるべきタスクを指定するには、
OperationクラスのaddDependency(_:)メソッドを使用します。引数には先に実行されるべきタスクを入れます。
import Foundation import PlaygroundSupport PlaygroundPage.current.needsIndefiniteExecution = true class FirstOperation: Operation { override func main() { print("This is FirstOperation") } } class SecendOperation: Operation { override func main() { print("This is SecondOperation") } } let queue = OperationQueue() queue.name = "com.example.my_operation_queue" queue.maxConcurrentOperationCount = 1 queue.qualityOfService = .userInteractive var operations: [Operation] = [] operations.append(FirstOperation()) operations.append(SecendOperation()) operations[0].addDependency(operations[1]) queue.addOperations(operations, waitUntilFinished: false) 実行結果 This is SecondOperation This is FirstOperationFirstOperation( )が先に実行されるはずでしたが、
operations[0].addDependency(operations[1])の部分で
依存関係を定義しているのでSecondOperation( )から実行されました。使うタイミング
複数の非同期処理を実装する
Operation、OperationQueueクラスは、
非同期処理をオブジェクト指向で抽象化したものです。単純なスレッドの切り替えの他に、
タスクの依存関係やキャンセル機能などが備わっているため、
GCDよりも複雑な処理に向いています。また、Operation、OperationQueueクラスは、内部でGCDを利用しているので
GCDで定義できることはOperation、OperationQueueクラスでも定義することができます。GCDはタスクをクロージャで表すことができ、
キューを生成しなくてもグローバルキューを利用できます。Operation、OperationQueueクラスはタスクの定義やキューの生成が必要ですが、
その分複雑な処理を行うことができます。したがって、単純な非同期処理はGCDで定義し、
キャンセルや依存関係の定義はOperation、OperationQueueクラスで定義しましょう!他にも非同期処理にはThreadクラスがありますが、
使う機会はほぼないらしいので紹介はしません。もし私が使う機会があったらその時に共有できたらなと思います。
以上、最後までご覧いただきありがとうございました。























