20200523のSwiftに関する記事は7件です。

【Swift】Protocolとはなにか、どんな時に便利か

Protocolとは

Protocolとはある機能を実現するclassやstructが必ず実装するべきメソッドやプロパティの名前や型を定義するものです。
他の言語いう抽象クラスやInterfaceにあたります。

protocol CanFly {
    func fly() // 実際の処理は書かない
}

protocolを定義するときは、必要なメソッドやプロパティの名前や型を定義するのみで実際の処理は書きません。

どんなときに便利か

特定のclassやstructが持っているべき要件(このメソッドやプロパティを持っているべき)を適用したい場合に便利です。
例えばBirdクラスを作ります。鳥の特徴は空を飛べて、メスは卵を産みますね。

class Bird {
    var isFemale = true
    func layEgg() {
        if isFemale {
            print("トリは卵を産みました")
        }
    }
    func fly() {
        print("トリは翼で羽ばたいて空を飛びました")
    }
}

次にEagleクラスとPenguinクラスを作りましょう。どちらも鳥の一種なのでBirdクラスを継承して定義してみます。
Eagleの特徴は上昇気流に利用して高く上がって滑空して移動すること、Penguinの特徴は泳ぐことですね。
ここでインスタンスを作って使えるメソッドを確認してみます。

class Eagle: Bird {
    func soar() {
        print("タカは上昇気流に乗って高く上がりました")
    }
}

class Penguin: Bird {
    func swim() {
        print("ペンギンは翼で羽ばたいて泳ぎました")
    }
}

let myEagle = Eagle()
myEagle.layEgg() // "トリは卵を産みました"
myEagle.fly() // "トリは翼で羽ばたいて空を飛びました"
myEagle.soar() // "タカは上昇気流に乗って高く上がりました"

let myPenguin = Penguin()
myEagle.layEgg() // "トリは卵を産みました"
myEagle.fly() // "トリは翼で羽ばたいて空を飛びました"
myEagle.swim() // "ペンギンは翼で羽ばたいて泳ぎました"

一見良さそうですが、ペンギンは空を飛ぶことはできませんのでこの実装だとおかしなことになってしまいます。

更に同じく空を飛ぶAirplaneクラスを作る場合はどうなるでしょう。Birdクラスがflyメソッドを持っていますが、翼で羽ばたくわけではないのでオーバーライドして少し内容を変えましょう。

class Airplane: Bird {
    override func fly() {
        print("飛行機はエンジンを使って空を飛びました")
    }
}

let myAirplane = Aiplane()
myAirplane.layEgg() // "トリは卵を産みました"
myAirplane.fly() // "飛行機はエンジンを使って空を飛びました"

これもよく見るとBirdクラスが持っているlayEggメソッドも継承しているので飛行機が卵を産むことになっていますね。おかしいです。
それなら継承せずに必要なクラスにだけflyメソッドを定義すればよいのですが、プログラムの規模が大きくなってくると、複雑になりそうですし実装漏れも起きそうです。

Protocolを使って実装する

そんなときにProtocolを使うと実装が必要なメソッドやプロパティを定義しておけるので便利です。
例えば今までの例でいうとEagleとAirplaneはflyメソッドを持っている必要がありますが、Penguinは必要ないですね。
従ってEagleとAirplaneのみにCanFlyを適用します。

protocol CanFly {
    func fly() // 必要なメソッドの名前を書くだけ、実装はclassやstructの定義時に書く
}

class Bird {
    var isFemale = true
    func layEgg() {
        if isFemale {
            print("トリは卵を産みました")
        }
    }
}

class Eagle: Bird, CanFly { // CanFlyを適用する
    func fly() { // flyメソッドを実装しないとエラーになる
        print("タカは翼で羽ばたいて空を飛びました")
    }
    func soar() {
        print("タカは上昇気流に乗って高く上がりました")
    }
}

