初めてのSwift5 その5

カード型データベースを作る

昔の初めての言語の本にはよくあったファイルの入出力を覚えるためによく作った簡単なカード型データベースです。
といっても難しい話ではなく、単語帳を思い出してください。
小さな紙が沢山挟んであってそこに英単語なんか書いて毎日それをめくって覚えたあれです。

今回はそれのiOS版を作っていきたいと思います。
といってもごめんなさい、個人情報管理カードです。
簡単に改造ができるので、後で修正してみてください。

プロジェクトを作る

画像1

何時も通り、今回もサクッと小さいアプリを作ります。
新しいプロジェクト作成するために、FileメニューからNewを選んでProjectを選択します。

画像2

今回もSingle View Appを選択して、Nextボタンを押します。

画像3

適当なプロジェクト名等をそれぞれ入力、Nextボタンを押します。

画像4

今回はこんな画面を作っていきたいと思います。
私もSwift5まだ初めて間もないのでまあこんなシンプルな画面です。
今回の新しいところは、トグルボタンがついたところですね。
後は見慣れたものしかないはずです。

トグルボタン

//  トグルボタン
Toggle(isOn: $bSex) {
	//  ONになっていない場合
	if(self.bSex != true){
		//  ラベル名を男性に変更する
       Text("男性")
   }
   //  ONになった場合
   else{
   		//  ラベル名を女性に変更する
       Text("女性")
   }
}
.padding()

トグルボタンはよくiPhoneのアプリででてくると思いますが、同じものです。
一寸ボタンと違って特殊なのは、スライドさせた状態の管理方法です。
ボタンはActionと呼ばれるところに書いてましたが、トグルボタンは、
メンバー変数を$をつけた状態でisOnのところにセットするだけで自動的に
管理してくれます。
後はそれがONになっているか、OFFなのかの処理を上記ソースコードのように書くだけです。

個人情報構造体

//  個人情報構造体
struct PersonalTbl : Codable{
   //  シーケンシャル番号
   var SeqNo: Int
   //  名前
   var name: String
   //  年齢
   var age: Int
   //  false: 男 true: 女
   var sex: Bool
   //  初期化処理
   init() {
       self.SeqNo = 0
       self.name = ""
       self.age = 0
       self.sex = false
   }
}

個人情報を管理する為に、構造体を作ります。
それぞれ管理する為の、シーケンシャル番号(システム的に自動で採番するレコード番号)と名前、年齢、性別を管理しています。
構造体のところに見慣れないCodableと言うのがありますが、これはJSON形式のデータとして取り扱う為のオマジナイです。
プロトコルというものに該当するのですが、JSONのデータ扱う時はつけとけばいいやぐらいでいいと思います。
難しい話は後で、兎に角動くもの作りましょう。

カード管理用配列

    //  個人情報管理二次元配列
   @State var CardData: [PersonalTbl] = []

 カードそのものを管理する、変数が必要になってきます。
それが上記の配列です。
これも今回始めてですね、二次元配列というやつです。
想像してみてください、ダンボールが横に並んでいくつもある姿を。
一つ一つのダンボールの中に、自分の好きなものを詰め込むと思いますが今回は個人情報を管理するので、その構造体名をセットしています。
CardDataというダンボールの配列にはこの構造体を沢山いれますよって意味になります。

配列操作

戻るボタン

//  カードのシーケンシャル番号が既に0か?
if(self.SeqNo <= 0){
  //  何もしない
}
else{
  //  現在の値を保存しておきます
  self.CardData[self.SeqNo].SeqNo = 0
  self.CardData[self.SeqNo].name = self.txtName
  //  年齢
  if let testVal = Int( self.txtAge )
  {
      //  カード情報に保存する
      self.CardData[self.SeqNo].age = testVal
  }
  else{
      self.CardData[self.SeqNo].age = 0
  }
  //  性別情報を保存するよ
  self.CardData[self.SeqNo].sex = self.bSex
  //  シーケンス番号を移動させます
  self.SeqNo = self.SeqNo - 1
  //  保存していた情報からデータを取り出して表示させる
  self.txtAge = self.CardData[self.SeqNo].age.description
  self.txtName = self.CardData[self.SeqNo].name
  self.bSex = self.CardData[self.SeqNo].sex
}

今回の仕様は、「戻るボタン」「次へボタン」を押す度に配列を作ったり自動で値を配列内に保存するといった仕様としています。
なので、一寸ソースコードが長くなってるのでご了承ください。

上記のコードは「戻るボタン」の部分です。
一寸長いですが、やっていることは以下の通りです。

・SeqNoを確認して0である場合は何もしない
・現在編集している値を配列に保存する
・SeqNoをデクリメントして戻った先の配列の情報を指す
・移動先の配列の情報を画面に表示する


