RealmSwiftを使ってオリジナルアプリを作成してみた

##これまで現場で携わったアプリ歴
・住宅関連のデータを表示するアプリ
・列車設備点検をアナログ作業からタブレットに切り替える為のアプリ
・オンラインで飲み会を開き、盛り上がり度に応じてお酒を勧めるアプリ

##環境
Mac(macOS Catalina 10.15.7)
Xcode(ver 12.2)

##使用ライブラリ
・RealmSwift
・FSCalendar

##アプリの目的
・毎月100時間学習するために、学習時間を見える化すること

##アプリの仕様
・グラフを100時間に対する当月の学習時間の割合に設定
・カレンダーから記録したい日付を選んで内容と時間を記述して保存
・保存後は、TextFieldを固定して、修正ボタンを押さないと変更できない仕様

##デザイン
アプリのデザインは、シンプルなものにしました
(画像は、著作権フリーのものを使っています)

##画面①
![スクリーンショット 2020-12-14 20.48.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864623/a92501c2-4f69-9d26-d457-196201ea2aae.png)

##画面②
![スクリーンショット 2020-12-14 20.49.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864623/53cced9f-76fc-e08e-6035-ae7b111c0a3e.png)

##ソースコード
###Realmに保存するデータ

    class RecordStudyData:Object {//画面②で保存するデータ
    @objc dynamic var contentText:String? //学習内容
    @objc dynamic var hourText:String?    //学習時間(時)
    @objc dynamic var minuteText:String?  //学習時間(分)
    }

    class allData: Object {
    @objc dynamic var saveDate:String?         //保存した日付
    @objc dynamic var hour:String?             
    @objc dynamic var minute:String?
    @objc dynamic var total_hour:String?      //合計時間(時)
    @objc dynamic var total_minute:String?    //合計時間(分)
    let recordData = List<RecordStudyData>()  //保存したデータをリストにまとめる
    }


###画面①

    class ViewController: UIViewController {
    
    @IBOutlet weak var thisMonthLabel: UILabel!       //今月
    @IBOutlet weak var totalTimeShow: UILabel!        //合計時間を表示
    @IBOutlet weak var studyGraphView: PieChartView!  //グラフ
    @IBOutlet weak var imageView: UIImageView!        //背景画像
    @IBOutlet weak var nextBtn: UIButton!             //戻るボタン
    
    
    var thisMonth = Date()             
    var thisMonthFormatter = DateFormatter().     
    var totalTime = ""  //画面②から戻る時に合計時間を受け取る変数
    var totalHour = 0   //画面②から戻る時に受け取る時間(時)
    var totalMinute = 0 //画面②から戻る時に受け取る時間(分)
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.navigationController?.isNavigationBarHidden = true
        print(Realm.Configuration.defaultConfiguration.fileURL!)
        thisMonthFormatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "MM", options: 0, locale: Locale(identifier: "ja_JP"))
        thisMonthLabel.text = "\(thisMonthFormatter.string(from: thisMonth))の学習時間"
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        //内部で保存したデータをアンラップ変数で受け取ってグラフの値を入れる
        if let total_hour = UserDefaults.standard.object(forKey: "totalHour"),let total_minute = UserDefaults.standard.object(forKey: "totalMinute"){
            totalTime = "\(total_hour)時間\(total_minute)分"
            studyGraphView.value = CGFloat((total_hour as! Int) * 60 + (total_minute as! Int))
            print(studyGraphView.value)
        }
        
        //合計時間の表示
        if totalTime == ""{
            totalTimeShow.text = "00時間00分"
        }else{
            totalTimeShow.text = totalTime
        }
        
       
    }
    
    //画面レイアウト調整
    override func viewDidLayoutSubviews() {
        let iphoneSize_width = UIScreen.main.bounds.width
        let iphoneSize_height = UIScreen.main.bounds.height
        
        //iPhoneSE1用のサイズ
        if(iphoneSize_width == 320){
            thisMonthLabel.frame.origin.x -= 30
            totalTimeShow.frame.origin.x -= 30
            totalTimeShow.frame.origin.y -= 10
            studyGraphView.frame.origin.x = 0
            studyGraphView.frame.origin.y -= 20
            studyGraphView.frame.size.width -= 20
            studyGraphView.frame.size.height -= 55
            nextBtn.frame.origin.y -= 88
            nextBtn.frame.origin.x = 0
            nextBtn.frame.size.width = totalTimeShow.frame.size.width
        }else if(iphoneSize_width == 375){
            //iPhoneX,XS,11Pro,12mini
            if(iphoneSize_height == 812){
                imageView.frame.size.height = iphoneSize_height
            }
        }else if(iphoneSize_width == 390){//iPhone12,Pro
            imageView.frame.size.width = iphoneSize_width
            imageView.frame.size.height = iphoneSize_height
            studyGraphView.frame.origin.x += 10
            nextBtn.frame.origin.x += 10
            totalTimeShow.frame.origin.x += 10
            thisMonthLabel.frame.origin.x += 10
        }else if(iphoneSize_width == 414){
            imageView.frame.size.height = iphoneSize_height
            imageView.frame.size.width = iphoneSize_width
            totalTimeShow.frame.origin.x += 10
            thisMonthLabel.frame.origin.x += 10
            studyGraphView.frame.origin.x += 17
            nextBtn.frame.origin.x += 17
        }else if(iphoneSize_width == 428){
            imageView.frame.size.height = iphoneSize_height
            imageView.frame.size.width = iphoneSize_width
            totalTimeShow.frame.origin.x += 20
            thisMonthLabel.frame.origin.x += 20
            studyGraphView.frame.origin.x += 24
            nextBtn.frame.origin.x += 24
        }
      }
    }