class Penguin: Bird { // Penguinは飛べないのでCanFlyは適用しない
    func swim() {
        print("ペンギンは翼で羽ばたいて泳ぎました")
    }
}

struct Airplane: CanFly { // CanFlyを適用する
    func fly() {  // flyメソッドを実装しないとエラーになる
        print("飛行機はエンジンを使って空を飛びました")
    }
}

こうするとペンギンが空を飛んだり、飛行機が卵を産んだりしなくて済みます。
因みに継承はclassではできてstructではできませんが、protocolの適用はclass/structどちらでもできるのが特徴です。

参考

https://docs.swift.org/swift-book/LanguageGuide/Protocols.html
https://www.udemy.com/course/ios-13-app-development-bootcamp/

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

Swift学習記#5 「カウントアプリの開発」

はじめに

前回の投稿で、swiftでの開発を行う上で必要な
基礎知識のアウトプットも終了しました。

とりあえずソースコードを見て、おおよそ何がしたいのか
理解できる様になってきたので、
今回からは実際にアプリを開発しながら
更なるレベルアップをして行きたいと思います。

もちろん知らないメソッドやプロパティなどが出てきたら、
随時アウトプットして行きます。

カウントアプリ開発

ということで、今回は簡単なカウントアプリの開発を通して
開発の手順と、Xcodeの機能の把握を行っていこうと思います!

開発環境

swift5.2.2Xcode(Version11.4.1)で、
シミュレーターはiphone11で行っています。

仕様書の作成

まずはXcodeを開く前にアプリの仕様書を作成しましょう。
最初はこんな感じで、
スクリーンショット 2020-05-19 15.07.50.png

①アプリの仕様、②作成手順、③アプリイメージくらいの
簡単な仕様書でいいと思います。

技術力を上げると共に仕様書の書き方もマスターしましょう!

新規プロジェクトの作成

基礎編の投稿ではplaygroundを利用していましたが、
アプリの開発ではprojectを利用して行きます。

新規プロジェクトを作成するには、
Xcodeを開きCreate a new Xcode projectを選択します。
スクリーンショット 2020-05-18 11.32.30.png
すると、こんな感じの画面に移ります。

今回作るアプリのViewページは1つなので、
Single View Appを選択します。
スクリーンショット 2020-05-18 11.36.13.png
すると、今度はこんな感じの画面に移ります。
スクリーンショット 2020-05-18 11.43.19.png

ここでは、プロジェクトの名前やドメインなどを決めて行きます。
Product Name
 プロジェクトの名称を決めます。
 今回は、カウントアプリなのでCountAppと入力しましょう。

Team
 これは、実機テストをする際には設定する必要がありますが、
 今回は設定しません。

Organization Name
 会社などの場合は入力が必要ですが、
 基本は入力しなくても大丈夫です。

Organization Identifier
 任意の値でOKです。
 しかし、App Storeにリリースする場合はこの値で
 アプリを識別するため、重複しないものでないといけません。
 オススメは、自身のメールアドレスを逆さにする事です。

Language
 使用する言語は、SwiftとObjective-Cを選べます。
 今回は、swiftを選びましょう。

User Interface
 ここでは、Storyboardを選択してください。

この後、プロジェクトを保存する場所を選択して、
新規プロジェクトの作成は終了です。

次からは、いよいよ開発に入って行きます!

アプリケーションの開発

ここからの作業は、アプリ仕様書の作業手順2以降の作業となります。

作業手順2 パーツの配置

2−1 配置

まずは、左側にあるNavigatorArea
Main.storyboardを開きます。
スクリーンショット 2020-05-19 12.40.19.png
ここに、ボタンやラベルなどのパーツを付け足して行きます。

次にLibraryを開いて、「UILabel」×1、「UIButton」×2を配置します。
Libraryは右上にある「+」のボタンから開けます。
スクリーンショット 2020-05-19 15.11.28.png
スクリーンショット 2020-05-19 15.15.11.png
今の段階では大きさや位置の調節などは行わなくて大丈夫です。

