LoginSignup
48
32

More than 1 year has passed since last update.

未経験エンジニアが35個のクソアプリをリリースした振り返り(1作目) 実装例あり

Posted at
自己紹介 私の職業はエンジニアではありません。理学療法士という職業であり、怪我や疾患を患った方に対して、身体的なリハビリを行う職業です。職務経歴は、病院勤務3年→在宅分野で訪問リハビリ4年(現職) です。在宅分野で、働いていると、アナログな場面を多々見かける機会が多く、会社の電子カルテがiPadになったことをきっかけに、swiftを学習し、iOSのアプリ開発を始めることにしました。2023年4月時点で、35個(主にUIKitで、3作はSwiftUI、1作はUnity)のアプリをリリースしました。それらを背景を踏まえながら、コードとともに振り返りを、2023年4月からして行きたいと思います。

2022年の振り返りは、以下のnoteに簡単に記しているので、興味ある方はのぞいみてください。
https://note.com/ryoryoryowww/n/n9c27604a84a0
アプリ集も良かったら、のぞいてみてください。
https://sites.google.com/view/muranakar

コードの書き方に関しては、
アプリ道場サロン(https://community.camp-fire.jp/projects/view/281055)
で学んだり、Twitterで猛者猛者エンジニアの方から学んだことばかりです。

1作目アプリリース時点から、本業以外にやってきたことの振り返り

時期 内容
2022年1月  1作目リリース
同年3月 2,3作目リリース
同年5月 4,5作目リリース
同年6月 6-11作目リリース
同年7月 12-16作目リリース
同年8月 17-20作目リリース
同年9月 21-22作目リリース
同年10月 23-28作目リリース
同年11月 簿記3級受験(合格)
同年12月 Unityでアプリ作成開始
2023年1月 29(Unity製),30作目リリース 、FP3級受験(合格)
同年2月 31,32作目リリース
同年3月 33-35作目リリース
自己紹介は、一度読んだ方は飛ばせるように、折りたたみにしています。 興味のある方は覗いていただければと思います。

以下の記載は、私自身が解釈した上でコードを書いているため、間違えてコードを記載したり、解釈した文章である可能性があるため、ご了承いただきたいです。気になる点などありましたら、コメントお待ちしております。また、過去に書いたコードであるため、過去の自分を否定しながら、記載してる部分もあります。

今回は1作目のアプリを振り返ります。まず、この振り返りを行う前に、アプリをダウンロードして、全体を触っていただけるとコードを理解しやすいと思います。なぜこのような実装をしているかのイメージがつかみやすいです。そのため、テキトウな値を入力して、アプリで遊んでいただけると嬉しいです。

FIMというアプリで、
https://apps.apple.com/jp/app/fim/id1606480076
で無料でダウンロード可能です。

スクリーンショット 2023-04-16 16.23.23.png

アプリの概要

FIMとは、一つの動作(食事、入浴など)に対して7段階の評価を行う評価指標です。この7段階は評価項目ごとに、こまかい評価基準があるが、あいまいに記録してしまうことがありました。また、電子カルテとは別で、記録して多職種との情報共有として医療従事者に郵送する書類に、身体の動作レベルを評価して記録する必要がありました。そのため、アプリを用いている事によって、①正しく評価できる、②ペーパーレスになる、と思い、アプリを作成しリリースしました。

このアプリ作成で学んだこと

・UIKitの簡単な実装(Button、Slider、TextView、TextField、TableView,など)
・AutoLayoutの実装
Dictionary、Arrayなどの使用方法
RealmでのローカルにおけるCRUD
質問事項をJsonで保存し、JSON→構造体への変換(Decodable)
・Line、Twitterへの共有
・検査結果のコピー&ペースト
検査結果のPDF出力
など

これらの一部の実装例をピックアップして、取り上げていく。

Realmを用いてModelの作成

このアプリは、一つのアプリ(一つの端末)で、複数人の検査者が使用するために作られている。なぜなら、病院のリハビリ室では、1つのiPadを複数人で用いられる場合がある。
そのため、以下の条件を踏まえたクラス・構造体である必要がある。
・複数の検査者のデータを保存する必要がある。
・検査者は、複数人の対象者を検査するため、複数人の対象者のデータを保存する必要がある。
・対象者は、複数回一定の期間毎に、検査をおこなう。そのため、複数の検査結果を保存する必要がある。

データベースとしては、以下のモデル名・プロパティを持ったModelを生成する必要があった。
スクリーンショット 2023-04-16 17.07.12.png

実装例は以下の通りである。
*以下の実装例は、アンチパータンです。なぜかは実装例の後に記載しています。考えながら読んでみてください。
修正した例は、2作目の記事で記載する予定です。

FIM.swift

import Foundation
import RealmSwift

// MARK: - Assessor 評価者
final class Assessor: Object {
    @objc dynamic var uuidString = UUID().uuidString
    @objc dynamic var name = ""
    var targetPersons = List<TargetPerson>()
    var uuid: UUID? {
        UUID(uuidString: uuidString)
    }
    override class func primaryKey() -> String? {
        "uuidString"
    }

    convenience init(name: String) {
        self.init()
        self.name = name
    }
}

// MARK: - TagetPerson 対象者
final class TargetPerson: Object {
    @objc dynamic var uuidString = UUID().uuidString
    @objc dynamic var name = ""
    var FIM = List<FIM>()
    var uuid: UUID? {
        UUID(uuidString: uuidString)
    }
    let assessors = LinkingObjects(fromType: Assessor.self, property: "targetPersons")

    override class func primaryKey() -> String? {
        "uuidString"
    }

    convenience init(name: String) {
        self.init()
        self.name = name
    }
}

// MARK: - FIM 評価指標
final class FIM: Object {
    @objc dynamic var uuidString = UUID().uuidString
    @objc dynamic var eating = 0
    @objc dynamic var grooming = 0
    @objc dynamic var bathing = 0
    @objc dynamic var dressingUpperBody = 0
    @objc dynamic var dressingLowerBody  = 0
    @objc dynamic var toileting = 0
    @objc dynamic var bladderManagement = 0
    @objc dynamic var bowelManagement = 0
    @objc dynamic var transfersBedChairWheelchair = 0
    @objc dynamic var transfersToilet = 0
    @objc dynamic var transfersBathShower = 0
    @objc dynamic var walkWheelchair = 0
    @objc dynamic var stairs = 0
    @objc dynamic var comprehension = 0
    @objc dynamic var expression = 0
    @objc dynamic var socialInteraction = 0
    @objc dynamic var problemSolving = 0
    @objc dynamic var memory = 0
    @objc dynamic var createdAt: Date?
    @objc dynamic var updatedAt: Date?

    let targetPersons = LinkingObjects(fromType: TargetPerson.self, property: "FIM")
    /// 運動項目合計値
    var sumTheMotorSubscaleIncludes: Int {
        eating + grooming + bathing + dressingUpperBody +
        dressingLowerBody + toileting + bladderManagement +
        bowelManagement + transfersBedChairWheelchair +
        transfersToilet + transfersBathShower +
        walkWheelchair + stairs
    }
    /// 認知項目合計値
    var sumTheCognitionSubscaleIncludes: Int {
        comprehension + expression + socialInteraction + problemSolving + memory
    }
    /// 全合計値
    var sumAll: Int {
        eating + grooming + bathing + dressingUpperBody + dressingLowerBody
        + toileting + bladderManagement + bowelManagement
        + transfersBedChairWheelchair + transfersToilet
        + transfersBathShower + walkWheelchair
        + stairs + comprehension + expression
        + socialInteraction + problemSolving + memory
    }

    var uuid: UUID? {
        UUID(uuidString: uuidString)
    }

    override class func primaryKey() -> String? {
        "uuidString"
    }

    convenience init(
        eating: Int,
        grooming: Int,
        bathing: Int,
        dressingUpperBody: Int,
        dressingLowerBody: Int,
        toileting: Int,
        bladderManagement: Int,
        bowelManagement: Int,
        transfersBedChairWheelchair: Int,
        transfersToilet: Int,
        transfersBathShower: Int,
        walkWheelchair: Int,
        stairs: Int,
        comprehension: Int,
        expression: Int,
        socialInteraction: Int,
        problemSolving: Int,
        memory: Int,
        createdAt: Date? = nil,
        updatedAt: Date? = nil
    ) {
        self.init()
        self.eating = eating
        self.grooming = grooming
        self.bathing = bathing
        self.dressingUpperBody = dressingUpperBody
        self.dressingLowerBody = dressingLowerBody
        self.toileting = toileting
        self.bladderManagement = bladderManagement
        self.bowelManagement = bowelManagement
        self.transfersBedChairWheelchair = transfersBedChairWheelchair
        self.transfersToilet = transfersToilet
        self.transfersBathShower = transfersBathShower
        self.walkWheelchair = walkWheelchair
        self.stairs = stairs
        self.comprehension = comprehension
        self.expression = expression
        self.socialInteraction = socialInteraction
        self.problemSolving = problemSolving
        self.memory = memory
        if let createdAt = createdAt {
             self.createdAt = createdAt
        }
        if let updatedAt = updatedAt {
            self.updatedAt = updatedAt
        }
    }
}

FIMRepository.swift

import Foundation
import RealmSwift

final class FIMRepository {
    // swiftlint:disable:next force_cast
    private let realm = try! Realm()

    // MARK: - AssessorRepository
    // 全評価者の呼び出し
    func loadAssessor() -> [Assessor] {
        let assessors = realm.objects(Assessor.self)
        let assessorsArray = Array(assessors)
        return assessorsArray
    }
    // 評価者UUIDによる評価者(一人)の呼び出し
    func loadAssessor(assessorUUID: UUID) -> Assessor? {
        let assessor = realm.object(ofType: Assessor.self, forPrimaryKey: assessorUUID.uuidString)
        return assessor
    }
    // 対象者UUIDによる評価者(一人)の呼び出し
    func loadAssessor(targetPersonUUID: UUID) -> Assessor? {
        guard let fetchedTargetPerson = realm.object(
            ofType: TargetPerson.self,
            forPrimaryKey: targetPersonUUID.uuidString
        ) else { return nil }
        return fetchedTargetPerson.assessors.first
    }
    // 評価者の追加
    func apppendAssessor(assesor: Assessor) {
        // swiftlint:disable:next force_cast
        try! realm.write {
            realm.add(assesor)
        }
    }
    // 評価者の更新
    func updateAssessor(uuid: UUID, name: String) {
        // swiftlint:disable:next force_cast
        try! realm.write {
            let assessor = realm.object(ofType: Assessor.self, forPrimaryKey: uuid.uuidString)
            assessor?.name = name
        }
    }
    // 評価者の削除
    func removeAssessor(uuid: UUID) {
        guard let assessor = realm.object(ofType: Assessor.self, forPrimaryKey: uuid.uuidString) else { return }
        // swiftlint:disable:next force_cast
        try! realm.write {
            realm.delete(assessor)
        }
    }

    // MARK: - TargetPersonRepository
    // 一人の評価者が評価するor評価した、対象者の配列の呼び出し
    func loadTargetPerson(assessorUUID: UUID) -> [TargetPerson] {
        let assessor = realm.object(ofType: Assessor.self, forPrimaryKey: assessorUUID.uuidString)
        guard let targetPersons = assessor?.targetPersons else { return [] }
        let targetPersonsArray = Array(targetPersons)
        return targetPersonsArray
    }
    // 一人の対象者のUUIDから、一人の対象者の呼び出し
    func loadTargetPerson(targetPersonUUID: UUID) -> TargetPerson? {
        let targetPerson = realm.object(ofType: TargetPerson.self, forPrimaryKey: targetPersonUUID.uuidString)
        return targetPerson
    }

    // 一つのFIMのUUIDから、そのFIMがどの対象者かの呼び出し
    func loadTargetPerson(fimUUID: UUID) -> TargetPerson? {
        guard let fetchedFIM = realm.object(ofType: FIM.self, forPrimaryKey: fimUUID.uuidString) else { return nil }
        return fetchedFIM.targetPersons.first
    }
    //  一人の評価者の対象者の追加
    func appendTargetPerson(assessorUUID: UUID, targetPerson: TargetPerson) {
        guard let list = realm.object(
            ofType: Assessor.self,
            forPrimaryKey: assessorUUID.uuidString
        )?.targetPersons else { return }
        // swiftlint:disable:next force_cast
        try! realm.write {
            list.append(targetPerson)
        }
    }
    // 一人の対象者のデータ更新
    func updateTargetPerson(uuid: UUID, name: String) {
        try! realm.write {
            let targetPerson = realm.object(ofType: TargetPerson.self, forPrimaryKey: uuid.uuidString)
            targetPerson?.name = name
        }
    }
    // 一人の対象者のデータ削除
    func removeTargetPerson(targetPersonUUID: UUID) {
        guard let fetchedTagetPerson = realm.object(
            ofType: TargetPerson.self,
            forPrimaryKey: targetPersonUUID.uuidString
        ) else { return }
        // swiftlint:disable:next force_cast
        try! realm.write {
            realm.delete(fetchedTagetPerson)
        }
    }

    // MARK: - FIMRepository
    // 一つのFIMのUUIDから、FIMのデータの呼び出し
    func loadFIM(fimUUID: UUID) -> FIM? {
        let fim = realm.object(ofType: FIM.self, forPrimaryKey: fimUUID.uuidString)
        return fim
    }
    // 一人の対象者のUUIDから、複数のFIMのデータの呼び出し(並び替えあり)
    func loadFIM(
        targetPersonUUID: UUID,
        sortedAscending: Bool
    ) -> [FIM] {
        let fimList = realm.object(
            ofType: TargetPerson.self,
            forPrimaryKey: targetPersonUUID.uuidString
        )?.FIM.sorted(
            byKeyPath: "createdAt",
            ascending: sortedAscending
        )
        guard let fimList = fimList else { return [] }
        let fimListArray = Array(fimList)
        return fimListArray
    }
    //  一人の対象者のFIMデータの追加
    func appendFIM(targetPersonUUID: UUID, fim: FIM) {
        guard let list = realm.object(
            ofType: TargetPerson.self,
            forPrimaryKey: targetPersonUUID.uuidString
        )?.FIM else { return }
        // swiftlint:disable:next force_cast
        try! realm.write {
            fim.createdAt = Date()
            list.append(fim)
        }
    }

    func updateFIM(fimItemNumArray: [Int], fimUUID: UUID) {
        // swiftlint:disable:next force_cast
        try! realm.write {
            let loadedFIM = realm.object(ofType: FIM.self, forPrimaryKey: fimUUID.uuidString)
            loadedFIM?.eating = fimItemNumArray[0]
            loadedFIM?.grooming = fimItemNumArray[1]
            loadedFIM?.bathing = fimItemNumArray[2]
            loadedFIM?.dressingUpperBody = fimItemNumArray[3]
            loadedFIM?.dressingLowerBody = fimItemNumArray[4]
            loadedFIM?.toileting = fimItemNumArray[5]
            loadedFIM?.bladderManagement = fimItemNumArray[6]
            loadedFIM?.bowelManagement = fimItemNumArray[7]
            loadedFIM?.transfersBedChairWheelchair = fimItemNumArray[8]
            loadedFIM?.transfersToilet = fimItemNumArray[9]
            loadedFIM?.transfersBathShower = fimItemNumArray[10]
            loadedFIM?.walkWheelchair = fimItemNumArray[11]
            loadedFIM?.stairs = fimItemNumArray[12]
            loadedFIM?.comprehension = fimItemNumArray[13]
            loadedFIM?.expression = fimItemNumArray[14]
            loadedFIM?.socialInteraction = fimItemNumArray[15]
            loadedFIM?.problemSolving = fimItemNumArray[16]
            loadedFIM?.memory = fimItemNumArray[17]
            loadedFIM?.updatedAt = Date()
        }
    }
    // FIMデータの削除
    func removeFIM(fimUUID: UUID) {
        guard let fetchedFIM = realm.object(ofType: FIM.self, forPrimaryKey: fimUUID.uuidString) else { return }
        // swiftlint:disable:next force_cast
        try! realm.write {
            realm.delete(fetchedFIM)
        }
    }
}

アンチパータンの理由としては、全体的にRealmに依存したコードになってしまうためです。

問題点

Realmからデータベースツールを変更する際に(例えばRealmから、CoreDataに変更するetc)、コード修正部分が多いことが欠点になります。ViewContoller(以下VCに略)上でも、class 〇〇:Object {} と定義しているRealmのモデルを呼び出す際に用いるクラスになってしまいます。そのため、VCファイルがRealmの影響を受けています。

修正点

VCファイルがRealmに依存しないように、Repositoryファイルで、structの型に変更する必要があります。そのため、Realmのモデルとは別に、構造体として、structを作成する必要があります。2作目で実装例を提示しますので、少々お待ちください。一応アンチパータンとして記しておきます。

PDF出力に関して

スクリーンショット 2023-04-16 18.21.32.png
上記の画像のようなPDF出力を行った。
実装例を記す。

FIMPDF.swift
import Foundation
import QuickLook

final class FIMPDF {
    private let assessor: Assessor
    private let targetPerson: TargetPerson
    private let fim: FIM

    // 検査者は誰で、対象者は誰で、いつの検査結果かを、PDFの出力する構造体の初期値として設定する必要がある。
    init(assessor: Assessor,
         targetPerson: TargetPerson,
         fim: FIM) {
        self.assessor = assessor
        self.targetPerson = targetPerson
        self.fim = fim
    }

    // PDFのデータをNSDataとして出力する。
    private func getPDF() -> NSData {
        let renderer = UIPrintPageRenderer()
        let paperSize = CGSize(width: 595.2, height: 841.8)
        let paperFrame = CGRect(origin: .zero, size: paperSize)
        renderer.setValue(paperFrame, forKey: "paperRect")
        renderer.setValue(paperFrame, forKey: "printableRect")
        let formatter = UIMarkupTextPrintFormatter(markupText: makeHTMLString())
        renderer.addPrintFormatter(formatter, startingAtPageAt: 0)
        let pdfData = NSMutableData()
        UIGraphicsBeginPDFContextToData(pdfData, .zero, [:])
        for pageI in 0..<renderer.numberOfPages {
            UIGraphicsBeginPDFPage()
            renderer.drawPage(at: pageI, in: UIGraphicsGetPDFContextBounds())
        }
        UIGraphicsEndPDFContext()
        return pdfData
    }
    
    // PDFのファイル名を設定して、PDFとして書き出す。
    func saveToTempDirectory() -> URL? {
        let tempDirectory = NSURL.fileURL(withPath: NSTemporaryDirectory(), isDirectory: true)
        let fileName = "FIM-" + "\(targetPerson.name)-\(String(describing: fim.createdAt!))" + ".pdf"
        let filePath = tempDirectory.appendingPathComponent(fileName)
        do {
            try getPDF().write(to: filePath)
            return filePath
        } catch {
            print(error.localizedDescription)
            return nil
        }
    }

    //どのようなPDFの内容かを、HTML・CSSで書き出す。
    //このHTML・CSSの中に、検査者・対象者・検査結果に関して、書き出す。
    // swiftlint:disable:next function_body_length
    private func makeHTMLString() -> String {
        var createdAtString = "--"
        if let createdAt = fim.createdAt {
            createdAtString  = dateFormatter(date: createdAt)
        }
        return """
        <!DOCTYPE html>
        <html>
            <head>
                    <title>FIM結果</title>
            <style>
                    table, th, td {
                      border: 1px solid black;
                      border-collapse: collapse;
                    }
            </style>
            <body>
                <h1>\(targetPerson.name) 様</h1>
                <h2 style="text-align:right"> 作成日 \(createdAtString)</h2>
                <h2>FIM(Functional Independence Measure、機能的自立度評価表)</h2>
                <table style="width:100%">
        <tr>
                    <td colspan="2">項目</td>
                    <td colspan="2">点数</td>
                </tr>
                <tr>
                    <td rowspan="6">セルフケア(42点)</td>
                    <td>A 食事(箸・スプーン)</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.eating).string)</td>
                </tr>
                <tr>
                    <td>B 整容</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.grooming).string)</td>
                </tr>
                <tr>
                    <td>C 清拭</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.bathing).string)</td>
                </tr>
                <tr>
                    <td>D 更衣(上半身)</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.dressingUpperBody).string)</td>
                </tr>
                <tr>
                    <td>E 更衣(下半身)</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.dressingLowerBody).string)</td>
                </tr>
                <tr>
                    <td>F トイレ</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.toileting).string)</td>
                </tr>
                <tr>
                    <td rowspan="2">排泄(14点)</td>
                    <td>G 排尿コントロール</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.bladderManagement).string)</td>
                </tr>
                <tr>
                    <td>H 排便コントロール</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.bowelManagement).string)</td>
                </tr>
                <tr>
                    <td rowspan="3">移乗(21点)</td>
                    <td>I ベッド、椅子、車椅子</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.transfersBedChairWheelchair).string)</td>
                </tr>
                <tr>
                    <td>J トイレ</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.transfersToilet).string)</td>
                </tr>
                <tr>
                    <td>K 浴槽、シャワー</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.transfersBathShower).string)</td>
                </tr>
                <tr>
                    <td rowspan="2">移動(14点)</td>
                    <td>L 歩行、車椅子</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.walkWheelchair).string)</td>
                </tr>
                <tr>
                    <td>M 階段</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.stairs).string)</td>
                </tr>
                <tr>
                    <td rowspan="2">コミュニケーション(14点)</td>
                    <td>N 理解(聴覚、視覚)</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.comprehension).string)</td>
                </tr>
                <tr>
                    <td>O 表出(音声、非音声)</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.expression).string)</td>
                </tr>
                <tr>
                    <td rowspan="3">社会認識(21点)</td>
                    <td>P 社会的交流</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.socialInteraction).string)</td>
                </tr>
                <tr>
                    <td>Q 問題解決</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.problemSolving).string)</td>
                </tr>
                <tr>
                    <td>R 記憶</td>
                    <td>1-7点</td>
                    <td>\(FIMString(fim: fim.memory).string)</td>
                </tr>
                <tr>
                    <td colspan="2">合計</td>
                    <td>18 - 126点</td>
                    <td>\(fim.sumAll)</td>
                </tr>
                </table>
                </body>
            </html>
        """
    }
}

// 初期値としてInt型を設定して、値が0であれば、「未入力」、値が0以外であればそのまま値をString型に変更するクラスである。
// アンチパターン。何をしているクラスかわからないし、`0=「未入力」` ではなく、`nil=「未入力」`とすべき
final private class FIMString {
    fileprivate var string = ""

    init(fim: Int) {
        if fim == 0 {
            self.string = "未入力"
            return
        }
        self.string = String(fim)
    }
}

private extension FIMPDF {
    func dateFormatter(date: Date) -> String {
        let dateFormatter = Foundation.DateFormatter()
        dateFormatter.locale = Locale(identifier: "ja_JP")
        dateFormatter.dateStyle = .medium
        dateFormatter.dateFormat = "yyyy-MM-dd"
        let dateString = dateFormatter.string(from: date)
        return dateString
    }
}

JSONファイルを、独自の構造体に変換する

JSONファイルを用いる理由としては、各項目ごとに注意点や、評価基準が異なるため、JSONで管理したほうが早いと思い、JSONファイルを作成しました。詳しく説明すると、検査を行うにあたって、各動作(食事、入浴、排泄etc)の各項目は、1-7点で採点を行い、その採点基準が各項目の1-7点ごとに異なっており、評価するにあたっての注意点を配慮する必要があります。

まず、最初にJSONファイルから変換する前に、ファイルに関連する構造体を定義する必要がある。

/// FIMの採点基準
    struct FimScoringCriteria: Decodable {
        var fimItem: String
        var seven: String
        var six: String
        var five: String
        var four: String
        var three: String
        var two: String
        var one: String
        var attention: String
    }

FimScoringCriteriaの中身は、fimItemは各項目の名前、sevenoneまでは、その各評価項目ごとに、1点から7点まで、細かく採点基準があり、その採点基準の説明文が入力される。attentionは各項目の採点前に注意しておくべき注意点の文章が入力される。

次に変換するための、JsonFileを用意する。

FIM.json
[
    {
        "fimItem": "食事",
        "seven": "完全自立",
        "six": "配膳前にきざんでもらってあり、1人で食べている。\n自助具を使用して自立していたり、エプロンを自分でかけて使用する。",
        "five": "肉を切る\n蓋をあける\nエプロンをかける。",
        "four": "口の中に食物が溜まっていないか、\n介助者が指で確認している場合",
        "three": "自助具をつけてもらい、\n食物をスプーンにのせてもらうと、\n自分で口に運び、\n嚥下する",
        "two": "・口に運ぶ\n・飲み込む\nの1つのみ行える",
        "one": "咀嚼や嚥下は可能であるが、口にまったく運べない。\n口に運ぶことのほうが、採点では重視されている。",
        "attention": "ーー注意点ーー\n"
    },
    {
        "fimItem": "整容",
        "seven": "歯・義歯を磨く\n櫛などで髪をとかす\n手洗い\n洗顔\n行っていればひげそりまたは化粧\nを全て自力で行っている。",
        "six": "時間を要す\n自助具を使用している",
        "five": "歯磨き粉を歯ブラシにつけてもらう。\nタオルを準備してもらう。\n自助具を準備・装着してもらう",
        "four": "歯・義歯を磨く\n櫛などで髪をとかす\n手洗い\n洗顔\n行っていればひげそりまたは化粧\n5項目のうち、1項目、介助を受けている",
        "three": "歯・義歯を磨く\n櫛などで髪をとかす\n手洗い\n洗顔\n行っていればひげそりまたは化粧\n5項目のうち2項目、介助を受けている",
        "two": "歯・義歯を磨く\n櫛などで髪をとかす\n手洗い\n洗顔\n行っていればひげそりまたは化粧\n5項目のうち、どの項目も半分以上介助を受けている",
        "one": "歯・義歯を磨く\n櫛などで髪をとかす\n手洗い\n洗顔\n行っていればひげそりまたは化粧\n5項目のうち、4項目、介助を受けている",
        "attention": "ーー注意点ーー\n整容は5つの動作の集まりです。\n1) 口腔ケア 2)洗顔\n3)手洗い 4)整髪\n5)化粧または髭剃り\nただし、5つめの化粧または髭剃りは、行う必要がないことが多いため、していない場合は、その他の4項目で評価します。それぞれの項目について、何%しているかを評価し、平均します。"
    }
 
           // 省略
]

以下の関数を用いて、JSONファイルから、独自の構造体へ変換する。

private var fimScoringCriteria: [FimScoringCriteria] = []

// 省略

// MARK: - JSONファイルのデコーダー 
 private func decodeFimJsonFile() {
        let data: Data?
        guard let file = Bundle.main.url(forResource: "FIM", withExtension: "json") else {
            fatalError("ファイルが見つかりません。メソッド名:[\(#function)]")
        }
        do {
            data  = try Data(contentsOf: file)
        } catch {
            fatalError("ファイルをロード不可。メソッド名:[\(#function)]")
        }

        do {
            guard let data = data else {
                fatalError("dataの中身が入っていない。メソッド名:[\(#function)]")
            }
            let decoder = JSONDecoder()
            fimScoringCriteria = try decoder.decode([FimScoringCriteria].self, from: data)
        } catch {
            fatalError("パース不可。メソッド名:[\(#function)]")
        }
    }

修正点

変換後にfimScoringCriteriaというArrayに値を代入しているが、この関数名からは、Arrayにデコードした値を代入していると理解できないので、✕。関数名を変更するか、配列に代入せずに配列を関数の返り値に変更する必要がある。

Dictionary・Arrayを用いて、UIButtonと関連付ける。

スクリーンショット 2023-04-17 20.18.27.png
スクリーンショット 2023-04-17 20.18.39.png

1のボタンを押した時に、1のボタンに関連する文章
3のボタンを押した時に、3のボタンに関連する文章
を表示するような挙動を行いたい時に、Dictionary・Arrayを用いて、関連付けて管理すると、個人的にわかりやすかったです。Twitterの猛者猛者エンジニアの方に、教えていただきました。

final class AssessmentViewController: UIViewController {
    @IBOutlet private weak var textView: UITextView!
    @IBOutlet private weak var button1: UIButton!
    @IBOutlet private weak var button2: UIButton!
    @IBOutlet private weak var button3: UIButton!
    @IBOutlet private weak var button4: UIButton!
    @IBOutlet private weak var button5: UIButton!
    @IBOutlet private weak var button6: UIButton!
    @IBOutlet private weak var button7: UIButton!
    private var buttons: [UIButton] {
        [
            button1, button2, button3, button4, button5, button6, button7
        ]
    }

    // 各ボタンと関連付ける際に、用いる文字列の配列。
    // この配列は、FIMの項目ごとに、配列の文字列が変化する。
    private var fimItemText: [String] {
        [
            fimScoringCriteria[fimItemCount].one,
            fimScoringCriteria[fimItemCount].two,
            fimScoringCriteria[fimItemCount].three,
            fimScoringCriteria[fimItemCount].four,
            fimScoringCriteria[fimItemCount].five,
            fimScoringCriteria[fimItemCount].six,
            fimScoringCriteria[fimItemCount].seven
        ]
    }
    // UIButtonが押された際に、そのボタンに合った文章を管理するため、辞書型で関連付けた。
    private var dictionaryButtonAndString: [UIButton: String] {
        [UIButton: String](uniqueKeysWithValues: zip(buttons, fimItemText))
    }
    @IBAction private func selectedFIMNum(sender: UIButton) {
        // ボタンが選択された際に、そのボタンと関連づけられた文字列を、テキストビューに反映させる。
        textView.text = dictionaryButtonAndString[sender]
    }

}

@IBActionで 引数にUIButtonを設定して、押されたUIButtonを用いて、Dictionaryで対応した文章を呼び出す挙動になっています。

以上振り返りでした。

書いてみて思ったのですが、全部振り返るのは面倒ですね。。
ぼちぼちやって行きます。

他にも良い方法があれば、コメントいただけると大変うれしいです。
良かったと思ったら、いいねやTwitterのフォローよろしくお願いいたします!

https://sites.google.com/view/muranakar
個人でアプリを作成しているので、良かったら覗いてみてください!

48
32
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
48
32