Posted at

【iOS】【swift】カレンダーを作ってみる

More than 3 years have passed since last update.

↑のようにスワイプで切り替えれるようなカレンダーを。

ひとまずswiftの試しが主目的ですが、swiftらしくない部分もあるかもしれません。

試し試しで実装した所があるのでベストな実装とは思えないので、

もう少し暖めようかと思ったのですが、ひとまずちょっと公開してみることにしました。

Xcode 6.1.1にて実装しました。


1.構成

・各日を表示するView(DayViewとします。)

・各月を表示するView(MonthViewとします。)

・月を切り替えるためのView(CalenderViewとします。)

この3つのViewを扱うようにします。


2.DayViewについて

曜日に合わせて表示を切り替えるなど想定されるため、

year/month/day/week

の四つの情報を取得するようにしています。

class DayView: UIView {

required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

init(frame:CGRect,year:Int,month:Int,day:Int,weekday:Int){
super.init(frame: frame)
var dayWidth:Int = Int( (UIScreen.mainScreen().bounds.size.width) / 7.0 )
var dayHeight:CGFloat = 30
var dayLabel:UILabel = UILabel(frame: CGRectMake(0, 0, CGFloat(dayWidth),dayHeight))
dayLabel.textAlignment = NSTextAlignment.Center
dayLabel.text = String(format:"%02d", day)
if weekday == 1 {
//日曜日は赤
dayLabel.textColor = UIColor.redColor()
} else if weekday == 7 {
//土曜日は青
dayLabel.textColor = UIColor.blueColor()
}
self.addSubview(dayLabel)
}
}


3.MonthViewについて

その月の最終日の取得や曜日、第何週目かの取得を行い、

DayViewを表示させています。

ざっくりですが、各DayViewの横幅は、frameのwidthを7で割った商を扱っています。

class MonthView: UIView {

required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(frame: CGRect,year:Int,month:Int) {
super.init(frame:frame)
self.setUpDays(year,month:month)
}

func setUpDays(year:Int,month:Int){

var subViews:[UIView] = self.subviews as [UIView]
for view in subViews {
if view.isKindOfClass(DayView) {
view.removeFromSuperview()
}
}

var day:Int? = self.getLastDay(year,month:month);
var dayWidth:Int = Int( frame.size.width / 7.0 )
var dayHeight:Int = dayWidth + 5
if day != nil {
//初日の曜日を取得
var weekday:Int = self.getWeekDay(year,month: month,day:1)
for var i:Int = 0; i < day!;i++ {
var week:Int = self.getWeek(year,month: month,day:i+1)
var x:Int = ((weekday - 1 ) * (dayWidth));
var y:Int = (week-1) * dayHeight
var frame:CGRect = CGRectMake(CGFloat(x),
CGFloat(y),
CGFloat(dayWidth),
CGFloat(dayHeight)
);

var dayView:DayView = DayView(frame: frame, year:year,month:month,day:i+1,weekday:weekday)
self.addSubview(dayView)
weekday++
if weekday > 7 {
weekday = 1
}

}
}
}

//その月の最終日の取得
func getLastDay(var year:Int,var month:Int) -> Int?{
var dateFormatter:NSDateFormatter = NSDateFormatter();
dateFormatter.dateFormat = "yyyy/MM/dd";
if month == 12 {
month = 0
year++
}
var targetDate:NSDate? = dateFormatter.dateFromString(String(format:"%04d/%02d/01",year,month+1));
if targetDate != nil {
//月初から一日前を計算し、月末の日付を取得
var orgDate = NSDate(timeInterval:(24*60*60)*(-1), sinceDate: targetDate!)
var str:String = dateFormatter.stringFromDate(orgDate)
//lastPathComponentを利用するのは目的として違う気も。。
return str.lastPathComponent.toInt();
}

return nil;
}

//曜日の取得
func getWeek(year:Int,month:Int,day:Int) ->Int{
var dateFormatter:NSDateFormatter = NSDateFormatter();
dateFormatter.dateFormat = "yyyy/MM/dd";
var date:NSDate? = dateFormatter.dateFromString(String(format:"%04d/%02d/%02d",year,month,day));
if date != nil {
var calendar:NSCalendar = NSCalendar.currentCalendar()
var dateComp:NSDateComponents = calendar.components(NSCalendarUnit.WeekOfMonthCalendarUnit, fromDate: date!)
return dateComp.weekOfMonth;
}
return 0;
}

//第何週の取得
func getWeekDay(year:Int,month:Int,day:Int) ->Int{
var dateFormatter:NSDateFormatter = NSDateFormatter();
dateFormatter.dateFormat = "yyyy/MM/dd";
var date:NSDate? = dateFormatter.dateFromString(String(format:"%04d/%02d/%02d",year,month,day));
if date != nil {
var calendar:NSCalendar = NSCalendar.currentCalendar()
var dateComp:NSDateComponents = calendar.components(NSCalendarUnit.WeekdayCalendarUnit, fromDate: date!)
return dateComp.weekday;
}
return 0;
}

}


