1

In my Swift iOS app, I have a table view which is supposed to show JSON objects from a mysql server, like a blog reader app. BUT now the table view is showing up empty because I tried implementing NSUserDefaults to save when the user clicks a button on each row to move to another section. I'm basically saving the indexPath and Section of the row that the user clicked on so when the user opens the app the next time, it remember where it was moved to. But the table view is showing up empty and I have no idea on how to fix it, hopefully someone here does. Thank You!

I get a warning in the code warning in code

ViewController.swift

// Outlets and Variables
@IBOutlet weak var myTableView: UITableView!

let searchController = UISearchController(searchResultsController: nil)

var jsonArray: NSMutableArray = []
var testArray = [Test]()
var followedArray = [Test]()
var filteredArray = [Test]()

// NSUserDefaults
var data: [Any]?
var items: [[Any]]?

override func viewDidLoad() {
    super.viewDidLoad()

    // Custom Cell
    self.myTableView.dataSource = self
    self.myTableView.delegate = self

    // Load Data from Server
    self.retrieveData()

    // NSUserDefaults
    self.fetchData()
}

// Number of Rows in Section
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

    return self.items?[section].count ?? 0
}

// Number of Sections
func numberOfSections(in tableView: UITableView) -> Int {

    return self.items?.count ?? 0
}

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

    let CellIdentifier = "Cell"
    var cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier) as! CustomCell

    if cell != cell {
        cell = CustomCell(style: UITableViewCellStyle.default, reuseIdentifier: CellIdentifier)
    }

    // Configuring the cell
    var testObject: Test

    if !(searchController.isActive && searchController.searchBar.text != "") {
        if indexPath.section == 0 {
            testObject = followedArray[indexPath.row] 
            cell.populateCell(testObject, isFollowed: true, indexPath: indexPath, parentView: self)
        }
        else if indexPath.section == 1 {
            testObject = testArray[indexPath.row] 
            cell.populateCell(testObject, isFollowed: false, indexPath: indexPath, parentView: self)
        }
    }
    else {
        testObject = filteredArray[indexPath.row] 
        cell.populateCell(testObject, isFollowed: false, indexPath: indexPath, parentView: self)
    }

    return cell
}

// Follow Button
@IBAction func followButtonClick(_ sender: UIButton!) {

    // Adding row to tag
    let buttonPosition = (sender as AnyObject).convert(CGPoint.zero, to: self.myTableView)
    if let indexPath = self.myTableView.indexPathForRow(at: buttonPosition) {

        let cell = self.myTableView.cellForRow(at: indexPath) as! CustomCell

        // Change Follow to Following
        (sender as UIButton).setImage(UIImage(named: "follow.png")!, for: .normal)

        // Checking wether to import from testArray or filteredArray to followedArray
        if !(searchController.isActive && searchController.searchBar.text != "") {

            self.myTableView.beginUpdates()

            // ----- Inserting Cell to followedArray -----
            followedArray.insert(testArray[indexPath.row], at: 0)
            myTableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .fade)

            // ----- Removing Cell from testArray -----
            testArray.remove(at: indexPath.row)
            let rowToRemove = indexPath.row
            self.myTableView.deleteRows(at: [IndexPath(row: rowToRemove, section: 1)], with: .fade)

            self.myTableView.endUpdates()

            // NSUserDefaults
            saveSorting() { "\($0)" }

            myTableView.reloadData()
        }
        else {

            self.myTableView.beginUpdates()

            // ----- Inserting Cell to followedArray -----
            let testObject: Test = filteredArray[indexPath.row]
            let indexOfObjectInArray = testArray.index(of: testObject)

            followedArray.insert(testObject, at: 0)

            // ----- Removing Cell from filteredArray -----
            filteredArray.remove(at: indexPath.row)
            testArray.remove(at: indexOfObjectInArray!)
            let rowToRemove = indexPath.row
            self.myTableView.deleteRows(at: [IndexPath(row: rowToRemove, section: 0)], with: .fade)

            self.myTableView.endUpdates()

            // NSUserDefaults
            saveSorting() { "\($0)" }

            myTableView.reloadData()
        }
    }
}

// Retrieving Data from Server
func retrieveData() {

    let getDataURL = "http://exampleip.org/get.php"
    let url: NSURL = NSURL(string: getDataURL)!

    do {

        let data: Data = try Data(contentsOf: url as URL)
        jsonArray = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! NSMutableArray

        // Looping through jsonArray
        for i in 0..<jsonArray.count {

            // Create Test Object
            let tID: String = (jsonArray[i] as AnyObject).object(forKey: "id") as! String
            let tName: String = (jsonArray[i] as AnyObject).object(forKey: "testName") as! String

            // Add Test Objects to Test Array
            testArray.append(Test(testName: tName, andTestID: tID))

        }
    }
    catch {
        print("Error: (Retrieving Data)")
    }

    myTableView.reloadData()
}

