20210302のiOSに関する記事は9件です。

【Xcode12】Libraryを常時表示させる方法

■はじめに
・この記事ではXcode12.4を使用しています。

■方法
Libraryを常時表示するには、
『optionキーを押しながらLibraryボタンクリック』です。

スクリーンショット 2021-03-02 23.27.08.png
スクリーンショット 2021-03-02 23.24.05.png

連続してObjectsを追加する際などに有用です。

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

【WidgetKit】WidgetKitのSwiftUIでURLから画像を表示する方法

WidgetKitでは独特なライフサイクルから描写のタイミングに制限があり、
従来の方法である非同期では画像を表示することができません。

解決方法

Data(ContentsOf: url)を使って予め画像データを読み込んでから描画することでこれを解決することができます。

サンプル実装

struct SimpleEntry: TimelineEntry {

    let date: Date
    let configuration: ConfigurationIntent
    let url: String

    func getImageData() -> UIImage {
        if let imageUrl = URL(string: self.url),
           let imageData = try? Data(contentsOf: imageUrl),
           let image = UIImage(data: imageData) {
            return image
        }
        // 読み込めない時の画像とか
        return .init()
    }
}

struct widgetEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        Image(uiImage: entry.getImageData())
            .resizable()
            .aspectRatio(contentMode: .fill)
    }
}
この実装でこのように表示することができました。

Simulator Screen Shot - iPhone 12 Pro - 2021-03-02 at 17.23.00.png

最後に

非同期が使えないのは結構な罠だと思うので気をつけてくださいねー?

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

【SwiftUI】sheetメソッドを使ってビューをモーダル表示にする

先日リリースした私のアプリに使用した技術の解説です。
私のアプリはこちら。

sheetメソッドとは

ビューをモーダル表示させるメソッド。下から出てきて画面の一番手前に表示される。

実際の動作

「わかった!」ボタンを押すと下から答え合わせのビューが出てきて、画面の一番手前に表示されているのがわかるかと思います。
また、このシートは上下に動かすことができ、一定の位置まで下げて指を離すと閉じるようになっています。

基本の書き方

sheetメソッド
.sheet(isPresented: ブール値, content: { 表示させたいビュー })

sheetメソッドはさまざまな引数を取りますが、一番シンプルな書き方はこのようになります。isPresentedがtrueになったとき、contentの{ }内のビューが表示されます。

ソースコード

上のサンプルアプリのコードの一部です。

ContentView.swift
import SwiftUI

struct ContentView: View {

    @State var answer = ""
    @State var moveAnswer = false //シートを出すためのブール型の変数

    var body: some View {
        VStack{
            Text("なぞなぞ")
                .font(.title)
                .padding()
            Text("パンはパンでも白黒のかわいいパンは\nぱ〜んだ?")
            TextField("こたえを書いてね", text: $answer)
                .frame(width: 200)
                .padding()
                .border(Color.black)
            // シートを出すトリガーになるボタン
            Button(action: {
                moveAnswer = true //シートを出すためのアクション
            }, label: {
                Text("わかった!")
                    .padding()
                    .background(Color.green)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            })
            //moveAnswerがtrueになったときに出てくるシート
            .sheet(isPresented: $moveAnswer, content: {
                Answer(answer: answer, moveAnswer: $moveAnswer)
            })
        }
    }
}

こちらのコードを部分ごとにわけて解説したいと思います。

シートを出すための仕組み

まず、ContentViewのプロパティとしてブール型の変数を用意します。

    @State var moveAnswer = false //シートを出すためのブール型の変数

アプリ起動時はシートが出ていない状態にしたいため、初期値にfalseを入れています。逆に言えば、初期値をtrueにすればアプリ起動時にシートを出すことができます。

次は、シートを出すトリガーになるボタンです。

// シートを出すトリガーになるボタン
            Button(action: {
                moveAnswer = true //シートを出すためのアクション
            }, label: {
                Text("わかった!")
                    .padding()
                    .background(Color.green)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            })

ボタンをタップするとmoveAnswerにtrueが代入されます。

