こんにちは、goorerです。
今回はSwiftUI自作カレンダーの作り方をご紹介したいと思います。(昨年の知識)
以下、少々お見苦しいコードがお目に触れますがご勘弁ください。先に謝っておきます!すみません!!!!
1.曜日を表示する
まずは、核となる部分に入る前のウォーミングアップということでカレンダーの一番上にある曜日を表示してみましょう。
// 曜日は日本表記でなく英語表記でもOK
let dayofweek = ["日","月","火","水","木","金","土"]
var body: some View {
VStack {
HStack {
ForEach(0..<self.dayofweek.count){ index in
ZStack {
RoundedRectangle(cornerRadius:5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text(dayofweek[index]) // ここで曜日を表示
.font(.system(size:10))
}
}
}
}
}
VStackやHStack等の説明は今回は皆さん知っているという前提で省略します。
このコードを書き込むとこうなります。綺麗に曜日が一列で表示できましたね。
2.日付を取得する
カレンダーを表示することにおいて最も重要な日付を取得しましょう。今回のカレンダーは開いたら現在の日付を取得して取得した日付のカレンダーを表示する使用です。
class AppDelegate: UIResponder, UIApplicationDelegate {
// 現在の日付から年のみを取得
let year : Int = Calendar.current.component(.year, from: Date())
// 月のみを取得
let month : Int = Calendar.current.component(.month, from: Date())
}
// 日は今回使用しないため取得していません。使用する方は取得しましょう!
@State var year : Int = AppDelegate().year
@State var month : Int = AppDelegate().month
let dayofweek = ["日","月","火","水","木","金","土"]
var body: some View {
VStack {
Text("\(self.month) \(String(self.year))")
.font(.system(size: 20))
HStack {
ForEach(0..<self.dayofweek.count){ index in
ZStack {
RoundedRectangle(cornerRadius: 5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text(dayofweek[index])
.font(.system(size:10))
}
}
}
}
}
1.のコードに追加をして、年と月を表示しましょう。
3.うるう年の判定をする
うるう年の判定もカレンダーを表示する上で重要なことです。判定のコードも短いのでサラッと紹介したいと思います。
func LeapYear(year:Int) -> Bool {
let result = (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0 )) ? true : false
return result
}
判定の部分は一行で書きたかったので、今回三項演算子を使用しています。switch文でも書けると思います。
戻り値は、trueがうるう年です。
4.日付から曜日を取得する
2.で取得した日付から当月1日の曜日を取得しましょう。1日目の曜日はカレンダーを表示する上で必要です。
使用するのは、ツェラーの公式という公式です。これは指定した日付が何曜日かを算出するというものです。従って当月1日を指定すれば自ずと曜日が分かります。
func DayofWeekCalc(year:Int,month:Int,day:Int) -> Int{
var result : Int = 0
if month == 1 || month == 2 { //monthが1,2の時は前年として計算をする
var changeyear : Int = year
var changemonth : Int = month
changeyear -= 1
changemonth += 12
result = (day + (26 * (changemonth + 1))/10 + changeyear + (changeyear / 4) + (5 * (changeyear / 100)) + ((changeyear / 100)/4) + 5) % 7 as Int
}else{
result = (day + (26 * (month + 1))/10 + year + (year / 4) + (5 * (year / 100)) + ((year / 100)/4) + 5) % 7 as Int
}
return result
}
ポイントは、1,2月は前年の続きとして計算することです。戻り値は0~6で、それぞれ日〜土の順になっています。
5.週数を取得する
続きまして、先ほど取得した当月の週数を取得します。週数は、4~6のいずれかとなっています。
func GetWeekNumber(year:Int,month:Int) -> Int {
var result : Int = 0
if CaseFourWeek(year: year, month: month){
result = 4
}else if CaseSixWeek(year: year, month: month){
result = 6
}else{
result = 5
}
return result
}
週数4の場合
private func CaseFourWeek(year:Int,month:Int) -> Bool {
let firstdayofweek = DayofWeekCalc(year: year, month: month, day: 1)
let result = (!LeapYear(year: year) && month == 2 && (firstdayofweek == 0)) ? true : false
return result
}
週数6の場合
private func CaseSixWeek(year:Int,month:Int) -> Bool{
let firstdayofweek = DayofWeekCalc(year: year, month: month, day: 1)
let days = DayNumber(year: year, month: month)
let result = ((firstdayofweek == 6 && days == 30) || (firstdayofweek >= 5 && days == 31)) ? true : false
return result
}
週数4でも6でもない場合は、週数5と決まるので判定はありません。
6.日数を取得する
最後に取得するのは、当月の日数です。日数に関しては詳しい説明は要らないと思いますので、早速コードをご覧ください。
func DayNumber(year:Int,month:Int) -> Int{
var result : Int = 0
switch month {
case 1,3,5,7,8,10,12:
result = 31
case 4,6,9,11:
result = 30
case 2:
if LeapYear(year: year){
result = 29
}else{
result = 28
}
default: break
}
return result
}
7.実際にカレンダーを表示する
カレンダー表示に必要なパーツは揃ったので、そろそろ表示をしていきます。表示方法は多数あると思いますが、今回紹介するのはほんの一例です。
CalendarView
CalendarView
struct CalendarView: View {
@State var year : Int = AppDelegate().year
@State var month : Int = AppDelegate().month
let dayofweek = ["日","月","火","水","木","金","土"]
var body: some View {
ZStack{
VStack {
Text("\(self.month) \(String(self.year))")
.font(.system(size: 20))
Spacer().frame(height:50)
HStack {
ForEach(0..<self.dayofweek.count){ index in
ZStack {
RoundedRectangle(cornerRadius: 5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text(dayofweek[index])
.font(.system(size:10))
}
}
}
Spacer()
// カレンダーの月を進める・戻るボタン
HStack {
Button(action: {
self.LastMonth()
}, label: {
Text("Back")
})
Button(action: {
self.NextMonth()
}, label: {
Text("Next")
})
}
}
VStack {
Spacer().frame(height:115)
CalendarList(year: $year, month: $month,
week: CalendarModel().GetWeekNumber(year: self.year, month: self.month),
start: CalendarModel().DayofWeekCalc(year: self.year, month: self.month, day: 1), days: CalendarModel().DayNumber(year: self.year, month: self.month))
Spacer()
}
}
}
func NextMonth(){
if self.month != 12{
self.month += 1
}else if self.month == 12{
self.year += 1
self.month = 1
}
}
func LastMonth(){
if self.month != 1{
self.month -= 1
}else if self.month == 1{
self.year -= 1
self.month = 12
}
}
}
実際にカレンダーの中を表示しているのは下記のCalendarListです。
CalendarList
CalendarList
struct CalendarList: View {
@Binding var year : Int
@Binding var month : Int
var startdaynumber : Int
var weeknumber : Int
var days : Int
var middleweek : Int
var lastweeknumber : Int
let column = 7
init(year:Binding<Int>,month:Binding<Int>,week:Int,start:Int,days:Int){
self._year = year
self._month = month
self.weeknumber = week
self.startdaynumber = start
self.days = days
self.middleweek = (days - (7 - start)) / 7
self.lastweeknumber = (days - (7 - start)) % 7
}
var body: some View {
// 1週
VStack {
HStack {
if self.startdaynumber != 0{
ForEach(0..<self.startdaynumber,id:\.self){ index in
ZStack{
RoundedRectangle(cornerRadius: 5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text("")
.font(.system(size: 20))
.foregroundColor(.black)
}
}
}
ForEach(0..<(self.column-self.startdaynumber),id:\.self){ index in
ZStack{
RoundedRectangle(cornerRadius: 5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text("\(index+1)")
.font(.system(size: 20))
.foregroundColor(.black)
}
}
}
// 2週
HStack{
ForEach(0..<self.column,id:\.self){ index in
ZStack{
RoundedRectangle(cornerRadius: 5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text("\((self.column-self.startdaynumber)+1+index)")
.font(.system(size: 20))
.foregroundColor(.black)
}
}
}
// 3週
HStack{
ForEach(0..<self.column,id:\.self){ index in
ZStack{
RoundedRectangle(cornerRadius: 5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text("\(((7-self.startdaynumber)+1+index)+7)")
.font(.system(size: 20))
.foregroundColor(.black)
}
}
}
// 4,5,6週
if self.weeknumber == 4{
HStack{
if self.lastweeknumber != 0{
ForEach(0..<self.lastweeknumber,id:\.self){ index in
ZStack{
RoundedRectangle(cornerRadius: 5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text("\(((7-self.startdaynumber)+1+index)+14)")
.font(.system(size: 20))
.foregroundColor(.black)
}
}
}else{
ForEach(0..<7,id:\.self){ index in
ZStack{
RoundedRectangle(cornerRadius: 5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text("\(((7-self.startdaynumber)+1+index)+14)")
.font(.system(size: 20))
.foregroundColor(.black)
}
}
}
}
}else if self.weeknumber == 5{
HStack{
ForEach(0..<self.column,id:\.self){ index in
ZStack{
RoundedRectangle(cornerRadius: 5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text("\(((7-self.startdaynumber)+1+index)+14)")
.font(.system(size: 20))
.foregroundColor(.black)
}
}
}
HStack{
if self.lastweeknumber != 0{
ForEach(0..<self.lastweeknumber,id:\.self){ index in
ZStack{
RoundedRectangle(cornerRadius: 5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text("\(((7-self.startdaynumber)+1+index)+21)")
.font(.system(size: 20))
.foregroundColor(.black)
}
}
ForEach(0..<(7-self.lastweeknumber),id:\.self){ index in
ZStack{
RoundedRectangle(cornerRadius: 5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text("")
.font(.system(size: 20))
.foregroundColor(.black)
}
}
}else{
ForEach(0..<7,id:\.self){ index in
ZStack{
RoundedRectangle(cornerRadius: 5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text("\(((7-self.startdaynumber)+1+index)+21)")
.font(.system(size: 20))
.foregroundColor(.black)
}
}
}
}
}else if self.weeknumber == 6{
HStack{
ForEach(0..<self.column,id:\.self){ index in
ZStack{
RoundedRectangle(cornerRadius: 5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text("\(((7-self.startdaynumber)+1+index)+14)")
.font(.system(size: 20))
.foregroundColor(.black)
}
}
}
HStack{
ForEach(0..<self.column,id:\.self){ index in
ZStack{
RoundedRectangle(cornerRadius: 5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text("\(((7-self.startdaynumber)+1+index)+21)")
.font(.system(size: 20))
.foregroundColor(.black)
}
}
}
HStack{
if self.lastweeknumber != 0{
ForEach(0..<self.lastweeknumber,id:\.self){ index in
ZStack{
RoundedRectangle(cornerRadius: 5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text("\(((7-self.startdaynumber)+1+index)+28)")
.font(.system(size: 20))
.foregroundColor(.black)
}
}
if self.lastweeknumber != 0{
ForEach(0..<(7-self.lastweeknumber),id:\.self){ index in
ZStack{
RoundedRectangle(cornerRadius: 5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text("")
.font(.system(size: 20))
.foregroundColor(.black)
}
}
}
}else{
ForEach(0..<self.lastweeknumber,id:\.self){ index in
ZStack{
RoundedRectangle(cornerRadius: 5).frame(width:40,height:40)
.foregroundColor(Color.clear)
Text("\(((7-self.startdaynumber)+1+index)+28)")
.font(.system(size: 20))
.foregroundColor(.black)
}
}
}
}
}
}
}
}
以上で完成です!
長々と拙いソースをご覧頂きありがとうございました。
至らない部分が多いと思いますが、温かい目で見ていただけると幸いです。