4

I need to sort / group JSON Data into sections on a tableview.

Right now, the JSON data can't be easily altered so it needs to be re-sorted in Swift.

Basically, I would like it to count how many different DATES there are (which is just a string value) and sort the data based upon it. Then, the sections should be the different DATES with the respected articles in the rows.

The tableview should look like this:

**January 1, 2016**
- Article A 
     - Title
     - Author
     - ETC...
- Article C
     - Title
     - Author
     - ETC...
**February 2, 2016**
- Article B
     - Title
     - Author
     - ETC...

Right now the JSON looks something like:

[
    {
        “x_author”: "",
        "excerpt" : 
            "rendered": “”
        },
        "x_featured_media" : "",
        "title": {
            "rendered": “A”
        },
        "x_date": “January 1, 2016",
    },
    {
        “x_author”: "",
        "excerpt" : 
            "rendered": “”
        },
        "x_featured_media" : "",
        "title": {
            "rendered": “B”
        },
        "x_date": “February 1, 2016",
    },
    {
        “x_author”: "",
        "excerpt" : 
            "rendered": “”
        },
        "x_featured_media" : "",
        "title": {
            "rendered": “C”
        },
        "x_date": “January 1, 2016",
    },
]

The ViewController looks like this:

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    @IBOutlet var tableview: UITableView!

    var articles: [Article]? = []

        override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        fetchArticles()
    }

    func fetchArticles() {
        let urlRequest = URLRequest(url: URL(string: "SITE")!)
        let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
            if error != nil {
                print(error)
                return
            }
            self.articles = [Article]()
            do {
                let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? AnyObject
                if let articlesFromJson = json as? [[String : AnyObject ]] {
                    for articleFromJson in articlesFromJson {
                        let article = Article()
                          if let title = articleFromJson["title"]?["rendered"] as? String, 
                              let author = articleFromJson["x_author"] as? String, 
                              let desc = articleFromJson["excerpt"]?["rendered"] as? String, 
                              let url = articleFromJson["link"] as? String, 
                              let urlToImage = articleFromJson["x_featured_media"] as? String, 
                              let date = articleFromJson["x_date"] as? String {

                            article.author = author
                            article.desc = desc
                            article.headline = title
                            article.url = url
                            article.imageUrl = urlToImage
                            article.date = date
                        }
                        self.articles?.append(article)
                     }
                }
                DispatchQueue.main.async {
                    self.tableview.reloadData()
                }
            } catch let error {
                print(error)
            }
        }
        task.resume()
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "articleCell", for: indexPath) as! articleCell
        cell.title.text = self.articles?[indexPath.item].headline
        cell.desc.text = self.articles?[indexPath.item].desc
        cell.author.text = self.articles?[indexPath.item].author
        cell.date.text = self.articles?[indexPath.item].date    
        cell.imgView.downloadImage(from: (self.articles?[indexPath.item].imageUrl!)!)
        return cell
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.articles?.count ?? 0
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let webVC = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "web") as! WebviewViewController
        webVC.url = self.articles?[indexPath.item].url
        self.present(webVC, animated: true, completion: nil)
    }
}

The Article class looks like:

import UIKit

class Article: NSObject {
    var headline: String?
    var desc: String?
    var author: String?
    var url: String?
    var content: String?
    var imageUrl: String?
    var date: String?
}

How do I go about doing this?

EDIT 1:

Following the advice of dmorrow, the view controller now looks like this:

    struct Issue {
    let dateName: String?
    var articles: [Article]?
}
struct Article {
    var headline: String?
    var desc: String?
    var author: String?
    var url: String?
    var imageUrl: String?
    var date: String?
}

var groupedArticles = [Issue]()

    override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    fetchArticles()
}

func fetchArticles() {
    let urlRequest = URLRequest(url: URL(string: "SITE")!)
    let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
        if error != nil {
            print(error)
            return
        }
        do {
            let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? AnyObject
            if let articlesFromJson = json as? [[String : AnyObject ]] {
                for articleFromJson in articlesFromJson {

                        self.groupedArticles.append(Issue(dateName: (articleFromJson["x_date"] as! String), articles: [Article(headline: (articleFromJson["title"] as! String), desc: (articleFromJson["excerpt"] as! String), author: (articleFromJson["x_author"] as! String), url: (articleFromJson["x_featured_media"] as! String), imageUrl: (articleFromJson["x_featured_media"] as! String), date: (articleFromJson["x_date"] as! String))]))                    
                   }
            }
            DispatchQueue.main.async {
                self.tableview.reloadData()
            }
        } catch let error {
            print(error)
        }
    }
    task.resume()
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "articleCell", for: indexPath) as! articleCell        
    cell.title.text = self.groupedArticles[indexPath.section].articles?[indexPath.row].headline
    cell.desc.text = self.groupedArticles[indexPath.section].articles?[indexPath.row].desc
    cell.author.text = self.groupedArticles[indexPath.section].articles?[indexPath.row].author
    cell.date.text = self.groupedArticles[indexPath.section].articles?[indexPath.row].date

    cell.imgView.downloadImage(from: (self.groupedArticles[indexPath.section].articles?[indexPath.row].imageUrl!)!)
    return cell
}