そして、sheetメソッドを見ていきます。

            .sheet(isPresented: $moveAnswer, content: {
                Answer(answer: answer, moveAnswer: $moveAnswer)
            })

ボタンのアクションによってmoveAnswerにtrueが入るとisPresentedがtrueになり、contentのAnswerビューが表示されます。

今回、AnswerビューはAnswer.swiftという別のファイルを作って書きました。そちらのビューも見てみましょう。

今回シートに表示したビュー

Answer.swift
import SwiftUI

struct Answer: View {

    let answer: String //前の画面のなぞなぞの答え
    @Binding var moveAnswer: Bool //前の画面のmoveAnswerを反映させるための変数

    var body: some View {
        VStack{
            //なぞなぞの答えが正解かどうかで表示を変える
            if answer == "パンダ" {
                Text("せいかい!!\nすごい!天才だ!")
                    .font(.title)
                Image("correct")

            }else{
                Text("ちがうよ。もういちどもんだいをよく読んでみよう。")
                    .font(.title)
                Image("fault")
            }
            //シートを閉じるためのトリガーになるボタン
            Button(action: {
                moveAnswer = false //シートを閉じるためのアクション
            }, label: {
                Text("もどる")
                    .font(.title)
                    .padding()
            })
        }
    }
}

動画内でもやっていたように、わざわざボタンを作らなくてもシートを閉じる操作はできますが、ボタンがあった方が便利な場面もあるかと思います。このボタンの作り方について解説したいと思います。

シートを閉じるためのボタンの作り方

シートを閉じるためには、ContentViewのmoveAnswerがfalseになる必要があります。そのため、まずはContentViewのmoveAnswerをAnswerビューに反映させます。

   @Binding var moveAnswer: Bool //前の画面のmoveAnswerを反映させるための変数

@Bindingをつけることで、このmoveAnswerに入れたブール値の状態を共有しています。つまり、AnswerのmoveAnswerがfalseになると、ContentViewのmoveAnswerもfalseに変わるようにしています。

そしてこちらがシートを閉じるためのトリガーとなるボタンです。

            //シートを閉じるためのトリガーになるボタン
            Button(action: {
                moveAnswer = false //シートを閉じるためのアクション
            }, label: {
                Text("もどる")
                    .font(.title)
                    .padding()
            })

このボタンを押すことでmoveAnswerにfalseが入り、シートを閉じることができます。

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

UnityでiOSのUIColorPickerViewControllerを使うPlugin

はじめに

iOS14から導入されたUIColorPickerViewControllerをUnityから使いたかったのでPluginを書きました。

レポジトリ

Unitypackage

UIColorPickerManager.unitypackage

Sample

public void Show(){
    Color currentColor = Color.white;
    UIColorPickerManager.Show(currentColor, OnSelectColor, OnFinish);
}

// call when color selected.
void OnSelectColor(Color selectedColor){
    // ...
}

// call UIColorPickerViewController finished.
void OnFinish(){

}

UIColorPickerManager.Show

UIColorPickerManager.Show にはいくつかのオーバーライドがあります。

選択された色をColorオブジェクトで受け取る

void Show (
    Color currentColor,
    OnColorSelectedCallback onColorSelectedCallback,  // Colorオブジェクトを受け取るコールバック関数
    OnFinishCallback onFinishCallback)

選択された色をRGBAで受け取る場合

void Show (
    Color currentColor,
    OnRGBColorSelectedCallback onRGBColorSelectedCallback,  // RGBAを受け取るコールバック関数
    OnFinishCallback onFinishCallback)

選択された色をColorオブジェクトで受け取り、iOS14未満の時の処理を必要とする場合

void Show (
    Color currentColor,
    OnColorSelectedCallback onColorSelectedCallback,
    OnFinishCallback onFinishCallback,
    OnEarlierIOSVersionsCallback onEarlierIOSVersionsCallback)

選択された色をRGBAで受け取り、iOS14未満の時の処理を必要とする場合

void Show (
    Color currentColor,
    OnRGBColorSelectedCallback onRGBColorSelectedCallback,
    OnFinishCallback onFinishCallback,
    OnEarlierIOSVersionsCallback onEarlierIOSVersionsCallback)

