0

EDIT 1: Added my revision cellForRowAtIndexPath code at the bottom of the post

EDIT 2: Added my new EditorialElement code

I am having difficulty properly unwrapping my UILabel text inputs properly, so all of my text says "Optional(Author name)" (for example, this is an app for a newspaper). I have tried to force unwrap my variables in different ways but was not able to make it work.

The text input for my UILabels are created in the following way. The corresponding class is "EditorialElement", which has the following property definitions:

class EditorialElement: NSObject {

var title: String!           // title
var nodeID: Int?             // nid
var timeStamp: Int       // revision_timestamp
var imageURL: String?       // image_url
var author: String?          // author

var issueNumber: String!     // issue_int
var volumeNumber: String!    // volume_int

var articleContent: String! // html_content

/* To get an NSDate objec from Unix timestamp
var date = NSDate(timeIntervalSince1970: timeStamp) */

init(title: String, nodeID: Int, timeStamp: Int, imageURL: String, author: String, issueNumber: String, volumeNumber: String, articleContent: String) {
    self.title = title
    self.nodeID = nodeID
    self.timeStamp = timeStamp
    self.imageURL = imageURL
    self.author = author
    self.issueNumber = issueNumber
    self.volumeNumber = volumeNumber
    self.articleContent = articleContent
}

override func isEqual(object: AnyObject!) -> Bool {
    return (object as! EditorialElement).nodeID == self.nodeID
}

override var hash: Int {
    return (self as EditorialElement).nodeID!
}

}

Then, I use this class to retrieve data from my JSON file and parse it into an "editorialObjects" array (sorry about all the commenting and bad spacing):

func populateEditorials() {
    if populatingEditorials {
        return
    }
    populatingEditorials = true

    Alamofire.request(GWNetworking.Router.Editorials(self.currentPage)).responseJSON() { response in
        if let JSON = response.result.value {
           /*
        if response.result.error == nil {
            */

            /* Creating objects for every single editorial is long running work, so we put that work on a background queue, to keep the app very responsive. */
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {


                /* Making an array of all the node IDs from the JSON file */
                var nodeIDArray : [Int]


                if (JSON .isKindOfClass(NSDictionary)) {

                    for node in JSON as! Dictionary<String, AnyObject> {

                    let nodeIDValue = node.0
                    var lastItem : Int = 0

                    self.nodeIDArray.addObject(nodeIDValue)

                    if let editorialElement : EditorialElement = EditorialElement(title: "init", nodeID: 0, timeStamp: 0, imageURL: "init", author: "init", issueNumber: "init", volumeNumber: "init", articleContent: "init") {

                editorialElement.title = node.1["title"] as! String
                editorialElement.nodeID = Int(nodeIDValue)

                let timeStampString = node.1["revision_timestamp"] as! String
                editorialElement.timeStamp = Int(timeStampString)!

                editorialElement.imageURL = String(node.1["image_url"])
                editorialElement.author = String(node.1["author"])
                editorialElement.issueNumber = String(node.1["issue_int"])
                editorialElement.volumeNumber = String(node.1["volume_int"])
                editorialElement.articleContent = String(node.1["html_content"])

                lastItem = self.editorialObjects.count

                print (editorialElement.nodeID)


                self.editorialObjects.addObject(editorialElement)


                /* Sorting the elements in order of newest to oldest (as the array index increases] */
                let timestampSortDescriptor = NSSortDescriptor(key: "timeStamp", ascending: false)
                self.editorialObjects.sortUsingDescriptors([timestampSortDescriptor])

                let indexPaths = (lastItem..<self.editorialObjects.count).map { NSIndexPath(forItem: $0, inSection: 0) }


               /*

                        nodeIDArray[nodeCounter] = jsonValue{nodeCounter}.string
                        let editorialInfos : EditorialElement = ((jsonValue as! NSDictionary].1["\(nodeIDArray[nodeCounter]]"] as! [NSDictionary]].map { EditorialElement(title: $0["title"] as! String, nodeID: $0["nid"] as! Int, timeStamp: $0["revision_timestamp"] as! Int, imageURL: $0["image_url"] as! String, author: $0["author"], issueNumber: $0["issue_int"] as! Int, volumeNumber: $0["volume_int"] as! Int, articleContent: $0["html_content"] as! String] // I am going to try to break this line down to simplify it and fix the build errors
*/


}

                print(self.editorialObjects.count)

            }
        }

                dispatch_async(dispatch_get_main_queue()) {
                    self.editorialsTableView.reloadData()
                }

                self.currentPage++
        }
    }

        self.populatingEditorials = false
}
}

