Last active
February 23, 2019 02:41
-
-
Save agibson73/8f76d4a9d4381e432a4e94af7f01dd4e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Cocoa | |
class ViewController: NSViewController,NSCollectionViewDelegate,NSCollectionViewDataSource,NSCollectionViewDelegateFlowLayout { | |
private let concurrentQueue = DispatchQueue(label: "calendarQueue", attributes: .concurrent) | |
//next 2 variables or speed and smoothness in loading | |
private var currentSection : Int! | |
private var currentOffset = 0 | |
private let calendar = Calendar.current | |
// var because we make some alterations on load | |
private var today = Date() | |
//default values but can be changed on start | |
var endDate = Date(timeIntervalSinceNow: 3153600000) | |
var startDate = Date(timeIntervalSince1970: 0) | |
//this is an array of dates for you to do your decorations | |
var dates = [Date]() | |
// when true the calendar will scroll to today | |
var scrollToToday = true | |
//our collectionview | |
@IBOutlet weak var collectionView: NSCollectionView! | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
normalizeToday() | |
configureCollectionView() | |
// on ios this is not needed but at the moment i don't know how to fix resizing on mac | |
NotificationCenter.default.addObserver(forName: NSNotification.Name.NSWindowWillStartLiveResize, object: nil, queue: OperationQueue.main, using: { | |
notificaiton in | |
self.collectionView.collectionViewLayout?.invalidateLayout() | |
}) | |
NotificationCenter.default.addObserver(forName: NSNotification.Name.NSWindowDidResize, object: nil, queue: OperationQueue.main, using: { | |
notificaiton in | |
self.collectionView.collectionViewLayout?.invalidateLayout() | |
}) | |
} | |
// could be unused | |
func setStartAndEndDate(startDate:Date?,endDate:Date?){ | |
if startDate != nil{ | |
self.startDate = startDate! | |
} | |
if endDate == nil{ | |
self.endDate = endDate! | |
} | |
} | |
func normalizeToday(){ | |
let dt = Date() | |
let dtComps = calendar.dateComponents([.year,.month,.day], from: dt) | |
today = calendar.date(from: dtComps)! | |
} | |
private func configureCollectionView() { | |
collectionView.layer?.backgroundColor = NSColor.white.cgColor | |
collectionView.register(NSNib(nibNamed: "DayCell", bundle: nil), forItemWithIdentifier: "dayCell") | |
collectionView.delegate = self | |
collectionView.dataSource = self | |
let boxSize = max(collectionView!.superview!.bounds.width, collectionView!.superview!.bounds.height) | |
let size = boxSize/7 | |
let layout = NSCollectionViewFlowLayout() | |
layout.itemSize = NSSize(width: size, height: size) | |
layout.minimumInteritemSpacing = 0 | |
layout.minimumLineSpacing = 0 | |
layout.headerReferenceSize = NSSize(width: self.collectionView.bounds.width, height: 100) | |
collectionView.maxNumberOfColumns = 7 | |
collectionView.collectionViewLayout = layout | |
view.wantsLayer = true | |
} | |
func numberOfSections(in collectionView: NSCollectionView) -> Int { | |
return self.numberOfMonthsToLoad(startDate, endDate: endDate) | |
} | |
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { | |
let offset = self.getShiftInSection(section) | |
let totalDays = self.daysInSection(section) | |
return offset + totalDays | |
} | |
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { | |
let cell = collectionView.makeItem(withIdentifier: "dayCell", for: indexPath) as! DayCell | |
var day : Date? | |
if (indexPath as NSIndexPath).item >= self.getShiftInSection((indexPath as NSIndexPath).section){ | |
// special background on today only | |
self.dateForIndexPath(indexPath, completion: { (date) in | |
DispatchQueue.main.async { | |
day = date | |
if self.isToday(day!) == true{ | |
cell.view.layer?.backgroundColor = NSColor.green.cgColor | |
}else{ | |
cell.view.layer?.backgroundColor = NSColor.white.cgColor | |
} | |
let components = self.calendar.component(.day, from: day!) | |
cell.dayNumberLabel.stringValue = "\(components)" | |
} | |
}) | |
}else{ | |
//style for blank cell | |
cell.dayNumberLabel.stringValue = "" | |
} | |
cell.view.layer?.shouldRasterize = true | |
return cell | |
} | |
func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize { | |
let size = collectionView.bounds.width/7 | |
return NSSize(width: size, height: size) | |
} | |
func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> NSView { | |
let header = collectionView.makeSupplementaryView(ofKind: NSCollectionElementKindSectionHeader, withIdentifier: "MonthHeader", for: indexPath) as! MonthHeader | |
header.bounds.size = NSSize(width: collectionView.bounds.width, height: 100) | |
let formatter = DateFormatter() | |
formatter.dateFormat = "MMM YYYY" | |
header.monthLabel.stringValue = formatter.string(from: self.getMonthAsDateForSection((indexPath as NSIndexPath).section)) | |
//print("The header size is \(header.bounds.height)") | |
return header | |
} | |
func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize{ | |
return CGSize(width: 0, height: 100) | |
} | |
func scrollToDate(date:Date){ | |
let indexPath = indexPathFor(date: date) | |
collectionView.scrollToItems(at: [indexPath], scrollPosition: .centeredVertically) | |
} | |
override func viewDidLayout() { | |
super.viewDidLayout() | |
if scrollToToday == true{ | |
self.scrollToDate(date: self.today) | |
} | |
} | |
// MARK : CollectionView and DateHelpers | |
func dateForIndexPath(_ indexPath : IndexPath,completion:(_ date:Date)->Void){ | |
concurrentQueue.sync { | |
let date = self.monthInSection((indexPath as NSIndexPath).section) | |
var yesComponents = (calendar as NSCalendar).components([NSCalendar.Unit.year, NSCalendar.Unit.month, NSCalendar.Unit.day], from: date) | |
yesComponents.day = (indexPath as NSIndexPath).item - self.getShiftInSection((indexPath as NSIndexPath).section) + 1 | |
let newDate = calendar.date(from: yesComponents) | |
completion(newDate!) | |
} | |
} | |
func getShiftInSection(_ section : Int )->Int{ | |
var offset = 0 | |
// we hang on to some of this info to speed up the load | |
// that's why we check this | |
if currentSection == nil || section != currentSection{ | |
let sectionDate : Date = Calendar.current.date(byAdding: .month, value: section, to: endDate)! | |
var oldComp = (calendar as NSCalendar).components([NSCalendar.Unit.year, NSCalendar.Unit.month, NSCalendar.Unit.weekday], from: sectionDate) | |
oldComp.day = 1 | |
let newDay = calendar.date(from: oldComp)! | |
let newComponents = (calendar as NSCalendar).components([NSCalendar.Unit.year, NSCalendar.Unit.month, NSCalendar.Unit.weekday], from: newDay ) | |
offset = newComponents.weekday! - 1 | |
currentSection = section | |
self.currentOffset = offset | |
}else{ | |
offset = self.currentOffset | |
} | |
// adjust offset | |
if offset == 7{ | |
return 0 | |
}else if offset > 7{ | |
return offset - 7 | |
}else{ | |
return offset | |
} | |
} | |
func getMonthForSection(_ dateIndex : Int)->Int{ | |
var comp = (calendar as NSCalendar).components([NSCalendar.Unit.year, NSCalendar.Unit.month, NSCalendar.Unit.day], from: startDate) | |
let monthSection = comp.month ?? 0 + dateIndex | |
return monthSection | |
} | |
func monthInSection(_ dateIndex: Int)->Date{ | |
var comp = (calendar as NSCalendar).components([NSCalendar.Unit.year, NSCalendar.Unit.month, NSCalendar.Unit.day], from: startDate) | |
comp.month = (comp.month ?? 0) + dateIndex | |
return calendar.date(from: comp)! | |
} | |
func daysInSection(_ section :Int)->Int{ | |
var componets = (calendar as NSCalendar).components([NSCalendar.Unit.year, NSCalendar.Unit.month, NSCalendar.Unit.weekday], from: startDate) | |
componets.month = componets.month! - section | |
let date = calendar.date(from: componets) | |
return self.numberOfDaysInMonthFrom(date: date!) | |
} | |
func getMonthAsDateForSection(_ dateIndex : Int)->Date{ | |
var comp = calendar.dateComponents([.year,.month,.day], from: startDate) | |
comp.month = comp.month! + dateIndex | |
return calendar.date(from: comp)! | |
} | |
func isToday(_ date : Date)-> Bool{ | |
if date == self.today{ | |
return true | |
}else{ | |
return false | |
} | |
} | |
func doesDatesContainDate(_ date : Date)->Bool{ | |
if dates.contains(date){ | |
return true | |
}else{ | |
return false | |
} | |
} | |
func numberOfMonthsToLoad(_ startDate : Date, endDate : Date)->Int{ | |
//start and end date and we load at least 1 | |
let time = Calendar.current.dateComponents([.month], from: startDate, to: endDate).month | |
if let tm = time{ | |
return tm + 1 | |
}else{ | |
return 1 | |
} | |
} | |
func numberOfDaysInMonthFrom(date:Date)->Int{ | |
return calendar.range(of: .day, in: .month, for: date)!.count | |
} | |
func indexPathFor(date:Date)->IndexPath{ | |
let section = calendar.dateComponents([.month], from: startDate, to: date).month ?? 0 | |
return IndexPath(item: 0, section: section + 1) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment