Article List View Controller Swift Program
//
// ArticleListViewController.swift
// TUM Blog
//
import UIKit
import Alamofire
import SwiftyJSON
/**
Controller object that manages the presentation of and interaction with TUM Blog Articles in form of a list.
*/
class ArticleListViewController: UITableViewController {
/// The Articles retrieved from the server.
var articles: [Article] = []
/// Instance to parse ISO date strings into `NSDate` objects.
let dateParser: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
return formatter
}()
/// Instance to format `NSDate`s for display.
let dateFormatter: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.doesRelativeDateFormatting = true
formatter.dateStyle = NSDateFormatterStyle.ShortStyle
formatter.timeStyle = NSDateFormatterStyle.ShortStyle
formatter.timeZone = NSTimeZone.localTimeZone()
return formatter
}()
// Called after the controller's view is loaded into memory.
override func viewDidLoad() {
super.viewDidLoad()
// Size table view cells via Auto Layout.
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 60
// Load initial data.
loadData()
}
// MARK: - Exercise 3.1 & Exercise 3.2
/// Called when data needs to be loaded from the server.
func loadData() {
// Exercise 3.1 & Exercise 3.2
request(.GET, "\(Shared.Server)/articles")
.validate()
.backgroundResponseJSON { request,response,rawJSONResult in
print("Finally received response")
// Parse raw JSON if a valid result was received.
let parsedArticles = rawJSONResult.value
.flatMap({ self.parseJSON($0) })
dispatch_async(dispatch_get_main_queue()) {
// If retrieving and parsing of the Articles was successful, update UI.
if let articles = parsedArticles {
self.articles = articles
self.tableView.reloadData()
}
// Notify UI that the pull-to-refresh was processed.
self.refreshControl?.endRefreshing()
}
}
print("Scheduled request from server")
}
// MARK: - Final Exercise
/** Called when an Article needs to be saved to the server.
- parameter article: the Article to be saved
* article.id is nil for new Comments
* article.id is set to a specific id for existing Comments
*/
func saveArticle(article: Article) {
let params:[String:AnyObject]
params = [
"email" : article.email,
"title": article.title,
"text": article.text
]
if let id = article.id {
request(.PUT, "\(Shared.Server)/articles/\(id)", parameters: params)
.validate()
.response { request, response, data, error in
self.loadData()
}
} else {
request(.POST, "\(Shared.Server)/articles/", parameters: params)
.validate()
.response { request, response, data, error in
self.loadData()
}
}
}
/// Called when an Article needs to be deleted from the server.
func deleteArticle(id: Int, indexPath: NSIndexPath) {
articles.removeAtIndex(indexPath.row)
request(.DELETE, "\(Shared.Server)/articles/\(id)")
.validate()
.response { request, response, data, error in
self.loadData()
}
}
// MARK: - Parse Response
/// Parses the raw json into an Article object.
func parseJSON(rawJSON: AnyObject) -> [Article]? {
// Return `nil` if the JSON is not an array.
guard let jsonArray = JSON(rawJSON).array else { return nil }
// Iterate through the JSON array, parse each element, and store results in an array.
var parsedArticles = [Article]()
for json in jsonArray {
// Skip element if it does not match expected schema.
guard let id = json["id"].int,
let title = json["title"].string,
let email = json["email"].string,
let text = json["text"].string,
let updatedString = json["updated"].string
else { continue }
parsedArticles.append(Article(
id: id,
title: title,
updated: dateParser.dateFromString(updatedString),
email: email,
text: text))
}
return parsedArticles
}
// MARK: - Table view data source
// Tells the data source to return the number of rows in a given section of a table view.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return articles.count
}
// Asks the data source for a cell to insert in a particular location of the table view.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let article = articles[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("blogEntry", forIndexPath: indexPath) as! ArticleListViewCell
cell.headingLabel.text = article.title
cell.dateLabel.text = article.updated.flatMap({ dateFormatter.stringFromDate($0) })
if article.email == Shared.YourEmail {
cell.accessoryType = .DetailDisclosureButton
}
if let image = article.profileImage {
cell.profileView.image = image
} else {
Gravatar.getImageForEmail(article.email, forImageView: cell.profileView) { image in
if let image = image {
dispatch_async(dispatch_get_main_queue()) {
article.profileImage = image
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
}
}
}
return cell
}
// Asks the data source to verify that the given row is editable.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return articles[indexPath.row].email == Shared.YourEmail
}
// Asks the data source to commit the insertion or deletion of a specified row in the receiver.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
guard let id = articles[indexPath.row].id
where editingStyle == .Delete
else {
self.loadData()
return
}
deleteArticle(id, indexPath: indexPath)
}
// MARK: - UI Actions
/// Called when the user "pulls to refresh".
@IBAction func refreshTriggered(sender: AnyObject) {
loadData()
}
// Called when the user wants to cancel the edit and the storyboard is unwound to the Article list.
@IBAction func cancelEditArticle(sender: UIStoryboardSegue) {
print("Canceled Edit")
}
// Called when the user wants to save the edit and the storyboard is unwound to the Article list.
@IBAction func saveEditArticle(sender: UIStoryboardSegue) {
print("Saving Edit")
let newArticleViewController = sender.sourceViewController as! ArticleEditViewController
let article = newArticleViewController.articleDetailsViewController.article
saveArticle(article)
}
// Notifies the view controller that a segue is about to be performed.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
super.prepareForSegue(segue, sender: sender)
guard let cell = sender as? ArticleListViewCell,
let indexPath = tableView.indexPathForCell(cell) else { return }
if let detailsViewController = segue.destinationViewController as? ArticleDetailsViewController {
detailsViewController.article = articles[indexPath.row]
} else if segue.identifier == "editBlogEntry",
let navController = segue.destinationViewController as? UINavigationController,
let editViewController = navController.viewControllers.first as? ArticleEditViewController {
editViewController.article = articles[indexPath.row]
}
}
}