参考:上記のコードで使われているdelegateの定義

    // - colorPickerViewControllerDidSelectColor: 用コールバック
    public delegate void OnColorSelectedCallback(Color color);
    public delegate void OnRGBColorSelectedCallback (float r, float g, float b, float a);

    // - colorPickerViewControllerDidFinish: 用コールバック
    public delegate void OnFinishCallback();

    // @available(iOS 14.0, *) がfalseの時用コールバック
    public delegate void OnEarlierIOSVersionsCallback();

ライセンス

ライセンスはMITです。

補足

いないかもしれませんが、ソースコードを読みたいという方がいれば、
先に、 [Unity]ネイティブプラグインからコールバック関数を呼ぶ[iOS]
という記事を読むことをお勧めします。このプラグインは、その記事の応用になっています。

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

[Swift5]UITextViewのキーボードをタップで閉じる方法(シンプル)

やりたいこと

UITextViewで文字を入力した後にキーボードを閉じたい場面があると思います。
また、Viewのどこをタップしてもキーボードを閉じることができれば便利ですよね。今回はそのような実装方法を紹介します。

コード紹介

 // Viewタップでキーボードを閉じる
 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
     self.view.endEditing(true)
 }

以上です。

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

iOS 14 / watchOS 7 HealthKit 変更点

iOS 14 / watchOS 7 から HealthKit に症状を記録するデータタイプや、歩行に関するより詳細なデータタイプが追加されるなどの変更がありました。

ECG 関連のアップデートが中心な感じですが、2021.1 の watchOS 7.3 アップデートで日本でも ECG が使えるようになったのでまとめてみました。

心拍に関するデータタイプ HKElectrocardiogramType の追加

wathOS 7.3 (iOS 14.4) より、日本でも Apple Watch で心電図(ECG)が使えるようになりました。アメリカので二年遅れてついに日本で使えるようになりました。Apple Watch S4,5,6 で使用可能です。

https://www.apple.com/jp/newsroom/2021/01/ecg-app-and-irregular-rhythm-notification-coming-to-apple-watch/

不規則な心拍(心房細動(AFib)の兆候がある不規則な心拍リズム)の通知を受け取ることができるようになり、心電図 Apple Watch アプリを使うと、心房細動、洞調律、低心拍数、高心拍数、判定不能のいずれかに分類することができるようになっています。

HKElectrocardiogramType

この有無が記録されている HKElectrocardiogram をチェックすることが iOS14/watchOS7 SDK からできるようになりました。

let healthStore = HKHealthStore()
let healthTypes = Set([
    HKCategoryType.electrocardiogramType()
])

healthStore.requestAuthorization(toShare: nil, read: healthTypes)
    { (success, error) in
        // ...
    }

https://developer.apple.com/documentation/healthkit/hkelectrocardiogram?changes=latest_major

HKElectrocardiogram を取得するには HKSampleQuery で取得します。

let ecgType = HKObjectType.electrocardiogramType()

let ecgQuery = HKSampleQuery(sampleType: ecgType,
                             predicate: nil,
                             limit: HKObjectQueryNoLimit,
                             sortDescriptors: nil) { (query, samples, error) in
    if let error = error {
        fatalError(error.localizedDescription)
    }

    guard let ecgSamples = samples as? [HKElectrocardiogram] else {
        fatalError(String(describing: samples))
    }

    for sample in ecgSamples {
        print(sample)
    }
}

healthStore.execute(ecgQuery)

HKElectrocardiogramQuery に HKElectrocardiogram オブジェクトを渡して詳細な測定値を取得できます。

let voltageQuery = HKElectrocardiogramQuery(ecgSample) { (query, result) in
    switch(result) {

    case .measurement(let measurement):
        if let voltageQuantity = measurement.quantity(for: .appleWatchSimilarToLeadI) {
            print(voltageQuantity)
        }

    case .done:
        print("done")

    case .error(let error):
        fatalError(error.localizedDescription)

    }
}

healthStore.execute(voltageQuery)
-4.39537 mcV
1.91129 mcV
8.0672 mcV
14.0773 mcV
... 省略