2−2 位置、大きさ

まずは、UILabelの調節から行って行きます。
① Utility areaを表示させる
② UILabelのTextを「0」に、FontのSizeを「100」に変更
③ Alignmentを中央に変更
スクリーンショット 2020-05-19 15.43.58.png

次にUIButtonの調節を行います。
① 左のUIButtonのUtility areaを表示させる
② UIButtonのTextを「UP」に、FontのSizeを「50」に変更
③ 右のUIButtonのUtility areaを表示させる
④ UIButtonのTextを「DOWN」に、FontのSizeを「50」に変更
スクリーンショット 2020-05-19 15.52.44.png
スクリーンショット 2020-05-19 16.02.30.png

最後にパーツの位置を調整して行きます。
調節にはXcodeのAutoLayoutの機能を利用します。

*AutoLayout機能

 AutoLayout機能とは、画面やパーツに幅や高さ、余白、位置などの
 制約を設定し、表示を調整できる機能です。

 また、ここで設定した機能は、
 縦、横の表示が変更されても自動で調節されます。

まずは、UILabelの余白の制約を設定します。
①右下の5つあるAutoLayout機能からAdd New Constraints
 を選択する。
Add New Constraintsの上部で余白を設定できます。
*注意:四角についている赤い点線が、直線になった時のみ
    入力した値が有効になります。
③下部にるAdd3~をクリックし、
 設定した余白がおかしくないか確認する。
スクリーンショット 2020-05-23 10.40.42.png
適用後
スクリーンショット 2020-05-23 10.56.18.png
こんな感じになっていればクリアです。

次に、今のままでは右寄りになっているUPボタンDOWNボタン
位置を設定して行きます。
①右下の5つあるAutoLayout機能からAlignを選択する。
Horizontally in ContainerVertically in Container
 値を入力する。
 ・「UPボタン」にHorizontally「-80」、Vertically「150」
 ・「DOWNボタン」にHorizontally「80」、Vertically「150」

 *Horizontally in Containerは水平方向の
  Vertically in Containerは垂直方向の位置を定義する。

水平方向とは、
スクリーンショット 2020-05-23 11.04.07.png
垂直方向とは、
スクリーンショット 2020-05-23 11.04.07のコピー.png
中央線から見て、プラス方向であればプラスの値を、
マイナス方向であればマイナスの値を入力する。

③下部にるAdd2~をクリックし、
 設定した位置がおかしくないか確認する。
スクリーンショット 2020-05-23 11.29.30.png
こんな感じになればクリア!

ここまでで、手順2は終了です。
ただ1つ注意して欲しいのが、AutoLayout機能についてです。

AutoLayout機能は便利ですが、
「値を間違った」、「思った通りの表示にならなかった」などで
調節したい時は、Utility areaのSize Inspecterから
行ってください。

AutoLayoutからもう一度値を入力すると、
二重で定義することとなり、エラーが起きてしまします‼︎
スクリーンショット 2020-05-23 11.38.25.png

作業手順3 パーツの接続

Main.storyboard上に配置したラベルやボタンを、
ソースコードから扱えるようにOutletやActionの接続を行います。

3−1 Outlet接続

Outlet接続とは、Storyboard上のパーツを、
ソースコード上の変数として使えるようにする仕組みです。

①まずは、Assistant editorを開きます。
 Assistant editorは右上のボタンから開けます。
スクリーンショット 2020-05-23 11.56.19.png
スクリーンショット 2020-05-23 11.59.35.png

②次に、Storyboardの「UILabel」を選択し、
 Controlキーを押しながら、Assistant editorで開いた
 View Controllerのソースコード上にドラッグ&ドロップします。
 Nameの部分には、「Label」と入力しておきましょう。
スクリーンショット 2020-05-23 12.06.43.png
 するとこんな感じになるはずです。
スクリーンショット 2020-05-23 12.09.28.png
 これで、Outlet接続は完了です。

3−2 Action接続

