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

#はじめに　

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

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

####実装できていること

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

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

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

####これから実装したいこと

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

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

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

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


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


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

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

#ファイル作成

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


#コード入力

AppDelegate.swift

```ruby: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

```ruby: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


```ruby: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

```ruby: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

```ruby: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

```ruby: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に下記のコードを入れてしまっています。
利用していただいても消してしまっても構いませんのでご自由にどうぞ！

```ruby: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

```


#最後に

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

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