ソースをいじる前に、Gitリポジトリにあげて置きましょう。
Githubがプライベートリポジトリが有料の時期ではよくBitbucketを使っておりましたが、今はGithubでもプライベートリポジトリが無料ですので、Githubを使ったりしてます。
Xcode作成したプロジェクトはgitディレクトリが含まれてますが、.gitignoreファイルがないので、Swift.gitignoreを参考にします。
上記.gitignoreファイルに.DS_Store
を追加しておくといいかもしれません。
前準備はここにしておきます。
設計
画面やクラス名、属性あたりはこんな感じでしょうか。
実装
カレンダの表示は色々な考え方ができるかと思いますが、今回は以下のように考えて実装します。
- 指定月の表示
- 指定月の1日をDate型として取得
- 1日の曜日をオフセットとして持つ
例えば11月だと水曜日、数字という4です。 - セルを左から右、上から下順番に、0~41のインデックスを指定すると、該当セルの日=インデックス−オフセット+2になります。
例えば1行目一番右のセルのインデックスは6、6-4+2=4になり、該当セルは11/4になります。
- 前月の表示
- 前述該当セルの日の計算では、マイナスになる時は前月の日になります。
例えば1行目一番左のセルのインデックスは0、0-4+2=-2になります。 - 前月の最終日を計算します。
例えば10月だと最終日は31です。 - 該当セルの日は前述2の結果+1の結果になります。
この場合、1行目一番左のセルの日は31-2=29になり、つまり10/29です。
- 前述該当セルの日の計算では、マイナスになる時は前月の日になります。
- 翌月の表示
- 前述該当セルの日の計算では、指定月の最終日を超えた時は翌月の日になります。
例えば一番右下のセルのインデックスは41、41-4+2=39になります。 - 指定月の最終日を計算します。
例えば11月だと最終日は30です。 - 該当セルの日は前述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
も書いてみました。
ソースコード
最後にソースコードを一式をこちらにアップロードします。
ソースはGithubで共有しようかとも考えてましたが、作り過程の共有が面倒なので、まあ、Zipでいいかなと思いました。
起動すると、次のようになるはずです。
MenuやTodayはまだ先ですが、一旦の仮置きです。
実装は1週間ほど前にできていましたが、風邪を引いてしまい、ブログの更新が遅れました。
今回の分量はあまり1日でできるものではない気がしますが、、どう分けるかまた微妙なところですので、そこは大目に見てもらえばと思います。
これで当初の計画をどう守るかが課題になりました。。
それでも、一旦これまでのものが少し参考になったら嬉しいです。
コメントを残す