HealthKit で症状を記録することができるデータタイプの追加

iOS 13.6 / watchOS 7 以降から症状についての記録のためのデータタイプが追加されています。

iOS 14 でもさらに追加され、「寒気」「鼻水が出る」「咳」「腰の痛み」「物忘れ」「頭痛」「胸の痛み」「ニキビ」など、ほか含め合計 39 種類の症状が発生したことを記録する HealthKit 対応のログアプリなどが作れるようになりました。

全て HKCategoryTypeIdentifier のデータタイプになっているので、他の HKCategoryType のサンプルのように取り扱うことができるようになっています。

https://developer.apple.com/documentation/healthkit/data_types/symptom_type_identifiers

歩行速度や歩幅、階段の昇降速度のサンプルデータ型が追加

歩くことに関するいくつかのサンプルデータが追加されました。全て HKQuantityType 型のサンプルデータになります。

6 分間歩行の推定データが取得できるようになりました。

static let sixMinuteWalkTestDistance: HKQuantityTypeIdentifier

6 分間歩行とは何か、近畿中央呼吸器センターさんのホームページに記載されている内容を引用します。

6 分間歩行試験とは、6 分間平地を歩いていただくことによって、肺や心臓の病気が日常生活の労作にどの程度障害を及ぼしているのか調べるための検査です。https://kcmc.hosp.go.jp/shinryo/hokou.html

歩行の速度や歩幅を示すデータ、さらに歩幅が左右で違う割合だとか、歩いている時に両足が地面についている瞬間の割合だとか、歩くことに関する複雑なデータが取得できるようになりました。

static let walkingSpeed: HKQuantityTypeIdentifier
static let walkingStepLength: HKQuantityTypeIdentifier
static let walkingAsymmetryPercentage: HKQuantityTypeIdentifier
static let walkingDoubleSupportPercentage: HKQuantityTypeIdentifier

階段登り下りの速度のデータが取得できるようになりました。

static let stairAscentSpeed: HKQuantityTypeIdentifier
static let stairDescentSpeed: HKQuantityTypeIdentifier

「手洗いした」データタイプが追加

watchOS 7 / Apple Watch S4,5,6 で、手洗いをマイクとモーションセンサーを使って自動で検知するようになりました。そのログデータを取り出すことができるようになります。

static let handwashingEvent: HKCategoryTypeIdentifier

HKSampleQuery で他のデータと同じようにサンプルを取り出すことができます。

SwiftUI で昨日今日の handwashingEvent を取得する例。他の HKCategoryType のデータタイプも同様の書き方で取得することができます。

import SwiftUI
import HealthKit

var healthStore: HKHealthStore!

struct ContentView: View {

    var body: some View {
        Button(action: {
            checkHealthKit()
        }, label: {
            Text("Button")
        })
    }

    func checkHealthKit() {
        healthStore = HKHealthStore()
        let healthTypes = Set([
            HKCategoryType.categoryType(forIdentifier: .handwashingEvent)!
        ])

        healthStore.requestAuthorization(toShare: nil, read: healthTypes)
            { (success, error) in
                fetchHandWashing()
            }
    }

    func fetchHandWashing() {
        let now = Date()
        let calendar = Calendar.current
        let yesterday = calendar.date(byAdding: .day, value: -1, to: calendar.startOfDay(for: now))
        let predicate = HKQuery.predicateForSamples(withStart: yesterday, end: now, options: [])

        let sortDescriptor = [NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: true)]
        let washingEvent = HKCategoryType.categoryType(forIdentifier: .handwashingEvent)!
        let query = HKSampleQuery(sampleType: washingEvent,
                                        predicate: predicate,
                                        limit: HKObjectQueryNoLimit,
                                        sortDescriptors: sortDescriptor) {
            (query, results, error) in

            guard error == nil else { print("error"); return }

            let format = DateFormatter()
            format.dateFormat = "yyyy-MM-dd HH:mm:ss"
            format.timeZone   = TimeZone(identifier: "Asia/Tokyo")
            print("\(format.string(from: yesterday!)) - \(format.string(from: now)) result ?:")

            if let tmpResults = results as? [HKCategorySample] {
                tmpResults.forEach { (sample) in
                    print(sample)
                }
            }
        }
        healthStore.execute(query)
    }
}