// NSUserDefaults
func fetchData() {

    // request from remote or local
    data = [testArray]

    // Update the items to first section has 0 elements,
    // and place all data in section 1
    items = [[], data ?? []]

    // apply ordering
    applySorting() { "\($0)" }

    // save ordering
    saveSorting() { "\($0)" }

    // refresh the table view
    myTableView.reloadData()
}

func applySorting(_ dataIdBlock: (Any) -> String) {

    // get all saved ordering
    guard let data = self.data else { return }
    let ordering = DataHandling.allSavedOrdering(data.count)

    var result: [[Any]] = [[], []]

    for (section, ordering) in ordering {
        guard section <= 1 else { continue } // make sure the section is 0 or 1
        let rows = data.filter({ obj -> Bool in
            return ordering.index(where: { $0.dataId == .some(dataIdBlock(obj)) }) != nil
        })
        result[section] = rows
    }

    self.items = result
}

func saveSorting(_ dataIdBlock: (Any) -> String) {

    guard let items = self.items else { return }

    for (section, rows) in items.enumerated() {
        for (row, item) in rows.enumerated() {
            let indexPath = IndexPath(row: row, section: section)
            let dataId = dataIdBlock(item)
            let ordering = DataHandling(dataId: dataId, indexPath: indexPath)
             // Warning is here
            ordering.save(defaults: indexPath.defaultsKey)
            }
        }
    }
}

extension IndexPath {
    var defaultsKey: String {
        return "data_handling_\(section)_\(row)"
    }
}

CustomCell.swift

class CustomCell: UITableViewCell {

func populateCell(_ testObject: Test, isFollowed: Bool, indexPath: IndexPath, parentView: Any) {

    if isFollowed {
        self.followedButton.tag = indexPath.row
        self.followedButton.addTarget(parentView, action: #selector(ViewController.followedButtonClick(_:)), for: .touchUpInside)

        self.followedButton.isHidden = false
        self.followButton.isHidden = true

    }
    else {
        self.followButton.tag = indexPath.row
        self.followButton.addTarget(parentView, action: #selector(ViewController.followButtonClick(_:)), for: .touchUpInside)

        self.followedButton.isHidden = true
        self.followButton.isHidden = false

    }
  }
}

DataHandling.swift Handling NSUserDefault

class DataHandling: NSObject, NSCoding {

var indexPath: IndexPath?
var dataId: String?

init(dataId: String, indexPath: IndexPath) {
    super.init()
    self.dataId = dataId
    self.indexPath = indexPath
}

required init(coder aDecoder: NSCoder) {

    if let dataId = aDecoder.decodeObject(forKey: "dataId") as? String {
        self.dataId = dataId
    }

    if let indexPath = aDecoder.decodeObject(forKey: "indexPath") as? IndexPath {
        self.indexPath = indexPath
    }

}

func encode(with aCoder: NSCoder) {
    aCoder.encode(dataId, forKey: "dataId")
    aCoder.encode(indexPath, forKey: "indexPath")
}

func save(defaults box: String) -> Bool {

    let defaults = UserDefaults.standard
    let savedData = NSKeyedArchiver.archivedData(withRootObject: self)
    defaults.set(savedData, forKey: box)
    return defaults.synchronize()

}

convenience init?(defaults box: String) {

    let defaults = UserDefaults.standard
    if let data = defaults.object(forKey: box) as? Data,
        let obj = NSKeyedUnarchiver.unarchiveObject(with: data) as? DataHandling,
        let dataId = obj.dataId,
        let indexPath = obj.indexPath {
        self.init(dataId: dataId, indexPath: indexPath)
    } else {
        return nil
    }

}

class func allSavedOrdering(_ maxRows: Int) -> [Int: [DataHandling]] {

    var result: [Int: [DataHandling]] = [:]
    for section in 0...1 {
        var rows: [DataHandling] = []
        for row in 0..<maxRows {
            let indexPath = IndexPath(row: row, section: section)
            if let ordering = DataHandling(defaults: indexPath.defaultsKey) {
                rows.append(ordering)
            }
            rows.sort(by: { $0.indexPath! < $1.indexPath! })
        }
        result[section] = rows
    }

    return result

  }

}
WokerHead
  • 947
  • 2
  • 15
  • 46
  • You have not shared the `retrieveData` function. Are you calling `myTableView .reloadData()` after retrieving the data from server? – Vishal Jan 27 '17 at 08:37
  • @Vishal Sorry I added the code at the bottom of ViewController and yes I did add .reloadData – WokerHead Jan 27 '17 at 12:04

0 Answers0