ここでは、ボタンの押されたときにアクションが動く様に
ソースコードとの関連づけを行います。

①まずは、「UPボタン」をControlを押しながら、
 Assistant editorにドラッグ&ドロップします。 
 そして、NameはUPBotton、TypeにはAnyと入力しましょう。
スクリーンショット 2020-05-23 12.20.57.png
 「DOWNボタン」も同じ手順でドラッグ&ドロップし、
 NameはDOWNBotton、TypeにはAnyと入力しましょう。
スクリーンショット 2020-05-23 12.23.08.png
するとこんな感じになるはずです。
スクリーンショット 2020-05-23 12.23.25.png
 これで、Action接続は完了です。

作業手順4

ここで、接続したアクションに処理を書いて行きます。
コードはそこまで難しくないので、この手順ではソースコードを
そのまま掲載します。

ViewController.swift
//
//  ViewController.swift
//  CountApp
//

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBOutlet weak var Label: UILabel!

    var number = 0

    @IBAction func UPBotton(_ sender: Any) {
        number = number + 1
        Label.text = String(number)
    }

    @IBAction func DOWNBotton(_ sender: Any) {
        if number < 1 {
            return
        } else {
            number = number - 1
            Label.text = String(number)
        }
    }
}

これでアクションの定義は完了です。

コードの詳しい解説はしなので、わからない事があれば
自分で調べてみてください!

作業手順5

ここまで来たらあとはテストです。
せっかく作っても動かないのでは意味がありませんからね。
シミュレーターでテストしてみましょう!

Build手順

①まずは、左上の「▶️」のボタンを押して、アプリをBuildします。
 この手順で、シミュレーターにアプリのデータがインストールされます。
②シミュレーターが起動するとアプリが起動するので、
 期待どうりの動作をするか確認しましょう!
 特に、DOWNボタンを押して0以下にならないかは
 見ておいてください。
スクリーンショット 2020-05-23 12.46.44.png
問題なければ完成です‼︎
エラーが出た場合は、諦めずに
エラーの箇所の解説に戻って再度挑戦しましょう‼︎

今回は小規規模の開発だったため、一度しかBuildしませんでしたが、
開発の規模が大きいのであれば、こまめにBuildすることをオススメします。

これまで

これでカウントアプリの開発は終わりです。
今回はボタンが二つだけでしたが、
複数配置して機能を増やしてみても面白いと思います‼︎

AutoLayout機能あたりは慣れないとミスすることも多いですが、
慣れてくると便利に感じてきます。
頑張って慣れましょう!

今回は初めての開発ということで、
画像を多めに使い、解説も出来るだけ詳しくすることを意識しています。
そのため、ごちゃごちゃしていて読みづらいかもしれません。
わかりにくい事があれば、コメントしてください‼︎

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

Swift、はじめました。

こんにちは、ka-min と申します。

swiftでのプログラミングをはじめました。

今回の成果は、iphoneへの転送です。

Macbook Air → iPhoneXs
に無事転送できました。

IMG_6120.PNG

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

[Swift] if文 を if式 にしてみた

if文は式じゃないから値をかえさないですよね。

条件によってある変数に異なる異なる値を与える、なんてことは日常的にありますが、swiftではifは関数ではありませんので値を返すことをしません。変数への代入は各ブロックの中で行うことになります。

let val: Int

if conditionA {
   val = val1
} else if conditionB {
   val = val2
} else if let val3 = doSome() {
   val = val3
} else {
   val = val4
}

では、このif文を関数化してみましょう!

やってみた

class ConditionalBranch<T> {
    private var val: T?

    func `if`( _ bool: Bool, _ ex: () -> T ) -> ConditionalBranch<T> {

        if bool {
            val = ex()
        }
        return self
    }

    func ifLet<E>(_ some: E?, ex: (E) -> T ) -> ConditionalBranch<T> {

        if val != nil {
            return self
        }

        guard let wapped = some else {
            return self
        }

        val = ex(wapped)
        return self
    }