ヘッドホンから有害とされる音量を受けた時の記録のデータタイプの追加

ヘッドホンからの音、うるさすぎるよ!っていうものです。

static let headphoneAudioExposureEvent: HKCategoryTypeIdentifier

これに合わせて、環境音がうるさすぎるの時のデータタイプ名はカテゴリを正しく表記する名称に変更になりました。

~~static let audioExposureEvent: HKCategoryTypeIdentifier~~

static let environmentalAudioExposureEvent: HKCategoryTypeIdentifier

ワークアウトタイプの追加

HKWorkoutActivityTypeCardioDance
HKWorkoutActivityTypeSocialDance
HKWorkoutActivityTypePickleball
HKWorkoutActivityTypeCooldown

Cardio Dance は YouTube とかで検索するといっぱい出てきますね。

Social Dance はフォークダンスなどパートナーとか何人かでやるダンス。

Pickle Ball っていう新しいアメリカで流行り始めている?スポーツが追加されたようです。歴史的には 50 年ぐらいあるらしい。 https://www.playpickleball.com

Cooldown は運動前後のクールダウン的な運動やストレッチを明示的にするための定数ですね。Apple Fitness+ サービス(日本では提供されていませんが)のメニューに対応した定数が今後も増えそうな気がします。

(iOS 14.3 / watchOS 7.3 以降)妊娠出産に関するヘルスケアデータタイプの追加

妊娠出産関連のカテゴリタイプが追加されたので、それに対応するデータタイプが追加されています。

static let contraceptive: HKCategoryTypeIdentifier // 避妊薬
static let lactation: HKCategoryTypeIdentifier // 授乳
static let pregnancy: HKCategoryTypeIdentifier // 妊娠

(iOS 14.3/watchOS 7.2 以降) HKSample.hashUndeterminedDuration の追加

var hasUndeterminedDuration: Bool { get }

サンプルの endDate プロパティが distantFuture の場合 true になります。

distantFuture とは「遠い未来の日付」で、イベントの終了待ちをするまでのテンポラリ値として入れておくなどの使い方をしたりします。

print(Date.distantFuture)
// 4001-01-01 00:00:00 +0000

おわり

HealthKit を解説している拙著「HealthKit Book for Beginners」にも本記事を追加しましてアップデート(v3.0)しましたので、購入していただいた方は PDF 版再ダウンロードおねがいしますー。(製本版は v1.0 のままです)

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

TodoAPPでRxSwift入門[part2]

概要

最近RxSwiftを勉強し始めて現在理解していることを備忘録として残せたらいいなと思い記事にします。
そもそもRxSwiftのRxとは

Rx(Reactive X)とは、「オブザーバパターン」「イテレータパターン」「関数型プログラミング」の概念を実装している拡張ライブラリです。
Rxを導入するメリットは、「値の変化を検知できる」「非同期の処理を簡潔に書ける」ということに尽きると思います。 値の変化というのは変数値の変化やUIの変化も含まれます。 例えばボタンをタッチする、という動作もボタンのステータスが変わったと捉えることができRxを使って記述することができます。

とのことです。
詳しくは以下のサイトを参照してください。
入門!RxSwift
RxSwiftについてようやく理解できてきたのでまとめることにした(1)

今回はPart2になります。
前回の記事はこちら
TodoAPPでRxSwift入門[part1]

画面構成

Simulator Screen Shot - iPhone 11 - 2021-03-02 at 09.01.56.png

画面はこんな感じにします。
タイトルを入力するためのUITextFieldと詳細を入力するためのUITextViewとTodoを登録するためのUIButtonをスタックビューに入れてあげてます。

画面上部のWelcome to Simple Todo Appはただのデザインで入れてみただけなのでなくても大丈夫です。
下部のShow Todo Listは登録したTodoを見るためのボタンになっています。

