全く需要が無さそうだがSwiftUIでヘブライ文字を覚えるiOSアプリを作った

# はじめに
iOSアプリ作りたいという人の精神的な手助けになったらいいなと思って書きます。というのは、アプリを作るということの全体像を描くのに意味があるのではと考えた次第です。なので細かいことはそこそこにして、完成までに何をやったかを一通り書きたいと思います。

#できたもの
ヘブライ文字の暗記クイズアプリ
https://github.com/tomita28/alephbeth
https://apps.apple.com/jp/app/%E3%83%98%E3%83%96%E3%83%A9%E3%82%A4%E6%96%87%E5%AD%97/id1512330663?l=en


# 動機
* 最近プログラム書いてないなあ。なんか書きたいなあ。
* 日本語でヘブライ語勉強するアプリ誰も作ってへんやないか。
* SwiftUIなら簡単にアプリが作れるらしい！（本当に？）
* ところで知り合いがヘブライ語勉強したいらしいがヘブライ文字暗記アプリがいいのないな。
* ないものは作るしかない♪
https://www.youtube.com/watch?v=6zr6pWwHACU

# リリースまでにやったこと
* SwiftUIのチュートリアルをやった。
* ヘブライ文字のラテンアルファベット転写について調べた。
* 作りながら仕様を考えた。
* XCodeでコードを書いた。（ここまでで三日くらい（大変））
* kritaでiconを描いた。（これに一日（大変））
* プライバシーポリシーの設置
* Googleのバナーを貼った
* DeployGateでテスト及びリリース作業をした。（これに一日（大変））
* いろいろ変更点が出てきてもう何がなんだか。（あれから何ヶ月たったろう）

# 詳しい話
## SwiftUIのチュートリアル
　よくチュートリアルを終えると完全に理解した気になるっていうけど、私みたいな初級者にとってはチュートリアルを最後までやり切る勤勉さというのが本当に必要なんだなって思いました。新しい概念やそもそも知らない概念が出てくるときに、一番理解できる可能性が高いのは結局チュートリアルのなかであって、それらを解説してくれる誰かの説明は、あくまで自分でもチュートリアルをやったあとで効いてくるのだなというのが正直な感想でした。
　とはいえ、自分のアプリに必要なものでいうと全部をやる必要もなかったので、SwiftUI TutoarlのEssentialsというところまでは最低やった方がいいと思いました。

公式　https://developer.apple.com/tutorials/swiftui/creating-and-combining-views

### 大事だった概念
すいません、私には厳密な説明ができないのでざっくりした"気持ち"の部分だけ。
####SwiftUIとView
従来のアプリの画面の作り方と異なり（やったことないけど）、ViewというObjectをコード上に書いていくことが、そのまま画面の配置になる。正直こういう仕組みでなければ自分は最後までこのアプリ作ろうと思えなかったと思う。画面のデザインをドラッグしていじったりというのはどうもうまく想像できない。
####@State
Viewの中で随時変更できる値を保持している
####@ObservedObject
Viewをまたいで保持可能だが、新たなインスタンスを作るごとに新しいデータが保持される。このアプリではネストしていくクイズの不正答のリストにあたる。

####@EnvironmentObject
アプリ全体を通して保持して欲しいデータ。このアプリではユーザー設定の部分。

## ヘブライ文字のラテンアルファベット転写方式
ヘブライ文字というのは広義のアルファベット一種であり（その源流と言ってもいい）、אבגדと言った文字のことです。これを覚えるためには例えばא(alef)と言ったように、英語にも使われているラテンアルファベットで表記するのが一番手取り早い方法です。しかし、ラテンアルファベット自体は世界共通の発音体系なわけではありません。また、ヘブライ語の全ての発音を網羅できているわけでもありません。そこで、いくつかの流儀によってヘブライ文字をラテンアルファベットに置き換える（転写する）方式を取らねばなりません。そこには恣意性があり、歴史性があります。最初のバージョンではイスラエルでよく見かける方式に統一していたのですが、某先生からある種の聖書（古典）ヘブライ語転写方式にも対応して欲しいと言われて、いろいろ教科書をひっぱり出しておりました。

###転写方式
* Hebrew Academy
* Thomas O Lambdin

