公開中のアプリ

[Swift/Xcode]初心者必見!iOSアプリ作成から公開まで徹底解説!#13「Realm編②」

ではRealmを使ってみたいと思います!

動作環境は下記の通りです!
Xcode Version 12.5 Swift version 5.4

何のデータが必要か考える!

データベースを使う前には必ずどんなデータがいるのか考える必要があります。
例えば、アカウント作成する場合だと

・アカウント名
・メールアドレス
・ID
・パスワード
・生年月日

などが必要っぽいですね。仕様によってはもっと色々必要になってくると思います。

では今回のアプリではどんなデータが必要でしょうか??
パッと思いつくのは下記でしょうか。
※本来ならここで徹底的に必要なデータを洗い出すみたいですけど、規模も小さいので僕はわりとざっくりしてます(^^;

①ボタンの状態(青色かどうか)
②貯金額の合計
③日数


①に関しては、365個あるボタンでアクティブなボタンだけを保存すれば解決しそうですね!

②③に関しては、普通に合計を保存すれば問題なさそうです。

では、①②③のデータを保存すれば良いかと言うと違うんですね(^^;

答えを先に言うと①だけ保存しておけばOKです!


詳しく説明しますね。

まず押されたボタンのタグ番号を配列に保存します。
例えば10円、30円、120円が押されたとしたら配列の中身は下記です。

[10, 30, 120]

今の状態としては、10円、30円、120円が青色です。
ここでアプリを起動し直すとグレイに戻ってしまいますね?

そうならないように保存した配列を使います。
配列に保存されているタグ番号のボタンを青色に変更するようにviewDidLoad() の中に記述します。

これでボタンの状態は解決です!

貯金額に関してはviewDidLoad() の中で配列のタグ番号を足してあげるだけで解決です!
日数に関しても同じくviewDidLoad() の中で足してあげればOKです!

イメージ湧いたでしょうか???

とりあえずやってみましょう!!

「RealmDB.swift」にコードを記述

では作成した「RealmDB.swift」にコードを書きます!
このファイルにはRealmを使うにあたって、必要なデータの変数と関数を書きます。
データの消去も必要ですが、とりあえずデータを登録と保存だけにしたいと思います。

import Foundation
import RealmSwift

class Btn365: Object {
    //おまじない
    static let realm = try! Realm()
    //必要なデータの変数を作る。
    @objc dynamic var tagNum: Int = 0
    
    //データの登録
    static func create() -> Btn365 {
            let addDate = Btn365()
            return addDate
    }
    //データの保存
    func save() {
        try! Btn365.realm.write {
            Btn365.realm.add(self)
        }
    }
}

Realmを使うにはまず2行目のimportをします。

8行目が今回、保存するタグ番号です!

例えば名前を保存したいなら

@objc dynamic var name: String = “”

とかを追加すればOKです!

あとは、11行目からデータの登録の記述で、16行目からはデータを保存する記述です。

この辺りの説明はググればすぐに出てきますので興味ある方はググってみて下さい!

Realmを使ったデータの保存と読み込み

ではデータの保存と読み込みをしていきましょう!

データの保存

データの保存ですが、まず保存するタイミングを確認しておきたいと思います。

ボタンが青色に変わってアクティブになった時なので、下記timerCounter()の中で保存すれば良さそうです!

@objc func timerCounter() {
    //ロングタップ成功
    longTapOK = true
    //ぽよよんアニメーションの実行
    animateView(topViewInner)
    //バイブさせる
    AudioServicesPlaySystemSound(1102)
    //ラベルの更新
    updateLabel()
    //RealmDBへ登録
    let a = Btn365.create()
    a.tagNum = btnTag
    a.save()
}

データの登録をしてるのは11行目からです。

11行目で新しくデータを追加しますよ〜と宣言してます。

12行目で押されたボタンのタグ番号を「RealmDB.swift」の8行目のtagNumに代入してます。

13行目でセーブしてます。

これでデータの保存は完了です!

一度、ビルドしていくつかボタンをアクティブにしてみて下さい!
うまくいっていれば保存されてるはずです。

保存されたか確認してみよう

保存されたかどうか確認するには、データを参照するコードを書くだけです。

今回は後々の事も考えて「消去」「参照」がすぐに実行できるように、ボタンを作っておきたいと思います。

下記をMainViewController.swiftのviewDidLoad() に追加して下さい!

referenceBtn = UIBarButtonItem(title: "参照", style: .done, target: self, action: #selector(self.referenceBtnTapped))
self.navigationItem.rightBarButtonItem = referenceBtn
resetBtn = UIBarButtonItem(title: "リセット", style: .done, target: self, action: #selector(self.resetBtnTapped))
self.navigationItem.leftBarButtonItem = resetBtn

下記はviewDidLoad() に追加して下さい!

// DB参照ボタン
var referenceBtn: UIBarButtonItem!
//DBリセットボタン
var resetBtn: UIBarButtonItem!

//DB参照
@objc func referenceBtnTapped() {
    let realm = try! Realm()
    let Btn365 = realm.objects(Btn365.self)
    print(Btn365)
    }
//DBリセット
@objc func resetBtnTapped() {
    let realm = try! Realm()
    try! realm.write {
      realm.deleteAll()
    }
}

これでビルドすると下記画像のようにボタンが二つできてると思うので参照ボタンを押して保存されているか確認してみて下さい!

ちょっと見にくいですけど下記画像のように出力されていればOKです!

データの読み込み

次はデータの読み込みです!

先ほど説明した通りviewDidLoad()時にデータを呼び出して、呼び出されたタグ番号のボタンを青色に変更します!

編集するファイルはMainViewController.swiftです!
import RealmSwiftを忘れないように書いて下さい〜

//realmの呼び出し
let realm = try! Realm()
//保存されたデータが定数Btn365に入る
let Btn365 = realm.objects(Btn365.self)
//定数Btn365をfor文で回し、ボタンの色を変更しつつ、貯金額を足しつつ、日数も足してる。
for i in 0..<Btn365.count {
    //ボタンの色を変更
    buttonArray[Btn365[i].tagNum - 1].backgroundColor = UIColor.blue
    //貯金額を足してる
    totalSavings += Btn365[i].tagNum
    //日数はアクティブなボタンの数と同じ
    whatDay += 1
}
//貯金額ラベルを更新
moneyLabel.text = "合計貯金額\(totalSavings)円"
//合計日数ラベルを更新
dayLabel.text = "\(whatDay)日目"

上記のコードのコメントを見ればわかると思いますが、少し補足します。

先ほどの出力結果を見たら気づいたかもしれませんが、Realmに登録されたデータは配列になってます。
なので0から順番にインデックス番号が振られてます。

10行目のBtn365[i].tagNumの部分は、Realmに登録された配列のi番目(for文で回ってる数)のタグナンバーとなります。

あと、前にも説明したかもですが8行目のtagNum – 1の部分は、配列buttonArrayが0から始まっている理由から、1を引いてます。

まとめ

これでアプリを起動し直しても貯金額とかが0にならずに行けたと思います!

あとは一度アクティブにしたボタンを元に戻したい時の実装も必要ですね〜!
戻す時はアラート出さないとですね!

もうお気づきの人もいるかとは思いますが、現状だとアクティブになったボタンをもう一度押すと元に戻ってしまいます。

それはちょっと修正しておきたいですね!

次回はその修正をしたいと思います!

ここまでのコード全部!

import UIKit
import AudioToolbox
import RealmSwift

class MainViewController: UIViewController {

//スクロールビュー
let scrollView = UIScrollView()
//ボタンを表示用のビュー
let inView: UIView = UIView()
//スタックビュー縦用
var stackV: UIStackView = UIStackView()
//ボタンの配列
var buttonArray: [UIButton] = []
//スタックビュー横を格納する為の配列
var stkArray: [UIStackView] = []
//貯金額合計
var totalSavings = 0
//貯金開始?日目
var whatDay = 0
// DB参照ボタン
var referenceBtn: UIBarButtonItem!
//DBリセットボタン
var resetBtn: UIBarButtonItem!

//オレンジ色のビュー
let topView: UIView = UIView()
//白色のビュー
let topViewInner: UIView = UIView()
//合計日数のラベル
let dayLabel: UILabel = UILabel()
//貯金額のラベル
let moneyLabel: UILabel = UILabel()


override func viewDidLoad() {
super.viewDidLoad()
    
//ビューの生成
viewCreate()
//ラベルの生成
labelCreate()
//ボタンの生成
btnCreate()
//スタックビューの生成
stackViewCreate()

//ナビゲーションコントローラーまわり
//タイトル
self.title = "365貯金"
//背景色
self.navigationController?.navigationBar.barTintColor = UIColor.orange
//セーフエリアとの境目の線を消す
self.navigationController?.navigationBar.shadowImage = UIImage()
//上部が黒くなるのを回避
view.backgroundColor = UIColor.systemBackground

//Realmテスト用ボタン右
referenceBtn = UIBarButtonItem(title: "参照", style: .done, target: self, action: #selector(self.referenceBtnTapped))
self.navigationItem.rightBarButtonItem = referenceBtn
//Realmテスト用ボタン左
resetBtn = UIBarButtonItem(title: "リセット", style: .done, target: self, action: #selector(self.resetBtnTapped))
self.navigationItem.leftBarButtonItem = resetBtn

//realmの呼び出し
let realm = try! Realm()
//保存されたデータが定数Btn365に入る
let Btn365 = realm.objects(Btn365.self)
//定数Btn365をfor文で回し、ボタンの色を変更しつつ、貯金額を足しつつ、日数も足してる。
for i in 0..<Btn365.count {
    //ボタンの色を変更
    buttonArray[Btn365[i].tagNum - 1].backgroundColor = UIColor.blue
    //貯金額を足してる
    totalSavings += Btn365[i].tagNum
    //日数はアクティブなボタンの数と同じ
    whatDay += 1
}
//貯金額ラベルを更新
moneyLabel.text = "合計貯金額\(totalSavings)円"
//合計日数ラベルを更新
dayLabel.text = "\(whatDay)日目"
}

//DB参照
@objc func referenceBtnTapped() {
    let realm = try! Realm()
    let Btn365 = realm.objects(Btn365.self)
    print(Btn365)
    }
//DBリセット
@objc func resetBtnTapped() {
    let realm = try! Realm()
    try! realm.write {
      realm.deleteAll()
    }
}
    
//ビュー生成
func viewCreate() {
    //オレンジ色のビューの表示
    view.addSubview(topView)
    //白色のビューの表示
    topView.addSubview(topViewInner)
    //スクロールビューの表示
    view.addSubview(scrollView)
    //ボタン表示用ビューの表示
    scrollView.addSubview(inView)

    //スクロールビューの設定
    //AutosizingをAutoLayoutに変換しないようにしている(おまじない)
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    //スクロールビューの上側の位置を設定
    scrollView.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: 20.0).isActive = true
    //スクロールビューの下側の位置を設定
    scrollView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -20.0).isActive = true
    //Y軸
    scrollView.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor).isActive = true
    //横幅
    scrollView.widthAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.widthAnchor, multiplier: 0.9).isActive = true


    //ボタン表示用ビューの設定
    //AutosizingをAutoLayoutに変換しないようにしている(おまじない)
    inView.translatesAutoresizingMaskIntoConstraints = false
    //横幅
    inView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: 1.0).isActive = true
    //この4つの制約をつけて初めてスクロールするっぽい
    inView.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 0).isActive = true
    inView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: 0.0).isActive = true
    inView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0.0).isActive = true
    inView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0.0).isActive = true


    //オレンジのビューのレイアウト
    //AutosizingをAutoLayoutに変換しないようにしている(おまじない)
    topView.translatesAutoresizingMaskIntoConstraints = false
    //X軸
    topView.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor).isActive = true
    //Y軸
    topView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
    //横幅
    topView.widthAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.widthAnchor, multiplier: 1).isActive = true
    //縦幅
    topView.heightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.heightAnchor, multiplier: 0.125).isActive = true
    //背景色
    topView.backgroundColor = UIColor.orange

    //白色のビューのレイアウト
    //(おまじない)
    topViewInner.translatesAutoresizingMaskIntoConstraints = false
    //X軸
    topViewInner.centerXAnchor.constraint(equalTo: topView.centerXAnchor).isActive = true
    //Y軸
    topViewInner.centerYAnchor.constraint(equalTo: topView.centerYAnchor).isActive = true
    //横幅
    topViewInner.widthAnchor.constraint(equalTo: topView.widthAnchor, multiplier: 0.9).isActive = true
    //縦幅
    topViewInner.heightAnchor.constraint(equalTo: topView.heightAnchor, multiplier: 0.75).isActive = true
    //背景色
    topViewInner.backgroundColor = UIColor.white
    }