UIの配置が面倒な人はこの記事が完結した時にgithubにあげるのでそこのStoryboardからコピペしてください。

AddTodoViewModel

前回の記事の最後に予告したViewModelの作成に移ります。
そもそもViewModelとは、なんでもかんでもViewControllerに書いちゃったらViewControllerがめっちゃ長くなって見にくいしあまりよろしくないよねということからMVVMというアーキテクチャが編み出されてそれのVM(ViewModel)の部分に当たるものです。
詳しいことはググってください。

では早速コードのほうを書いていきましょう。

AddTodoViewModel.swift
import Foundation
import RxCocoa
import RxSwift

protocol AddTodoViewPresentable {
    typealias Input = (
        titleText: Driver<String>,
        detailText: Driver<String>
    )
    typealias Output = (
        isValid: Driver<Bool>, ()
    )

    var input: AddTodoViewPresentable.Input { get }
    var output: AddTodoViewPresentable.Output { get }
}

これで全部ではないですがとりあえずここまでで一旦解説を挟みます。
まずprotocol?と思われた方もいるかもしれないですがこれはプログラムを疎結合にするためです。疎結合とは?の方は

iOSアプリの基本設計を考える:疎結合の概念から構造化、MVVM、RxSwiftまで

を参照してください。

typealiasでInput(入力を受けとる)とOutput(受け取った値を変換したりして返す)を作っています。ちなみにtappleは要素が一つだけだとエラーになるのでOutputにはisValid()にしています。
次にDriverの説明をします。
Driverとは
・エラーを流さない
・メインスレッドでの通知が保証される
・shareReplayLatestWhileConnectedを使ったCold-Hot 変換
の特徴を持ったRxSwift(RxCocoa)独自のものらしいです。

今度はAddViewModelの中身です。

AddTodoViewModel.swift
class AddTodoViewModel: AddTodoViewPresentable {
    var input: AddTodoViewPresentable.Input
    var output: AddTodoViewPresentable.Output

    var storeManager: StoreManager

    init(input: AddTodoViewPresentable.Input, storeManager: StoreManager) {
        self.input = input
        self.output = AddTodoViewModel.output(input: self.input)
        self.storeManager = storeManager
    }
}

private extension AddTodoViewModel {
    static func output(input: AddTodoViewPresentable.Input) -> AddTodoViewPresentable.Output {
        let titleObservable = input.titleText.asObservable()
        let detailObservable = input.detailText.asObservable()

        let isValid = Observable.combineLatest(titleObservable, detailObservable) { (title, detail) -> Bool in
            return !title.isEmpty && !Validator.removeSpaceAndNewLine(text: detail).isEmpty
        }.asDriver(onErrorJustReturn: false)

        return (
            isValid: isValid, ()
        )
    }
}

extension AddTodoViewModel {
    func insertTodoToFireStore(title: String, detail: String) {
        storeManager.insertTodoToFireStore(title: title, detail: detail)
    }
}


ここはそこまで説明いらないかなと思いますが、 ViewModelを先程のPresentableに準拠させていないとエラーになるのでfixでもおしておいてください。
outputを初期化するためのoutput関数ですが外部から利用されないようにするためと読みやすくするためにprivate extensionで拡張します。
また、staticがついているのは、ついていないと変数のoutputが初期化されていないのにそんなの使えないよと怒られるのでこうしています。
initでクロージャでもいいとは思いますがそれだとinitが長くなってしまうので今のところこれがベストかと思いこうしています。

output関数の中を少し解説します。
inputで受け取った値をasObservable()でObservableに変えています。

そしてcombineLatest(observable1, observable2)

(違う型でも可の)直近の最新値同士を組み合わせたイベントを作ります。

引数に指定した値のどちらかがが変化すれば{}ないの処理を実行する感じですかね?

あと、さらっとValidatorというのが出ていますがこれはdetailが空白や改行のみだとfalseを返します。
コードはこちらです。

Validator.swift
import Foundation

class Validator {

