Skip to content

Instantly share code, notes, and snippets.

@AlexMarshall12
Last active January 29, 2019 22:06
Show Gist options
  • Save AlexMarshall12/915faf49037eaf165465290fb8d5b8c6 to your computer and use it in GitHub Desktop.
Save AlexMarshall12/915faf49037eaf165465290fb8d5b8c6 to your computer and use it in GitHub Desktop.
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