    func elseIf( _ bool: Bool, _ ex: () -> T ) -> ConditionalBranch<T> {
        if val != nil { return self }
        return self.if(bool,ex)
    }

    func `else`(_ ex: () -> T) -> T {
        return val ?? ex()
    }
}

使い方はこんな感じです。

let val =
       ConditionalBranch<Int>()
       .if(conditionA) {
           return val1
        }
        .elseIf(conditionB) {
            return val2
        }
        .ifLet(doSome()) { val3 in
            return val3
        }
        .else {
            return val4
        }

想像以上に綺麗!(自己満足)なので、Swift6で採用してもらいたいです 笑

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

【Swift】QRコードを読み取って文字列を取得する

簡単に実装したサンプルです。参考になると幸いです。
asa08/QRreader

カメラを設定する

import AVFoundationして、AVCaptureSessionをインスタンスを生成します。

private let session = AVCaptureSession()

次に、カメラの設定を行います。

// カメラの設定
// 今回は背面カメラなのでposition: .back
let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .back)

let devices = discoverySession.devices
if let backCamera = devices.first {
   do {
      // カメラでQRの読み取りに成功した時の処理
      let deviceInput = try AVCaptureDeviceInput(device: backCamera)
      doInit(deviceInput: deviceInput)
   } catch {
      print("Error occured while creating video device input: \(error)")
   }
}

doInit()の中でデコードを行います。

private func doInit(deviceInput: AVCaptureDeviceInput) {
    if !session.canAddInput(deviceInput) { return }
    session.addInput(deviceInput)
    let metadataOutput = AVCaptureMetadataOutput()

    if !session.canAddOutput(metadataOutput) { return }
    session.addOutput(metadataOutput)

    metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
    metadataOutput.metadataObjectTypes = [.qr]

    // カメラを起動
    let previewLayer = AVCaptureVideoPreviewLayer(session: session)
    previewLayer.frame = view.bounds
    previewLayer.videoGravity = .resizeAspectFill
    caputureView.layer.addSublayer(previewLayer)

    session.startRunning()
}

func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {

   for metadata in metadataObjects as! [AVMetadataMachineReadableCodeObject] {
       // QRのtype: metadata.type
       // QRの中身: metadata.stringValue
       guard let value = metadata.stringValue else { return }

       session.stopRunning()
       textLavel.text = value
       caputureView.isHidden = true
   }
}

これでカメラで読み取ったQRから文字列を取得することができます。

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

こんなソースコードはイヤだ-関数の機能不足と引数

プログラムのソースコードのより良い書き方をまとめていこうと思います。
これは実際にあるアプリで使われていたものです。

関数の機能不足と引数

sample.swift
//パラメータを受け取り、APIを投げる
  func getUser(parameters: [String:String]) {
    let url = Const.UserUrl + "/" + parameters["id"]
    api.connectGet(url: url, queryParameters: parameters) { (data, error) in
      //レスポンスの処理
      //....
    }
  } 


  func start() {
    var parameters = [String:String]()
    parameters["id"] = self.userId
    parameters["type"] = "male"
    parameters["country"] = "japan"

    getUser(parameters: parameters)
  }

どのように変更できるのか

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

iOS ResearchKit を利用して非公式の医療研究試験を実施する

ResearchKit とは?

ResearchKitApple が医学研究目的で開発したオープンソースのフレームワークで、多くの機能が搭載されています。ここでは、基本的な医学的検査を例に、このフレームワークの使い方を紹介します。ここで得られる検査結果は公式のものではなく、健康状態を結論づける公式なデータとしては使えないことにご注意ください。

本フレームワークは英語のみで提供されていますが、一部の検査は言語非依存です。

学習内容

  • (記憶)空間記憶
  • タッピングスピード (iPhone)
  • ハノイの塔
  • トレイルメイキング(数字と文字を順に結んでいく)
  • 視覚コントラスト検査
  • 視力検査
  • PSAT(前の2つの数字を合計)
  • (聴覚)トーン聴覚検査
  • (聴覚)dBHLトーン聴覚検査