    static func removeSpaceAndNewLine(text: String) -> String {
        var removeText = text.trimmingCharacters(in: .init(charactersIn: " "))
        removeText = removeText.trimmingCharacters(in: .init(charactersIn: " "))
        removeText = removeText.trimmingCharacters(in: .init(charactersIn: "\n"))
        return removeText
    }

}

Stringを返していますがBoolを返せばよかったと思います。

まとめ

正直あまり理解できていないところもあり、まだまだ勉強しないといけないことだらけで泣きそうです。
疎結合なプログラムも連鎖的なバグを防ぐためなのはなんとなく理解できていますが、その恩恵を得られるほどのプログラムを書いた経験がないのでパッとしないです。
もっと勉強して就職して恩恵を感じたいですね。
次回はViewControllerになります。

TodoAPPでRxSwift入門[part3]

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

【iOS】LiDARセンサーを用いたサンプルコード集を公開しました!

iOSのLiDARセンサーを用いたサンプルコード集をGitHubに公開したので紹介します。
本記事の執筆時点でGitHubで91スターを頂いています!

TokyoYoshida/ExampleOfiOSLiDAR
?よさそうだと思った方はスターをお願いします⭐

例えば、こんなサンプルが入っています。

iPad Pro/iPhone 12 ProにLiDARが搭載されたとき、いつかは現実世界をスキャンしてみたいと思っていたかたもいらっしゃると思いますが、このサンプルを使えば簡単に実現できてしまいます。

ビルド方法

XCodeでExampleOfiOSLiDAR.xcodeprojを開いてビルドして、iPhone 12 Pro/Pro MAXかLiDARセンサーを搭載したiPad Proにインストールします。

サンプル一覧

Depth Map

深度情報を画像にしたものです。スクリーンショットは静止画ですがリアルタイムに動作します。

Confidence Map

深度情報の信頼度を画像にしたものです。リアルタイムに動作します。

Collision

現実世界に、仮想的なオブジェクトがぶつかったときの判定をします。

Export

現実世界のオブジェクトをスキャンして、.objファイルにエクスポートします。
エクスポートしたファイルはXcode,Macのプレビューなどで閲覧したり、Blenderで編集できます。


?

Scan with Texture

現実世界のオブジェクトをカラースキャンします。こちらは、サンプルアプリ内でのみ閲覧できます。

最後に

LiDARセンサーはなかなかおもしろい技術なので、これからも追っていきたいと思っています。作品やサンプルができたら、Twitterで発信していきますのでフォローをお願いします?

Twitter
https://twitter.com/jugemjugemjugem

Note こちらはiOS開発、AR、機械学習などについては発信しています。
https://note.com/tokyoyoshida

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

Swiftでカメラキャプチャーするための最小コード

準備

Info.plist に以下の設定を追加。

    <key>NSCameraUsageDescription</key>
    <string>[カメラ使用に関する説明]</string>

コード

import UIKit
import AVFoundation

class CaptureViewController: UIViewController {

    private let previewLayer: AVCaptureVideoPreviewLayer = {
        let previewLayer = AVCaptureVideoPreviewLayer()
        previewLayer.videoGravity = .resizeAspectFill
        return previewLayer
    }()

    init() {
        super.init(nibName: nil, bundle: nil)

        let session = AVCaptureSession()
        previewLayer.session = session

        if let device = AVCaptureDevice.default(for: .video),
           let input = try? AVCaptureDeviceInput(device: device),
           session.canAddInput(input) {
            session.addInput(input)
        }

        let output = AVCaptureVideoDataOutput()
        output.setSampleBufferDelegate(self, queue: .global())
        if session.canAddOutput(output) {
            session.addOutput(output)
            output.connection(with: .video)?.videoOrientation = .portrait
        }

        session.startRunning()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        view.layer.addSublayer(previewLayer)
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        previewLayer.frame = CGRect(origin: .zero, size: view.bounds.size)
    }

}

extension CaptureViewController: AVCaptureVideoDataOutputSampleBufferDelegate {

    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        // sampleBufferを処理する
    }

}

環境

Xcode 12.2

Apple Swift version 5.3.1 (swiftlang-1200.0.41 clang-1200.0.32.8)
Target: x86_64-apple-darwin19.6.0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む