##仕様を考える
プログラマをしていた時期が少しだけありましたが、ここまで自由に自分で仕様を考えることはありませんでした。結局仕様は作りながら考えるのが早かった。幸い、SwiftUIはPreviewsという概念のおかげで、Viewのコードを書いたそのすぐ下に、テストになるもの（具体的な値を代入した時のViewクラスの挙動を確認する仕組み）があるので、ほとんどテストコードを同時にかけるのでめちゃ便利でした。逆にこのPreviewsの機能を誤解していたせいで後述のNavigationViewを画面一つ一つに書かなければならないと思ってしまったところがあります。NavigationViewの子要素ではNavigationLinkの挙動を直接確認できないというのがPreviewsの仕様だと自分では納得しております。
で、結局仕様ですが、

* ヘブライ文字子音の呼称一覧
* 母音記号の呼称一覧
* 文字（子音、母音両方）クイズ
* 不正答の文字を一覧で再度表示する
* 不正答の文字だけをクイズする
* 再度不正答の文字について何層でもクイズ、一覧表示できるようにする。
* ラテンアルファベット転写方式を最初の画面で選択できるようにする。

となりました。



## XCodeで頑張った
ここでは簡単にお世話になった概念を紹介するのみにします。

###NavigationView
矢印をタップするとどんどん次の画面が開きます（iPhoneの設定画面みたいなやつ）
大事なことですがNavigationLinkを貼るのだけはたくさんやってよくて、NavigaitonViewを設置し直すと、目次のバーが重複します。このような使い方はあまりないと思いますので、通常は大元のViewの中に（例えばContentViewの直下に)NavigationViewを設置し、その下は全てNavigationLinkを貼るだけにしてください。

```Swift:MainView.swift
import SwiftUI

struct MainView: View {
    @EnvironmentObject var userData: UserData
    @State var showingSetting = false
    
    var body: some View {
        VStack{
            NavigationView{
                List{
                    HStack{
                        Spacer()
                        Text("メニューを選んでね")
                        .fontWeight(.thin)
                    }
                    NavigationLink(destination: ContentView(
                            letters: lettersData[0].letters,
                            pickers: lettersData[0].letters,
                            title: "ヘブライ文字クイズ"
                            ))
                            ...
```

```Swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    var ...
    
    var body: some View {
        VStack {
            ...
            Spacer()
            HStack{
                NavigationLink(
                    destination: ResultView(
                        quizData: quizData,
                        withUnderScores: self.withUnderScores,
                        questionAmount: self.questionedLetters?.count,
                        letters: letters,
                        pickers: pickers,
                        percent: self.completedRate()
                    )
                    .environmentObject(userData),
                    isActive: $goResultView) {
                    Text("")
                }
                ...
```

###Alert
AlertからNavigationLinkを使う方法が面倒かった。

```Swift:ContentView.swift
NavigationLink(
    destination: ResultView(
        quizData: quizData,
        withUnderScores: self.withUnderScores,
        questionAmount: self.questionedLetters?.count,
        letters: letters,
        pickers: pickers,
        percent: self.completedRate()
    )
    .environmentObject(userData),
    isActive: $goResultView) {
    Text("")
}
Button("結果を見る"){
    if(self.quizData.unQuestionedLetters.count > 0){
        self.showingUnfinishedAlert = true
    } else {
        self.goResultView = true
    }
}.alert(isPresented: $showingUnfinishedAlert) {
    Alert(title: Text("未終了"), 
    message: Text("まだ全ての問題に回答していません。このクイズを終了して結果を見ますか？"),
    primaryButton: .destructive(Text("結果を見る")) {
        self.goResultView = true
        }, 
    secondaryButton: .cancel())
}
```
###カスタムフォント
今回ヘブライ文字の筆記体を表示する必要があったので、フォントを使いしました。info.plistを編集する必要があるので注意してください。
####既知の問題
筆記体のヘブライ文字はイタリックの文字のように右に傾いており、上下に幅の広い文字の場合、右上と左下に大きく伸びます。これが表示上左右の出っ張りが切れて見えなくなるという問題があります。ちなみに左右にスペースを入れても直りません。今回解決できなかったので、左右に_を入れて対処しました。

<img width="242" alt="Screen Shot 2020-08-01 at 13.19.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189285/1d3cb978-58e1-b9fc-7a0f-1fdd0f5ca483.png">
これがアレフ。

<img width="235" alt="Screen Shot 2020-08-01 at 13.19.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189285/15fcee85-cbea-2cb2-581a-1dedf7449072.png">
ちょっと右が切れてるのわかります？

<img width="235" alt="Screen Shot 2020-08-01 at 13.21.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189285/921b7b87-b8b8-b2fb-4855-a409caccbe8e.png">
これがギメル

