カレンダーアプリ開発(iPhone版)④ – 1ヶ月分のカレンダー表示

投稿日:

更新日:

カテゴリ:

ソースをいじる前に、Gitリポジトリにあげて置きましょう。

Githubがプライベートリポジトリが有料の時期ではよくBitbucketを使っておりましたが、今はGithubでもプライベートリポジトリが無料ですので、Githubを使ったりしてます。

Xcode作成したプロジェクトはgitディレクトリが含まれてますが、.gitignoreファイルがないので、Swift.gitignoreを参考にします。
上記.gitignoreファイルに.DS_Storeを追加しておくといいかもしれません。

前準備はここにしておきます。

設計

画面やクラス名、属性あたりはこんな感じでしょうか。

実装

カレンダの表示は色々な考え方ができるかと思いますが、今回は以下のように考えて実装します。

  1. 指定月の表示
    1. 指定月の1日をDate型として取得
    2. 1日の曜日をオフセットとして持つ
      例えば11月だと水曜日、数字という4です。
    3. セルを左から右、上から下順番に、0~41のインデックスを指定すると、該当セルの日=インデックス−オフセット+2になります。
      例えば1行目一番右のセルのインデックスは6、6-4+2=4になり、該当セルは11/4になります。
  2. 前月の表示
    1. 前述該当セルの日の計算では、マイナスになる時は前月の日になります。
      例えば1行目一番左のセルのインデックスは0、0-4+2=-2になります。
    2. 前月の最終日を計算します。
      例えば10月だと最終日は31です。
    3. 該当セルの日は前述2の結果+1の結果になります。
      この場合、1行目一番左のセルの日は31-2=29になり、つまり10/29です。
  3. 翌月の表示
    1. 前述該当セルの日の計算では、指定月の最終日を超えた時は翌月の日になります。
      例えば一番右下のセルのインデックスは41、41-4+2=39になります。
    2. 指定月の最終日を計算します。
      例えば11月だと最終日は30です。
    3. 該当セルの日は前述1の結果-2の結果になります。
      この場合、一番右下のセルの日は39-30=9になり、つまり12/9です。

上記考えのもと、参考1を参考にDateを拡張しました。

extension Date {
  var startOfDay: Date {
    return Calendar.current.startOfDay(for: self)
  }
  
  var startOfMonth: Date {
    let calendar = Calendar(identifier: .gregorian)
    let components = calendar.dateComponents([.year, .month], from: self)
    return calendar.date(from: components)!
  }
  
  var endOfDay: Date {
    var components = DateComponents()
    components.day = 1
    components.second = -1
    return Calendar.current.date(byAdding: components, to: startOfDay)!
  }
  
  var endOfMonth: Date {
    var components = DateComponents()
    components.month = 1
    components.second = -1
    return Calendar(identifier: .gregorian).date(byAdding: components, to: startOfMonth)!
  }
  
  var startOfNextMonth: Date {
    var components = DateComponents()
    components.second = 1
    return Calendar(identifier: .gregorian).date(byAdding: components, to: endOfMonth)!
  }

  var endOfLastMonth: Date {
    var components = DateComponents()
    components.day = -1
    return Calendar(identifier: .gregorian).date(byAdding: components, to: startOfMonth)!
  }
  
  var weekdayOfDay: Int {
    return Calendar.current.component(.weekday, from: self)
  }
  
}

そうしますと、次のように渡したインデックスもとに、日付の計算ができます。

  func dateOfIndex(idx: Int) -> Date {
    let day = idx - offset + 2
    if ( day < 0) {
      let prevMonthYear = calendar.component(.year, from: self.curDate.endOfLastMonth)
      let prevMonth = calendar.component(.month, from: self.curDate.endOfLastMonth)
      let prevMonthDay = calendar.component(.day, from: self.curDate.endOfLastMonth)
      return calendar.date(from: DateComponents(year: prevMonthYear, month: prevMonth, day: prevMonthDay + day))!
    } else if (day > endOfMonth) {
      let nextMonthYear = calendar.component(.year, from: self.curDate.startOfNextMonth)
      let nextMonth = calendar.component(.month, from: self.curDate.startOfNextMonth)
      return calendar.date(from: DateComponents(year: nextMonthYear, month: nextMonth, day: day - endOfMonth))!
    }
    return calendar.date(from: DateComponents(year: year, month: month, day: day))!
  }

 

トラブルシューティング

日付計算の実装している中でハマった問題として、指定した日付の当月1日を取得してprintしてみたら、何故か1日ではなく、先月の最終日になったりするところです。

何故か日付がズレてます。。何度もstartOfDayの実装を疑ってましたが、見た感じなんの問題もなさそうでした。。

結局は参考5のように、printしたらタイムゾーンがズレていることが原因のようで、DateFormatterで出力したらちゃんと1日になっておりました。

ついでに参考6を参考に関連のUnit TestsのDateExtensionTestsも書いてみました。

ソースコード

最後にソースコードを一式をこちらにアップロードします。

UkiCalendar_20231109.zip

ソースはGithubで共有しようかとも考えてましたが、作り過程の共有が面倒なので、まあ、Zipでいいかなと思いました。

起動すると、次のようになるはずです。

MenuやTodayはまだ先ですが、一旦の仮置きです。

実装は1週間ほど前にできていましたが、風邪を引いてしまい、ブログの更新が遅れました。

今回の分量はあまり1日でできるものではない気がしますが、、どう分けるかまた微妙なところですので、そこは大目に見てもらえばと思います。

これで当初の計画をどう守るかが課題になりました。。

それでも、一旦これまでのものが少し参考になったら嬉しいです。

参考

  1. first and last day of the current month in swift
  2. SOLVED: Calculating first and last day of the month.
  3. Swiftの日付関連についてまとめてみる
  4. 【Swift】Dateの王道 【日付】
  5. Swift date components incorrect return of date day number
  6. Getting started with Unit Tests in Swift
  7. Swift の Calendar で最終の曜日、および指定した月の第何曜日、指定週の曜日を取得する方法

投稿日

カテゴリー:

投稿者:

コメント

コメントを残す

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