4.CalenderViewについて

こちらでは、ScrollViewを配置し、その上に3つのMonthViewを配置するのみとしています。

この3つの表示をスクロールに合わせて表示を切り替えるような形で想定しています。

こちら参考させていただきました。

http://cocoadays.blogspot.jp/2010/09/1.html


class CalenderView: UIView,UIScrollViewDelegate{

var currentYear:Int = 0
var currentMonth:Int = 0
var currentDay:Int = 0
var scrollView:UIScrollView!
var prevMonthView:MonthView!
var currentMonthView:MonthView!
var nextMonthView:MonthView!

required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(frame:CGRect){
super.init(frame: frame)

var dateFormatter:NSDateFormatter = NSDateFormatter();
dateFormatter.dateFormat = "yyyy/MM/dd";
var dateString:String = dateFormatter.stringFromDate(NSDate());
var dates:[String] = dateString.componentsSeparatedByString("/")
currentYear = dates[0].toInt()!
currentMonth = dates[1].toInt()!

scrollView = UIScrollView(frame: self.bounds)
scrollView.backgroundColor = UIColor.clearColor()
scrollView.contentSize = CGSizeMake(frame.size.width * 3.0,frame.size.height);
scrollView.contentOffset = CGPointMake(frame.size.width , 0.0);
scrollView.delegate = self;
scrollView.pagingEnabled = true;
scrollView.showsHorizontalScrollIndicator = false;
scrollView.showsVerticalScrollIndicator = false;
scrollView.scrollsToTop = false;

self.addSubview(scrollView)

currentMonthView = MonthView(frame: CGRectMake(frame.size.width, 0, frame.size.width,frame.size.height),
year:currentYear,month:currentMonth)

//翌月
var ret = self.getNextYearAndMonth()
nextMonthView = MonthView(frame: CGRectMake(frame.size.width * 2.0, 0, frame.size.width,frame.size.height),
year:ret.year,month:ret.month)

//前月
ret = self.getPrevYearAndMonth()
prevMonthView = MonthView(frame: CGRectMake(0.0, 0, frame.size.width,frame.size.height),
year:ret.year,month:ret.month)

scrollView.addSubview(currentMonthView);
scrollView.addSubview(nextMonthView);
scrollView.addSubview(prevMonthView);

}

func scrollViewDidScroll(scrollView:UIScrollView)
{
var pos:CGFloat = scrollView.contentOffset.x / scrollView.bounds.size.width
var deff:CGFloat = pos - 1.0
if fabs(deff) >= 1.0 {
if (deff > 0) {
self.showNextView()
} else {
self.showPrevView()
}
}
}

func showNextView (){
currentMonth++;
if( currentMonth > 12 ){
currentMonth = 1;
currentYear++;
}
var tmpView:MonthView = currentMonthView
currentMonthView = nextMonthView
nextMonthView = prevMonthView
prevMonthView = tmpView

var ret = self.getNextYearAndMonth()
nextMonthView.setUpDays(ret.year, month:ret.month)

self.resetContentOffSet()

}

func showPrevView () {
currentMonth--
if( currentMonth == 0 ){
currentMonth = 12
currentYear--
}

var tmpView:MonthView = currentMonthView
currentMonthView = prevMonthView
prevMonthView = nextMonthView
nextMonthView = tmpView
var ret = self.getPrevYearAndMonth()
prevMonthView.setUpDays(ret.year, month:ret.month)

//position調整
self.resetContentOffSet()
}

func resetContentOffSet () {
//position調整
prevMonthView.frame = CGRectMake(0, 0, frame.size.width,frame.size.height)
currentMonthView.frame = CGRectMake(frame.size.width, 0, frame.size.width,frame.size.height)
nextMonthView.frame = CGRectMake(frame.size.width * 2.0, 0, frame.size.width,frame.size.height)

var scrollViewDelegate:UIScrollViewDelegate = scrollView.delegate!
scrollView.delegate = nil
//delegateを呼びたくないので
scrollView.contentOffset = CGPointMake(frame.size.width , 0.0);
scrollView.delegate = scrollViewDelegate

}

func getNextYearAndMonth () -> (year:Int,month:Int){
var next_year:Int = currentYear
var next_month:Int = currentMonth + 1
if next_month > 12 {
next_month=1
next_year++
}
return (next_year,next_month)
}
func getPrevYearAndMonth () -> (year:Int,month:Int){
var prev_year:Int = currentYear
var prev_month:Int = currentMonth - 1
if prev_month == 0 {
prev_month = 12
prev_year--
}
return (prev_year,prev_month)
}

}


5 CalenderViewの利用

ViewControllerにて利用する場合は以下のような形を想定しています。


class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var calenderView:CalenderView = CalenderView(frame: CGRectMake(0, 20,
UIScreen.mainScreen().bounds.size.width, 500));
self.view.addSubview(calenderView)
}
}

(縦幅は適当です。。

ソースはこちらに

https://github.com/kitanoow/SwiftCalendarDemo/