SeqNoというメンバー変数ですが、これは現在編集している配列の場所ということになります。
どこのダンボールあけてその中の情報を触っているのかを示すものになります。
やっていることは単純ですね。

次へボタン

if(self.SeqNo>=99){
  //  何もしない
  //  リミッターを100件程度とします。
  //  上限を上げたい場合はここを変更します。
}
else{
  //  まだ一枚もカード情報がない場合
  if(self.CardData.count <= 0){
      //  配列がない場合は、作成して保存します
      self.CardData.append(PersonalTbl())
      self.CardData[0].SeqNo = 0
      self.CardData[0].name = self.txtName
      //  年齢を取り出す
      if let testVal = Int( self.txtAge )
      {
          self.CardData[0].age = testVal
      }
      else{
          self.CardData[0].age = 0
      }
      //  性別を保存する
      self.CardData[0].sex = self.bSex
  }
  else{
      //  現在の値を保存しておきます
      self.CardData[self.SeqNo].SeqNo = 0
      self.CardData[self.SeqNo].name = self.txtName
      //  年齢を取り出す
      if let testVal = Int( self.txtAge )
      {
          self.CardData[self.SeqNo].age = testVal
      }
      else{
          self.CardData[self.SeqNo].age = 0
      }
      //  性別を保存する
      self.CardData[self.SeqNo].sex = self.bSex
  }
  //  シーケンシャル番号を上げておきます
  self.SeqNo = self.SeqNo + 1
  //  カード情報が不足している場合
  if(self.CardData.count <= self.SeqNo){
      //  カード情報を追加します
      self.CardData.append(PersonalTbl())
      //  初期化します
      self.txtAge = "0"
      self.txtName = ""
      self.bSex = false
  }
  else{
      //  既にあるカードからデータを取り出して画面に表示します
      self.txtAge = self.CardData[self.SeqNo].age.description
      self.txtName = self.CardData[self.SeqNo].name
      self.bSex = self.CardData[self.SeqNo].sex
  }
}

戻るボタンより少し複雑なのは、このボタンでカード情報を作成している処理が入っているからです。
このボタンの処理は以下の通りです。

・既に100個の配列を参照している場合は何もしない
・配列がない場合は配列を作成し、その配列に現在編集しているデータを保存する
・SeqNoをインクリメントして次の配列を指す
・次の配列が存在しない場合は配列を作成し画面に空のデータを表示する
・次の配列が存在する場合は、その配列データを画面に表示する

やっていることは戻るボタンと共通する部分もありますが配列を作るところが異なります。

ファイルにデータを保存

これでデータを保存できるようになりました。
但し配列に保存しているだけでアプリを終了したりするとデータが全部消えてしまいます。

そこで、ファイルにデータを保存します。
ファイル形式はJSON形式として保存します。

//  個人情報データをファイルに保存します(JSON)
func PersonalDataWrite() -> Bool{
  
  //  ドキュメントパス取得するよ
  let path = GetUserDocPath()
  //  ファイルパス作成するよ。
  let filepath = URL(fileURLWithPath: "PersonalFile", relativeTo: path).appendingPathExtension(".json")
  let encoder = JSONEncoder()
  encoder.outputFormatting = .prettyPrinted
  
  do{
      //  JSON形式に変換する
      let data = try encoder.encode(self.CardData)
      try data.write(to: filepath)
      //  ファイルの書込に成功しました
      return true
  }
  catch{
      //  ファイルの書込に失敗しました
      return false
  }
}

今回は、JSON形式ということで、JSONEncoderというものがでてきます。
これを使用することで、構造体のデータをまるごとJSON形式に変換してくれてファイルに保存することができます。
encodeという関数を使用することで、配列全体をまるごとJSON形式として保存してくれるのでこういうもんだぐらいで今はいいと思います。
オマジナイですね。

ファィルデータの読み出し

保存したら、取り出して使用したいですよね。
それも簡単で以下のようなコードで実現することができます。

//  Jsonファイルを読み込みます
func PersonalDataRead() -> [PersonalTbl]{
  
  //  ドキュメントパス取得するよ
  let path = GetUserDocPath()
  //  ファイルパス作成するよ。このあたりは書き込みの時と同じ
  let filepath = URL(fileURLWithPath: "PersonalFile", relativeTo: path).appendingPathExtension(".json")
  
  do{
      //  ファイルからテキストデータ取り出すよ
      //  Data構造体として戻ってくるよ
      let savedData = try Data(contentsOf: filepath)
      let convdata = String(data: savedData, encoding: .utf8)
      print(convdata ?? "")
      return try JSONDecoder().decode([PersonalTbl].self, from: savedData)

  }
  catch{
      //  ファイルの読込に失敗しました
      return [PersonalTbl()]
  }
}