//ラベル生成
func labelCreate() {
    //合計日数のラベルを表示
    topViewInner.addSubview(dayLabel)
    //貯金額のラベルを表示
    topViewInner.addSubview(moneyLabel)

    //合計日数のラベルのレイアウト
    //(おまじない)
    dayLabel.translatesAutoresizingMaskIntoConstraints = false
    //X軸
    dayLabel.leftAnchor.constraint(equalTo: topViewInner.leftAnchor, constant: 20.0).isActive = true
    //Y軸
    dayLabel.centerYAnchor.constraint(equalTo: topViewInner.centerYAnchor).isActive = true
    //ラベルのテキスト
    dayLabel.text = "\(whatDay)日目"

    //貯金額のラベルのレイアウト
    //(おまじない)
    moneyLabel.translatesAutoresizingMaskIntoConstraints = false
    //X軸
    moneyLabel.rightAnchor.constraint(equalTo: topViewInner.rightAnchor, constant: -20.0).isActive = true
    //Y軸
    moneyLabel.centerYAnchor.constraint(equalTo: topViewInner.centerYAnchor).isActive = true
    //ラベルのテキスト
    moneyLabel.text = "合計貯金額 \(totalSavings)円"
}
    
//スタックビュー生成
func stackViewCreate() {
    //スタックビュー縦の設定
    //スタックビューの方向を縦に
    stackV.axis = .vertical
    //中のオブジェクトをどこに揃えて配置するか
    stackV.alignment = .fill
    //どう配置するか
    stackV.distribution = .fill
    //オブジェクト同士のスペース
    stackV.spacing = 4
    //おまじない
    stackV.translatesAutoresizingMaskIntoConstraints = false
    //stackVの背景色
    stackV.backgroundColor = UIColor.white
    //stackVを表示
    inView.addSubview(stackV)
    //X軸
    stackV.centerXAnchor.constraint(equalTo: inView.centerXAnchor).isActive = true
    //トップ位置
    stackV.topAnchor.constraint(equalTo: stackV.topAnchor, constant: 0.0).isActive = true
    //横幅
    stackV.widthAnchor.constraint(equalTo: inView.widthAnchor, multiplier: 1.0).isActive = true
    //縦幅
    stackV.heightAnchor.constraint(equalTo: inView.heightAnchor, multiplier: 1.0).isActive = true

    //スタックビュー横を自動生成
    for i in 0 ..< 73 {
        //StackHの生成
        let stackH:UIStackView = UIStackView()
        //スタックビューの方向を横に
        stackH.axis = .horizontal
        //オブジェクト同士のスペース
        stackH.spacing = 4
        //中のオブジェクトをどこに揃えて配置するか
        stackH.alignment = .fill
        //どう配置するか
        stackH.distribution = .fillEqually
        //スタックビュー配列に追加
        stkArray.append(stackH)
        //stackVの中にstackHを格納
        stackV.addArrangedSubview(stkArray[i])
    }

    //ボタンの配置
    //for文のカウンター変数
    var count = 0
    //for文で365個のボタンをどのstackHに入れるか決めている
    for i in 1 ..< 366 {
        //カウントの数に応じてどのstackHに入るか決定する。
        stkArray[count].addArrangedSubview(buttonArray[i - 1] as UIView)
        //ボタンは5列なので5の倍数でcountを一つ増やす。
        if i % 5 == 0 {
            count += 1
        }
    }
}

