I just started using Swift and I would like to display the result of parsing a JSON file in a UITableView. The problem comes from the initialization of my NSMutableArray news which will contain all the objects to display.
I have the Error :
error : "/Users/******/Documents/developper/******/*******
/NewsTableViewController.swift:79:22: 'NSMutableArray?'
does not have a member named 'count'
Code is :
import UIKit
class NewsTableViewController: UITableViewController {
var bytes: NSMutableData?
// var news: NSArray = []
var news: NSMutableArray?
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
// this is our remote end point (similar to URLRequest in AS3)
let request = NSURLRequest(URL: NSURL(string: "http://mangerdulion.com/wp-content/newsInnoven.json")!)
// this is what creates the connection and dispatches the varios events to track progression, etc.
let loader = NSURLConnection(request: request, delegate: self, startImmediately: true)
}
func connection(connection: NSURLConnection!, didReceiveData conData: NSData!) {
self.bytes?.appendData(conData)
}
func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
self.bytes = NSMutableData()
}
func connectionDidFinishLoading(connection: NSURLConnection!) {
// we serialize our bytes back to the original JSON structure
let jsonResult: Dictionary = NSJSONSerialization.JSONObjectWithData(self.bytes!, options: NSJSONReadingOptions.MutableContainers, error: nil) as Dictionary<String, AnyObject>
// we grab the colorsArray element
let results: NSArray = jsonResult["newsArray"] as NSArray
self.news?.addObjectsFromArray(results)
/*
// we iterate over each element of the colorsArray array
for item in results {
// we convert each key to a String
var titre: String = item["titreNews"] as String
var date: String = item["dateNews"] as String
var contenu: String = item["contenuNews"] as String
println("\(titre): \(date):\(contenu)")
}
*/
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
//println(self.news?.count)
return self.news.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath) as UITableViewCell
let titre: NSDictionary = self.news[indexPath.row] as NSDictionary
cell.textLabel?.text = titre["titreNews"] as? String
println(cell.textLabel?.text)
// Get the formatted price string for display in the subtitle
let contenu: NSString = titre["contenuNews"] as NSString
cell.detailTextLabel?.text = contenu
return cell
}
Create a mutable array at the top of your class, and don't put any "?" or "!" after news.
var news: NSMutableArray = []
You also need to call reloadData on your table view at the end of the connectionDidFinishLoading method
Putting ? character at the end of your class while declaring a variable creates an Optional variable. Optional variables can be nil or have a value Optional(value).
You have to unwrap the Optional to use it.
var news:NSMutableArray? creates an optional array.
items?.count will also return an optional Optional(Int). This cannot be returned in the UITableView count method.
I don't think you need to use an optional here. Instead you can use an empty array for convenience.
var news: NSMutableArray = []
Related
Please help! I've tried everything. If anyone has any advice on how i can display my data in the table view cell, I would be eternally grateful. I'm new to iOS and am learning on a very steep pace. I grabbed data from an API that returned data in the form of JSON, parsed it, created my table view with its table view cells, but i can't seem to figure out how to print the data i parsed through in the table view cell.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var myTableView: UITableView! {
didSet {
myTableView.dataSource = self
myTableView.delegate = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "https://api.viacom.com/apiKey=someKey")!
let request = NSMutableURLRequest(URL: url)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { data, response, error in
if let response = response, data = data {
var json: [String: AnyObject]!
do {
json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as! [String : AnyObject]
} catch {
print(error)
}
//2 - Store in model, forloop through them, store into temparray,add to main array?
let episodes = json["response"] as! [String: AnyObject]
let meta = episodes["episodes"] as! [AnyObject]
let description = meta[2]["description"]! as! String?
//let title = meta[2]["title"] as! String?
let episodeNumber = meta[2]["episodeNumber"]! as! String?
dispatch_async(dispatch_get_main_queue(), {
self.myTableView.reloadData()})
data = [episodeNumber!, description!]
print("Episode Number: \(episodeNumber!)\n" + "Description: \(description!)")
} else {
print(error)
}
}
task.resume()
}
let data = [description]
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel!.text = "\(self.data)"
return cell
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Your codes look very messy to me. However, I'm just assuming that you have successfully fetched the JSON data. Fetching data is asynchronous. You therefore need to add a dispatch code inside.
After your this line of code:
let episodeNumber = meta[2]["episodeNumber"]! as! String?
Add this
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()})
EDIT:
#IBOutlet weak var myTableView: UITableView! {
didSet {
myTableView.dataSource = self
myTableView.delegate = self // Add This
}
}
The reason for the failure is too much of data manipulation. There is no need to use so many variables and pass around data unnecessarily. You are getting correct output in console when printing it because you used variables "episodeNumber" and "description".
print("Episode Number: \(episodeNumber!)\n" + "Description: \(description!)")
And getting wrong data in variable "data".
So better thing would be that you should use episodeNumber and description variables to print data in Cell.
cell.textLabel!.text = "Episode Number: \(self.episodeNumber)\n" + "Description: \(description)"
But for this you have to make variable episodeNumber a global variable.
So declare it outside the function.
var episodeNumber = String()
and remove the let keyword from line
let episodeNumber = meta[2]["episodeNumber"]! as! String?
You have to add some self. keywords which the compiler will suggest you so you don't have to worry about that, just keep on double clicking the suggestions.
Now, your code looks fine to run and get desired output.
let data = [description]
is a short form of
let data = [self.description]
and self.description() is the viewController's description method used for printing debug description. That is why
cell.textLabel!.text = "\(self.data)"
gives you [(Function)], as you just created an array with a stored function in it.
I am trying to use the data from my network call to display in the UItableview as cell names
Here is my current view controller
import UIKit
import Alamofire
import SwiftyJSON
class ViewController: UIViewController, UITableViewDataSource{
var articles = [Article]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//get data from network call
loaddata()
//end view did load
}
func loaddata(){
Alamofire.request(.GET, "http://ip-address/test.json")
.responseJSON { response in
// print(response.request) // original URL request
// print(response.response) // URL response
//print(response.data) // server data
//print(response.result) // result of response serialization
/*if let JSON = response.result.value {
print("JSON: \(JSON)")
}*/
//get json from response data
let json = JSON(data: response.data!)
//print(json)
//for loop over json and write all article titles articles array
for (key, subJson) in json["Articles"] {
if let author = subJson["title"].string {
let artTitle = Article(name: author)
self.articles.append(artTitle!)
}
/*if let content = subJson["content"].string {
// self.Content.append(content)
}*/
}
// print("\(self.titles)")
//print("\(self.Content[0])")
//print(self.articles)
//set variable to articles number 6 to check append worked
let name = self.articles[6].name
//print varibale name to check
print("\(name)")
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//let num = articles.count
// print(num)
//return number of rows
return articles.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
//let (artTitle) = articles[indexPath.row]
// Fetches the appropriate article for the data source layout.
let article = articles[indexPath.row]
//set cell text label to article name
cell.textLabel?.text = article.name
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//end of class
}
Here is the Article.swift file
class Article {
// MARK: Properties
var name: String
// MARK: Initialization
init?(name: String) {
// Initialize stored properties.
self.name = name
// Initialization should fail if there is no name or if the rating is negative.
if name.isEmpty {
return nil
}
}
}
I think I am almost there, here is what I can do so far
The network call with alamofire is working
I can get the article title using swiftJson
I then append the article title
I can then print from articles after the for loop so I know its working.
I just can't set it to the cell name
Is this because the UItableview is loaded before the data is loaded and hence article will be empty at that point?
Can you point me towards best practice for something such as this/ best design patterns and help me load data in
After you get the data from the request, call reloadData on your tableView on the main thread like so.
dispatch_async(dispatch_get_main_queue(), {
[unowned self] in
self.tableView.reloadData()
})
This will make the tableView refresh all of its contents, and your data should show up then.
Alamofire.request(.GET, Urls.menu).responseJSON { response in
if let jsonData = response.data {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let json = JSON(data: jsonData)
//for loop over json and write all article titles articles array
newArticles = [Articles]()
for (key, subJson) in json["Articles"] {
if let author = subJson["title"].string {
if let artTitle = Article(name: author) {
newArticles.append(artTitle)
}
}
}
dispatch_async(dispatch_get_main_queue()) {
self.articles += newArticles
tableView.reloadData()
/*if let content = subJson["content"].string {
// self.Content.append(content)
}*/
}
// print("\(self.titles)")
//print("\(self.Content[0])")
//print(self.articles)
//set variable to articles number 6 to check append worked
let name = self.articles[6].name
//print varibale name to check
print("\(name)")
}
}
}
I tried everything and I cant solve this, maybe im too tired to see, idk.
What i want is to retrive all objects from parse and list them in a tableview. So each row in the tableview must represent a row in the Parse Class. Objective: Show all the restaurants available.
Right now i can get all the objects from the Parse Class, but shows the same title on all table rows.
Here is the output (as you can see, always show the same name: "Renato" because its the last one that is retrived)
My code:
import UIKit
import Parse
class ListaTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 3
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> ListaTableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! ListaTableViewCell
var query = PFQuery(className:"Restaurantes")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) Restaurantes.")
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
cell.textCell.text = object["nome"] as? String
println(object.objectId)
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
//cell.textCell.text = "Hello world"
cell.imageBg.image = UIImage(named: "www.maisturismo.jpg")
return cell
}
}
Println Output
You are currently iterating through the whole objects array which will show always the last
for object in objects {
cell.textCell.text = object["nome"] as? String
}
You need to do it like this
if let objects = objects as? [PFObject] {
cell.textCell.text = objects[indexPath.row]["nome"] as? String
}
Also you should take another "way" of using the UITableViewController Subclass... Take a look, I quickly wired you up some code to see how you should do it...
https://gist.github.com/DennisWeidmann/740cbed1856da856926e
I'm quite new to working with Parse and I'm building a todo list as part of a CRM. Each task in the table view shows the description, due date, and client name. The description and due date are in my Task class, as well as a pointer to the Deal class. Client is a string in the Deal class. I'm able to query the description and due date properly, but I am not able to retrieve the client attribute from within the Deal object by using includeKey. I followed the Parse documentation for includeKey.
The description and due date show up properly in the resulting table view, but not the client. The log shows client label: nil and the printed task details include <Deal: 0x7ff033d1ed40, objectId: HffKOiJrTq>, but nothing about the client attribute. How can I retrieve and assign the pointer object's attribute (client) to my label within the table view? My relevant code is below. Thank you in advance.
Edit: I've updated my code with func fetchClients() based on this SO answer, but I'm still not sure whether my function is complete or where to call it.
class TasksVC: UITableViewController {
var taskObjects:NSMutableArray! = NSMutableArray()
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
println("\(PFUser.currentUser())")
self.fetchAllObjects()
self.fetchClients()
}
func fetchAllObjects() {
var query:PFQuery = PFQuery(className: "Task")
query.whereKey("username", equalTo: PFUser.currentUser()!)
query.orderByAscending("dueDate")
query.addAscendingOrder("desc")
query.includeKey("deal")
query.findObjectsInBackgroundWithBlock { (tasks: [AnyObject]!, error:NSError!) -> Void in
if (error == nil) {
var temp:NSArray = tasks! as NSArray
self.taskObjects = temp.mutableCopy() as NSMutableArray
println(tasks)
self.tableView.reloadData()
} else {
println(error?.userInfo)
}
}
}
func fetchClients() {
var task:PFObject = PFObject(className: "Task")
var deal:PFObject = task["deal"] as PFObject
deal.fetchIfNeededInBackgroundWithBlock {
(deal: PFObject!, error: NSError!) -> Void in
let client = deal["client"] as NSString
}
}
//MARK: - Tasks table view
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.taskObjects.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("TaskCell", forIndexPath: indexPath) as TaskCell
var dateFormatter:NSDateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "M/dd/yy"
var task:PFObject = self.taskObjects.objectAtIndex(indexPath.row) as PFObject
cell.desc_Lbl?.text = task["desc"] as? String
cell.date_Lbl.text = dateFormatter.stringFromDate(task["dueDate"] as NSDate)
cell.client_Lbl?.text = task["client"] as? String
var clientLabel = cell.client_Lbl?.text
println("client label: \(clientLabel)")
return cell
}
}
If the deal column is a pointer then includeKey("deal") will get that object and populate it's properties for you. There is no need to perform a fetch of any type on top of that.
You really should be using Optionals properly though:
if let deal = task["deal"] as? PFObject {
// deal column has data
if let client = deal["client"] as? String {
// client has data
cell.client_Lbl?.text = client
}
}
Alternatively you can replace the last if let with a line like this, which handles empty values and uses a default:
cell.client_Lbl?.text = (deal["client"] as? String) ?? ""
In your posted cellForRowAtIndexPath code you are trying to read client from the task instead of from the deal: task["client"] as? String.
I am trying to add an item to my array (which was declared as a var), using anything that might work (+=, append, insert), however I keep getting the error 'Immutable value of type 'AnyObject[]' only has mutating members named 'append''.
Here is where the error occurs:
func testSave(item : NSString, date : NSString){
itemsArray.append(item)
UPDATE: HERE IS THE FULL CODE:
import UIKit
class ToDoListTableViewController: UITableViewController, UITableViewDelegate, UITableViewDataSource, UIAlertViewDelegate {
var itemsArray = NSUserDefaults .standardUserDefaults().arrayForKey("items")
var dateArray = NSUserDefaults .standardUserDefaults().arrayForKey("dates")
override func viewDidLoad() {
super.viewDidLoad()
NSUserDefaults .standardUserDefaults().setObject("test", forKey: "items")
NSUserDefaults .standardUserDefaults().setObject("test", forKey: "dates")
self.itemsArray = NSUserDefaults .standardUserDefaults().arrayForKey("items")
self.dateArray = NSUserDefaults .standardUserDefaults().arrayForKey("dates")
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// #pragma mark - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView?) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
if itemsArray{
return itemsArray.count}
else{
return 0}
}
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell!{
//variable type is inferred
/*var cell = tableView.dequeueReusableCellWithIdentifier("Cell") as? UITableViewCell
if !cell {
cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "Cell")
}
//we know that cell is not empty now so we use ! to force unwrapping
cell!.textLabel.text = self.itemsArray[indexPath.row] as String
cell!.detailTextLabel.text = self.dateArray[indexPath.row] as String
*/
let cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier:"Cell")
if itemsArray{
println("Working")
cell.textLabel.text = itemsArray[indexPath.row] as String
cell.detailTextLabel.text = dateArray[indexPath.row] as String
}
return cell
}
#IBAction func addItem(sender : UIBarButtonItem) {
var alert = UIAlertController(title: "Alert", message: "Message", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: {(action: UIAlertAction!) in
var stringText = alert.textFields[0].text
var dateText = alert.textFields[0].text
self .testSave(stringText, date: dateText)
}))
alert.addTextFieldWithConfigurationHandler(nil)
self.presentViewController(alert, animated: true, completion: nil)
}
func testSave(item : NSString, date : NSString){
itemsArray.append(item)
/* NSUserDefaults .standardUserDefaults().setObject(item, forKey: "items")
/* NSUserDefaults .standardUserDefaults().setObject(stringText, forKey: "items")
NSUserDefaults .standardUserDefaults().setObject(dateText, forKey: "dates")
NSUserDefaults.standardUserDefaults().synchronize()
*/
self.dateArray = NSUserDefaults .standardUserDefaults().arrayForKey("dates")
self.tableView .reloadData() */
}
func alertviewClick(){
}
}
This works fine:
var itemsArray = ["one", "two"]
func testSave(item : NSString, date : NSString){
itemsArray.append(item)
}
testSave("three", "today")
itemsArray
Problem is here:
var itemsArray = NSUserDefaults.standardUserDefaults().arrayForKey("items")
See the Apple's document:
func arrayForKey(_ defaultName: String!) -> AnyObject[]!
The returned array and its contents are immutable, even if the
values you originally set were mutable.
But if you are 100% sure that "items" is not an empty NSArray, then you can downcast it to Array then .append() will work:
var itemsArray = NSUserDefaults.standardUserDefaults().arrayForKey("items") as Array
If the return object from .arrayForKey() is nil or can't be cast to Array (e.g. it's objects can't be casted into Swift type), error will rise as it can't unwrap the optional.
Put this code in the sandbox and you will see that the value "hello" is appended properly. Figuring out what is different between my code and yours will be a great learning experience for you:
class ToDoListTableViewController: UITableViewController /*...*/ {
var itemsArray: Array<AnyObject>!
override func viewDidLoad() {
super.viewDidLoad()
if let savedItems = NSUserDefaults.standardUserDefaults().arrayForKey("items") {
itemsArray = savedItems
}
else {
itemsArray = []
}
}
func testSave(item : NSString, date : NSString) {
itemsArray.append(item)
}
}
let list = ToDoListTableViewController()
list.viewDidLoad()
list.testSave("hello", date: "")
list.itemsArray
It is not that your code is syntactically wrong, it is just that the method "arrayForKey" always gives back an immutable array which you can not modify.
You are trying to append, which modifies the length, hence it is not allowed.
You can verify this in the documentation here is an extract for the return value:
arrayForKey: Returns the array associated with the specified key.
...
Return
Value The array associated with the specified key, or nil if the key
does not exist or its value is not an NSArray object.
Special Considerations The returned array and its contents are
immutable, even if the values you originally set were mutable.
If you are doing Mix and Match (gradually migrating from objc to swift)
Then use
nsMutableArray.addObject(nsMutableDictionary)
The replacement of append you are looking for is addObject
Hope this helps someone in future.
Even if you declare something as var it still needs a type to be anything other than AnyObject. Var I believe is just a keyword declaring that that the following identifier (the variable name) is to be inferred by it's assignment
Since you want a Mutable Array try this
var itemsArray: NSMutableArray
or
var itemsArray = NSMutableArray()
Edit: Don't know why I keep getting thumbs down. Now that that the question's been updated it seems that I was right. When you called
NSUserDefaults.standardUserDefaults().arrayForKey("items")
The return type is NSArray. The type inferred for the variable itemsArray is therefore NSArray, which is not mutable. Therefore wrap it in a mutable array
var itemsArray: NSMutableArray = NSMutableArray(array:NSUserDefaults.standardUserDefaults().arrayForKey("items"))