保存したファイルから、今度はJSONDecoderというものを使用して取り出します。
エンコードする時と違ってデコードする時に、どういう風に保存しているかを示す情報を渡す必要があります。
配列の中に、構造体が並んでいる方式で保存していましたから、それように指定してあげています。
ここも今はそういうものだぐらいでいいと思います。

動かしてみよう

まあ、回りくどいことはいいので、動かして確認してみましょう

画像5

そうすると、上記のような画面がでてくると思います。
適当にデータをそれぞれ入力してみましょう。

画像6

こんな感じでどんどん作成して、最後に保存を押しましょう。
そして、一旦プログラムを終了し再度実行してまみしょう。

画像7

こんな感じで、保存した値が正しく表示されていると思います。
今回は、簡単な個人情報を管理するカード型データベースでしたがオリジナルの単語帳を作ってみるのもいいかもしれませんね。
それではまた!

今回の全コード

//
//  ContentView.swift
//  HogeHoge3
//
//  Created by melon on 2020/05/09.
//  Copyright © 2020 melon-group. All rights reserved.
//

import SwiftUI

//  個人情報構造体
struct PersonalTbl : Codable{
   //  シーケンシャル番号
   var SeqNo: Int
   //  名前
   var name: String
   //  年齢
   var age: Int
   //  false: 男 true: 女
   var sex: Bool
   //  初期化処理
   init() {
       self.SeqNo = 0
       self.name = ""
       self.age = 0
       self.sex = false
   }
}

struct ContentView: View {
   //  メッセージ用表示変数
   @State var txtMessage = ""
   //  表示しているシーケンシャル番号
   @State var SeqNo = 0
   //  名前を格納する変数
   @State var txtName = ""
   //  年齢を格納する変数
   @State var txtAge = ""
   //  性別
   @State private var bSex = false
   //  個人情報管理二次元配列
   @State var CardData: [PersonalTbl] = []
   
