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

<details><summary>自己紹介</summary>
私の職業はエンジニアではありません。理学療法士という職業であり、怪我や疾患を患った方に対して、身体的なリハビリを行う職業です。職務経歴は、病院勤務３年→在宅分野で訪問リハビリ４年（現職）　です。在宅分野で、働いていると、アナログな場面を多々見かける機会が多く、会社の電子カルテがiPadになったことをきっかけに、swiftを学習し、iOSのアプリ開発を始めることにしました。２０２３年４月時点で、３５個（主にUIKitで、3作はSwiftUI、１作は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で猛者猛者エンジニアの方から学んだことばかりです。

１作目アプリリース時点から、本業以外にやってきたことの振り返り
| 時期 | 内容 |
|:-:|:-:|
| ２０２２年１月　| １作目リリース  |
| 同年3月  | ２,3作目リリース  |
| 同年5月  | 4,5作目リリース  |
| 同年6月  | 6-11作目リリース  |
| 同年7月  | 12-16作目リリース  |
| 同年8月  | 17-20作目リリース  |
| 同年9月  | 21-22作目リリース  |
| 同年10月  | 23-28作目リリース  |
| 同年11月  | 簿記３級受験（合格）  |
| 同年12月  | Unityでアプリ作成開始  |
| ２０２3年１月 | 29(Unity製),30作目リリース　、FP３級受験（合格）  |
| 同年2月  | 31,32作目リリース  |
| 同年３月  |  33-35作目リリース |

</details>
自己紹介は、一度読んだ方は飛ばせるように、折りたたみにしています。
興味のある方は覗いていただければと思います。

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

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

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

![スクリーンショット 2023-04-16 16.23.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/799713/8f98f958-807c-fee8-ab19-de567e2dc983.png)

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

## このアプリ作成で学んだこと
・UIKitの簡単な実装（Button、Slider、TextView、TextField、TableView,など）
・AutoLayoutの実装
・**Dictionary、Arrayなどの使用方法**
・**RealmでのローカルにおけるCRUD**
・**質問事項をJsonで保存し、JSON→構造体への変換（Decodable）**
・Line、Twitterへの共有
・検査結果のコピー＆ペースト
・**検査結果のPDF出力**
など

これらの一部の実装例をピックアップして、取り上げていく。
## Realmを用いてModelの作成
このアプリは、一つのアプリ（一つの端末）で、複数人の検査者が使用するために作られている。なぜなら、病院のリハビリ室では、１つのiPadを複数人で用いられる場合がある。
そのため、以下の条件を踏まえたクラス・構造体である必要がある。
・複数の検査者のデータを保存する必要がある。
・検査者は、複数人の対象者を検査するため、複数人の対象者のデータを保存する必要がある。
・対象者は、複数回一定の期間毎に、検査をおこなう。そのため、複数の検査結果を保存する必要がある。

データベースとしては、以下のモデル名・プロパティを持ったModelを生成する必要があった。
![スクリーンショット 2023-04-16 17.07.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/799713/9c83797b-49ea-9ec8-eb88-dde2441a1f02.png)

実装例は以下の通りである。
＊以下の実装例は、アンチパータンです。なぜかは実装例の後に記載しています。考えながら読んでみてください。
修正した例は、２作目の記事で記載する予定です。
```swift: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
        }
    }
}

```

```swift: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`を作成する必要があります。２作目で実装例を提示しますので、少々お待ちください。一応アンチパータンとして記しておきます。


## PDF出力に関して
![スクリーンショット 2023-04-16 18.21.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/799713/e18594b1-b33d-d084-0d9a-459b18ee4480.png)
上記の画像のようなPDF出力を行った。
実装例を記す。

```swift: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であれば、「未入力」、値が０以外であればそのまま値をString型に変更するクラスである。
// アンチパターン。何をしているクラスかわからないし、`０＝「未入力」`　ではなく、`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点ごとに異なっており、評価するにあたっての注意点を配慮する必要があります。

まず、最初にJSONファイルから変換する前に、ファイルに関連する構造体を定義する必要がある。
```swift
///　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`は各項目の名前、`seven`〜`one`までは、その各評価項目ごとに、１点から７点まで、細かく採点基準があり、その採点基準の説明文が入力される。`attention`は各項目の採点前に注意しておくべき注意点の文章が入力される。

次に変換するための、JsonFileを用意する。
```json: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行っていればひげそりまたは化粧\n５項目のうち、１項目、介助を受けている",
        "three": "歯・義歯を磨く\n櫛などで髪をとかす\n手洗い\n洗顔\n行っていればひげそりまたは化粧\n５項目のうち２項目、介助を受けている",
        "two": "歯・義歯を磨く\n櫛などで髪をとかす\n手洗い\n洗顔\n行っていればひげそりまたは化粧\n５項目のうち、どの項目も半分以上介助を受けている",
        "one": "歯・義歯を磨く\n櫛などで髪をとかす\n手洗い\n洗顔\n行っていればひげそりまたは化粧\n５項目のうち、４項目、介助を受けている",
        "attention": "ーー注意点ーー\n整容は5つの動作の集まりです。\n1） 口腔ケア　2）洗顔\n3）手洗い　4）整髪\n5）化粧または髭剃り\nただし、5つめの化粧または髭剃りは、行う必要がないことが多いため、していない場合は、その他の4項目で評価します。それぞれの項目について、何％しているかを評価し、平均します。"
    }
 
           // 省略
]
```

以下の関数を用いて、JSONファイルから、独自の構造体へ変換する。
```swift
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/799713/92fc6f6a-80ae-62b3-7e89-4bf6a5d7a1a7.png)
![スクリーンショット 2023-04-17 20.18.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/799713/80d2abb6-0298-d203-e8d7-ec6bc16e5a21.png)

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

```swift
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
個人でアプリを作成しているので、良かったら覗いてみてください！