Mamta
Mamta

Reputation: 921

TableView repeated code in all view controllers - Swift 3

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

Answers (2)

Yogesh Makwana
Yogesh Makwana

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()

            }

        }
  • connect your parentVC @IBOutlet weak var tableView: UITableView! from storyboard storyboard image

Upvotes: 2

Ahmad F
Ahmad F

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

Related Questions