LoginSignup
83
83

More than 5 years have passed since last update.

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

Posted at

ss.jpg

↑のようにスワイプで切り替えれるようなカレンダーを。
ひとまず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/

83
83
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
83
83