LoginSignup
4
3

More than 5 years have passed since last update.

【Xcode】【swift】【カレンダー 】【横スクロール】初心者の作成

Last updated at Posted at 2018-08-21

はじめに 

こんにちは!プログラミング初心者なのでお手柔らかにお願いします!

Xcodeのswiftを使用してカレンダーアプリの作成に挑んでいるのですが、わからない点があるため、これまでの作成結果を見ていただき、みなさんの知恵をお借りしたいと思い投稿させていただきました!

実装できていること

・左右にスクロールすることで月ごとのカレンダーに切り替える

こちらを参考にさせていただきました
https://qiita.com/kitanoow/items/65b1418527eabf31e45b

今回投稿させていただくコードは上の参考にさせていただいたコードを現在仕様にしただけなので作成過程は上のリンクを参考にしていただくと幸いです!

これから実装したいこと

1、カレンダーにヘッダーを作る

2、ヘッダーにラベルを貼り付ける

3、ラベルがカレンダーの表示している年月を2018/8のように表示する
(スワイプするごとに自動的に年月が切り替わるよう実装)

4、これらの部品はスクロールビュー内外どちらに配置しても大丈夫ですが内側に配置する場合、上記の部品はスワイプしても動かないように固定しておく

これが実装したいことになります!
部品の配置はこちらを参考にさせていただいております!
https://qiita.com/sakuran/items/3c2c9f22cbcbf4aff731

Xcodeで新規プロジェクトを作成

プロジェクト名は「Calendarrrr」にしました

ファイル作成

・CalendarManager.swift
・CalendarView.swift
・CalCollectionView.swift
・CalCollectionCell.swift

コード入力

AppDelegate.swift

qiita.rb

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var scheduleAt: Date?
    var eventType = 1

    var selCalDayID : Int?
    var selCalView: CalCollectionView?

ViewController.swift

qiita.rb

import UIKit

class ViewController: UIViewController {

    var selectedDate = NSDate()

    @IBOutlet weak var headerTitle: UILabel!
    @IBOutlet weak var calendarheaderView: UIView!
    @IBOutlet weak var calendarCollectionView: UICollectionView!

    func changeHeaderTitle(date: NSDate)->String{
        let formatter:DateFormatter = DateFormatter()
        formatter.dateFormat = "M/yyyy"
        let selectMonth = formatter.string(from: date as Date)
        return selectMonth

    }


    override func viewDidLoad() {
        super.viewDidLoad()

        let calView = CalendarView(frame: CGRect(x: 0, y: 287, width: UIScreen.main.bounds.size.width, height: 290))
        self.view.addSubview(calView)

        calendarheaderView.backgroundColor = UIColor.lightRed()

        headerTitle.text = changeHeaderTitle(date: selectedDate)




        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }





}


CalendarManager.swift

qiita.rb

import UIKit



class CalendarManager {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate

    //表記する月の配列
    var currentMonthOfDates = [Date]()
    // カレンダー表示用
    var currentDate = Date()
    let daysPerWeek: Int = 7
    var numberOfItems: Int!
    var cal = Calendar.current

    //月ごとのセルの数を返すメソッド
    func daysAcquisition() -> Int {
        // 週の数
        let numOfWeeks = Calendar.current.range(of: .weekOfMonth, in: .month, for: firstDateOfMonth())!.count
        // 週の数 x 列の数
        numberOfItems = numOfWeeks * daysPerWeek
        return numberOfItems
    }

    //月の初日を取得
    func firstDateOfMonth() -> Date {
        var components = cal.dateComponents([.year, .month, .day], from:currentDate)
        components.day = 1
        let firstDateMonth = cal.date(from: components)!

        return firstDateMonth
    }

    //  月の初日が週の何日目かを計算
    func ordinalityDay() -> Int {
        let ordinalityDay = cal.ordinality(of: .day, in: .weekOfMonth, for: firstDateOfMonth())!

        return ordinalityDay
    }

    // 月の日数
    func daysOfMonth() -> Int {
        let year = cal.component(.year, from: currentDate)
        let month = cal.component(.month, from: currentDate)

        let date = cal.date(from: DateComponents(year: year, month: month))!
        // 日数
        let days = cal.range(of: .day, in: .month, for: date)?.count

        return days!
    }

