0

Using Swift 3

Error causing crash when I click button followButtonClick in MainController.swift. blogID is resulting in nil but when I print mainArray all the objects are there, so there is something wrong in my code that I can't fix. blogID is not being accessed right.

fatal error: unexpectedly found nil while unwrapping an Optional value

Instead of using NSCoder and UserDefaults to save the entire array of mainArray and followedArray, I'm saving the followed blogsID and then displaying those that we're saved by grabbing the objects that had their IDs saved.

MainController.swift

var mainArray = [Blog]()
var followedArray = [Blog]()
var filteredArray = [Blog]()
var followedIdentifiers = Set<String>()

// viewDidLoad
override func viewDidLoad() {
    super.viewDidLoad()

    retrieveDataFromServer()
    loadUserDefaults()
}

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

if !(searchController.isActive && searchController.searchBar.text != "") {

    if section == 0 {
        return "Followed Blogs"
    }
    else {
        return "All Blogs"
    }
}
return "Filtered Blogs"
}

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

if !(searchController.isActive && searchController.searchBar.text != "") {

    if section == 0 {

        return followedArray.count
    }
    else if (section == 1) {

        return mainArray.count
    }
}
return filteredArray.count
}

// 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 blogObject: Blog

if !(searchController.isActive && searchController.searchBar.text != "") {
    if indexPath.section == 0 {
        blogObject = followedArray[indexPath.row] 
        cell.populateCell(blogObject, isFollowed: true, indexPath: indexPath, parentView: self)
    }
    else if indexPath.section == 1 {
        blogObject = mainArray[indexPath.row] 
        cell.populateCell(blogObject, isFollowed: false, indexPath: indexPath, parentView: self)
    }
}
else {
    blogObject = filteredArray[indexPath.row] 
    cell.populateCell(blogObject, 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) {

        // Showing Status Labels
        let cell = self.myTableView.cellForRow(at: indexPath) as! CustomCell
        cell.firstStatusLabel.isHidden = false
        cell.secondStatusLabel.isHidden = false

        // Change Follow to Following
        (sender as UIButton).setImage(UIImage(named: "follow.png")!, for: .normal)
        cell.followButton.isHidden = true
        cell.followedButton.isHidden = false

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

            self.myTableView.beginUpdates()

            // -*- Error breaks here -*- 
            // Save identifier into followedIdentifier array
     self.followedIdentifiers.insert(mainArray[indexPath.row].blogID)

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

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

            self.myTableView.endUpdates()

            myTableView.reloadData()

            // After Updating Table, Save the Archived to UserDefaults
            saveUserDefaults()
        }
        else {

            self.myTableView.beginUpdates()

            // Remove identifier into followedIdentifier array
 self.followedIdentifiers.remove(followedArray[indexPath.row].blogID)

            // ----- Inserting Cell to followedArray -----
            let blogObject: Blog = filteredArray[indexPath.row]
            let indexOfObjectInArray = mainArray.index(of: blogObject)

            followedArray.insert(blogObject, at: 0)

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

            self.myTableView.endUpdates()

            myTableView.reloadData()

            // After Updating Table, Save the Archived to UserDefaults
            saveUserDefaults()
        }
    }
}

// Unfollow Button
@IBAction func followedButtonClick(_ 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) {

        // Hiding Status Labels
        let cell = self.myTableView.cellForRow(at: indexPath) as! CustomCell
        cell.firstStatusLabel.isHidden = true
        cell.secondStatusLabel.isHidden = true

        // Change Following to Follow
        (sender as UIButton).setImage(UIImage(named: "followed.png")!, for: .normal)
        cell.followButton.isHidden = false
        cell.followedButton.isHidden = true

        self.myTableView.beginUpdates()

        // Remove identifier into followedIdentifier array
        self.followedIdentifiers.remove(followedArray[indexPath.row].blogID)

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

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

        self.myTableView.endUpdates()

        myTableView.reloadData()

        // After Updating Table, Save the Archived to UserDefaults
        saveUserDefaults()
    }
}

// Saving UserDefaults
func saveUserDefaults() {

    let key = "followedID"
    UserDefaults.standard.setValue(self.followedIdentifiers, forKey: key)
    UserDefaults.standard.synchronize()
}

