Reputation: 921
I have used a third-party RSS feed parser library in my app. I have almost 15 pages that show RSS feed in tableviews in each page. The problem is that all the view controllers have the same code except for the RSS feed link. I don't know how to reduce the code in such a way that all the tableviews work as they are working currently whereas at the same time duplicacy of code is reduced.
The code that i'm using is :
import UIKit
class TopStoriesViewController: UIViewController, FeedParserDelegate, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var tableView: UITableView!
var parser: FeedParser?
var entries: [FeedItem]?
var spinnerActivity: MBProgressHUD! = nil
override func viewDidLoad() {
super.viewDidLoad()
self.spinnerActivity = MBProgressHUD.showAdded(to: self.view, animated: true);
self.spinnerActivity.label.text = "Loading";
self.spinnerActivity.detailsLabel.text = "Please Wait!";
self.spinnerActivity.isUserInteractionEnabled = false;
entries = []
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async(execute: { () -> Void in
self.parser = FeedParser(feedURL: topStoriesLink) // this is the link i need to change in all view controllers
self.parser?.delegate = self
self.parser?.parse()
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
// MARK: - FeedParserDelegate methods
func feedParser(_ parser: FeedParser, didParseChannel channel: FeedChannel) {
}
func feedParser(_ parser: FeedParser, didParseItem item: FeedItem) {
}
func feedParser(_ parser: FeedParser, successfullyParsedURL url: String) {
}
func feedParser(_ parser: FeedParser, parsingFailedReason reason: String) {
}
func feedParserParsingAborted(_ parser: FeedParser) {
}
// MARK: - Network methods
func loadImageSynchronouslyFromURLString(_ urlString: String) -> UIImage? {
}
}
Please help. I am new to Swift. I am using Swift 3.0. I need all the methods shown above for each tableview to populate properly.
Upvotes: 0
Views: 285
Reputation: 448
// create super viewcontroller and write all your common code in that viewcontroller
class ParentVC: UIViewController, FeedParserDelegate, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var tableView: UITableView!
var parser: FeedParser?
var entries: [FeedItem]?
var spinnerActivity: MBProgressHUD! = nil
override func viewDidLoad() {
super.viewDidLoad()
self.spinnerActivity = MBProgressHUD.showAdded(to: self.view, animated: true);
self.spinnerActivity.label.text = "Loading";
self.spinnerActivity.detailsLabel.text = "Please Wait!";
self.spinnerActivity.isUserInteractionEnabled = false;
entries = []
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async(execute: { () -> Void in
self.parser = FeedParser(feedURL: topStoriesLink) // This is the only thing changing in every view controller
self.parser?.delegate = self
self.parser?.parse()
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return entries?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "FeedItemCell", for: indexPath) as UITableViewCell
let item = entries![(indexPath as NSIndexPath).row]
// image
if let imageView = cell.viewWithTag(1) as? UIImageView {
if item.mainImage != nil {
imageView.image = item.mainImage
} else {
if item.imageURLsFromDescription == nil || item.imageURLsFromDescription?.count == 0 {
item.mainImage = UIImage(named: "roundedDefaultFeed")
imageView.image = item.mainImage
}
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async(execute: { () -> Void in
for imageURLString in item.imageURLsFromDescription! {
if let image = self.loadImageSynchronouslyFromURLString(imageURLString) {
item.mainImage = image
DispatchQueue.main.async(execute: { () -> Void in
imageView.image = image
self.tableView.reloadRows(at: [indexPath], with: .automatic)
})
break;
}
}
})
}
}
// title
if let titleLabel = cell.viewWithTag(2) as? UILabel {
titleLabel.text = item.feedTitle ?? "Untitled feed"
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
if let item = entries?[(indexPath as NSIndexPath).row] {
let nextScene = storyboard?.instantiateViewController(withIdentifier: "NewsDetailViewController") as! NewsDetailViewController
nextScene.newsTitle = item.feedTitle ?? "Untitled feed"
nextScene.newsDate = item.feedPubDate
nextScene.newsImgLink = item.mainImage
nextScene.newsDetail = item.feedContentSnippet ?? item.feedContent?.stringByDecodingHTMLEntities() ?? ""
nextScene.navBarTitle = "Top Stories"
let url = item.feedLink ?? ""
nextScene.websiteURL = url
self.navigationController?.pushViewController(nextScene, animated: true)
}
}
// MARK: - FeedParserDelegate methods
func feedParser(_ parser: FeedParser, didParseChannel channel: FeedChannel) {
// Here you could react to the FeedParser identifying a feed channel.
DispatchQueue.main.async(execute: { () -> Void in
print("Feed parser did parse channel \(channel)")
})
}
func feedParser(_ parser: FeedParser, didParseItem item: FeedItem) {
DispatchQueue.main.async(execute: { () -> Void in
print("Feed parser did parse item \(item.feedTitle)")
self.entries?.append(item)
})
}
func feedParser(_ parser: FeedParser, successfullyParsedURL url: String) {
DispatchQueue.main.async(execute: { () -> Void in
if ((self.entries?.count)! > 0) {
print("All feeds parsed.")
self.spinnerActivity.hide(animated: true)
self.tableView.reloadData()
} else {
print("No feeds found at url \(url).")
self.spinnerActivity.hide(animated: true)
//show msg - no feeds found
}
})
}
func feedParser(_ parser: FeedParser, parsingFailedReason reason: String) {
DispatchQueue.main.async(execute: { () -> Void in
print("Feed parsed failed: \(reason)")
self.entries = []
self.spinnerActivity.hide(animated: true)
//show msg - feed parsing failed
})
}
func feedParserParsingAborted(_ parser: FeedParser) {
print("Feed parsing aborted by the user")
self.entries = []
self.spinnerActivity.hide(animated: true)
//show msg - feed parsing aborted
}
// MARK: - Network methods
func loadImageSynchronouslyFromURLString(_ urlString: String) -> UIImage? {
if let url = URL(string: urlString) {
let request = NSMutableURLRequest(url: url)
request.timeoutInterval = 30.0
var response: URLResponse?
let error: NSErrorPointer? = nil
var data: Data?
do {
data = try NSURLConnection.sendSynchronousRequest(request as URLRequest, returning: &response)
} catch let error1 as NSError {
error??.pointee = error1
data = nil
}
if (data != nil) {
return UIImage(data: data!)
}
}
return nil
}
}
//TopStoriesViewController.swift
// Use ParentVC instead of UIViewController
class TopStoriesViewController : ParentVC {
override func viewDidLoad() {
super.viewDidLoad()
}
}
//AnotherViewController.swift
// Use ParentVC instead of UIViewController
class AnotherViewController : ParentVC {
override func viewDidLoad() {
super.viewDidLoad()
}
}
Upvotes: 2
Reputation: 31655
I suggest to:
Create a base class, of type UIViewController
called -for example- BaseViewController
and let all the view controllers that have the same code except for the RSS feed link to inherit from it; Add the whole needed functionality in it, by default, all subclasses should have the same functionality implemented in the base class.
For changing the url for each view controller, you should declare a property in the base view controller called -for example- url
and change its value to be desired for the current view controller. Note that this also should be applied for any property that should has its different value in the sub class.
For reusability, you should let any objects with that needs to set its datasource
/delegate
to conformed by the base class (for example: tableView.delegate = super.self()
).
Example:
BaseViewController:
class BaseViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var url: String = "default url"
var cellIdentifier: String = "cell"
override func viewDidLoad() {
super.viewDidLoad()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("default implemntation")
}
}
TopStoriesViewController (subclass):
class TopStoriesViewController: BaseViewController {
@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
url = "top stories url"
tableView.dataSource = super.self()
tableView.delegate = super.self()
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("a cell has been selected, custom implemntation")
}
}
Note that the example is an demonstration of the logic of achieving this, you will need to implement the other features in a way that is logical to you.
Hope this helped.
Upvotes: 2