    // ⑴表記する日にちの取得
    func dateForCellAtIndexPath(numberOfItems: Int) {
        // ①「月の初日が週の何日目か」を計算する
        let ordinalityOfFirstDay = cal.ordinality(of: .day, in: .weekOfMonth, for: firstDateOfMonth())

        for i in 0 ..< numberOfItems {
            // ②「月の初日」と「indexPath.item番目のセルに表示する日」の差を計算する
            var dateComponents = DateComponents()
            dateComponents.day = i - (ordinalityOfFirstDay! - 1)

            //  表示する月の初日から②で計算した差を引いた日付を取得
            let date = cal.date(byAdding: dateComponents, to: firstDateOfMonth())!

            // ④配列に追加
            currentMonthOfDates.append(date)
        }
    }

    // ⑵表記の変更
    func conversionDateFormat(indexPath: NSIndexPath) -> String {
        dateForCellAtIndexPath(numberOfItems: numberOfItems)
        let formatter = DateFormatter()
        formatter.dateFormat = "d"

        return formatter.string(from: currentMonthOfDates[indexPath.row])
    }

    func compareDate(selDate: Date) -> Bool {
        let isSameDay = cal.isDate(appDelegate.scheduleAt!, inSameDayAs: selDate)

        return isSameDay
    }
}

CalendarView.swift

qiita.rb

import UIKit



class CalendarView : UIView, UIScrollViewDelegate {

    let appDelegate = UIApplication.shared.delegate as! AppDelegate

    var currentYear:Int = 0
    var currentMonth:Int = 0
    var currentDay:Int = 0

    let daysPerWeek: Int = 7
    var currentDate = Date()
    var scrollView: UIScrollView!

    var currentMonthView : CalCollectionView!
    var nextMonthView : CalCollectionView!
    var prevMonthView : CalCollectionView!

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

    var getNextMonth:Date!
    var getPrevMonth:Date!

    override init(frame:CGRect){
        super.init(frame: frame)

        let screenWidth : CGFloat = frame.size.width
        let screenHeight : CGFloat = 290

        // まずはscrollviewを追加
        scrollView = UIScrollView(frame: self.bounds)
        scrollView.backgroundColor = UIColor.white
        scrollView.contentSize = CGSize(width: frame.size.width *  3.0,height: frame.size.height)
        scrollView.contentOffset = CGPoint(x: screenWidth , y: 0.0)
        scrollView.delegate = self
        scrollView.isPagingEnabled = true
        scrollView.showsHorizontalScrollIndicator = false
        scrollView.showsVerticalScrollIndicator = false

        self.addSubview(scrollView)

        let cal = Calendar.current

        if appDelegate.eventType != 0 {
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-DD"
            appDelegate.scheduleAt = formatter.date(from: "2017-01-31")!
            currentDate = appDelegate.scheduleAt!
        }

        getNextMonth = cal.date(byAdding: .month, value: 1, to: cal.startOfDay(for: currentDate))
        getPrevMonth = cal.date(byAdding: .month, value: -1, to: cal.startOfDay(for: currentDate))

        currentMonthView = CalCollectionView(frame: CGRect(x: screenWidth, y: 0, width: screenWidth, height: screenHeight), selDate: currentDate)

        nextMonthView = CalCollectionView(frame: CGRect(x: screenWidth * 2, y: 0, width: screenWidth, height: screenHeight), selDate: getNextMonth!)

        prevMonthView = CalCollectionView(frame: CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight), selDate: getPrevMonth!)

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

    }


    func scrollViewDidScroll(_ scrollView:UIScrollView) {

        let pos:CGFloat  = scrollView.contentOffset.x / scrollView.bounds.size.width

        let deff:CGFloat = pos - 1.0
        if fabs(deff) >= 1.0 {
            if (deff > 0) {
                self.showNextView()
            } else {
                self.showPrevView()
            }
        }
    }

    func showNextView (){
        currentMonth += 1
        if( currentMonth > 12 ){
            currentMonth = 1
            currentYear += 1
        }

        let tmpView = currentMonthView
        currentMonthView = nextMonthView
        nextMonthView    = prevMonthView
        prevMonthView    = tmpView

        let nextYearAndMonth = self.getNextYearAndMonth()
        nextMonthView.setUpDays(nextYearAndMonth)

        self.resetContentOffSet()
    }

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

        let tmpView = currentMonthView
        currentMonthView = prevMonthView
        prevMonthView    = nextMonthView
        nextMonthView    = tmpView

        let prevYearAndMonth = self.getPrevYearAndMonth()
        prevMonthView.setUpDays(prevYearAndMonth)

        //position調整
        self.resetContentOffSet()
    }

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

        let scrollViewDelegate:UIScrollViewDelegate = scrollView.delegate!
        scrollView.delegate = nil
        //delegateを呼びたくないので
        scrollView.contentOffset = CGPoint(x: frame.size.width , y: 0.0)
        scrollView.delegate = scrollViewDelegate
    }

    func getNextYearAndMonth () -> Date {
        var next_year:Int = currentYear
        var next_month:Int = currentMonth + 1
        if next_month > 12 {
            next_month = 1
            next_year += 1
        }

        let cal = Calendar.current
        let increaseYear = cal.date(byAdding: .year, value: next_year, to: cal.startOfDay(for: currentDate))
        let increaseMonth = cal.date(byAdding: .month, value: next_month, to: cal.startOfDay(for: increaseYear!))

        return increaseMonth!
    }

    func getPrevYearAndMonth () -> Date {
        var prev_year:Int = currentYear
        var prev_month:Int = currentMonth - 1
        if prev_month == 0 {
            prev_month = 12
            prev_year -= 1
        }

        let cal = Calendar.current
        let decreaseYear = cal.date(byAdding: .year, value: prev_year, to: cal.startOfDay(for: currentDate))
        let decreaseMonth = cal.date(byAdding: .month, value: prev_month, to: cal.startOfDay(for: decreaseYear!))

        return decreaseMonth!
    }
}

