Model Class:
class CountryModel: NSObject {
var name:NSString!
var countryId:NSString!
init(name: NSString, countryId: NSString) {
self.name = name
self.countryId = countryId
}
}
ViewController:
var nameArr = [CountryModel]() // Model Class object
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
let arr = nameArr[indexPath.row] // How to do if let here
let str:String? = "\(arr.countryId) \(arr.name)"
if let sstr = str{
cell.textLabel?.text = sstr
}
return cell
}
How should one unwrap this because output is an optional, if I try to unwrap nameArr[indexPath.row] gives an error initializer for conditional binding must have optional type, not "country modal"
It works fine I am not concatenating arr.countryId with arr.name
This works fine
var nameArr = [CountryModal]()
nameArr.append(CountryModal.init(name: "harshal", countryId: "india"))
nameArr.append(CountryModal.init(name: "james", countryId: "australia"))
let arr = nameArr[0]
let str:String? = "\(arr.countryId!) \(arr.name!)"
if let sstr = str{
print(sstr)
}
let arr2 = nameArr[1]
let str2:String? = "\(arr2.countryId!) \(arr2.name!)"
if let sstr2 = str2{
print(sstr2)
}
You can try this library https://github.com/T-Pham/NoOptionalInterpolation. It does exactly that
import NoOptionalInterpolation
let n: Int? = 1
let t: String? = nil
let s: String? = "string1"
let o: String?? = "string2"
let i = "\(n) \(t) \(s) \(o)"
print(i) // 1 string1 string2
NoOptionalInterpolation gets rid of "Optional(...)" and "nil" in Swift's string interpolation. This library ensures that the text you set never ever includes that annoying additional "Optional(...)". You can also revert to the default behaviour when needed.
Also, please note that this does not affect the print function. Hence, print(o) (as opposed to print("(o)"), o as in the example above) would still print out Optional(Optional("string2"))
Avoid forced unwrapping as much as possible. i.e. using '!'. Whenever you see !, try and think of it as code smell.
For json parsing you would not know for sure if the values for countryId and name exists or not. So, it makes sense to keep them as optionals and gracefully try to unwrap them later.
class CountryModel {
var name: String?
var countryId: String?
init(name: String?, countryId: String?) {
self.name = name
self.countryId = countryId
}
}
var nameArr = [CountryModel]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
fatalError("cell should always be initialised")
}
var str: String?
let arr = nameArr[indexPath.row] // How to do if let here
if let countryId = arr.countryId, let name = arr.name {
str = "\(countryId) \(name)"
}
if let unwrappedString = str {
cell.textLabel?.text = unwrappedString
}
return cell
}
Bonus:
You could simplify your json parsing a lot using the Object Mapper library. It simplifies a lot of your parsing logic.
Related
I have a json file being imported to my project (https://api.myjson.com/bins/ywv0k). The json attributes are decoded and stored in my struct class "News", which has the same attributes like the json file.
in a second step I populate a table with the string attribute "timestamp" from my struct class "News", which is actually a UNIX time.
My problem now is that I am lost how to change this UNIX time to a string of format "dd/mm/yy HH:mm:ss", since I get an error when I try to put a function
let date = NSDate(timeIntervalSince1970: timestamp) //error since timestamp is currently defined as string. If I make it a long variable, I cannot populate the table with it any more, since the label requires a text with string format.
let dayTimePeriodFormatter = NSDateFormatter()
dayTimePeriodFormatter.dateFormat = "dd/mm/yy HH:mm:ss"
let dateString = dayTimePeriodFormatter.stringFromDate(date)
into the do-encoding-loop as well as when I put it into this table function: func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell.
Swift 4
import UIKit
// structure from json file
struct News: Codable{
let type: String
let timestamp: String // UNIX format, eg. "1531294146340"
let title: String
let message: String
}
class HomeVC: BaseViewController, UITableViewDelegate, UITableViewDataSource {
var myNewsItems: [News] = []
#IBOutlet weak var myNewTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let nibName = UINib(nibName: "CustomTableViewCell", bundle: nil)
myNewTableView.register(nibName, forCellReuseIdentifier: "tableViewCell")
// JSON Decoding
let url=URL(string:"https://api.myjson.com/bins/ywv0k")
let session = URLSession.shared
let task = session.dataTask(with: url!) { (data, response, error) in
guard let data = data else { return }
do {
let myNewsS = try
JSONDecoder().decode([News].self, from: data)
print(myNewsS)
self.myNewsItems = myNewsS
DispatchQueue.main.async {
self.myNewTableView.reloadData()
}
} catch let jsonErr {
}
}
task.resume()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myNewsItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath) as!
CustomTableViewCell
// populate table with json content
cell.commonInit(timestamp: myNewsItems[indexPath.row].timestamp, message: myNewsItems[indexPath.row].message)
return cell
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.backgroundColor = UIColor(white: 1, alpha: 0.5)
}
}
First of all the date format is wrong. It has to be "dd/MM/yy HH:mm:ss"
The most efficient solution – if you are responsible for the JSON – send the value for timestamp as Double. Then it's sufficient to declare timestamp
let timestamp: Date // UNIX format, eg. 1531294146340
and add the date decoding strategy
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .millisecondsSince1970
Another solution is to put the date conversion code into the struct
struct News: Codable{
let type: String
let timestamp: String // UNIX format, eg. "1531294146340"
let title: String
let message: String
enum CodingKeys: String, CodingKey { case type, timestamp, title, message}
let dateFormatter : DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/yy HH:mm:ss"
return formatter
}()
var dateString : String {
let timeInterval = TimeInterval(timestamp)!
let date = Date(timeIntervalSince1970: timeInterval / 1000)
return dateFormatter.string(from:date)
}
}
The computed property dateString contains the date string.
Further you could declare type as enum
enum Type : String, Codable {
case organizational, planning
}
struct News: Codable{
let type: Type
...
You should be able to convert the timestamp to date and then format it to specific format and convert back to String to display on UILabel. See if the following helps
func string(from timestamp: String) -> String {
if let timeInterval = TimeInterval(timestamp) {
let date = Date(timeIntervalSince1970: timeInterval)
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/yy HH:mm:ss"
return formatter.string(from: date)
}
return "" //return empty if somehow the timestamp conversion to TimeInterval (Double) fails
}
1) As a first suggestion, do NOT store date as string for a number or reasons.
(Apple says to use the very basic type... so use a 64bit for UnixTimestamp OR NSDate.. far more flexible, for example performing calcs, difference, localisations and so on... (and far better memory usage.. (Ints do not even use ARC...))
(and use optional for fields.... far more secure..)
2) so use an extension to save as Date (for example)
Let's start from a int unixTimestamp:
(I added a complete sample for a controller...)
//
// ViewController.swift
// sampleDate
//
// Created by ing.conti on 16/08/2018.
// Copyright © 2018 com.ingconti. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//sample with int...
if let dm =
// Thursday, January 1, 2015 12:00:00 AM GMT+01:00
(1420066800000/1000).fromUnixTimeStamp(){
// note: usual timestamp from server come with milliseincods..
//now You get a date... use and format!
print(dm.description)
}
let testString = "1420066800"
if let n = Int(testString), let dm = n.fromUnixTimeStamp(){
print(dm.description)
}
}
}
extension Int {
func fromUnixTimeStamp() -> Date? {
let date = Date(timeIntervalSince1970: TimeInterval(self))
return date
}
}
so use extension AND change your to use date.
A final note: codable is fine, but not fine for "edge cases"ad apple says in
"Reflecting on Reflection" (https://developer.apple.com/swift/blog/?id=37) sometimes is better to write parser by hand... for a small piece of JSON.
So for example use:
(I rewrote a bit your class...)
typealias Dict = [String : Any]
struct News{
let type: String?
// NO! let timestamp: String // UNIX format, eg. "1531294146340"
let timestamp: Date?
let title: String?
let message: String?
init?(dict : Dict?) {
guard let d = dict else{
return nil
}
if let s = d["timestamp"] as? String, let n = Int(s) {
timestamp = n.fromUnixTimeStamp()
}else{
timestamp = nil // or other "default" ..
}
// go on parsing... other fields..
if let s = d["type"] as? String{
type = s
}else{
type = nil // or other "default" ..
}
if let s = d["title"] as? String {
title = s
}
else{
title = nil // or other "default" ..
}
if let s = d["message"] as? String {
message = s
}else{
message = nil // or other "default" ..
}
}
}
so use in this way:
...
let new = News(dict: dict)
I usually extract data form JSON in this way:
...
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? Dict
else{
return
}
guard let dict = json else{
return
}
..
let new = News(dict: dict)
i am trying to populate data into cells inside of a table view. I created a chosenPlanData var which is initialized to an object with the data inside of it.. The object has properties such as "name" and "event location". An issue occurs when inside of 'cellForRowAt'. It does not let me add [indexPath.row] to the cell i am creating, which in turn does not populate the cells correctly.
For instance - i removed indexPath.row from the first cell.nameLbl.text call - and in turn every single name label in the table view was the same. here is piece of the code
var chosenPlanData = ChosenPlan()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "individualPlansCell") as? PlanitsHomeViewCell else { return UITableViewCell() }
cell.nameLbl.text = chosenPlanData.nameOfEvent[indexPath.row] // error Cannot assign value of type 'Character' to type 'String?'
cell.dateAndTimeLbl.text = chosenPlanData.eventStartsAt[indexPath.row] as? String // error 'subscript' is unavailable: cannot subscript String with an Int, see the documentation comment for discussion
cell.nameLbl.text = chosenPlanData.nameOfEvent // This works - but every single cell has the same nameLbl though
return cell
}
// Here is the call back where i initialize the value for chosenPlanData
let EventbriteTVC = segue.destination as! EventbriteTableView
EventbriteTVC.callbackChosePlan = { result in
DispatchQueue.main.async {
self.individualPlanitsTableView.reloadData()
}
self.chosenPlanData = result
}
import Foundation
import UIKit
class ChosenPlan {
var nameOfEvent : String = ""
var eventStartsAt : String = ""
var eventLocationIs : String = ""
var eventURL : String = ""
var imageForPlan : String?
convenience init( eventName: String, eventTime: String, eventLocation: String, eventImage: String){
self.init()
self.nameOfEvent = eventName
self.eventStartsAt = eventTime
self.eventLocationIs = eventLocation
//self.eventURL = eventLink
self.imageForPlan = eventImage
//eventLink: String,
}
}
Your chosenPlanData variable is a single instance of ChosenPlan - You cannot subscript a single instance.
It needs to be an array of ChosenPlan:
var chosenPlanData = [ChosenPlan]()
Then you can index into this array:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "individualPlansCell") as? PlanitsHomeViewCell else { return UITableViewCell() }
cell.nameLbl.text = chosenPlanData[indexPath.row].nameOfEvent
cell.dateAndTimeLbl.text = chosenPlanData[indexPath.row].eventStartsAt
extending my comment
var chosenPlanData = ChosenPlan()
chosenPlanData is object of ChosenPlan
Now in cellForRow you writing chosenPlanData.nameOfEvent[indexPath.row] but nameOfEvent is String as per your ChosenPlan that you mentioned in question.
For more info,
chosenPlanData.nameOfEvent[indexPath.row] this line represents you using the n th (indexPath.row) object of nameOfEvent which is object of chosenPlanData
Hope now will be more cleared.
Solution
var chosenPlanData = [ChosenPlan]() <- create array
In cellForRow chosenPlanData[indexPath.row].nameOfEvent that means you'r using nameOfEvent of nth object of chosenPlanData.
Type handling is very important in Swift. Try this.
cell.nameLbl.text = String(chosenPlanData.nameOfEvent[indexPath.row])
let index = chosenPlanData.eventStartsAt.index(chosenPlanData.eventStartsAt.startIndex, offsetBy: 3)
cell.dateAndTimeLbl.text = String(chosenPlanData.eventStartsAt[index])
if
var chosenPlanData = [ChosenPlan]()
Try this:-
cell.nameLbl.text = "\(chosenPlanData[indexPath.row].nameOfEvent)"
or
cell.nameLbl.text = "\(chosenPlanData[indexPath.row].nameOfEvent ?? "")"
I made a struct dictionary to get the user title and URL, and then I store them on the phone but when I come to retrieve the data in cellForRow method the cell label is empty, what should appear is the title.(tableView starts off empty until user starts to populate it with the AddArticle action)
So my question is if I'm doing it right because the cell label just turns out nil?
Struct Dictionary:
struct AddMagazine {
let rssUrl: String
let title: String
init(dict: [String : String]){
title = dict["title"] ?? ""
rssUrl = dict["rssUrl"] ?? ""
}
}
var userMagazineTitle = [AddMagazine]()
Getting values from textField:
#IBAction func AddArticle(_ sender: Any) {
animateIn()
tableView.isScrollEnabled = false
}
func addArticleTitle() {
let UserMagazines = AddMagazine.init(dict: ["title": RssTitle.text!, "rssUrl": RssText.text!])
let storedRssUrl = UserMagazines.rssUrl
self.dataString = storedRssUrl
//setting
defaults.set(dataString, forKey: "storedArray")
userMagazineTitle.append(UserMagazines)
tableView.reloadData()
}
Trying to retrieve title here:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyFeedTableViewCell
let headlineName = defaults.object(forKey: "storedArray") as? AddMagazine
cell.myHeadline.text = headlineName?.title
cell.indentationLevel = 3
return cell
}
You’re storing a String object in defaults for “storedArray” but then you typecast it to an AddMagazine when you read it from defaults. Change what you store or read it as a string.
I agree with #Joakim Danielson. You are storing storedRssUrl which is a string into userdefaults and while retrieving you are type casting as AddMagazine hence it will be nil.
self.dataString = storedRssUrl
//setting
defaults.set(dataString, forKey: "storedArray") --> Here you are storing string
let headlineName = defaults.object(forKey: "storedArray") as? AddMagazine --> Here you are fetching as AddMagazine.
//It should be like this
let headlineName = defaults.object(forKey: "storedArray") as? String
In my custom collectionview cell I have
#IBOutlet weak var boardNameLabel: UILabel!
var boardInfoDic: Dictionary? = [String : AnyObject]()
func updateItemAtIndexPath(_ indexPath: NSIndexPath) {
if let string = boardInfoDic?["description"]
{
boardNameLabel.text = String(format: "%#", string as! String)
}
}
and i am sending data to boardInfoDic from collectionView cellForItemAt indexPath: as
let boardsCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: KBoardsCollectionViewCellIdentifier, for: indexPath) as! BoardsCollectionViewCell
boardsCollectionViewCell.boardInfoDic = self.boardsDataArray?[indexPath.item] as Dictionary<String, AnyObject>?
boardsCollectionViewCell.updateItemAtIndexPath(indexPath as NSIndexPath)
but I am getting fatal error: unexpectedly found nil while unwrapping an Optional value, I was tried in multiple ways but no use. How can I fix this issue?
Outlet connection with UICollectionViewCell
First confirm your boardInfoDic is not empty. Use this
func updateItemAtIndexPath(_ indexPath: NSIndexPath) {
print(boardInfoDic)
boardNameLabel.text = String(self.boardInfoDic["description"]!)
}
Try optional conversion
if let string = boardInfoDic?["description"] as? String {
boardNameLabel.text = String(format: "%#", string)
}
This worked for me
if let string = boardInfoDic?["description"] as? String
{
boardNameLabel?.text = string
}
when you do if let string = boardInfoDic?["description"] that variable string is not of type String it is of type AnyObject. As a result, when you cast string to be a String, it is unable to make this type type cast, and, as a result, returns nil. in order to get a string from your dictionary, you need to access it with something of type AnyObject. For example
if let string = boardInfoDic?["description"]
{
boardNameLabel.text = String(format: "%#", string as! String)
}
Be sure to mark this as the answer if it helps you.
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
}
}
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 }
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.
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
}