//ボタン生成
func btnCreate() {
    //ボタンの生成と設定
    //ボタンにtagを付ける為の変数
    var tagNumber = 1
    //for文でボタンを生成
    for _ in 0...364 {
        //ボタン生成
        let button: UIButton = UIButton(type: .custom)
        //タイトルの色
        button.setTitleColor(UIColor.black, for: UIControl.State())
        //タイトルは数字なのでtagナンバーを指定
        button.setTitle(String(tagNumber), for: UIControl.State())
        //tag設定
        button.tag = tagNumber
        //背景色
        button.backgroundColor = UIColor.lightGray
        //ボーダーの横幅
        button.layer.borderWidth = 3
        //ボターの色
        button.layer.borderColor = UIColor.black.cgColor
        //AutosizingをAutoLayoutに変換しないようにしている(おまじない)
        button.translatesAutoresizingMaskIntoConstraints = false
        //ボタンの横幅が可変なのでボタンの高さは横幅と同じ長さを指定する事で正方形にしてる。
        button.heightAnchor.constraint(equalTo: button.widthAnchor, multiplier: 1.0).isActive = true
        //変数tagNumberに1追加
        tagNumber += 1
        //ボタン長押し
        let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPressed(_:))
        )
        //長押しを認識する時間の設定
        longPressGesture.minimumPressDuration = 0.01
        //ボタンに長押し機能を付ける
        button.addGestureRecognizer(longPressGesture)
        //ボタンの配列に追加
        buttonArray.append(button)
    }
}