CalCollectionView.swift

qiita.rb

import UIKit



class CalCollectionView : UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {

    let appDelegate = UIApplication.shared.delegate as! AppDelegate

    let calMng = CalendarManager()
    var collectionView : UICollectionView!
    var cellSize: CGFloat = 0.0
    let cellMargin: CGFloat = 0
    let weekArray = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]

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

    init(frame: CGRect, selDate: Date){
        super.init(frame: frame)
        self.setUpDays(selDate)
    }

    func setUpDays(_ selDate: Date){
        calMng.currentMonthOfDates = []
        calMng.currentDate = selDate

        let screenWidth : CGFloat = frame.size.width
        let screenHeight : CGFloat = 290

        // CollectionViewのレイアウトを生成.
        let layout = UICollectionViewFlowLayout()

        // Cell一つ一つの大きさ.
        cellSize = frame.size.width / 7
        layout.itemSize = CGSize(width: cellSize, height: 40)

        // CollectionViewを生成.
        collectionView = UICollectionView(frame: CGRect(x: screenWidth, y: 0, width: frame.size.width, height: CGFloat(screenHeight)), collectionViewLayout: layout)

        collectionView.backgroundColor = UIColor.white
        collectionView.isScrollEnabled = false

        collectionView.center = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)

        // Cellに使われるクラスを登録.
        collectionView.register(CalCollectionCell.self, forCellWithReuseIdentifier: "calCell")

        collectionView.delegate = self
        collectionView.dataSource = self

        collectionView.frame =  CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)

        self.addSubview(collectionView)
    }

    // セクションの数
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 2
    }

    // Cellの総数を返す
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        // Section毎にCellの総数を変える.
        if section == 0 {
            return 7
        } else {
            return calMng.daysAcquisition()
        }
    }

    // セルの内容を表示
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell: CalCollectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: "calCell", for: indexPath as IndexPath) as! CalCollectionCell

        if indexPath.row % 7 == 0 {
            cell.textLabel?.textColor = UIColor.lightRed()
        } else if indexPath.row % 7 == 6 {
            cell.textLabel?.textColor = UIColor.lightBlue()
        } else {
            cell.textLabel?.textColor = UIColor.gray
        }

        if indexPath.section == 0 {
            cell.textLabel?.text = weekArray[indexPath.row]
        } else {
            let day = calMng.conversionDateFormat(indexPath: indexPath as NSIndexPath)
            let ordinalityDay = calMng.ordinalityDay() - 2
            // 月の日数
            let daysOfMonth = calMng.daysOfMonth()
            let lastRow = ordinalityDay + daysOfMonth



            if indexPath.row > ordinalityDay {
                // 現在のセルアイテムの日付を取得
                let itemDay = calMng.currentMonthOfDates[indexPath.row]
                // イベント予定日と同じかどうかをチェック
                let isSelectedDay = calMng.compareDate(selDate: itemDay)

                if isSelectedDay == true {
                    appDelegate.selCalDayID = indexPath.row
                    cell.imgView.image = UIImage(named: "cal-bg")
                    cell.textLabel?.textColor = UIColor.white
                    cell.textLabel?.text = day
                } else {
                    cell.textLabel?.text = day
                    cell.imgView.image = UIImage()
                }
            } else {
                cell.isUserInteractionEnabled = false
                cell.textLabel?.text = nil
            }

            if indexPath.row > lastRow {
                cell.isUserInteractionEnabled = false
                cell.textLabel?.text = nil
            }
        }

        return cell
    }

    //セルの垂直方向のマージンを設定
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return cellMargin
    }

    //セルの水平方向のマージンを設定
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return cellMargin
    }

    // セルをタップした時の処理
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

        if indexPath.section == 1 {
            // タップしたセルアイテムの日付を取得
            let selItemDay = calMng.currentMonthOfDates[indexPath.row]
            // イベント予定日と同じかどうかをチェック
            let isSelectedDate = calMng.compareDate(selDate: selItemDay)

            if isSelectedDate == false {
                // 現状の選択したIDを一旦保持
                let tmpItemID = appDelegate.selCalDayID
                // 選択したIDを新規にセット
                appDelegate.selCalDayID = indexPath.row
                // 日付の内容を変更
                appDelegate.scheduleAt = calMng.currentMonthOfDates[indexPath.row]
                // 古いセルをリロード
                reloadItem(selItem: tmpItemID!)
                // 新しいセルをリロード
                reloadItem(selItem: appDelegate.selCalDayID!)
            }
        }
    }

    func reloadItem(selItem: Int) {
        collectionView.reloadItems(at: [IndexPath(row: selItem, section: 1)])
    }
}