// Load UserDefaults
func loadUserDefaults() {

    let key = "followedID"
    UserDefaults.standard.setValue(Array(self.followedIdentifiers), forKey: key)
    self.followedIdentifiers = Set(UserDefaults.standard.stringArray(forKey: key)!)
}

// Retrieving Data from Server
func retrieveDataFromServer() {

    let getDataURL = "http://example.com/receiving.php"
    let url: NSURL = NSURL(string: getDataURL)!

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

        // Clear the arrays
        self.followedArray = [Blog]()
        self.mainArray = [Blog()]

        // Looping through jsonArray
        for jsonObject in jsonArray {

            if let blog = Blog.createBlog(from: jsonObject as AnyObject) {

                // Check if identifiers match
                if followedIdentifiers.contains(blog.blogID) {
                    self.followedArray.append(blog)
                } else {
                    self.mainArray.append(blog)
                }
            }
        }
    } catch {
        print("Error: (Retrieving Data)")
    }
    myTableView.reloadData()
}

Blog.swift - Handles the Blogs objects

import UIKit

class Blog: NSObject, NSCoding {

var blogName: String!
var blogStatus1: String!
var blogStatus2: String!
var blogURL: String!
var blogID: String!
var blogType: String!
var blogDate: String!
var blogPop: String!

static func createBlog(from jsonObject: AnyObject) -> Blog? {

    guard let bID: String = jsonObject.object(forKey: "id") as? String,
        let bName: String = jsonObject.object(forKey: "blogName") as? String,
        let bStatus1: String = jsonObject.object(forKey: "blogStatus1") as? String,
        let bStatus2: String = jsonObject.object(forKey: "blogStatus2") as? String,
        let bURL: String = jsonObject.object(forKey: "blogURL") as? String,
        let bType: String = jsonObject.object(forKey: "blogType") as? String,
        let bDate: String = jsonObject.object(forKey: "blogDate") as? String,
        let bPop: String = jsonObject.object(forKey: "blogPop") as? String

        else {
          print("Error: (Creating Blog Object)")
          return nil
}

let blog = Blog()
    blog.blogName = bName
    blog.blogStatus1 = bStatus1
    blog.blogStatus2 = bStatus2
    blog.blogURL = bURL
    blog.blogID = bID
    blog.blogType = bType
    blog.blogDate = bDate
    blog.blogPop = bPop
    return blog
}

convenience required init?(coder aDecoder: NSCoder) {
    self.init ()
    self.blogName = aDecoder.decodeObject(forKey: "blogName") as! String
    self.blogStatus1 = aDecoder.decodeObject(forKey: "blogStatus1") as! String
    self.blogStatus2 = aDecoder.decodeObject(forKey: "blogStatus2") as! String
    self.blogURL = aDecoder.decodeObject(forKey: "blogURL") as! String
    self.blogID = aDecoder.decodeObject(forKey: "blogID") as! String
    self.blogType = aDecoder.decodeObject(forKey: "blogType") as! String
    self.blogDate = aDecoder.decodeObject(forKey: "blogDate") as! String
    self.blogPop = aDecoder.decodeObject(forKey: "blogPop") as! String
}

func encode(with aCoder: NSCoder) {
    aCoder.encode(blogName, forKey: "blogName")
    aCoder.encode(blogStatus1, forKey: "blogStatus1")
    aCoder.encode(blogStatus2, forKey: "blogStatus2")
    aCoder.encode(blogURL, forKey: "blogURL")
    aCoder.encode(blogID, forKey: "blogID")
    aCoder.encode(blogType, forKey: "blogType")
    aCoder.encode(blogDate, forKey: "blogDate")
    aCoder.encode(blogPop, forKey: "blogPop")
 }
}
WokerHead
  • 947
  • 2
  • 15
  • 46
  • which line does it crash on? Use the debugging techniques described in the linked duplicate – Paulw11 Jun 22 '17 at 22:29
  • In MainController, followButtonClick, `self.followedIdentifiers.insert(mainArray[indexPath.row].blogID)` – WokerHead Jun 22 '17 at 22:30
  • So use the debugger and work out which value is nil - is `self.followedIdentifiers` nil? is `mainArray[indexPath.row].blogID` nil? – Paulw11 Jun 22 '17 at 22:31
  • `blogID` is coming back nil, I tried printing it out and crashed, I can print `mainArray` which is where `blogID` is at. – WokerHead Jun 22 '17 at 22:33
  • I can see that you have an error in your `Blog` object; All of the properties are implicitly unwrapped optionals but you are assigning optionals to them, so you cannot guarantee that the properties have a value. If the property should be optional then make it an optional and unwrap where required. If the property shouldn't be optional, then make it that way, don't make it an implicitly unwrapped optional just to get around compiler errors – Paulw11 Jun 22 '17 at 22:35
  • What part of the code is the error you see? `blogID` keeps coming back `nil` for me and now that you closed this post I'm stuck – WokerHead Jun 23 '17 at 02:29
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/147414/discussion-between-paulw11-and-brosimple). – Paulw11 Jun 23 '17 at 02:31