###画面②

    class StudyTimeRecordViewController: UIViewController,UITextViewDelegate,UITextFieldDelegate,FSCalendarDelegate,FSCalendarDataSource,FSCalendarDelegateAppearance{
    
    @IBOutlet weak var calendar: FSCalendar!
    @IBOutlet weak var contentTextView: UITextView!
    @IBOutlet weak var hourTextField: UITextField!
    @IBOutlet weak var minuteTextField: UITextField!
    @IBOutlet weak var backBtn: UIButton!
    @IBOutlet weak var saveBtn: UIButton!
    @IBOutlet weak var updateBtn: UIButton!
    @IBOutlet weak var editView: UIView!
    @IBOutlet weak var studyLabel: UILabel!
    @IBOutlet weak var hourLabel: UILabel!
    @IBOutlet weak var minuteLabel: UILabel!
    @IBOutlet weak var imageView: UIImageView!
    
    
    var dateFomat:DateFormatter = DateFormatter()
    let all_data = allData()
    let recordData = RecordStudyData()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        contentTextView.delegate = self
        hourTextField.delegate = self
        minuteTextField.delegate = self
        calendar.delegate = self
        
        contentTextView.isEditable = true
        hourTextField.isEnabled = true
        minuteTextField.isEnabled = true
        
        backBtn.layer.cornerRadius = 8.0
        saveBtn.layer.cornerRadius = 8.0
        updateBtn.layer.cornerRadius = 8.0
        
        self.navigationController?.isNavigationBarHidden = true
        
        //キーボード出現時
        NotificationCenter.default.addObserver(self, selector: #selector(StudyTimeRecordViewController.keyboardWillShow(_ :)), name: UIResponder.keyboardWillShowNotification, object: nil)
        //キーボードが隠れる時
        NotificationCenter.default.addObserver(self, selector: #selector(StudyTimeRecordViewController.keyboardWillHide(_ :)), name: UIResponder.keyboardWillHideNotification, object: nil)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(true)
        let today = Date()
        dateFomat.dateFormat = DateFormatter.dateFormat(fromTemplate: "ydMMM", options: 0, locale: Locale(identifier:"ja_JP"))
        all_data.saveDate = dateFomat.string(from: today)
        
        let recordRealm = try! Realm()
        let results = recordRealm.objects(allData.self)
        
        //画面②の画面が出てくる際に、過去に保存したデータが有れば表示する
        for i in 0..<results.count {
            
            if all_data.saveDate == results[i].saveDate{
                contentTextView.text = results[i].recordData[0].contentText
                hourTextField.text = results[i].recordData[0].hourText
                minuteTextField.text = results[i].recordData[0].minuteText
                contentTextView.isEditable = false
                hourTextField.isEnabled = false
                minuteTextField.isEnabled = false
            }
        }
    }
    
    //カレンダーで日付を押した時、過去に保存されたデータがあれば表示
    func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
        dateFomat.dateFormat = DateFormatter.dateFormat(fromTemplate: "ydMMM", options: 0, locale: Locale(identifier:"ja_JP"))
        print(dateFomat.string(from: date))
        all_data.saveDate = dateFomat.string(from: date)
        
       
        let recordRealm = try! Realm()
        let results = recordRealm.objects(allData.self).sorted(byKeyPath: "saveDate")
        
        for i in 0..<results.count {
            
            if all_data.saveDate == results[i].saveDate{
                contentTextView.text = results[i].recordData[0].contentText
                hourTextField.text = results[i].recordData[0].hourText
                minuteTextField.text = results[i].recordData[0].minuteText
                contentTextView.isEditable = false
                hourTextField.isEnabled = false
                minuteTextField.isEnabled = false
                
                return
                
            }else {
                contentTextView.text = ""
                hourTextField.text = ""
                minuteTextField.text = ""
                contentTextView.isEditable = true
                hourTextField.isEnabled = true
                minuteTextField.isEnabled = true
            }
        }
    }
    
    //当月に保存した学習時間の合計時間を計算
    func totalStudyTimeOfMonth(){
        let savedAllDataRealm = try! Realm()
        let savedAllData = savedAllDataRealm.objects(allData.self).sorted(byKeyPath: "saveDate")
        var totalHour = 0
        var totalMinute = 0
        
        print(savedAllData)
        for i in 0..<savedAllData.count {
            
            if let hour = savedAllData[i].recordData[0].hourText, let minute = savedAllData[i].recordData[0].minuteText{
                if hour == "" || minute == ""{
                    return
                }
                print(hour)
                totalHour += Int(hour)!
                totalMinute += Int(minute)!
            }
            
            if totalMinute >= 60{
                totalHour += totalMinute / 60
                totalMinute = totalMinute % 60
            }
        }
        
        UserDefaults.standard.set(totalHour, forKey: "totalHour")
        UserDefaults.standard.set(totalMinute,forKey: "totalMinute")
        
        navigationController?.popViewController(animated: true)
    }
    
    @IBAction func saveAction(_ sender: Any) {
        //空白のまま、保存ボタンを押した時、未入力のフィールドの枠を赤色にする
        if contentTextView.text == ""{
            contentTextView.layer.borderColor = #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)
            contentTextView.layer.borderWidth = 1.5
            return
        }else if hourTextField.text == ""{
            hourTextField.layer.borderColor = #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)
            hourTextField.layer.borderWidth = 1.5
            contentTextView.layer.borderColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
            contentTextView.layer.borderWidth = 0.0
            return
        }else if minuteTextField.text == "" {
            minuteTextField.layer.borderColor = #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)
            minuteTextField.layer.borderWidth = 1.5
            contentTextView.layer.borderColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
            contentTextView.layer.borderWidth = 0.0
            hourTextField.layer.borderColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
            hourTextField.layer.borderWidth = 0.0
            return
        }
        
        
        let savedDataRealm = try! Realm()
        let savedDataResults = savedDataRealm.objects(allData.self).sorted(byKeyPath: "saveDate")
        
        if savedDataResults.count > 0 {
            print(savedDataResults)
            //過去に保存されていないかをチェック
            for i in 0..<savedDataResults.count {
                if all_data.saveDate == savedDataResults[i].saveDate{
                    try! savedDataRealm.write{
                        savedDataRealm.delete(savedDataResults[i])
                    }
                    break
                }
            }
        }
        
        //記入事項を保存する処理
        if let studyContent = contentTextView.text,let hour = hourTextField.text, let minute = minuteTextField.text{
            recordData.contentText = studyContent
            recordData.hourText = hour
            recordData.minuteText = minute
            
            let saveData = allData(value: [
                "saveDate":all_data.saveDate!,
                "hour":recordData.hourText!,
                "minute":recordData.minuteText!,
                "recordData": [
                    ["contentText":recordData.contentText!,"hourText":recordData.hourText!,"minuteText":recordData.minuteText!]
                ]
            ])
            
            let saveDataRealm = try! Realm()
            
            try! saveDataRealm.write{
                saveDataRealm.add(saveData)
            }
        }
        contentTextView.isEditable = false
        hourTextField.isEnabled = false
        minuteTextField.isEnabled = false
        
        totalStudyTimeOfMonth()
    }
    
    @IBAction func updateAction(_ sender: Any) {
       contentTextView.isEditable = true
       hourTextField.isEnabled = true
       minuteTextField.isEnabled = true
    }
    
    @IBAction func back(_ sender: Any) {
        navigationController?.popViewController(animated: true)
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        view.endEditing(true)
    }
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
    
    @objc func keyboardWillShow(_ notification:Notification){
        let keyboardHeight = ((notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as Any) as AnyObject).cgRectValue?.height
        
        editView.frame.origin.y = UIScreen.main.bounds.height - keyboardHeight! - editView.frame.size.height
    }
    
    @objc func keyboardWillHide(_ notification:Notification){
        editView.frame.origin.y = UIScreen.main.bounds.height - editView.frame.size.height
        
        guard let _ = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue,
            let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval else {return}
        
        UIView.animate(withDuration: duration){
            let transform = CGAffineTransform(translationX: 0, y: 0)
            
            self.view.transform = transform
        }

    }
    
    override func viewDidLayoutSubviews() {
        
        let iphoneSize_width = UIScreen.main.bounds.width
        let iphoneSize_height = UIScreen.main.bounds.height
        
        //iPhoneSE1用のレイアウト
        if(iphoneSize_width == 320){
            editView.frame.origin.y += 90
            editView.frame.size.width = iphoneSize_width
            editView.frame.size.height -= 90
            contentTextView.frame.size.width -= 55
            contentTextView.frame.size.height -= 20
            updateBtn.frame.origin.x -= 55
            updateBtn.frame.origin.y -= 85
            saveBtn.frame.origin.y -= 85
            backBtn.frame.origin.y -= 25
            studyLabel.frame.origin.y -= 20
            hourLabel.frame.origin.y -= 25
            minuteLabel.frame.origin.y -= 25
            hourTextField.frame.origin.y -= 25
            minuteTextField.frame.origin.y -= 25
            calendar.frame.size.width = iphoneSize_width
            calendar.frame.size.height -= 20
            calendar.frame.origin.y -= 20
        }else if(iphoneSize_width == 375 && iphoneSize_height == 812){
            editView.frame.origin.y = UIScreen.main.bounds.height - editView.frame.size.height
            calendar.frame.size.height += 100
            calendar.frame.size.width -= 20
            calendar.frame.origin.x += 10
            calendar.frame.origin.y += 20
            backBtn.frame.origin.y += 20
        }else if(iphoneSize_width == 390){
            imageView.frame.size.width = iphoneSize_width
            editView.frame.size.width = iphoneSize_width
            editView.frame.origin.y = UIScreen.main.bounds.height - editView.frame.size.height
            contentTextView.frame.size.width += 16
            calendar.frame.size.height += 100
            calendar.frame.size.width = iphoneSize_width
            calendar.frame.size.width -= 20
            calendar.frame.origin.x += 10
            calendar.frame.origin.y += 20
            updateBtn.frame.origin.x += 15
            backBtn.frame.origin.y += 20
        }else if(iphoneSize_width == 414){
            if(iphoneSize_height == 736){
                imageView.frame.size.width = iphoneSize_width
                imageView.frame.size.height = iphoneSize_height
                editView.frame.size.width = iphoneSize_width
                editView.frame.origin.y = UIScreen.main.bounds.height - editView.frame.size.height
                contentTextView.frame.size.width += 37
                updateBtn.frame.origin.x += 40
                calendar.frame.size.width = iphoneSize_width
                calendar.frame.size.height += 40
                calendar.frame.origin.y += 30
                backBtn.frame.origin.y += 20
                
            }else{
                imageView.frame.size.width = iphoneSize_width
                imageView.frame.size.height = iphoneSize_height
                editView.frame.size.width = iphoneSize_width
                editView.frame.origin.y = UIScreen.main.bounds.height - editView.frame.size.height
                contentTextView.frame.size.width += 37
                updateBtn.frame.origin.x += 40
                calendar.frame.size.width = iphoneSize_width
                calendar.frame.size.height += 80
                calendar.frame.origin.y += 80
                backBtn.frame.origin.y += 20
            }
        }else if(iphoneSize_width == 428){
            imageView.frame.size.width = iphoneSize_width
            imageView.frame.size.height = iphoneSize_height
            editView.frame.size.width = iphoneSize_width
            editView.frame.origin.y = UIScreen.main.bounds.height - editView.frame.size.height
            contentTextView.frame.size.width += 50
            updateBtn.frame.origin.x += 50
            calendar.frame.size.width = iphoneSize_width
            calendar.frame.size.height += 100
            calendar.frame.origin.y += 80
            backBtn.frame.origin.y += 20
        }
      }
    }

##今回のアプリ制作で苦労したこと
・DBに保存するデータを決める
・デバイス毎のレイアウトの調整
・RealmSwiftの保存処理の実装時のエラー解決
・合計時間を計算するメソッドの作成

##参考
####グラフ
https://dev.classmethod.jp/articles/pie-chart-class-in-ios/


####Realm
https://loooooovingyuki.medium.com/swiftでrealm-databaseを使う-4c8b94524bf3

###Udemy
https://www.udemy.com/course/ios14-iphone-ios-boot-camp/