0

I am pretty new to Xcode and Swift. I am trying to display data (restaurants names) in alphabetical order in TableView. I have a JSON file that sorts each restaurant into the correct neighborhood.The code works perfectly in the view controller where I m displaying the name of the neighborhood in sections and the name of the restaurant in rows. My problem is that I am trying to sort all the restaurants names in a different view controller where the sections display the Alphabet (A,B,C...) and under each sections I am trying to display the restaurants alphabetically with index on the side. Something similar to the Contact App on the iPhone but instate of contact names I need to show the restaurant names. Hope I make sense. My code looks like this:

class BarsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, SWRevealViewControllerDelegate, UISearchBarDelegate {


@IBOutlet var btnMenuButton: UIBarButtonItem!
@IBOutlet var tableView: UITableView!
@IBOutlet var searchButton: UIBarButtonItem!

var noDataLabel = UILabel()

let urlString = "http://barhoppersf.com/json/neighborhoods.json"

var restaurantArray = Array<Restaurant>()

var filteredRestaurants = [Restaurant]()

var shouldShowSearchResults = false

var searchBar = UISearchBar()
var logoImageView: UIImageView!

let restaurantsName = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","#"]
let indexName = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","#"]
let voidIndex = [""]


var restSections = [String]()
var restDictionary = [String : [String]]()

    override func viewDidLoad() {
    super.viewDidLoad()


    self.downloadJsonWithURL() // This loads tableview with data from url 

tableView.reloadData()
} 
   **//JSON FUNC**
func downloadJsonWithURL() {

    let url = NSURL(string: urlString)



    URLSession.shared.dataTask(with: (url as URL?)!, completionHandler: {(data, response, error) -> Void in

        if let error = error {

            print(error.localizedDescription)

            return
        }

        if let data = data {

            guard let json = try? JSONSerialization.jsonObject(with: data) else { return }

            guard let dict = json as? Dictionary<String,Dictionary<String,Dictionary<String,Array<Dictionary<String,String>>>>> else { return }

            guard let hoods = dict["hoods"] else { return }

            guard let names = hoods["neighborhoodNames"] else { return }

            for (key, value) in names {

                let neighborhood = NeighborhoodRestaurants(name: key, data: value)

                self.tableData.append(neighborhood)

                self.tableData.sort { $0.name < $1.name }

                self.filteredRestaurants = self.tableData


            }


            DispatchQueue.main.async {



                self.tableView.reloadData()
            }

        }

    }).resume()
}


func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

    return self.restaurantArray[section].restaurants.count

}

func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {

    return self.restaurantArray[section].name
}

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    tableView.rowHeight = 40

    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! RestaurantsTableViewCell

    cell.barLabel.text = self.restaurantArray[indexPath.section].restaurants[indexPath.row].name


    return cell
}

I have a separate swift file with all the structure for the JSON

struct Restaurant {

var name: String
var cuisine: String
var hours: String
var description: String
var address: String
var phone: String
var website: String
var sports: String
var image: String



init?(dict:Dictionary<String,String>) {

    guard

        let name = dict["name"],
        let cuisine = dict["cuisine"],
        let hours = dict["hours"],
        let description = dict["description"],
        let address = dict["address"],
        let phone = dict["phone"],
        let website = dict["website"],
        let sports = dict["sports"],
        let image = dict["image"]


        else {
            return nil
    }

    self.name = name
    self.cuisine = cuisine
    self.hours = hours
    self.description = description
    self.address = address
    self.phone = phone
    self.website = website
    self.sports = sports
    self.image = image


}

//MARK: Function for the data from ViewControler
}

struct NeighborhoodRestaurants {

var name: String

var restaurants: Array<Restaurant>




init(name:String, data:Array<Dictionary<String,String>>) {

    self.name = name

    self.restaurants = Array<Restaurant>()

    for dict in data {

        if let restaurant = Restaurant(dict: dict) {

            self.restaurants.append(restaurant)

            self.restaurants.sort { $0.name < $1.name }

        }

    }
}

}

This is the JSON file: http://barhoppersf.com/json/neighborhoods.json

This is a image of the neighborhoods view controller that works great. You can get the idea! Thanks again in advance!!

Dian
  • 115
  • 3
  • 12

1 Answers1

2