extension UIColor {
    class func lightBlue() -> UIColor {
        return UIColor(red: 50.0 / 255, green: 149.0 / 255, blue: 218.0 / 255, alpha: 1.0)
    }

    class func lightRed() -> UIColor {
        return UIColor(red: 222.0 / 255, green: 32 / 255, blue: 63 / 255, alpha: 1.0)
    }
}

CalCollectionCell.swift

qiita.rb

import UIKit



class CalCollectionCell: UICollectionViewCell {

    var textLabel : UILabel?
    var imgView : UIImageView!

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        // UILabelを生成.
        textLabel = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: frame.height))
        textLabel?.textAlignment = NSTextAlignment.center

        imgView = UIImageView(frame: CGRect(x: 0, y: 0, width: 34, height: 34))
        imgView.center = CGPoint(x: frame.width / 2, y: frame.height / 2)
        imgView.contentMode = .scaleAspectFit

        // Cellに追加.
        self.contentView.addSubview(imgView)
        self.contentView.addSubview(textLabel!)
    }
}

これで全てのコードが書き終わりました!
基本的に上記のコードをコピペしていただければ実装はできると思いますが、
試行錯誤の結果としてViewController/swiftに下記のコードを入れてしまっています。
利用していただいても消してしまっても構いませんのでご自由にどうぞ!

qiita.rb

import UIKit

class ViewController: UIViewController {

//この下のコード全て
    var selectedDate = NSDate()


    func changeHeaderTitle(date: NSDate)->String{
        let formatter:DateFormatter = DateFormatter()
        formatter.dateFormat = "M/yyyy"
        let selectMonth = formatter.string(from: date as Date)
        return selectMonth

最後に

とても長い記事になってしまいましたが、読んでいただきありがとうございます!
これからカレンダーアプリの作成をする方はご参考程度に、
そしてはじめに書かせていただいた実装したいことについて何かアドバイスできる方はコメントしていただけると幸いです!

ありがとうございました!!

4
3
0

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
4
3