// MARK: - ここからアニメーション関係
//タイマー初期化
var timer: Timer!
//2秒長押ししたかどうか判定する変数
var longTapOK = false
//ボタンのタグを関数の外に出す為の変数
var btnTag = 0

//長押し機能
@objc func longPressed(_ sender: UILongPressGestureRecognizer) {

    if sender.state == .began {
        print("長押し開始")
        btnTag = sender.view!.tag
        UIView.animate(withDuration: 2) {
            self.buttonArray[sender.view!.tag - 1].backgroundColor = UIColor.blue
        }
        startTimer()

    } else if sender.state == .ended {
        print("長押し終了")
        if longTapOK == false {
            UIView.animate(withDuration: 0.1) {
                self.buttonArray[sender.view!.tag - 1].backgroundColor = UIColor.lightGray
            }
            timer.invalidate()
        } else {
            print("ロングタップ成功")
            longTapOK = false
        }
    }
}

//タイマーの設定
func startTimer() {
    timer = Timer.scheduledTimer(
        timeInterval: 2, //タイマーの間隔を設定
        target: self,
        selector: #selector(self.timerCounter), //メソッドの設定
        userInfo: nil,
        repeats: false) //リピートするかどうか
}
    
//タイマーで実行される処理
@objc func timerCounter() {
    //ロングタップ成功
    longTapOK = true
    //ぽよよんアニメーションの実行
    animateView(topViewInner)
    //バイブさせる
    AudioServicesPlaySystemSound(1102)
    //ラベルの更新
    updateLabel()
    //RealmDBへ登録
    let a = Btn365.create()
    a.tagNum = btnTag
    a.save()
}
    
//ラベルの値を更新する(長押し認識してから2秒後に実行される)
func updateLabel() {
    //貯金額を更新
    totalSavings += btnTag
    //合計日数を更新
    whatDay += 1
    //貯金額ラベルを更新
    moneyLabel.text = "合計貯金額\(totalSavings)円"
    //合計日数ラベルを更新
    dayLabel.text = "\(whatDay)日目"
}
    
//ぽよよんアニメーション
func animateView(_ viewToAnimate:UIView) {
    UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseIn, animations: {
        viewToAnimate.transform = CGAffineTransform(scaleX: 1.08, y: 1.08)
    }) { (_) in
        UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 10, options: .curveEaseOut, animations: {
            viewToAnimate.transform = .identity

        }, completion: nil)
    }
}
    
}

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

アプリ