警告

これらの検査は研究目的のみで実施されるものです!医学的検査を実施できるのは医師のみです。本記事は、このフレームワークの機能の使用法の紹介のみを目的としています。

実装

ステップ1. ResearchKit フレームワークを複製する

ResearchKit フレームワークを既存のプロジェクトに複製します: git@github.com:ResearchKit/ResearchKit.git

ステップ2. フレームワークを Xcode に追加する

次のように、フレームワーク Xcode のプロジェクトファイルをあなたのプロジェクトファイルにドラッグします:

Screen Shot 2020-05-21 at 18.41.27.png

次に、ResearchKit のビルドされた製品をプロジェクトターゲットのフレームワーク、ライブラリ、埋め込みコンテンツセクションに追加します:
「+」アイコンをクリックし、 ResearchKit を選択します。 そして、タイプを Embed & Sign に変更します

Screen Shot 2020-05-21 at 18.49.15.png

ステップ3. テストコードを追加して結果を受け取る

テストを提示し、その結果を受け取る基本的な構造は次のとおりです:

import ResearchKit

テストビューを表示:

let taskViewController = ORKTaskViewController(task: task, taskRun: nil)
taskViewController.outputDirectory = FileManager.default.temporaryDirectory
taskViewController.delegate = self
present(alert, animated: true, completion: nil)

結果を確認するには:

extension testingTableView: ORKTaskViewControllerDelegate {

    func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskViewControllerFinishReason, error: Error?) {
        taskViewController.dismiss(animated: true, completion: nil)
    }

    func taskViewController(_ taskViewController: ORKTaskViewController, didChange result: ORKTaskResult) {
        //結果はここで受け取られ、`result.identifier` で取り込むことができます
        //特定のテストの結果を受け取る方法について説明します。
    }

}

各テストの固有コード

(記憶)空間記憶

ezgif-2-fd6c1f9c1cc7.gif

let task = ORKOrderedTask.spatialSpanMemoryTask(withIdentifier: "spatialSpanMemory", intendedUseDescription: nil, initialSpan: 2, minimumSpan: 1, maximumSpan: 5, playSpeed: 1, maximumTests: 8, maximumConsecutiveFailures: 2, customTargetImage: nil, customTargetPluralName: nil, requireReversal: false, options: [])
let taskViewController = ORKTaskViewController(task: task, taskRun: nil)
taskViewController.outputDirectory = FileManager.default.temporaryDirectory
taskViewController.delegate = self
present(alert, animated: true, completion: nil)
extension testingTableView: ORKTaskViewControllerDelegate {
    ...
    func taskViewController(_ taskViewController: ORKTaskViewController, didChange result: ORKTaskResult) {
        if result.identifier == "spatialSpanMemory" {
            if let memoryResult = result.stepResult(forStepIdentifier: "cognitive.memory.spatialspan")?.results?.first as? ORKSpatialSpanMemoryResult {
                let score = String(memoryResult.score) //スコア
                let numFailed = String(memoryResult.numberOfFailures) //失敗したカウント
                let totalNum = String(memoryResult.numberOfGames) //総数
                //TODO
            }
        }
    }
    ...
}

タッピングスピード (iPhone)

picture

let task = ORKOrderedTask.twoFingerTappingIntervalTask(withIdentifier: "tappingTest", intendedUseDescription: nil, duration: 10, handOptions: .both, options: [])
//ORKTaskViewController...
//delegate...
func taskViewController(_ taskViewController: ORKTaskViewController, didChange result: ORKTaskResult) {
    if result.identifier == "tappingTest" {
        if let leftHandResults = result.stepResult(forStepIdentifier: "tapping.left")?.results,
            let rightHandResults = result.stepResult(forStepIdentifier: "tapping.right")?.results {
            if leftHandResults.count == 2 && rightHandResults.count == 2 {
                let leftClickCount = (leftHandResults[1] as? ORKTappingIntervalResult)?.samples?.count ?? 0 //左側の数
                let rightClickCount = (rightHandResults[1] as? ORKTappingIntervalResult)?.samples?.count ?? 0 //右側の数
                //TODO
            }
        }
    }
}
//...