1 Answers1

0

Your immediate problem is that you have an implicitly unwrapped optional blogID that has not been assigned a value. The underlying cause of this is being masked by your use of implicitly unwrapped optionals because it is not revealing the location where you are failing to initialise a Blog instance correctly.

Rather than using a static function to create a Blog from a JSON dictionary, you should use a failable initialiser. This will allow you to use non-optional properties where appropriate and give you a compiler or runtime error when the object isn't initialised correctly.

import Foundation

class Blog: NSObject, NSCoding {

    var blogName: String
    var blogStatus1: String
    var blogStatus2: String
    var blogURL: String     // Note this should probably be a URL rather than a String
    var blogID: String
    var blogType: String
    var blogDate: String    // Note this should probably be a Date rather than a String
    var blogPop: String


    private init (name: String,status1: String,status2: String,url: String,id: String,type: String,date: String,pop: String) {
        blogName = name
        blogStatus1 = status1
        blogStatus2 = status2
        blogURL = url
        blogID = id
        blogType = type
        blogDate = date
        blogPop = pop
        super.init()
    }

    convenience init?(jsonObject: [String:Any]) {

        guard let bID = jsonObject["id"] as? String,
            let bName = jsonObject["blogName"] as? String,
            let bStatus1 = jsonObject["blogStatus1"] as? String,
            let bStatus2 = jsonObject["blogStatus2"] as? String,
            let bURL = jsonObject["blogURL"] as? String,
            let bType = jsonObject["blogType"] as? String,
            let bDate = jsonObject["blogDate"] as? String,
            let bPop = jsonObject["blogPop"] as? String

            else {
                print("Error: (Creating Blog Object)")
                return nil
        }

        self.init(name: bName, status1: bStatus1, status2: bStatus2, url: bURL, id: bID, type: bType, date: bDate, pop: bPop)

    }

    convenience required init?(coder aDecoder: NSCoder) {
        guard let blogName = aDecoder.decodeObject(forKey: "blogName") as? String,
            let blogStatus1 = aDecoder.decodeObject(forKey: "blogStatus1") as? String,
            let blogStatus2 = aDecoder.decodeObject(forKey: "blogStatus2") as? String,
            let blogURL = aDecoder.decodeObject(forKey: "blogURL") as? String,
            let blogID = aDecoder.decodeObject(forKey: "blogID") as? String,
            let blogType = aDecoder.decodeObject(forKey: "blogType") as? String,
            let blogDate = aDecoder.decodeObject(forKey: "blogDate") as? String,
            let blogPop = aDecoder.decodeObject(forKey: "blogPop") as? String else {
                print("Error: (Creating Blog Object)")
                return nil
        }
        self.init(name: blogName, status1: blogStatus1, status2: blogStatus2, url: blogURL, id: blogID, type: blogType, date: blogDate, pop: blogPop)
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(blogName, forKey: "blogName")
        aCoder.encode(blogStatus1, forKey: "blogStatus1")
        aCoder.encode(blogStatus2, forKey: "blogStatus2")
        aCoder.encode(blogURL, forKey: "blogURL")
        aCoder.encode(blogID, forKey: "blogID")
        aCoder.encode(blogType, forKey: "blogType")
        aCoder.encode(blogDate, forKey: "blogDate")
        aCoder.encode(blogPop, forKey: "blogPop")
    }
}

Note that this may not immediately resolve your problem of showing the ID, but it will help you find where you aren't initialising the Blog instance correctly

Paulw11
  • 108,386
  • 14
  • 159
  • 186