swift独学⑤, datepickerで誕生日を入力し自動で星座が表示されるようにする

Image by freestocks.org (license)

離乳食の分量を計算してくれるiOSアプリを、プログラミング素人が作るという挑戦中!

オンラインのプログラミングコースの受講を終え、2021年11月からいよいよ実際に制作に取り掛かりました!

つまずきながらも進んでいく経過を残していきたいと思います♪

12月前半の進捗をまとめます。

目次

現在辿り着いているアプリの状態

この記事には、

「Datapickerで誕生日を選ぶ」

「Datapickerで誕生日を選んだ時に星座が選ばれる」ためのコードについてまとめます。

Datapickerで誕生日を入力する

私のアプリには赤ちゃんの情報を入力する画面があり、そこに誕生日を入れる部分を作ります。

誕生日の入力はよくアプリで見かけるクルクル回ってカチカチ言いながら年・月・日を選べるような形にしようと決めました。

参考にした主なサイトは以下のようなところです。

→Datapickerのコードの概要の理解に役立ちました。

jQuery UIのDatepickerで和暦を設定したときのメモ

→年・月・日の表示にすることが出来ました。

Datapickerで選択した誕生日に対応する星座を入力する

ついでに星座も表示できると星座占い好きの人(私の妻です)は喜ぶだろうと星座が表示できるようにしました。

参考にした主なサイトは以下の通りです。

【Swift】UIDatePickerに入力された生年月日から星座を判定する

「Datapickerで誕生日を選ぶ」ために

そして、

「Datapickerで誕生日を選んだ時に星座が選ばれる」ために 最終的にたどり着いたコードは以下のものです。

コード内に私が理解した限りの解説を付けます。

import UIKit

class BabyInformationViewController: AdvViewController {
    
    @IBOutlet weak var nameTF: UITextField!
    
    @IBOutlet weak var birthDay: UITextField!
    
    @IBOutlet weak var asterismLF: UILabel!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
       
        let date = Date()
    
        let toolbar = UIToolbar()
        toolbar.sizeToFit()
        //完了ボタンを挿入するツールバーをまず作る
        
        let doneBtn = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(donePressed))
        
        toolbar.setItems([doneBtn], animated: true)
        //完了ボタンを作る .done と入力することで日本語の完了に変換される
        
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy年MM月dd日"
        birthDay.text = formatter.string(from: date)
        
        let datePicker = UIDatePicker()
        datePicker.datePickerMode = .date
        datePicker.addTarget(self, action: #selector(datePickerValueChanged(sender:)), for: UIControl.Event.valueChanged)
        //下記に定義するdatePickerValueChanged というmethodが発動される
        datePicker.frame.size = CGSize(width: 0, height: 250)
        //大きさの設定
        datePicker.preferredDatePickerStyle = .wheels
        //種類の設定。クルクル回るやつ。他にカレンダータイプとか選べる
        datePicker.maximumDate = Date()
        //datePickerが出現した時に最初に表示されるのを本日の日付に
        
        datePicker.locale = Locale(identifier: "ja")
        //現在海外に住んでいてデフォルトが日本語ではないので、日本語表示に変更
        birthDay.inputAccessoryView = toolbar
        birthDay.inputView = datePicker
        //birthDayというラベルの入力方法をdatePickerに設定
        
        if let bd = defaults.string(forKey: "BirthDay") {
            birthDay.text = bd
        }
        //すでにデータがUserDefaultsにある場合はそれを引っ張ってくる
        
        if let ar = defaults.string(forKey: "Asterism") {
            asterismLF.text = ar
        }
        //すでにデータがUserDefaultsにある場合はそれを引っ張ってくる
    }
    
    @objc func datePickerValueChanged (sender: UIDatePicker){
        // when date picker is changed, this function will be notified
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy年MM月dd日"
        birthDay.text = formatter.string(from: sender.date)
        defaults.set(birthDay.text, forKey: "BirthDay")
        //データをUserDefaultsに記録
        
        //MARK:- calculation of number of month
            let controller = storyboard!.instantiateViewController(identifier: "CalculationVC") as! CalculationViewController
            controller.calculateNumberOfMonth()
        
        
        //MARK:- asterism
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "MMdd"
        // 西暦は必要無いので、dateFormatを月日の取得だけにする。
        
        let birthdayString = dateFormatter.string(from: sender.date)
        // DatePickerから取得したDateをDateFormatterでString型に変換する
        
        let birthdayInt = Int(birthdayString)
        // String型のbirthdayをInt型に変換する
        
        // オプショナル型のbirthdayIntをオプショナルバインディング
        if let birthdayInt = birthdayInt {
            
            let seiza = seizaCheck(withBirthday: birthdayInt)
            //以下のseizaCheck methodが発動し誕生日(birtdayInt)に該当する星座を取得
            
            asterismLF.text = seiza
            //星座チェック関数で取得した文字列をasterismLFというLabel.textに代入
            defaults.set(asterismLF.text, forKey: "Asterism")
            //データをUserDefaultsに記録
        }
    }
    
    @objc func donePressed (){
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        formatter.timeStyle = .none
        self.view.endEditing(true)
        //完了ボタンの設定と、押されたら消えるように
    }
    
    private func seizaCheck(withBirthday birtyday: Int) -> String {
        
        switch birtyday {
        case 321...419:
            return "(牡羊座)"
        case 420...520:
            return "(牡牛座)"
        case 521...621:
            return "(双子座)"
        case 622...722:
            return "(蟹座)"
        case 723...822:
            return "(獅子座)"
        case 823...922:
            return "(乙女座)"
        case 923...1023:
            return "(天秤座)"
        case 1024...1122:
            return "(蠍座)"
        case 1123...1221:
            return "(射手座)"
        case 120...218:
            return "(水瓶座)"
        case 219...320:
            return "(魚座)"
        default:
            return "(山羊座)"
        }
    }
}