func numberOfSections(in tableView: UITableView) -> Int {
    return groupedArticles.count
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return groupedArticles[section].articles?.count ?? 0
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let webVC = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "web") as! WebviewViewController
    webVC.url = self.groupedArticles[indexPath.section].articles?[indexPath.row].url
    self.present(webVC, animated: true, completion: nil)
}

HOWEVER, getting error at:

self.groupedArticles.append(Issue(dateName: (articleFromJson["x_date"] as! String), articles: [Article(headline: (articleFromJson["title"] as! String), desc: (articleFromJson["excerpt"] as! String), author: (articleFromJson["x_author"] as! String), url: (articleFromJson["x_featured_media"] as! String), imageUrl: (articleFromJson["x_featured_media"] as! String), date: (articleFromJson["x_date"] as! String))]))

"Could not cast value of type '__NSDictionaryM' (0x106a35260) to 'NSString' (0x1031d9c40)."

Was this the right track? How do I solve this?


EDIT 2

Updated fetchArticles function:

func fetchArticles() {
    let urlRequest = URLRequest(url: URL(string: "X")!)
    let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
        if error != nil {
            print(error)
            return
        }
        do {
            let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? AnyObject
            if let articlesFromJson = json as? [[String : AnyObject]] {
                for articleFromJson in articlesFromJson {
                    // Get All Articles
                    var articleEntries = [Issue]()

                    articleEntries.append(Issue(dateName: (articleFromJson["x_date"] as! String), articles: [Article(headline: (articleFromJson["title"]?["rendered"] as! String), desc: (articleFromJson["excerpt"]?["rendered"] as! String), author: (articleFromJson["x_author"] as! String), url: (articleFromJson["x_featured_media"] as! String), imageUrl: (articleFromJson["x_featured_media"] as! String), date: (articleFromJson["x_date"] as! String))]))

                    // SORT Articles by KEY of X_Date(String)
                    var groupedArts = [String: [Issue]]()
                    var groupedKeys = Array<String>()

                    for article in articleEntries {
                        let index = (article.dateName?.startIndex)!..<(article.dateName?.endIndex)!
                        let keys = String(describing: article.dateName?[index])
                        if groupedArts[keys] != nil {
                            groupedArts[keys]?.append(article)
                            print("this array exists")
                        } else {
                            groupedArts[keys] = [article]
                            groupedKeys.append(keys)
                            print("this array does not exist")
                        }
                    }
                    print(groupedArts)
                    print(groupedKeys)
                }
            }
            DispatchQueue.main.async {
                self.tableview.reloadData()
            }
        } catch let error {
            print(error)
        }
    }
    task.resume()
}

From my understanding, I should be parsing the entries then re-ordering them using something similar to the sample code but its producing results individually:

["Optional(\"February 14, 2016\")": [Journal.ViewController.Issue(dateName: Optional("February 14, 2016"), articles: Optional([Journal.ViewController.Article(headline: Optional("Case File: Shocking out with severe hypoxia"), desc: Optional("ABC"), author: Optional("AUTHOR"), url: Optional("X"), imageUrl: Optional("X"), date: Optional("February 14, 2016"))]))]] ["Optional(\"February 14, 2016\")"]

and so forth for each article. I tried to parse the articles in the IF statement but that crashes everything. What can I do?

nini
  • 41
  • 7

1 Answers1

2

In general, you'll need to create an array of Section for your datasource.

struct Section {
     let date:Date
     var articles:[Article]
}

var groupedArticles = [Section]()

While you are parsing your JSON into Article, you should be storing them in this array. Article should probably be a struct, and you should look into https://github.com/Hearst-DD/ObjectMapper or https://github.com/SwiftyJSON/SwiftyJSON

Then you can use these methods

func numberOfSections(in tableView: UITableView) -> Int {
    return groupedArticles.count
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return groupedArticles[section].articles.count ?? 0
}

See this similar answer - https://stackoverflow.com/a/42277283/1189470

Community
  • 1
  • 1
dmorrow
  • 5,152
  • 5
  • 20
  • 31
  • Thank you for your response. I am super new but I believe I implemented the changes you suggested but it has still not generated the correct results. Please take a look at 'Edit 1'. Am I on right track? – nini Mar 06 '17 at 23:52
  • You are on the right track, however you need to parse your `Article`s and then parse them into `Section`s. You can't just `append` each into `groupedArticles`. For each Article you create, you need to see if it should be added to an existing `Section`, or if a new `Section` needs to be created. Then you need to add all those Sections into the `groupedArticles` array. The error you are seeing is both "Title" and "excerpt" are not strings in your JSON - they are Dictionaries - "rendered":{value}. You should look at the JSON libraries I suggested, they will make your life much easier. – dmorrow Mar 07 '17 at 03:44
  • Please see my "Edit 2" - I think I've followed your directions but I am still not getting the right result. I used your sample code and tried to adapt it and this is what happens 1) All entries and indexes print separately, so they don't seem to be grouping 2) the IF statement is always "the array does not exist" and I can't for the life of me see why. In terms of SwifyJson - i've taken a look and added it my project but i'm really a beginner - this looks super complicated.. is there a way to accomplish what I need to without SwifyJson? – nini Mar 09 '17 at 03:42