And then I just use those objects for my labels in my cellForRowAtIndexPath method:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let row = indexPath.row

    let cell = tableView.dequeueReusableCellWithIdentifier(EditorialTableCellIdentifier, forIndexPath: indexPath) as! EditorialsTableViewCell

    let title = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).title

    let timeStamp = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).timeStamp
    let timeStampDateObject = NSDate(timeIntervalSince1970: NSTimeInterval(Int((editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).timeStamp)))
    timeStampDateString = dateFormatter.stringFromDate(timeStampDateObject)

    let imageURL = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).imageURL

    let author : String! = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).author!

    let issueNumber = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).issueNumber
    let volumeNumber = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).volumeNumber

    let articleContent = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).articleContent

    let nodeID = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).nodeID

    /* Unlike the Pictures Collection View, there is no need to create another Alamofire request here, since we already have all the content we want from the JSON we downloaded. There is no URL that we wish to place a request to to get extra content. */

    cell.editorialHeadlineLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
    cell.editorialHeadlineLabel.text = title

    cell.editorialAuthorLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
    cell.editorialAuthorLabel.text = author

    cell.editorialPublishDateLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
    cell.editorialPublishDateLabel.text = timeStampDateString

    return cell
}

Where should I be force-unwrapping my variables ?

EDIT 1: Revised code

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let row = indexPath.row

    guard let cell = tableView.dequeueReusableCellWithIdentifier(EditorialTableCellIdentifier, forIndexPath: indexPath) as? EditorialsTableViewCell else {
        print ("error: editorialsTableView cell is not of class EditorialsTableViewCell, we will use RandomTableViewCell instead")
        return tableView.dequeueReusableCellWithIdentifier(EditorialTableCellIdentifier, forIndexPath: indexPath) as! RandomTableViewCell
    }

    if let editorialObject = editorialObjects.objectAtIndex(indexPath.row) as? EditorialElement {
        // we just unwrapped editorialObject

        let title = editorialObject.title ?? "" // if editorialObject.title == nil, then we return an empty string.

        let timeStampDateObject = NSDate(timeIntervalSince1970: NSTimeInterval(editorialObject.timeStamp))
        let timeStampDateString = dateFormatter.stringFromDate(timeStampDateObject)

        let author = editorialObject.author ?? ""

        let issueNumber = editorialObject.issueNumber ?? ""
        let volumeNumber = editorialObject.volumeNumber ?? ""

        let articleContent = editorialObject.articleContent ?? ""

        let nodeID = editorialObject.nodeID ?? 0


        cell.editorialHeadlineLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
        cell.editorialHeadlineLabel.text = title

        cell.editorialAuthorLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
        cell.editorialAuthorLabel.text = String(author)

        cell.editorialPublishDateLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
        cell.editorialPublishDateLabel.text = timeStampDateString

    } else {

    }

    return cell
}

EDIT 2: new EditorialElement code

class EditorialElement: NSObject {

var title: String           // title
var nodeID: Int             // nid
var timeStamp: Int       // revision_timestamp
var imageURL: String       // image_url
var author: String       // author

var issueNumber: String     // issue_int
var volumeNumber: String    // volume_int

var articleContent: String // html_content

/* To get an NSDate objec from Unix timestamp
var date = NSDate(timeIntervalSince1970: timeStamp) */

init(title: String, nodeID: Int, timeStamp: Int, imageURL: String, author: String, issueNumber: String, volumeNumber: String, articleContent: String) {
    self.title = title
    self.nodeID = nodeID
    self.timeStamp = timeStamp
    self.imageURL = imageURL
    self.author = author
    self.issueNumber = issueNumber
    self.volumeNumber = volumeNumber
    self.articleContent = articleContent
}

override func isEqual(object: AnyObject!) -> Bool {
    return (object as! EditorialElement).nodeID == self.nodeID
}

override var hash: Int {
    return (self as EditorialElement).nodeID
}

}
Jack Berstrem
  • 535
  • 1
  • 5
  • 22