   var body: some View {
       //  縦に並べるぞっ!
       VStack {
           HStack{
               //  トップラベル名
               Text("個人情報管理カード")
                   .padding()

               }
               .border(Color.blue, width: 1)
               .padding()
           HStack{
               Text( "管理番号 : " + String().appendingFormat( "%u",self.SeqNo + 1 ) )
           }
           //  氏名を入力するフィールド
           HStack {
               //  ラベル名
               Text("氏名")
               //  テキストフィールド
               TextField("名前入れてね", text: $txtName)
                   //  外側のラインを丸めてみる
                   .textFieldStyle(RoundedBorderTextFieldStyle())
                   .padding()
           }
           //  年齢を入力するフィールド
           HStack {
               //  ラベル名
               Text("年齢")
               //  テキストフィールド
               TextField("年齢を入れてね", text: $txtAge)
                   //  外側のラインを丸めてみる
                   .textFieldStyle(RoundedBorderTextFieldStyle())
                   .padding()
           }
           //  を入力するフィールド
           HStack {
               //  ラベル名
               Text("性別")
               //  トグルボタン
               Toggle(isOn: $bSex) {
                   //  ONになっていない場合
                   if(self.bSex != true){
                       //  ラベル名を男性に変更する
                       Text("男性")
                   }
                   //  ONになった場合
                   else{
                       //  ラベル名を女性に変更する
                       Text("女性")
                   }
               }
               .padding()
           }
           HStack{
               //  戻るボタン
               Button(action:
               {
                   //  カードのシーケンシャル番号が既に0か?
                   if(self.SeqNo <= 0){
                       //  何もしない
                   }
                   else{
                       //  現在の値を保存しておきます
                       self.CardData[self.SeqNo].SeqNo = 0
                       self.CardData[self.SeqNo].name = self.txtName
                       //  年齢
                       if let testVal = Int( self.txtAge )
                       {
                           //  カード情報に保存する
                           self.CardData[self.SeqNo].age = testVal
                       }
                       else{
                           self.CardData[self.SeqNo].age = 0
                       }
                       //  性別情報を保存するよ
                       self.CardData[self.SeqNo].sex = self.bSex
                       //  シーケンス番号を移動させます
                       self.SeqNo = self.SeqNo - 1
                       //  保存していた情報からデータを取り出して表示させる
                       self.txtAge = self.CardData[self.SeqNo].age.description
                       self.txtName = self.CardData[self.SeqNo].name
                       self.bSex = self.CardData[self.SeqNo].sex
                   }
               }
               ) {
                   //  ボタンのラベル名
                   Text("戻る")
               }
               Spacer().padding()
               //  次へのボタン
               Button(action:
               {
                   if(self.SeqNo>=99){
                       //  何もしない
                       //  リミッターを100件程度とします。
                       //  上限を上げたい場合はここを変更します。
                   }
                   else{
                       //  まだ一枚もカード情報がない場合
                       if(self.CardData.count <= 0){
                           //  配列がない場合は、作成して保存します
                           self.CardData.append(PersonalTbl())
                           self.CardData[0].SeqNo = 0
                           self.CardData[0].name = self.txtName
                           //  年齢を取り出す
                           if let testVal = Int( self.txtAge )
                           {
                               self.CardData[0].age = testVal
                           }
                           else{
                               self.CardData[0].age = 0
                           }
                           //  性別を保存する
                           self.CardData[0].sex = self.bSex
                       }
                       else{
                           //  現在の値を保存しておきます
                           self.CardData[self.SeqNo].SeqNo = 0
                           self.CardData[self.SeqNo].name = self.txtName
                           //  年齢を取り出す
                           if let testVal = Int( self.txtAge )
                           {
                               self.CardData[self.SeqNo].age = testVal
                           }
                           else{
                               self.CardData[self.SeqNo].age = 0
                           }
                           //  性別を保存する
                           self.CardData[self.SeqNo].sex = self.bSex
                       }
                       //  シーケンシャル番号を上げておきます
                       self.SeqNo = self.SeqNo + 1
                       //  カード情報が不足している場合
                       if(self.CardData.count <= self.SeqNo){
                           //  カード情報を追加します
                           self.CardData.append(PersonalTbl())
                           //  初期化します
                           self.txtAge = "0"
                           self.txtName = ""
                           self.bSex = false
                       }
                       else{
                           //  既にあるカードからデータを取り出して画面に表示します
                           self.txtAge = self.CardData[self.SeqNo].age.description
                           self.txtName = self.CardData[self.SeqNo].name
                           self.bSex = self.CardData[self.SeqNo].sex
                       }
                   }
               }
               ) {
                   Text("次へ")
               }
           }
           Spacer()
           HStack{
               //  読込ボタン
               Button(action:
               {
                   //  個人情報をファイルを読込する(JSON形式)
                   self.CardData = self.PersonalDataRead()
                   self.SeqNo = 0
                   self.txtAge = self.CardData[self.SeqNo].age.description
                   self.txtName = self.CardData[self.SeqNo].name
                   self.bSex = self.CardData[self.SeqNo].sex
               }
               ) {
                   //  ボタンのラベル名
                   Text("読込")
               }
               Spacer()
               Button(action:
               {
                   //  個人情報をファイルに保存します(JSON形式)
                   if( self.PersonalDataWrite() )
                   {
                       //  正常処理
                   }
                   else{
                       //  異常処理
                   }
               }
               ) {
                   Text("保存")
               }
           }
       }
   }
   //  ユーザパスの取得
   func GetUserDocPath() ->
       URL{
           //  ユーザのドキュメントディレクトリのパスを取得してURL構造体で返すよ
           return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
   }
   //  Jsonファイルを読み込みます
   func PersonalDataRead() -> [PersonalTbl]{
       
       //  ドキュメントパス取得するよ
       let path = GetUserDocPath()
       //  ファイルパス作成するよ。このあたりは書き込みの時と同じ
       let filepath = URL(fileURLWithPath: "PersonalFile", relativeTo: path).appendingPathExtension(".json")
       
       do{
           //  ファイルからテキストデータ取り出すよ
           //  Data構造体として戻ってくるよ
           let savedData = try Data(contentsOf: filepath)
           let convdata = String(data: savedData, encoding: .utf8)
           print(convdata ?? "")
           return try JSONDecoder().decode([PersonalTbl].self, from: savedData)

       }
       catch{
           //  ファイルの読込に失敗しました
           return [PersonalTbl()]
       }
   }
   //  個人情報データをファイルに保存します(JSON)
   func PersonalDataWrite() -> Bool{
       
       //  ドキュメントパス取得するよ
       let path = GetUserDocPath()
       //  ファイルパス作成するよ。
       let filepath = URL(fileURLWithPath: "PersonalFile", relativeTo: path).appendingPathExtension(".json")
       let encoder = JSONEncoder()
       encoder.outputFormatting = .prettyPrinted
       
       do{
           //  JSON形式に変換する
           let data = try encoder.encode(self.CardData)
           try data.write(to: filepath)
           //  ファイルの書込に成功しました
           return true
       }
       catch{
           //  ファイルの書込に失敗しました
           return false
       }
   }
}

struct ContentView_Previews: PreviewProvider {
   static var previews: some View {
       ContentView()
   }
}

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