How to retrieve UserDefaults because cells are empty - ios

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

Related

swift table view cells does not let me use indexPath.row

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 ?? "")"

Array filled and displayed in function but no displayed in tableview swift

I am having trouble with an array that it is filled correctly in a separated function, the issue is when i try to fill in the elements of my cell in my tableview, i can only find the last element however when i want to display the number of elements in that array while filling the cell it displays the correct number of elements, can anybody help please.
this is my function for retrieving and filling in the array:
func downloadUserDetails(completed: #escaping DownloadComplete){
let Ful_Url = "http://192.168.1.4:8888/phps/select.php"
Alamofire.request(Ful_Url).responseJSON(completionHandler: { (response) in
if let userDect = response.result.value as? [Dictionary<String,AnyObject>]{
for ex in 0...userDect.count-1
{
if let firstnames = userDect[ex]["firstname"] as? String{
self.users?.firstname = firstnames}
if let emails = userDect[ex]["email"] as? String{
self.users?.email = emails}
if let lastnames = userDect[ex]["lastname"] as? String{
self.users?.lastname = lastnames}
print("---------------------------------")
self.items.append(self.users!)
// self.items.insert(self.users!, at: self.i)
print(self.items[ex].email)
print(self.items.count)
}
}
completed()
self.tableview.reloadData()
})
}
this is how i am trying to fill the cell's labels:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("####################")
print("nombre items")
print(self.items.count)
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier:"myCell" , for:indexPath)
let email:UILabel = cell.viewWithTag(11) as! UILabel
let firstname:UILabel = cell.viewWithTag(12) as! UILabel
let lastname:UILabel = cell.viewWithTag(13) as! UILabel
print("=========================email=========================")
print(items[indexPath.row].email)
email.text = items[indexPath.row].email
firstname.text = items[indexPath.row].firstname
lastname.text = items[indexPath.row].lastname
return cell
}
I think trouble in insert method:
self.item.insert(self.users!, at:self,i);
You can try :
self.item.insert(self.users!, at:ex);
I think that your issue is that you are using one single instance of user and then appending it to the array, Each item in the array points to the same item (classes are passed by reference).
You do not need to do this, you dont need to maintain a count or index during iteration either.
This code should work fine..
if let usersDict = response.result.value as? [Dictionary<String,AnyObject>] {
self.users = usersDict.map({ dict in
let user = User()
if let firstnames = dict["firstname"] as? String{
user.firstname = firstnames }
if let emails = dict["email"] as? String{
user.email = emails }
if let lastnames = dict["lastname"] as? String{
user.lastname = lastnames }
return user
})
self.tableView.reloadData()
}
Or even better, allow your User object to be intialised with a dictionary and then do
if let usersDict = response.result.value as? [Dictionary<String,AnyObject>] {
self.users = usersDict.map({ User($0) })
self.tableView.reloadData()
}
Just use local variables during your loop, no need for class properties here. To use the bottom one, you will need to be able to initialise the User object with a dictionary. Similar to this method:
struct User
{
var firstName:String
var lastName:String
var email:String
init(dict:Dictionary<String,AnyObject>) {
email = dict["email"] as? String
firstName = dict["firstName"] as! String
lastName = dict["lastName"] as! String
}
}
UPDATE:
I just wrote this in a playground which works fine
class User {
var firstName: String!
var lastName: String!
var email: String!
init(dict: [String:AnyObject]) {
self.firstName = dict["firstName"] as! String
self.lastName = dict["lastName"] as! String
self.email = dict["email"] as! String
}
}
let usersDict: [[String:String]] = [
["firstName": "John", "lastName": "Smith", "email": "john#example.com"],
["firstName": "John", "lastName": "Smithy", "email": "john#example.com"],
["firstName": "John", "lastName": "Stevens", "email": "john#example.com"],
["firstName": "John", "lastName": "Smithen", "email": "john#example.com"]
]
let users = usersDict.map({ User(dict: $0 as! [String : AnyObject]) })
for user in users {
print(user.firstName, user.lastName)
}
Output:
John Smith
John Smithy
John Stevens
John Smithen
Actually i have just found the solution for any one who faces the same problem, it is actually very simple, the declaration of Class User should be inside the loop, not as a class variable, so now i create a new user at each element found and i add the old element to the array.