誕生日のデータから本日の月齢計算するという壁を越えられない

これがめちゃくちゃ難しいです。

Datapickerで誕生日を入力した際に誕生日データをUserDefaults にセーブする。

異なるViewControllerにおいて、UserDefalts から誕生日データを取り出す。

取り出した誕生日データはString形式なので、Date形式に変更する。

本日(Date type) – 誕生日 (Date type)を計算して月齢を出す。

月齢をStringとしてLabelのtextに表示する。

という手順でコードを以下のように書いているんですが

 func calculateNumberOfMonth () {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy年MM月dd日"
        
        if let bd = defaults.string(forKey: "BirthDay")  {
            let babyBirthDay = dateFormatter.date(from: bd)
            let ageComponents = Calendar.current.dateComponents([.month], from: babyBirthDay!, to: now)
            if let month = ageComponents.month {
            numberOfMonth.text = String(month)
                
            } else {
                numberOfMonth.text = "-"
            }
        } else {
            numberOfMonth.text = "-"
        }
        }
    }

っていう事をしたいのですが、以下のエラーに当たっています。。

Unexpectedly found nil while implicitly unwrapping an Optional value 

いろんなレベルでsafety unwrapping を試みたいのですが解決できず。

地道に用語の確認からやっているところです。

まず、implicitly unwrappingとは?

Hacking WITH SWIFT には以下のように説明がなされています。

An implicitly unwrapped optional – written as String! – may also contain a value or be nil, but they don’t need to be checked before they are used. Checking an optionals value is called “unwrapping”, because we’re looking inside the optional box to see what it contains. Implicitly unwrapping that optional means that it’s still optional and might be nil, but Swift eliminates the need for unwrapping.

( implicitly unwrapped optional は、String! と書かれているが、値を含んでいたり nil であったりすることもありますが、使用前にチェックする必要はありません。オプショナルの値をチェックすることを「アンラップ」といいますが、これはオプショナルの箱の中に何が入っているかを見ていることになるからです。 暗黙のうちに( implicitly )そのオプショナルをアンラップする( unwrap)ことは、それがまだオプショナルであり、nil であるかもしれないことを意味しますが、Swift はアンラップする必要性を排除します。 )

【初心者向け】Swiftの”?”と”!”,はじめからていねいに (1/2)でも以下の記載があります。

var piyo: Int! //暗黙的アンラップInt型(nilを許容する)

自分でvariableを: ●●! と定義はしていないので、人の真似をして書いたコードの中で知らない間に implicitly unwrapped optional を定義してしまっている可能性が高いと思っています。

怪しいのはCalendarを使った月齢の計算だと思っているので、次はCalendarの勉強をして問題がないか確認したいと思います。

まとめ

Datapickerを使った日付の選択、誕生日から星座を自動入力する方法をまとめました。

素人の私の仕事なので、回りくどいコードかもしれませんが、他の困っているビギナーの人にも理解できるコードだと思います。

間違いの指摘やアドバイスを頂けると嬉しいです。

引き続きエラーの解決にタックルしていきます!

フォローしてね♪

シェアしてね!

コメントを残す

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