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"))
Related
Attached at very bottom of this question is my inventory controller file. My problem is I'm getting duplicate results in all the sections. I narrowed down the reason to
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
My code in that function does not account for how many rows there are in each section. As such I'm just printing out the same duplicate results every section.
The actual question is listed after the images below...
Refer to images below:
I also have the ability to change the index from my settings menu so it can index by numbers, like 0-9. Refer to image below:
That said, I currently load the data from Core Data. Attached is reference image of the entities I use and there relationships.
The Question:
My question is, how can I get the results from coreData to be sorted into the A,B,C type sections or 1,2,3 sections so that navigating the table will be simple.
My hunch is the line that says let inventoryRecords = try moc.executeFetchRequest(inventoryFetchRequest) as? [Inventory] needs a sort descriptor to sort based on how I like, but how I then take the data and put into the correct array structure to split into the sections I need...I have no idea.
globals.swift
import Foundation
import CoreData
//Array of Inventory & Store Core Data Managed Objects
var g_inventoryItems = [Inventory]()
var g_storeList = [Store]()
var g_appSettings = [AppSettings]()
var g_demoMode = false
InventoryController.swift
import UIKit
import CoreData
class InventoryController: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var inventoryTable: UITableView!
var numberIndex = ["0","1","2","3","4","5","6","7","8","9"]
var letterIndex = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]
var moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext //convinience variable to access managed object context
// Start DEMO Related Code
func createInventoryDummyData(number: Int) -> Inventory{
let tempInventory = NSEntityDescription.insertNewObjectForEntityForName("Inventory", inManagedObjectContext: moc) as! Inventory
tempInventory.name = "Test Item # \(number)"
tempInventory.barcode = "00000000\(number)"
tempInventory.currentCount = 0
tempInventory.id = number
tempInventory.imageLargePath = "http://website.tech//uploads/inventory/7d3fe5bfad38a3545e80c73c1453e380.png"
tempInventory.imageSmallPath = "http://website.tech//uploads/inventory/7d3fe5bfad38a3545e80c73c1453e380.png"
tempInventory.addCount = 0
tempInventory.negativeCount = 0
tempInventory.newCount = 0
tempInventory.store_id = 1 //belongs to same store for now
//Select a random store to belong to 0 through 2 since array starts at 0
let aRandomInt = Int.random(0...2)
tempInventory.setValue(g_storeList[aRandomInt], forKey: "store") //assigns inventory to one of the stores we created.
return tempInventory
}
func createStoreDummyData(number:Int) -> Store{
let tempStore = NSEntityDescription.insertNewObjectForEntityForName("Store", inManagedObjectContext: moc) as! Store
tempStore.address = "100\(number) lane, Miami, FL"
tempStore.email = "store\(number)#centraltire.com"
tempStore.id = number
tempStore.lat = 1.00000007
tempStore.lng = 1.00000008
tempStore.name = "Store #\(number)"
tempStore.phone = "123000000\(number)"
return tempStore
}
// End DEMO Related Code
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
print("InventoryController -> ViewDidLoad -> ... starting inits")
//First check to see if we have entities already. There MUST be entities, even if its DEMO data.
let inventoryFetchRequest = NSFetchRequest(entityName: "Inventory")
let storeFetchRequest = NSFetchRequest(entityName: "Store")
do {
let storeRecords = try moc.executeFetchRequest(storeFetchRequest) as? [Store]
if(storeRecords!.count<=0){
g_demoMode = true
print("No store entities found. Demo mode = True. Creating default store entities...")
var store : Store //define variable as Store type
for index in 1...3 {
store = createStoreDummyData(index)
g_storeList.append(store)
}
}
let inventoryRecords = try moc.executeFetchRequest(inventoryFetchRequest) as? [Inventory]
if(inventoryRecords!.count<=0){
g_demoMode = true
print("No entities found for inventory. Demo mode = True. Creating default entities...")
var entity : Inventory //define variable as Inventory type
for index in 1...20 {
entity = createInventoryDummyData(index)
g_inventoryItems.append(entity)
}
print("finished creating entities")
}
}catch{
fatalError("bad things happened \(error)")
}
print("InventoryController -> viewDidload -> ... finished inits!")
}
override func viewWillAppear(animated: Bool) {
print("view appearing")
//When the view appears its important that the table is updated.
//Look at the selected Store & Use the LIST of Inventory Under it.
inventoryTable.reloadData()//this is important to update correctly for changes that might have been made
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
print("inventoryItemControllerPrepareForSegueCalled")
if segue.identifier == "inventoryInfoSegue" {
let vc = segue.destinationViewController as! InventoryItemController
if let cell = sender as? InventoryTableViewCell{
vc.inventoryItem = cell.inventoryItem! //sets the inventory item accordingly, passing its reference along.
}else{
print("sender was something else")
}
}
}
func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
//This scrolls to correct section based on title of what was pressed.
return letterIndex.indexOf(title)!
}
func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
//Use correct index on the side based on settings desired.
if(g_appSettings[0].indextype=="letter"){
return letterIndex
}else{
return numberIndex
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//TODO: Need to figure out how many rows for ...column A,B,C or 1,2,3 based on indexType using~
//To do this we need to organize the inventory results into a section'ed array.
if(g_appSettings[0].selectedStore != nil){
return (g_appSettings[0].selectedStore?.inventories!.count)! //number of rows is equal to the selected stores inventories count
}else{
return g_inventoryItems.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("InventoryTableCell", forIndexPath: indexPath) as! InventoryTableViewCell
if(g_appSettings[0].selectedStore != nil){
//Get the current Inventory Item & Set to the cell for reference.
cell.inventoryItem = g_appSettings[0].selectedStore?.inventories?.allObjects[indexPath.row] as! Inventory
}else{
//This only happens for DEMO mode or first time.
cell.inventoryItem = g_inventoryItems[indexPath.row]//create reference to particular inventoryItem this represents.
}
cell.drawCell() //uses passed inventoryItem to draw it's self accordingly.
return cell
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if(g_appSettings[0].indextype == "letter"){
return letterIndex[section]
}else{
return numberIndex[section]
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if(g_appSettings[0].selectedStore != nil){
if(g_appSettings[0].indextype=="letter"){
return letterIndex.count
}else{
return numberIndex.count
}
}else{
return 1//only one section for DEMO mode.
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//dispatch_async(dispatch_get_main_queue()) {
//[unowned self] in
print("didSelectRowAtIndexPath")//does not recognize first time pressed item for some reason?
let selectedCell = self.tableView(tableView, cellForRowAtIndexPath: indexPath) as? InventoryTableViewCell
self.performSegueWithIdentifier("inventoryInfoSegue", sender: selectedCell)
//}
}
#IBAction func BarcodeScanBarItemAction(sender: UIBarButtonItem) {
print("test of baritem")
}
#IBAction func SetStoreBarItemAction(sender: UIBarButtonItem) {
print("change store interface")
}
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
print("text is changing")
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
print("ended by cancel")
searchBar.text = ""
searchBar.resignFirstResponder()
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
print("ended by search")
searchBar.resignFirstResponder()
}
func searchBarTextDidEndEditing(searchBar: UISearchBar) {
print("ended by end editing")
searchBar.resignFirstResponder()
}
#IBAction func unwindBackToInventory(segue: UIStoryboardSegue) {
print("unwind attempt")
let barcode = (segue.sourceViewController as? ScannerViewController)?.barcode
searchBar.text = barcode!
print("barcode="+barcode!)
inventoryTable.reloadData()//reload the data to be safe.
}
}
//Extention to INT to create random number in range.
extension Int
{
static func random(range: Range<Int> ) -> Int
{
var offset = 0
if range.startIndex < 0 // allow negative ranges
{
offset = abs(range.startIndex)
}
let mini = UInt32(range.startIndex + offset)
let maxi = UInt32(range.endIndex + offset)
return Int(mini + arc4random_uniform(maxi - mini)) - offset
}
}
Update:: **
So I was looking around and found this article (I implemented it).
https://www.andrewcbancroft.com/2015/03/05/displaying-data-with-nsfetchedresultscontroller-and-swift/
I'm really close now to figuring it out. Only problem is I can get it to auto create the sections, but only on another field, like for example store.name, I can't get it to section it into A,B,C sections or 1,2,3.
This is my code for the fetchedResultsController using the methods described in that article.
//Create fetchedResultsController to handle Inventory Core Data Operations
lazy var fetchedResultsController: NSFetchedResultsController = {
let inventoryFetchRequest = NSFetchRequest(entityName: "Inventory")
let primarySortDescriptor = NSSortDescriptor(key: "name", ascending: true)
let secondarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
inventoryFetchRequest.sortDescriptors = [primarySortDescriptor, secondarySortDescriptor]
let frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "store.name",
cacheName: nil)
frc.delegate = self
return frc
}()
Question is what to put for sectionNameKeyPath: now that will make it section it on A B C and I got this !
Found a stackoverflow post very similar to my issue, but need swift answer.
A-Z Index from NSFetchedResultsController with individual section headers within each letter?
Here is another similar article but all objective-c answers.
NSFetchedResultsController with sections created by first letter of a string
Update::
Found another article I think with my exact issue (How to have a A-Z index with a NSFetchedResultsController)
Ok I figured it out, phew was this confusing and took a lot of research.
Okay, so first thing you have to do is create a transient property on the data model. In my case I called it lettersection. To do this in the entity just create a new attribute and call it lettersection and in graph mode if you select it (double click it), you will see option in inspector for 'transient'. This means it won't be saved to the database and is used more for internal reasons.
You then need to manually set up the variable in the extension area of the model definition. Here is how it looks for me.
import Foundation
import CoreData
extension Inventory {
#NSManaged var addCount: NSNumber?
#NSManaged var barcode: String?
#NSManaged var currentCount: NSNumber?
#NSManaged var id: NSNumber?
#NSManaged var imageLargePath: String?
#NSManaged var imageSmallPath: String?
#NSManaged var name: String?
#NSManaged var negativeCount: NSNumber?
#NSManaged var newCount: NSNumber?
#NSManaged var store_id: NSNumber?
#NSManaged var store: Store?
var lettersection: String? {
let characters = name!.characters.map { String($0) }
return characters[0].uppercaseString
}
}
Once you do this, you simply call this new 'lettersection' with the fetchedResultsController like so...
let frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "lettersection",
cacheName: nil)
and everything will work! It sorts by the name of my inventory items, but groups them by the first letters, for a nice A,B,C type list!
"My question is, how can I get the results from coreData to be sorted into the A,B,C type sections or 1,2,3 sections so that navigating the table will be simple."
Using "Store" as your entity and property "name" to be what you want to sort the records by.
override func viewDidLoad() { super.viewDidLoad()
let fetchRequest = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Store", inManagedObjectContext: managedObjectContext)
fetchRequest.entity = entity
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
do {
let foundObjects = try managedObjectContext.executeFetchRequest(fetchRequest)
locations = foundObjects as! [Location]
} catch {
fatalCoreDataError(error) }
}
You are going to use this function to set the number of sections:
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return letterindex.count // if that is how you want to construct sections
}
I learned this from the Ray Wenderlich e-book "iOS Apprentice". From Lesson 3 - MyLocations. Highly recommend this and their e-book book on CoreData.
I'm trying to figure out how the array works with Swift. I understand that you use let to create an immutable array and var to create a mutable array. Yet, Swift's array is not quite the same as Objective's NSArray and NSMutableArray. I get that.
In the example that follows, I create a mutable array with one element. Actually, I want to start with no element. But if I do, I won't be able to add a new string to it. If I start with one element, then I won't be able to add a new string to it after the original element is removed. So what am I doing wrong?
Thank you
EDIT
class ViewController: UIViewController {
let textCellIdentifier = "TextCell"
var myArray:[String] = ["GGG"] // or var myArray:[String] = []
#IBAction func add1Tapped(sender:AnyObject) {
let index = tableView1.indexPathForSelectedRow;
let selectedRow = index()?.row
if selectedRow < 0 {
return
} else {
let txt = nameField1.text
myArray.append(txt)
tableView1.reloadData()
}
}
func tableView(tableView: UITableView,numberOfRowsInSection section:Int) -> Int {
return myArray.count
}
func tableView(tableView:UITableView,cellForRowAtIndexPath indexPath:NSIndexPath) -> UITableViewCell {
let cell:UITableViewCell=UITableViewCell(style: UITableViewCellStyle.Subtitle,reuseIdentifier:textCellIdentifier)
let row = indexPath.row
cell.textLabel!.text = myArray[row]
return cell
}
}
all your needs should work as expected:
// create an empty array of strings
var myArray: [String] = []
// add two elements
myArray.append("the first element")
myArray.append("the second element")
// remove both elements
myArray.removeAll()
// add another element
myArray.append("the third but now first element")
myArray.count
EDIT
try and change your add method like this:
#IBAction func add1Tapped(sender:AnyObject) {
if let _ = tableView1.indexPathForSelectedRow, txt = nameField1.text {
print("will append \(txt) to myArray")
myArray.append(txt)
tableView1.reloadData()
}
}
This is part of another question, but since that original question got an answer, it will be more clear in the future if I face this problem here, single way.
EDIT: added an exception breakpoint, at runTime appeared this:
I got this message: Cast from 'String' to unrelated type 'PFObject' always fails
here the warning screenshot
console also says "Could not cast value of type 'PFObject' (0x10980e1e8) to 'NSString' (0x10b8e78e0).
(lldb) "
In my opinion I'm failing to access data the proper way, but don't know where
thanks in advance
//
// TimelineTVC2.swift
// lug15ParseChat
//
//
import UIKit
import Parse
class TimelineTVC2: UITableViewController {
var timelineData : [String] = []
func loadData() {
timelineData.removeAll(keepCapacity: true) //erase previus contents
var findTimeLineDataQuery = PFQuery(className: "Messages")
findTimeLineDataQuery.findObjectsInBackgroundWithBlock({
(objects : [AnyObject]?, error : NSError?) -> Void in
if error == nil {
for singleObject in objects! {
self.timelineData.append(singleObject as! String)
}
let reversedArray : Array = self.timelineData.reverse() //remeber always!
self.timelineData = reversedArray as Array
self.tableView.reloadData()
}
})
}
// MARK: Parse
override func viewDidAppear(animated: Bool) {
self.loadData()
if PFUser.currentUser() == nil {
var loginAlertController = UIAlertController(title: "Sign up / login", message: "please sign up or login", preferredStyle: UIAlertControllerStyle.Alert)
loginAlertController.addTextFieldWithConfigurationHandler({
textfField in
textfField.placeholder = "Your username"
})
loginAlertController.addTextFieldWithConfigurationHandler({
textfField in
textfField.placeholder = "Your password"
textfField.secureTextEntry = true
})
// MARK: login action in the array
loginAlertController.addAction(UIAlertAction(title: "Login Action", style: UIAlertActionStyle.Default, handler: {
alertAction in
let textFields : NSArray = loginAlertController.textFields!
let usernameTextField : UITextField = textFields[0] as! UITextField
let passwordTextField : UITextField = textFields[1] as! UITextField
//MARK: Parse login problem - 15:39
PFUser.logInWithUsernameInBackground(usernameTextField.text, password: passwordTextField.text){
(user: PFUser?, error: NSError?) -> Void in
if user != nil {
println("login success!")
} else {
println("login failed!")
}
}
}))
// MARK: sign up action in the array
loginAlertController.addAction(UIAlertAction(title: "Sign up", style: UIAlertActionStyle.Default, handler: {
alertAction in
let textFields : NSArray = loginAlertController.textFields!
let usernameTextField : UITextField = textFields[0] as! UITextField
let passwordTextField : UITextField = textFields[1] as! UITextField
var messageSender = PFUser() //16:42
messageSender.username = usernameTextField.text
messageSender.password = passwordTextField.text
messageSender.signUpInBackgroundWithBlock({
(success: Bool, error: NSError?) -> Void in
if error == nil {
println("sign up successful")
} else {
// let errorString = error!.userInfo["error"] as! String
let errorString = error!.localizedDescription
println(errorString)
}
})
}))
self.presentViewController(loginAlertController, animated: true, completion: nil)
}
}
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 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return timelineData.count
}
//MARK: WARNING! Cast from 'String' to unrelated type 'PFObject' always fails
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : SweetTableViewCell = tableView.dequeueReusableCellWithIdentifier("cellReuseID", forIndexPath: indexPath) as! SweetTableViewCell
var textMessage : PFObject = self.timelineData[indexPath.row] as! PFObject
cell.sweetTextView.text = textMessage["content"] as! String
return cell
}
}
What you're trying to do is convert a whole PFObject to a String, which as the compiler points out will never work. What you need to do instead is access the fields inside the PFObject that contain the strings you're trying to get. So for instance, on the User class, the email field is a string. In order to get that from the PFUser (which is a subclass of PFObject), you need to access the field, and then assign that to the string. It would look like this (pretending that singleObject is a PFUser because I don't know your fields):
for singleObject in objects! {
if let stringData = singleObject["email"] as? String {
timelineData.append(stringData)
}
}
Another option, only the syntax is different.
for singleObject in objects! {
if let stringData = singleObject.objectForKey("email") as? String {
timelineData.append(stringData)
}
}
Both will work and give you the desired result.
Also, as an additional point, instead of reversing the array after you've put the objects in it, you can simply call findTimeLineDataQuery.orderByDescending("createdAt"). This will save you some overhead since it will be done before the data is returned to you, rather than it being done on the device.
EDIT: To fix cellForRowAtindexPath:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : SweetTableViewCell = tableView.dequeueReusableCellWithIdentifier("cellReuseID", forIndexPath: indexPath) as! SweetTableViewCell
cell.sweetTextView?.text = self.timelineData[indexPath.row]
return cell
}
You were trying to convert the strings that you just took out of your PFObject and turn them back into PFObjects. Since you have the text you want to display now, in your cellForRowAtindexPath: all you have to do is access the value stored at that location in your timelineData array.
What you really want to do here is cast a property of PFObject - not the object itself. In the example below, someProperty is the field on the Messages object that you wish to access.
It should look something like this:
for singleObject in objects! {
var someProperty = singleObject["somePropertyName"] as String
self.timelineData.append(someProperty)
}
I am loading some data into NSUserDefaults on application startup and when the user views the according View loading the data out into a TableView. I'm having an issue with the data types though and always seem to get an error whichever way I try.
Where am I going wrong. I am skipping some code and only giving the important parts. I keep getting errors like "Can't assign Bool to AnyObject"
// startup function which loads the data
var categorys : [[String:Bool]] = [["All": true]]
for (index: String, category: JSON) in output {
categorys.append([category["name"].string!: true])
}
NSUserDefaults.standardUserDefaults().setObject(categorys, forKey: "categorys")
// view controller
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var categorys: [[String:Bool]] = []
func buildTableView() {
if let categorys = NSUserDefaults.standardUserDefaults().arrayForKey("categorys") {
for category in categorys {
self.categorys.append([category["name"] as! String: category["selected"] as! Bool])
}
// Was trying to just assign straight away but fails
// self.categorys = categorys
}
}
// then later on I want to be able to do the following
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
cell.textLabel?.text = self.categorys[indexPath.row]["name"] as? String
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if (self.categorys[indexPath.row]["selected"] as? Bool == true) {
self.categorys[indexPath.row]["selected"] = "false"
}
}
}
I should probably also mention i was trying a different datatype before which is more logical for my usage but also had similar issues.
["name": [category["name"].string!, "selected": true]]
I should also mention this is data for my tableview and I want to update the boolean value for if the cell is selected to not.
You said in the comment that you've got an error about "finding nil", but it's not really the same as an error about "Can't assign Bool to AnyObject"...
For your nil error, you have to replace those forced typecasts:
category["name"] as! String
with optional binding:
if let name = category["name"] as? String {
}
Now it will fail but not crash if a property is nil, and will properly append to the array of dictionaries:
let category = ["name":"me", "selected":true]
var categorys: [[String:Bool]] = []
if let name = category["name"] as? String, let selected = category["selected"] as? Bool {
categorys.append([name:selected])
} else {
// oops, a value was nil, handle the error
}
Same for your other operations, don't use forced casts:
if let myBool = self.categorys[indexPath.row]["selected"] where myBool == true {
self.categorys[indexPath.row]["selected"] = false
}
And since self.categorys[indexPath.row]["selected"] is a Bool type, don't assign a string like "false" but the actual false value.
In order to use Bool in NSUserDefaults, you must use setBool: forKey: :
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "key")
otherwise it's problematic and you have to do workarounds, like storing the Bool in NSNumber or String.
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 = []