3 Answers3

0

Couple things. 1. You should only be force unwrapping them if you know that there is going to be something there. But if you're super confident in that (or you don't expect/want the app to work without them) then you should just have them be forced unwrapped from the get go. Theres no point in doing var imageURL: String? only to write imageURL! everywhere in the code. The point of optionals is to allow you to gracefully handle situations wherein the object might be nil.

Anyway, assuming this is the correct structure for you the first thing I would do is if let the return of object at index. So writing:

if let element = editorialObjects.objectAtIndex(indexPath.row) as? EditorialElement

Now you can use element as an EditorialElement throughout cell for row. From there you can decide how/when to unwrap everything else.

So let author : String! = (editorialObjects.objectAtIndex(indexPath.row) as! EditorialElement).author! would become either

let author = element.author! or you could if let to avoid a crash and handle the case where it is nil. if let author = element.author { // do something }

Peter Foti
  • 5,526
  • 6
  • 34
  • 47
0

My opinion, change your variable declaration to not nil variable, example: var author: String!

Then, when you set value to your variable, set it is empty character (or default value) if it's nil:

editorialElement.author = String(node.1["author"]) ?? ""

After that, you can use your variable without unwrap everywhere.

t4nhpt
  • 5,264
  • 4
  • 34
  • 43
0

In most of the cases forced unwrapping is a code smell. Don't use it unless you're linking IBOutlets or in some other exceptional cases. Try to properly unwrap your variables.

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let row = indexPath.row

    guard let cell = tableView.dequeueReusableCellWithIdentifier(EditorialTableCellIdentifier, forIndexPath: indexPath) as? EditorialsTableViewCell else {
      // if we fall here cell isn't a EditorialsTableViewCell
      // handle that properly
    }

    if let editorialObject = editorialObjects.objectAtIndex(indexPath.row) as? EditorialElement {
        // editorialObject here is unwrapped
        let title = editorialObject.title ?? "" // if editorialObject.title == nil we store empty string
        let timeStampDateObject = NSDate(timeIntervalSince1970: NSTimeInterval(editorialObject.timeStamp))
        let timeStampDateString = dateFormatter.stringFromDate(timeStampDateObject)

        cell.editorialHeadlineLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
        cell.editorialHeadlineLabel.text = title

        cell.editorialPublishDateLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
        cell.editorialPublishDateLabel.text = timeStampDateString    
    } else {
      // no such editorial object - log error. 
      // return empty cell or do more sofisticated error handling

    }

    return cell
}
Community
  • 1
  • 1
OgreSwamp
  • 4,602
  • 4
  • 33
  • 54
  • can you please provide sample project or paste your new code. I doubt title can be optional now. [Here is a screenshot](https://www.dropbox.com/s/c19nxflaike017c/Screenshot%202015-10-15%2000.35.58.png?dl=0) of the similar example to your title from Playground (check output on the right) – OgreSwamp Oct 14 '15 at 23:37
  • I'm sorry, I thought title in your `EditorialElement` is `String?`, but just realised that it is `String!`. In any case my recommendation regarding unwrapping is valid - don't do forse unwrap. I tried your `EditorialElement` class and simple example in Playground and [everything seems fine](https://www.dropbox.com/s/zzfj8cib60jhlw3/Screenshot%202015-10-15%2000.49.54.png?dl=0). If you publish example project - I may be able to help – OgreSwamp Oct 14 '15 at 23:50
  • Your JSON parsing can also be better. Your code will crash if you receive JSON with a bit different structure or missing `title`. – OgreSwamp Oct 14 '15 at 23:55