How to Unwrap a Modal class object in Swift 3

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.

Access values in multidimentional Swift array?

I'd like to populate a tableView with results from a Firebase query. I successfully get the results and store them into an array but I'm struggling with how to put those values into the table.
I'm getting back a "userID" and a "title". I'm storing those in an array [[String]]. An example of an array after getting back a value would be [["OYa7U5skUfTqaGBeOOplRLMvvFp1", "manager"],["JQ44skOblqaGBe98ll5FvXzZad", "employee"]]
How would I access an individual value, such as the userID? This is what I have so far in my cellForRowAtIndexPath function:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
let invite = inviteArray[indexPath.row]
print(inviteArray)
print(invite)
return cell
}
Thanks!
EDIT: added function to store snapshot in an array
var dictArray: [Dictionary<String, String>] = []
func getAlerts(){
let invitesRef = self.rootRef.child("invites")
let query = invitesRef.queryOrderedByChild("invitee").queryEqualToValue(currentUser?.uid)
query.observeEventType(.Value, withBlock: { snapshot in
for child in snapshot.children {
guard let invitee = child.value["invitee"] as? String else{
return
}
guard let role = child.value["role"] as? String else{
return
}
self.dictArray.append(child as! Dictionary<String, String>)
print(self.dictArray)
}
})
}

strange bug with tableView reload data

I have a tableView with messages.
Messages are store into parse.com.
I download asynchronously, put the message in a struct message array
import UIKit
var messMgr : messageObjet = messageObjet()
var date : NSDate = NSDate()
struct message {
var commentText = ""
var commentDate = ""
}
class messageObjet: NSObject {
var messageData = [message]()
func addMessage(comment : String, date : String) {
//messageData.append(message(commentText: comment, commentDate: date))
var mess = message(commentText: comment, commentDate: date)
messageData.insert(mess, atIndex: 0)
}
}
and populate my tableView
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:CommentTableViewCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as CommentTableViewCell
cell.tag = indexPath.row
// Configure the cell...
// check if it is in the cache
println("the messageCache in cellForRow in commentTableViewController is \(self.commentCache.objectForKey(indexPath.row))")
if let messageCached: AnyObject = self.commentCache.objectForKey(indexPath.row){
cell.messageLabel.text = messageCached as? String
}
if let dateCached: AnyObject = self.dateCache.objectForKey(indexPath.row){
cell.dateLabel.text = dateCached as? String
}
else if messMgr.messageData.count != 0 {
if cell.tag == indexPath.row {
cell.messageLabel.text = messMgr.messageData[indexPath.row].commentText
self.commentCache.setObject(cell.messageLabel.text!, forKey: indexPath.row)
cell.dateLabel.text = messMgr.messageData[indexPath.row].commentDate
self.dateCache.setObject(cell.dateLabel.text!, forKey: indexPath.row)
}
}
return cell
}
I have modal viewController to add a new message.
In order to display immediately the message after dismissing the modal VC
i did this
#IBAction func sendComment(sender: AnyObject) {
let uuid = userDefaults.stringForKey("ApplicationUniqueIdentifier")
var comment = PFObject(className:"Comment")
comment.setObject(textToSend.text, forKey: "CommentText")
comment.setObject(post, forKey: "Post")
comment.setObject(uuid, forKey: "from")
comment.saveEventually()
let date = NSDate()
newMessage.commentText = textToSend.text as String
newMessage.commentDate = date.relativeTimeToString() as String
messMgr.messageData.insert(newMessage, atIndex: 0)
// messMgr.addMessage(textToSend.text as String, date: date.relativeTimeToString() as String)
NSNotificationCenter.defaultCenter().postNotificationName("reloadMessageTableView", object: nil)
println(messMgr.messageData)
self.dismissViewControllerAnimated(true, completion: nil)
}
The problem is when i come back to my tableView the message added to the index 0 is always displayed as the previous message and when i print my message array the index 0 message is the good one..
Any idea ?
Ok the problem was that i store the message in a NSCache with the indexPath as a key...
Removed this and everyThings work good.

Resources