ハノイの塔

picture

let task = ORKOrderedTask.towerOfHanoiTask(withIdentifier: "towerOfHanoi", intendedUseDescription: nil, numberOfDisks: 4, options: [])
//ORKTaskViewController...
//delegate...
func taskViewController(_ taskViewController: ORKTaskViewController, didChange result: ORKTaskResult) {
    if result.identifier == "towerOfHanoi" {
        if let stepResult = result.stepResult(forStepIdentifier: "towerOfHanoi")?.results?.first as? ORKTowerOfHanoiResult,
            let moves = stepResult.moves,
            let time = moves.last?.timestamp {
            let movesTaken = String(moves.count)
            let timeCompleted = String(format: "%.2f", time)
            //TODO
        }
    }
}
//...

トレイルメイキング(数字と文字を順に結んでいく)

picture

let task = ORKOrderedTask.trailmakingTask(withIdentifier: "trailMaking", intendedUseDescription: nil, trailmakingInstruction: nil, trailType: .B, options: [])
//ORKTaskViewController...
//delegate...
func taskViewController(_ taskViewController: ORKTaskViewController, didChange result: ORKTaskResult) {
    if result.identifier == "trailMaking" {
        if let trailResult = result.stepResult(forStepIdentifier: "trailmaking")?.results?.first as? ORKTrailmakingResult,
            let timeTaken = trailResult.taps.last?.timestamp {
            let numErrors = trailResult.numberOfErrors //エラー数
            //TODO
        }
    }
}
//...

視覚コントラスト検査

picture

let task = ORKOrderedTask.landoltCContrastSensitivityTask(withIdentifier: "landoltCcontrast", intendedUseDescription: nil)
//ORKTaskViewController...
//delegate...
func taskViewController(_ taskViewController: ORKTaskViewController, didChange result: ORKTaskResult) {
    if result.identifier == "landoltCcontrast" {
        if let stepResults = result.stepResult(forStepIdentifier: "landoltCStep")?.results {
            var passedTestCount = 0
            for stepResult in stepResults {
                if let convertedResult = stepResult as? ORKLandoltCResult {
                    if (convertedResult.outcome ?? false) { passedTestCount += 1}
                }
            }
            //TODO: `passedTestCount` 正しい選択の数, `String(stepResults.count)` 総数
        }
    }
}
//...

視力検査

picture

let task = ORKOrderedTask.landoltCVisualAcuityTask(withIdentifier: "visualAcuity", intendedUseDescription: nil)
//ORKTaskViewController...
//delegate...
func taskViewController(_ taskViewController: ORKTaskViewController, didChange result: ORKTaskResult) {
    if result.identifier == "visualAcuity" {
        if let visualResults = result.stepResult(forStepIdentifier: "landoltCStep")?.results {
            var passed = 0
            for result in visualResults {
                if let convertedResult = result as? ORKLandoltCResult {
                    if (convertedResult.outcome ?? false) {
                        passed += 1
                    }
                }
            }
            //TODO: `passed` は、合格したテストの数を意味します。
        }
    }
}
//...

PSAT(前の2つの数字を合計)

picture

let task = ORKOrderedTask.psatTask(withIdentifier: "psat", intendedUseDescription: nil, presentationMode: .visual, interStimulusInterval: 4.5, stimulusDuration: 4.5, seriesLength: 12, options: [])
//ORKTaskViewController...
//delegate...
func taskViewController(_ taskViewController: ORKTaskViewController, didChange result: ORKTaskResult) {
    if result.identifier == "psat" {
        if let stepResult = result.stepResult(forStepIdentifier: "psat")?.results?.first as? ORKPSATResult {
            let totalCount = String(stepResult.totalCorrect) //総数
            let correctCount = String(stepResult.samples?.count ?? 0) //正しい選択の数
            //TODO
        }
    }
}
//...