<img width="241" alt="Screen Shot 2020-08-01 at 13.21.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189285/410546fb-31c0-3760-05e9-abae435c49e6.png">
あー右が切れた。

```Swift:LetterExplanation.swift
ScrollView{
    Text("いいから覚えるんだ")
        .padding()
    if(withUnderScores ?? true){
        Text("左右の横線（アンダースコア）を文字の高さの基準にしてください。")
            .padding()
    }
    self.displayFont(text: Text(self.textWithUnderScores(string: letter.script)), name: "活字体")
    if(!(letter.dagesh ?? true)){
        self.displayFont(text: Text(self.textWithUnderScores(string: letter.script))
            .font(.custom("KtavYadCLM-BoldItalic", size: 150))
        , name:"筆記体")

    }

...

func textWithUnderScores(string: String) -> String {
    if (self.withUnderScores ?? true ) {
        return "_" + string + "_"
    }else{
        return string
    }
}
```
この_をとったらこの現象がおこると理解していただければ。いやあ、どうしたらいいんだろ。


###SheetView
したから上にスッと出てくる画面ですね。設定画面で使いました。意外と面倒。

```Swift:ContentView.swift
Button(action: {
    self.showingSetting.toggle()
}) {
    Image(systemName: "gear").imageScale(.large)
}.sheet(isPresented: $showingSetting) {
    SettingView(
        showSheetView: self.$showingSetting// userData: self.userData
    )
    .environmentObject(self.userData)
}
```
```Swift:SettingView.swift
import SwiftUI

struct SettingView: View {
    
    @Binding var showSheetView: Bool
    @EnvironmentObject var userData: UserData

    var body: some View {
        NavigationView {
            Form {
                Picker(
                    selection: $userData.transliterationMode,
                label: Text("ラテン文字転写の方法"))
                {
                    ForEach(
                    TransliterationMode
                        .allCases
                        .filter{switch $0 {
                            case TransliterationMode.Common: return false
                            default: return true
                            }}
                    , id: \.self) {
                        Text($0.rawValue).tag($0)
                    }
                }
            }
            .navigationBarTitle(Text("設定"), displayMode: .inline)
            .navigationBarItems(trailing:
                Button(action: {
                    print("Dismissing sheet view...")
                    self.showSheetView = false
                })
                {
                    Text ("完了")
                }
            )
        }
    }
}
```
##kritaでアイコンを作ろう！
自分一人でアプリ作ろうとしたらアイコンも自作か...まじめんどくさいな。いろいろソフトあるみたいですがkritaというのをインストールしました。大したことはしてないのですが。この辺は気合ですね。デザインに死ぬほど自信がないので他人に見てもらったりしました。

## プライバシーポリシーの設置
最近は個人の開発者でもプライバシーポリシーを設置することを求められるそうです。というかリリース作業の途中にめっちゃAppleに念押しされました。というわけでGitHub上にサイトを設置し、そこへのリンクを貼っておきました。今のところこれでBanとかはないです(2020/7)。

##バナーをはろう！
正直全く儲かる気がしないのだけど、それでも練習と思ってGADBannerというのを貼りました。


## DeployGateは全てを解決する！
他人にテストしてもらったり、そもそもリリースとかバージョンアップとか、DeployGateめっちゃ便利。
そもそもどうやったらApple Storeにアップできるのか全然わからない状況においてDeployGateのマニュアルは非常に優秀だった。

##リリース作業
実はここで一番ハマったのは「アプリ紹介用のスクショを貼る」という作業。simulatorでスクショしても全然サイズ合わへんヤンけ！
そもそもsimulatorはpixelを合わせて画面上に表示される（よくわかってない）。一応windowのタブからリアルなサイズに合わせることもできるようだが、それでも必要なサイズにはならない。結局無理やりリサイズした。なんかスマートな方法はないものか。



## その後
某先生からの指摘によりユーザー設定で転写方式を変えるためにいろいろしました。そのへんを含めてXCodeのところには書いておきました。

#感想
実際にやってみるっていうのは一番楽しいことであって、採算とか目的とかはある程度度外視して、このアプリ作ってみて本当に楽しかった。出来上がったらいつもお世話になってるqiitaになんか書こうと思ってたのですが、いろいろ引っ張ってきてたリンクをもうたどることができない...。みんなどうやって記事書いてるんだ。作りながら書いてる？　今度からは記事を書きながら作業するのが目標になりそう。