見出し画像

SwiftUIで単語帳アプリを作ってみたからメモ

単語帳アプリって、有料のものが多かったり自分に合わなかったりするものが多いので自分用に作ってみました。
初心者なので間違い等あるかもしれません😭

私、最新バージョンのMacを持っていないのでiPadのPlaygroundsで作ってみました!

見た目はこんな感じです!

単語帳
カード
左右にスワイプして「わかった」「わからなかった」分類できる
テスト
範囲を決めて4択のテストができる


単語データの保存

単語のデータは以下のようなデータモデルを作って保存しています。

struct Word: Identifiable, Codable {
    let id = UUID()
    let no: String
    let en: String
    let ja: String
    var status: Int
}

初回起動時にアプリ内のCSVからデータを読み込んでUserDefaultsに保存し、それ以降はUserDefaultsからデータを読み込んでいます。

ちなみにCSVからデータを取り出すコードは以下です

func loadCSV(fromFile filename: String) -> [Word] {
    var words: [Word] = []
    
    if let path = Bundle.main.path(forResource: filename, ofType: "csv") {
        do {
            let csvString = try String(contentsOfFile: path, encoding: .utf8)
            let csvLines = csvString.components(separatedBy: .newlines)
            for line in csvLines {
                let values = line.components(separatedBy: ",")
                if values.count == 3 {
                    let word = Word(no: values[0], en: values[1], ja: values[2], status: 0)
                    words.append(word)
                }
            }
        } catch {
            print("Error reading CSV file: \(error)")
        }
    } else {
        print("File not found")
    }
    
    return words
}

単語データのリスト表示

単語データを読み込んだら一番最初の画像みたいにリストで表示していきます。

ただ、普通にVStackで表示すると読み込みに時間がかかってしまい、動作がすごく重くなります🥺

なので、LazyVStackというものを使って表示していきます!
こいつを使うと、表示されている部分のみ読み込むので動作が重くなりません!

LazyVStack{
	ForEach(words) { word in
  		HStack(spacing:15){
      	    Text(word.no)
            VStack(spacing:0){
        	    Text(word.en)
          	    Text(word.ja)
			}
		}
	}
}


また、このリストには他にも機能があり、例えば以下の画像のように赤シートみたいに答えを隠せる機能!

これはどのように実装しているのかというと、
GeometryReaderを使って画面の半分より上に来たら答えのテキストを表示するようにしています!

GeometryReader { geometry in
    Text(flip ? word.en : word.ja)
        .font(.system(size: flip ? 18 : 15, weight: .bold, design: ThemeFont))
        .foregroundColor(geometry.frame(in: .global).minY < UIScreen.main.bounds.height / 2 ? Color.cyan.opacity(0.7) : .clear)
}

単語のステータス

単語を覚えたかどうか4段階で記録しています!
(ー 判定なし)
×     覚えてない
△    曖昧
○   覚えてきた
◎  覚えた

これは冒頭で紹介した単語のデータモデルのstatus(Int)に
それぞれ0、1、2、3、4として保存しています。

単語リスト表示する画面では、ピッカーを表示してステータスを変更できるようにしています
このステータスの変更が少し手こずりました😭

let index = words.firstIndex(where: { $0.id == word.id })
Picker(word.en, selection: $words[index].status) {
    Image(systemName: "minus.circle.fill")
        .tint(Color.primary.opacity(0.5))
        .tag(0)
    Image(systemName: "multiply.circle.fill")
        .tint(Color.red.opacity(0.5))
        .tag(1)
    Image(systemName: "triangle.fill")
        .tint(Color.orange.opacity(0.5))
        .tag(2)
    Image(systemName: "circle")
        .tint(Color.green.opacity(0.5))
        .tag(3)
    Image(systemName: "circle.circle")
        .tint(Color.blue.opacity(0.5))
        .tag(4)
}
.pickerStyle(.palette)
.onChange(of: words[index].status){
    WordsDataStore.shared.saveWords(words)
}

このように、ピッカーで直接wordのstatusの値を変更しています。
また、onChange で値が変化した時にデータをUserDefaultsに保存するコードを実行しています

また、設定したそれぞれのステータスでフィルターしてリストに表示することもできます。
リスト表示のコードのwordsにfilter をつければ実装できます

var selectedStatuses = [0,1,2,3,4]
words.filter{selectedStatuses.contains($0.status) }

単語のシャッフル

これは簡単。.shuffled()をつけるだけ!

ForEach(isShuffle ? words.shuffled() : words) { word in
//
}

このコードでは、isShuffleがtrueならシャッフルしたデータを表示するようにしています。

カード表示機能、テスト機能についてはまた次回紹介するかもしれません!
気が向いたら書きます!

この記事が気に入ったらサポートをしてみませんか?