import Foundation
class FeedParser: NSObject, XMLParserDelegate {
fileprivate var rssItems = [(title: String, description: String, pubDate: String)]()
fileprivate var currentElement = ""
fileprivate var currentTitle = "" {
didSet {
currentTitle = currentTitle.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
}
}
fileprivate var currentDescription = "" {
didSet {
currentDescription = currentDescription.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
}
}
fileprivate var currentPubDate = "" {
didSet {
currentPubDate = currentPubDate.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
}
}
fileprivate var parserCompletionHandler: (([(title: String, description: String, pubDate: String)]) -> Void)?
func parseFeed(feedURL: String, completionHandler: (([(title: String, description: String, pubDate: String)]) -> Void)?) -> Void {
parserCompletionHandler = completionHandler
guard let feedURL = URL(string:feedURL) else {
print("feed URL is invalid")
return
}
URLSession.shared.dataTask(with: feedURL, completionHandler: { data, response, error in
// ⚠️ TODO:: PARSE XML from data
}).resume()
}
// MARK: - XMLParser Delegate
func parserDidStartDocument(_ parser: XMLParser) {
rssItems.removeAll()
}
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
currentElement = elementName
if currentElement == "item" {
currentTitle = ""
currentDescription = ""
currentPubDate = ""
}
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
/// Note: current string may only contain part of info.
// ⚠️ TODO:: Update current Title, Description and PubDate based on CurrentElement
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == "item" {
let rssItem = (title: currentTitle, description: currentDescription, pubDate: currentPubDate)
rssItems.append(rssItem)
}
}
func parserDidEndDocument(_ parser: XMLParser) {
parserCompletionHandler?(rssItems)
}
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
print(parseError.localizedDescription)
}
}
import UIKit
class NewsTableViewController: UITableViewController {
fileprivate let feedParser = FeedParser()
fileprivate let feedURL = "http://www.apple.com/main/rss/hotnews/hotnews.rss"
fileprivate var rssItems: [(title: String, description: String, pubDate: String)]?
fileprivate var cellStates: [CellState]?
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 140
tableView.rowHeight = UITableView.automaticDimension
tableView.separatorStyle = UITableViewCell.SeparatorStyle.singleLine
feedParser.parseFeed(feedURL: feedURL) { [weak self] rssItems in
self?.rssItems = rssItems
self?.cellStates = Array(repeating: .collapsed, count: rssItems.count)
DispatchQueue.main.async {
self?.tableView.reloadSections(IndexSet(integer: 0), with: .none)
}
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let rssItems = rssItems else {
return 0
}
return rssItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// ⚠️ TODO:: using rssItems and NewsTableViewCell, fill out each cell's title, description, and date
let cell = UITableViewCell()
return cell
}
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let cell = tableView.cellForRow(at: indexPath) as? NewsTableViewCell {
// ⚠️ TODO:: Toggle cellStates between .collapsed and .expanded
// ⚠️ TODO:: Toggle cell.descriptionLabel.numberOfLines between 0 and 4
// ⚠️ TODO:: Syncronize tableView update
}
}
}