(聴覚)トーン聴覚検査

picture

Present the test view:

let task = ORKOrderedTask.toneAudiometryTask(withIdentifier: "toneAudiometry", intendedUseDescription: nil, speechInstruction: nil, shortSpeechInstruction: nil, toneDuration: 60, options: [])
//ORKTaskViewController...

Checking the result

//delegate...
func taskViewController(_ taskViewController: ORKTaskViewController, didChange result: ORKTaskResult) {
       if let audioResults = result.stepResult(forStepIdentifier: "tone.audiometry")?.results {
           var matchedCount = 0 //正しい選択の数
           var missedChannel = [String]() //失敗したオーディオチャンネル
           guard let audioResult = audioResults.first as? ORKToneAudiometryResult else { return }
           guard let audioSamples = audioResult.samples else { return }
           let totalTestCount = audioSamples.count //実施されたテストの総数
           for sample in audioSamples {
               if sample.channel == sample.channelSelected {
                   matchedCount += 1 //一致した
               } else {
                   missedChannel.append("周波数 " + String(format: "%.2f", sample.frequency)) //違う
               }
           }
       }
   }
//...

(聴覚)dBHLトーン聴覚検査

let task = ORKOrderedTask.dBHLToneAudiometryTask(withIdentifier: "dBHLToneAudiometry", intendedUseDescription: nil, options: [])
//ORKTaskViewController...
//delegate...
func taskViewController(_ taskViewController: ORKTaskViewController, didChange result: ORKTaskResult) {
    if result.identifier == "dBHLToneAudiometry" {
        var resultStr = ""
        if let stepResult1 = result.stepResult(forStepIdentifier: "dBHL1.tone.audiometry") {
            if let firstResult = stepResult1.results?.first as? ORKdBHLToneAudiometryResult {
                resultStr += "オーディオチャンネル 0:\n" + processSingleDBHLAudioResult(firstResult: firstResult) + "\n"
            }
        }
        if let stepResult2 = result.stepResult(forStepIdentifier: "dBHL2.tone.audiometry") {
            if let secondResult = stepResult2.results?.first as? ORKdBHLToneAudiometryResult {
                resultStr += "オーディオチャンネル 1:\n" + processSingleDBHLAudioResult(firstResult: secondResult) + "\n"
            }
        }
    }
}

func processSingleDBHLAudioResult(firstResult: ORKdBHLToneAudiometryResult) -> String {
    var resultStr = ""
    if let samples = firstResult.samples {
        /*
         各サンプルには異なる可聴周波数が含まれています
         */
        for sample in samples {
            var subResultStr = "\n周波数 " + String(sample.frequency) + "\n"
            /*
             単位は、周波数内での異なるdB(音量レベル)のテストを指します
             */
            if let units = sample.units {
                var successfulTests = 0
                for unit in units {
                    let dBValue = String(format: "%.2f", unit.dBHLValue)
                    let userTapped = (unit.userTapTimeStamp != Double.zero)
                    if userTapped { successfulTests += 1 }
                    subResultStr += dBValue + " dB " + userTapped.getYesNo() + ", "
                }
                subResultStr += String(successfulTests) + " / " + String(units.count) + " 合格しました" + "\n"
            }
            resultStr += subResultStr
        }
    }
    return resultStr
}

//...

extension Bool {

    func getYesNo() -> String {
        if self { return "タップ済みです" }
        else { return "タップしていません" }
    }

}


警告

これらの検査は研究目的のみで実施されるものです!医学的検査を実施できるのは医師のみです。本記事は、このフレームワークの機能の使用法の紹介のみを目的としています。

:sunny:

iOS開発、Swift、プログラミングに関することなら何でも、遠慮なくTwitterで質問してください。
@MaShunzhe

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