I tried to solve your problem using some different approach. Here is the Sample I made.
Here is my controller code and tried to make it as similar to contact app as per the data provided by the API. I just took restaurant name in model as It will only be needed for sorting in alphabetical order. All the other details and explanation are mentioned in comments within the code.

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var restaurantsTableView: UITableView!

    //The main array for tableView
    var dataArray = [(String,[Restaurant])]()

    var indexTitles = [String]()

    override func viewDidLoad() {
        super.viewDidLoad()
        getData()
    }

    func getData() {

        let url = URL(string: "http://barhoppersf.com/json/neighborhoods.json")
        URLSession.shared.dataTask(with: url!) { (data, response, error) in

            guard let data = data else {return}

            let json = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! [String : AnyObject]

            guard let hoods = json?["hoods"] else { return }

            guard let names = hoods["neighborhoodNames"] as? [String:[AnyObject]] else { return }

            self.makeDataSource(names: names)

            DispatchQueue.main.async {
                self.restaurantsTableView.reloadData()
            }
        }.resume()
    }

    // The main logic for sorting and making the data like Contacts App tableView
    func makeDataSource(names:[String:[AnyObject]]) {
        //Temporary array to hold restaurants on different indexes
        var dict = [String:[Restaurant]]()

        //Character set taken to check whether the starting key is alphabet or any other character
        let letters = NSCharacterSet.letters

        for (_,value) in names {
            //Iterating Restaurants
            for resObj in value {
                if let restaurantName = resObj["name"] as? String {
                    let restaurant = Restaurant(name: restaurantName)
                    var key = String(describing: restaurant.name.characters.first!)

                    key = isKeyCharacter(key: key, letters: letters) ? key : "#"

                    if let keyValue = dict[key] {
                        //Already value exists for that key
                        var filtered = keyValue
                        filtered.append(restaurant)

                        //Sorting of restaurant names alphabetically
                        filtered = filtered.sorted(by: {$0.0.name < $0.1.name})
                        dict[key] = filtered
                    } else {
                        let filtered = [restaurant]
                        dict[key] = filtered
                    }
                }
            }
        }
        //To sort the key header values
        self.dataArray = Array(dict).sorted(by: { $0.0 < $1.0 })

        //Logic to shift the # category to bottom
        let temp = self.dataArray[0]
        self.dataArray.removeFirst()
        self.dataArray.append(temp)

        //For setting index titles
        self.indexTitles = Array(dict.keys.sorted(by: <))

        //Making the index title # at the bottom
        let tempIndex = self.indexTitles[0]
        self.indexTitles.removeFirst()
        self.indexTitles.append(tempIndex
    }
}

//Function to check whether key is alphabet or not
func isKeyCharacter(key:String,letters:CharacterSet) -> Bool {
    let range = key.rangeOfCharacter(from: letters)
    if let _ = range {
        //Your key is an alphabet
        return true
    }
    return false
}

extension ViewController: UITableViewDataSource, UITableViewDelegate {

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

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataArray[section].1.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "RestaurantsTableCell") as! RestaurantsTableCell
        let restaurant = dataArray[indexPath.section].1[indexPath.row]
        cell.restaurantNameLabel.text = restaurant.name
        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        //Pass this model to detail VC
        let restaurant = dataArray[indexPath.section].1[indexPath.row]

        let detailVC = self.storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
        detailVC.restaurant = restaurant
        self.navigationController?.pushViewController(detailVC, animated: true)
    }  

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 30
    }

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return dataArray[section].0
    }

    //For index titles
    func sectionIndexTitles(for tableView: UITableView) -> [String]? {
        return self.indexTitles
    }
}

The Restaurant class is now separated. Also modified the Sample in the link and created a new DetailViewController class.

import Foundation

class Restaurant {
    var name = ""

    init(name:String) {
        self.name = name
    }
}

This code can be improved in the sort logic. Improvements are welcome.

Here is the Output:

enter image description here

Here is the detail controller:

enter image description here

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Rajan Maheshwari
  • 14,465
  • 6
  • 64
  • 98
  • Rajan, Thanks for your help. I am trying to implement your code with what I have. I have a DetailViewController that shows details of the restaurants. That is call from the structure I have in separate swift file. It seems that the code brakes right now but I am trying to fix it. Also, is there any way to put the "#" sign after the Z. I like the table to start with A. I have been trying to find a function but no luck. Thanks again. I appreciate – Dian Jul 04 '17 at 19:22
  • Doesn't matter if you have your struct in different files. Yo just have to pass the model of Restaurant from your TableView didSelect delegate to your detailController. You can grab the complete object at first index which is '#' and append it at last. In that way you can make # at last of Z – Rajan Maheshwari Jul 05 '17 at 02:51
  • How do you grab the complete object at first index? I have tried few different ways but it is not working! Is there a function you can show me? Sorry, I am still new and learning. I was trying to use: self.tableData.sort { $0.name < $1.name } Thanks man. – Dian Jul 05 '17 at 23:43
  • @Dian I have edited the project in the link and also added some code regarding the # category shift to bottom logic and as per your needs. Kindly download the new Sample link and have a look. I have added the snippet in the answer too. If it helped you, kindly accept as a tick. – Rajan Maheshwari Jul 06 '17 at 03:22
  • Rajan, Thanks for your help. Much appreciate it! – Dian Jul 10 '17 at 19:18