Last active
January 29, 2019 22:06
-
-
Save AlexMarshall12/915faf49037eaf165465290fb8d5b8c6 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
class TimelineViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{ | |
var tag: Tag! | |
var tagName: String? | |
var postTimes: [Date] = [] | |
let timelineCellWidth: CGFloat = 2.5 | |
var currentPost: Post! | |
var firstTime = true | |
var firstPostDate: Date? | |
var lastPostDate: Date? | |
var timelineStart: Date? | |
var timelineEnd: Date? | |
var postsFetchedResultsController: NSFetchedResultsController<Post>! | |
var blockOperations: [BlockOperation] = [] | |
lazy var dateFormatter: DateFormatter = { | |
let formatter = DateFormatter() | |
formatter.dateStyle = .short | |
formatter.timeStyle = .medium | |
return formatter | |
}() | |
fileprivate lazy var posts: NSFetchedResultsController<Post> = { | |
let appDelegate = | |
UIApplication.shared.delegate as? AppDelegate | |
let managedContext = | |
appDelegate?.persistentContainer.viewContext | |
let request: NSFetchRequest<Post> = NSFetchRequest(entityName: "Post") | |
request.predicate = NSPredicate(format: "%@ IN self.tags", tag) | |
let timeSort = NSSortDescriptor(key: "timeStamp", ascending: true) | |
request.sortDescriptors = [timeSort] | |
let posts = NSFetchedResultsController(fetchRequest: request, managedObjectContext: managedContext!, sectionNameKeyPath: nil, cacheName: nil) | |
posts.delegate = self | |
return posts | |
}() | |
@IBOutlet weak var timelineCollectionView: UICollectionView! | |
@IBOutlet weak var postsCollectionView: UICollectionView! | |
@IBOutlet weak var dayLabel: UILabel! | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
self.title = tagName | |
self.navigationItem.largeTitleDisplayMode = .never | |
let deleteButton = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(TimelineViewController.editOrDelete)) | |
self.navigationItem.setRightBarButton(deleteButton, animated: false) | |
if let layout = timelineCollectionView.collectionViewLayout as? TimelineLayout { | |
layout.delegate = self | |
} | |
timelineCollectionView.register( | |
UINib(nibName: "MonthHeader", bundle: nil), | |
forSupplementaryViewOfKind: TimelineLayout.Element.month.kind, | |
withReuseIdentifier: TimelineLayout.Element.month.id) | |
timelineCollectionView.register( | |
UINib(nibName: "TimelineDayCell", bundle: nil), | |
forSupplementaryViewOfKind: TimelineLayout.Element.day.kind, | |
withReuseIdentifier: TimelineLayout.Element.day.id) | |
timelineCollectionView.register( | |
UINib(nibName: "YearLabel", bundle: nil), | |
forSupplementaryViewOfKind: TimelineLayout.Element.year.kind, | |
withReuseIdentifier: TimelineLayout.Element.year.id) | |
guard let appDelegate = | |
UIApplication.shared.delegate as? AppDelegate else { | |
return | |
} | |
let managedContext = | |
appDelegate.persistentContainer.viewContext | |
let tagsFetchRequest: NSFetchRequest<Tag> = Tag.fetchRequest() | |
do { | |
let results = try managedContext.fetch(tagsFetchRequest) | |
tag = results.first(where: { $0.name == tagName }) | |
} catch let error as NSError { | |
print("Tag Fetch error: \(error) description: \(error.userInfo)") | |
} | |
do { | |
try posts.performFetch() | |
//success | |
self.postsCollectionView.delegate = self | |
self.postsCollectionView.dataSource = self | |
self.timelineCollectionView.delegate = self | |
self.timelineCollectionView.dataSource = self | |
firstPostDate = posts.object(at: IndexPath(item:0,section:0)).timeStamp as Date? | |
timelineStart = Calendar.current.startOfDay(for: firstPostDate!) | |
let postCount = posts.fetchedObjects?.count | |
let lastPostIndex = IndexPath(item: max(postCount! - 1,0), section: 0) | |
postsCollectionView.setNeedsLayout() | |
postsCollectionView.layoutIfNeeded() | |
postsCollectionView.scrollToItem(at: lastPostIndex, at: .centeredHorizontally, animated: false) | |
currentPost = posts.object(at: lastPostIndex) | |
timelineCollectionView.setNeedsLayout() | |
timelineCollectionView.layoutIfNeeded() | |
timelineCollectionView.scrollToItem(at: lastPostIndex, at: .centeredHorizontally, animated: false) | |
dayLabel.text = String(Calendar.current.component(.day, from: currentPost.timeStamp! as Date)) | |
dayLabel.isHidden = false | |
} catch let error as NSError { | |
print("Could not fetch. \(error), \(error.userInfo)") | |
} | |
} | |
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { | |
postsCollectionView.collectionViewLayout.invalidateLayout() | |
timelineCollectionView.collectionViewLayout.invalidateLayout() | |
super.viewWillTransition(to: size, with: coordinator) | |
} | |
//CollectionView functions | |
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { | |
//both collection views have same number of items | |
let objects = posts.fetchedObjects | |
return objects?.count ?? 0 | |
} | |
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { | |
switch kind { | |
case TimelineLayout.Element.month.kind: | |
let supplementaryView = timelineCollectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: TimelineLayout.Element.month.id, for: indexPath) | |
if let monthLabelView = supplementaryView as? MonthHeader { | |
let active = Calendar.current.date(byAdding: .month, value: indexPath.item, to: timelineStart!) | |
let dateFormatter = DateFormatter() | |
dateFormatter.dateFormat = "MMM" | |
let monthString = dateFormatter.string(from: active!) | |
monthLabelView.monthLabel.text = monthString | |
} | |
return supplementaryView | |
case TimelineLayout.Element.year.kind: | |
let supplementaryView = timelineCollectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: TimelineLayout.Element.year.id, for: indexPath) | |
if let yearLabelView = supplementaryView as? YearLabel { | |
let labelDate = Calendar.current.date(byAdding: .year, value: indexPath.item, to: timelineStart!)! | |
let year = Calendar.current.component(.year, from: labelDate) | |
yearLabelView.yearLabel.text = String(year) | |
} | |
return supplementaryView | |
default: | |
fatalError("This should never happen!!") | |
} | |
} | |
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { | |
if collectionView == timelineCollectionView { | |
let cell = timelineCollectionView.dequeueReusableCell(withReuseIdentifier: "dayCell", for: indexPath) as! TimelineCollectionViewCell | |
cell.backgroundColor = UIColor(red:0.22, green:0.45, blue:0.62, alpha:1.0) | |
return cell | |
} else { | |
let cell = postsCollectionView.dequeueReusableCell(withReuseIdentifier: "postCell", for: indexPath) as! PostViewCell | |
cell.backgroundColor = UIColor.white | |
let post = posts.object(at: indexPath) | |
cell.mediaFileName = post.mediaFilename | |
cell.postImageName = post.postImage | |
cell.delegate = self | |
cell.tagsView.promptText = "" | |
cell.tagsView.isUserInteractionEnabled = false | |
cell.tagsView.direction = .horizontal | |
cell.notesView.text = post.notes | |
if let postImageName = post.postImage { | |
FileService.getMediaURL(mediaFilename: postImageName){ url in | |
let imageData = try! Data(contentsOf: url!) | |
cell.postImage.image = UIImage(data: imageData) | |
} | |
} | |
for tag in post.tags ?? [] { | |
let tag = tag as! Tag | |
let token = KSToken(title: tag.name!) | |
cell.tagsView.addToken(token) | |
} | |
cell.playButton.isHidden = !post.isVideo | |
return cell | |
} | |
} | |
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { | |
if collectionView == timelineCollectionView { | |
return CGSize(width: CGFloat(2.5), height: collectionView.bounds.height) | |
} else { | |
return CGSize(width: collectionView.bounds.size.width, height: collectionView.bounds.size.height) | |
} | |
} | |
//callable functions | |
@objc func editOrDelete(_ sender: AnyObject){ | |
let alertController = UIAlertController(title: nil, message: nil , preferredStyle: .actionSheet) | |
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (action) in | |
print(action) | |
} | |
let editAction = UIAlertAction(title: "Edit", style: .default){ (action) in | |
self.performSegue(withIdentifier: "editPost", sender: nil) | |
} | |
alertController.addAction(editAction) | |
alertController.addAction(cancelAction) | |
let destroyAction = UIAlertAction(title: "Delete", style: .destructive) { (action) in | |
self.deletePost() | |
} | |
alertController.addAction(destroyAction) | |
if let popoverController = alertController.popoverPresentationController { | |
popoverController.barButtonItem = sender as? UIBarButtonItem | |
} | |
self.present(alertController,animated: true) | |
} | |
func deletePost(){ | |
let alertController = UIAlertController(title: "Delete Post?", message: "are you sure?", preferredStyle: .alert) | |
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (action) in | |
print(action) | |
} | |
alertController.addAction(cancelAction) | |
let destroyAction = UIAlertAction(title: "Yes", style: .destructive) { (action) in | |
let appDelegate = | |
UIApplication.shared.delegate as? AppDelegate | |
let managedContext = | |
appDelegate?.persistentContainer.viewContext | |
managedContext?.delete(self.currentPost) | |
do { | |
try managedContext?.save() // <- remember to put this :) | |
} catch { | |
fatalError("couln't save context") | |
} | |
} | |
alertController.addAction(destroyAction) | |
self.present(alertController, animated: true) | |
} | |
deinit { | |
// Cancel all block operations when VC deallocates | |
for operation: BlockOperation in blockOperations { | |
operation.cancel() | |
} | |
blockOperations.removeAll(keepingCapacity: false) | |
} | |
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { | |
if segue.identifier == "editPost" { | |
let controller = segue.destination as! EditPostViewController | |
controller.post = self.currentPost | |
} | |
} | |
} | |
extension TimelineViewController: NSFetchedResultsControllerDelegate { | |
//autoUpdate cells | |
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, | |
didChange anObject: Any, | |
at indexPath: IndexPath?, | |
for type: NSFetchedResultsChangeType, | |
newIndexPath: IndexPath?){ | |
switch type { | |
case .insert: | |
print("Insert Object: \(String(describing: newIndexPath))") | |
blockOperations.append( | |
BlockOperation(block: { [weak self] in | |
if let this = self { | |
this.postsCollectionView!.insertItems(at: [newIndexPath!]) | |
this.timelineCollectionView!.insertItems(at: [newIndexPath!]) | |
} | |
}) | |
) | |
case .update: | |
blockOperations.append( | |
BlockOperation(block: { [weak self] in | |
if let this = self { | |
this.postsCollectionView!.reloadItems(at: [indexPath!]) | |
this.timelineCollectionView!.reloadItems(at: [indexPath!]) | |
} | |
}) | |
) | |
case .move: | |
blockOperations.append( | |
BlockOperation(block: { [weak self] in | |
if let this = self { | |
this.postsCollectionView!.moveItem(at: indexPath!, to: newIndexPath!) | |
this.timelineCollectionView!.moveItem(at: indexPath!, to: newIndexPath!) | |
} | |
}) | |
) | |
case .delete: | |
blockOperations.append( | |
BlockOperation(block: { [weak self] in | |
if let this = self { | |
this.postsCollectionView!.deleteItems(at: [indexPath!]) | |
this.timelineCollectionView!.deleteItems(at: [indexPath!]) | |
} | |
}) | |
) | |
default: break | |
} | |
} | |
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { | |
postsCollectionView!.performBatchUpdates({ () -> Void in | |
for operation: BlockOperation in self.blockOperations { | |
operation.start() | |
} | |
}, completion: { (finished) -> Void in | |
self.blockOperations.removeAll(keepingCapacity: false) | |
}) | |
} | |
} | |
extension TimelineViewController { | |
//Scroll Stuff | |
func scrollViewDidScroll(_ scrollView: UIScrollView) { | |
if scrollView == timelineCollectionView { | |
if let path = timelineCollectionView.centerCellIndexPath { | |
if scrollView.isDragging { | |
postsCollectionView.scrollToItem(at: path, at: .centeredHorizontally, animated: false) | |
} | |
let offset = scrollView.contentOffset.x + scrollView.contentInset.left | |
let days = Int(round(offset / timelineCellWidth)) | |
let centerCellDate = Calendar.current.date(byAdding: .day, value: Int(days), to: timelineStart!) | |
dayLabel.text = String(Calendar.current.component(.day, from: centerCellDate!)) | |
dayLabel.isHidden = false | |
} else { | |
dayLabel.isHidden = true | |
} | |
} | |
} | |
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { | |
if scrollView == postsCollectionView { | |
let visibleRect = CGRect(origin: postsCollectionView.contentOffset, size: postsCollectionView.bounds.size) | |
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY) | |
let visibleIndexPath = postsCollectionView.indexPathForItem(at: visiblePoint)! | |
currentPost = posts.object(at: visibleIndexPath) | |
timelineCollectionView.scrollToItem(at: visibleIndexPath, at: .centeredHorizontally, animated: true) | |
} | |
} | |
/* | |
// MARK: - Navigation | |
// In a storyboard-based application, you will often want to do a little preparation before navigation | |
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { | |
// Get the new view controller using segue.destination. | |
// Pass the selected object to the new view controller. | |
} | |
*/ | |
} | |
extension TimelineViewController : PostViewCellDelegate { | |
func playButtonPressed(playbackURL: URL?) { | |
let player = AVPlayer(url: playbackURL!) | |
let playerController = AVPlayerViewController() | |
playerController.player = player | |
self.present(playerController, animated: true) { | |
player.play() | |
} | |
} | |
} | |
extension TimelineViewController : TimelineLayoutDelegate { | |
func collectionView(_ collectionView: UICollectionView, dateAtIndexPath indexPath: IndexPath) -> Date? { | |
let post = posts.object(at: indexPath) | |
let date = post.timeStamp! as Date | |
return date | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment