I have an array of NSURL array, and I am able to use functions removeAtIndex and insert. I know the fromIndexPath and toIndexPath and this method helps me to accomplish the same for [[NSURL]] using this Delegate method (check var data below):
func moveDataItem(fromIndexPath : NSIndexPath, toIndexPath: NSIndexPath) {
let name = self.data[fromIndexPath.section][fromIndexPath.item]
self.data[fromIndexPath.section].removeAtIndex(fromIndexPath.item)
self.data[toIndexPath.section].insert(name, atIndex: toIndexPath.item)
// do same for UIImage array
}
However, I have an array of UIImage with 3 empty elements with running.
var newImages = [UIImage?]()
viewDidLoad() {
newImages.append(nil)
newImages.append(nil)
newImages.append(nil)
}
My question is how can I use the newImages array inside moveDataItem(), as well as the data and be able to run that lines for rearranging the order for UIImage array.
I tried these but unfortunately I couldn't make them work..
self.newImages[fromIndexPath.section].removeAtIndex(fromIndexPath.item)
// and
self.newImages[fromIndexPath.row].removeAtIndex(fromIndexPath.item)
For clarification, the data array looks like this
lazy var data : [[NSURL]] = {
var array = [[NSURL]]()
let images = self.imageURLsArray
if array.count == 0 {
var index = 0
var section = 0
for image in images {
if array.count <= section {
array.append([NSURL]())
}
array[section].append(image)
index += 1
}
}
return array
}()
This should work for rearranging any 2d array:
func move<T>(fromIndexPath : NSIndexPath, toIndexPath: NSIndexPath, items:[[T]]) -> [[T]] {
var newData = items
if newData.count > 1 {
let thing = newData[fromIndexPath.section][fromIndexPath.item]
newData[fromIndexPath.section].removeAtIndex(fromIndexPath.item)
newData[toIndexPath.section].insert(thing, atIndex: toIndexPath.item)
}
return newData
}
example usage:
var things = [["hi", "there"], ["guys", "gals"]]
// "[["hi", "there"], ["guys", "gals"]]\n"
print(things)
things = move(NSIndexPath(forRow: 0, inSection: 0), toIndexPath: NSIndexPath(forRow:1, inSection: 0), items: things)
// "[["there", "hi"], ["guys", "gals"]]\n"
print(things)
And this will work with a normal array:
func move<T>(fromIndex : Int, toIndex: Int, items:[T]) -> [T] {
var newData = items
if newData.count > 1 {
let thing = newData[fromIndex]
newData.removeAtIndex(fromIndex)
newData.insert(thing, atIndex: toIndex)
}
return newData
}
Related
I am currently using 3 separate arrays to populate a sectioned UITableView and I'd like to use just a single array for filtering purposes but still maintain the sections within my table.
The data is fed by a single JSON string which I then iterate through to create a dictionary of a single 'Event' and then based on the value of 'Event Status', it is added to one of three arrays (open, closed and deferred). These arrays are then uses to populate each section of a UITableView.
let myDictionary: [String: String] = [
"EventID" : item["EventID"] as! String,
"EventTitle" : item["EventTitle"] as! String,
"EventSummary" : item["EventSummary"] as! String,
"EventStatus" : item["Status"] as! String
]
if (item["Status"] as! String == "Open") {
self.openEvents.append(myDictionary as AnyObject)
}
if (item["Status"] as! String == "Closed") {
self.closedEvents.append(myDictionary as AnyObject)
}
if (item["Status"] as! String == "") {
self.deferredEvents.append(myDictionary as AnyObject)
}
Then in cellForRowAt:
var tmpDict: [String: String] = ["":""]
if sectionHeaders[indexPath.section] == "Open" {
tmpDict = openEvents[indexPath.row] as! [String : String]
}
if sectionHeaders[indexPath.section] == "Closed" {
tmpDict = closedEvents[indexPath.row] as! [String : String]
}
if sectionHeaders[indexPath.section] == "Deferred" {
tmpDict = deferredEvents[indexPath.row] as! [String : String]
}
Problem is that in order to allow filtering of this data, I'd have to run the filter against three different arrays which means three different 'filtered data' arrays. Which is far from ideal.
What I'd like to achieve is to have a single array of structured data (as opposed to dictionaries) that the user can filter through and the result returned to a single 'filtered data' array. I am doing this elsewhere with good effect, however, that is for a single list, not sectioned.
I have a struct setup and adding items to it as follows:
let data = EventItem(EventID: item["EventID"] as! String, EventTitle: item["EventTitle"] as! String, EventSummary: item["EventSummary"] as! String, EventStatus: item["Status"] as! String)
self.eventList.append(data)
and filtering using:
filteredData = eventList.filter() {
($0.EventTitle.lowercased() as NSString).contains(searchText.lowercased()) || ($0.EventSummary.lowercased() as NSString).contains(searchText.lowercased())
}
eventsTable.reloadData()
Is it possible to split this single array over different sections in a TableView? if so, how?
I would suggest giving a little more responsibility to your EventItem struct to help with the data source response. The you can then use a two dimensional array to manage both the section and row contents.
For example:
struct EventItem
{
var EventID = ""
var EventTitle = ""
var EventSummary = ""
var EventStatus = ""
func matches(_ searchText:String) -> Bool
{
return EventTitle.lowercased().contains(searchText.lowercased())
|| EventSummary.lowercased().contains(searchText.lowercased())
}
var section:Int
{
return EventStatus == "Opened" ? 0
: EventStatus == "Closed" ? 1
: 2
}
}
// you can maintain an "master" array of events (or build them from the dictionary as needed)
// I' using an array to keep the example simple
var eventList:[EventItem] = []
// This 2D array in your controller will serve as the store for your table view data source
var sectionData:[[EventItem]] = []
// applying filter is one line once you've added utility functions to your struct
sectionData = Array(0..<3).map{ section in return eventList.filter{$0.section == section && $0.matches(searchText)} }
// responding to tableview data source protocol only requires one line
// (which will not change even if you break down your data into more sections)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{ return sectionData[section].count }
// getting to the data to populate the table view cell will be quite straightforward
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let rowData = sectionData[indexPath.section][indexPath.row]
// setup cell ...
}
You can just use an extension of the approach you already have for filteredData
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
switch section {
case 0:
let sortedList = newItems.sorted { $0.eventStatus == "Open"}
return sortedList.count
case 1:
let sortedList = newItems.sorted { $0.eventStatus == "Closed"}
return sortedList.count
case 2:
let sortedList = newItems.sorted { $0.eventStatus == ""}
return sortedList.count
default:
return 0 // shouldn't happen
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var tmpDict: [String: String] = ["":""]
switch section {
case 0:
tmpDict = newItems.sorted { $0.eventStatus == "Open"}
case 1:
tmpDict = newItems.sorted { $0.eventStatus == "Closed"}
case 2:
tmpDict = newItems.sorted { $0.eventStatus == ""}
default:
return 0 // shouldn't happen
// setup cell
}
I have an application that loads list of questions from JSON data and shows them on TableView.
Everything is working fine most of the time but it seems to be that I am doing something wrong and that is why - app crashes.
It happens rarely so it is hard to detect but I am sure that there must a problem with the logic.
So I have model class for question and array for question items :
class questionItem {
var id = 0
var title : String = ""
var question : String = ""
}
var questions = [questionItem]()
Inside my ViewController I have IBOutlet for TableView and I placed data loading inside viewDidLoad
class QuestionsListVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var questionsTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
get_questions()
}
func get_questions()
{
let request = NSMutableURLRequest(URL:myURL!)
request.HTTPMethod = "POST"
let postString = ""
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{
data, response, error in
if error != nil {
print("error=\(error)")
return
}
//clearing array for new items
questions.removeAll(keepCapacity: false)
dispatch_async(dispatch_get_main_queue(),{
var json = JSON(data: data!)
if let items = json["questions"].array {
for item in items {
let question = questionItem()
question.id = item["id"].int!
question.title = item["title"].string!;
question.question = item["question"].string!
questions.append(question)
}
}
self.questionsTableView.reloadData()
});
}
task.resume()
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return questions.count
}
Error is shown inside cellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : QuestionsListCell = self.questionsTableView.dequeueReusableCellWithIdentifier("QuestionsListCell") as! QuestionsListCell
//error happens here - Index out of range
print(questions[indexPath.row].title)
It happens one time in six cases and there is no errors in other 5 of 6 tests - but I don't understand why.
This points to a problem with the
numberOfSectionsInTableView
and/or
numberOfRowsInSection
Can you post your current implementation of these?
If you only displaying one continuous list, the numberOfSectionsInTableView should always return 1, and you need to check numberOfRowsInSection is accurately returning the number of items in the datasource.
Edit:
Can you try clearing the existing datasource on the main thread immediately before updating with the new items as in the code below:
dispatch_async(dispatch_get_main_queue(),{
questions.removeAll(keepCapacity: false)
var json = JSON(data: data!)
if let items = json["questions"].array {
for item in items {
let question = questionItem()
question.id = item["id"].int!
question.title = item["title"].string!;
question.question = item["question"].string!
questions.append(question)
}
}
self.questionsTableView.reloadData()
});
The call to questions.removeAll in your code makes the following sequence of events possible:
numberOfRowsInSection is called before questions.removeAll, returning the old non-zero capacity
questions.removeAll clears questions
cellForRowAtIndexPath is called before questions are re-populated, causing index out of range exception
One way to fix is is relatively straightforward: make a newQuestions array, populate it in get_questions, and swap it in when numberOfRowsInSection is called:
// Add this to your class
var newQuestions : [questionItem]
// Change get_questions:
dispatch_async(dispatch_get_main_queue(), {
var json = JSON(data: data!)
if let items = json["questions"].array {
var tmpQuestions = [questionItem]()
for item in items {
let question = questionItem()
question.id = item["id"].int!
question.title = item["title"].string!;
question.question = item["question"].string!
tmpQuestions.append(question)
}
newQuestions = tmpQuestions
self.questionsTableView.reloadData()
}
});
// Change numberOfRowsInSection
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
if newQuestions != nil {
questions = newQuestions
newQuestions = nil
}
return questions.count
}
Note how get_questions does not populate newQuestions directly. Instead, it builds tmpQuestions, and sets it to newQuestions only when it is fully built.
Try with below code,
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell : QuestionsListCell = self.questionsTableView.dequeueReusableCellWithIdentifier("QuestionsListCell") as! QuestionsListCell
if let objModel : questionItem = questions[indexPath.row] as? questionItem
{
print(objModel.title)
}
}
Change your cellForRow with this method. Hope this helps you.
Just to avoid crash, I would have added following three safe checks.
1. Check Array count before clearing out.
if questions.count > 0
{
questions.removeAll()
}
2. Check array count before table Reload
if questions.count > 0
{
self.questionsTableView.reloadData()
}
3. In cellForRowAtIndex method Check for value in array of object, before putting on Cell
if let objModel : questionItem = questions[indexPath.row] as? questionItem
{
print(" title is \(objModel.title)")
}
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()
}
}
I have a UICollectionView in which the user can select data from cells to add to an array. I am attempting to highlight the selected cells when tapped, and un-highlight them when tapped again. In the same bit of code that highlights and un-highlights, I would like to add/remove the data from the array. I have no problem adding the data to the array, but I can't figure out how to remove it when un-highlighted.
Code here:
var removeFromList = [AnyObject]()
func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
var cell = self.collectionView2.cellForItemAtIndexPath(indexPath)
if cell!.tag == 0 {
cell!.layer.borderWidth = 2.0
cell!.layer.borderColor = UIColor.blueColor().CGColor
removeFromList.append(objectIds[indexPath.row])
cell!.tag = 1
} else {
cell!.layer.borderWidth = 0.0
cell!.tag = 0
removeFromList.//WHAT CAN I PUT HERE?
}
return true
}
Use removeAtIndex(index: Int) method to remove an item
var removeFromList = [NSString]()
if let index = find(removeFromList, objectIds[indexPath.row] as! NSString) {
removeFromList.removeAtIndex(index)
}
Use Dictionary for this:
var removeFromList = [NSIndexPath:AnyObject]()
if cell!.tag == 0 {
cell!.layer.borderWidth = 2.0
cell!.layer.borderColor = UIColor.blueColor().CGColor
removeFromList[indexPath] = objectIds[indexPath.row]
cell!.tag = 1
} else {
cell!.layer.borderWidth = 0.0
cell!.tag = 0
removeFromList.removeValueForKey(indexPath)
}
Because the array is being used as a list, you need to find the index before you can remove the element. Use Swift's find method:
removeFromList.removeAtIndex(find(removeFromList, objectIds[indexPath.row])!)
The problem with this method is that find does not work with AnyObject. Looping through the array and comparing each term won't work either, because AnyObject is not comparable. You need to change removeFromList and objectIds to a more specific class.
To make this code "prettier" and prevent your app from crashing when it tries to remove something not in the array, you should use an extension of the array class. Unfortunately, you'll have to make a new function for each data type.
func removeObjMyClass(inout arr: [MyClass], obj: MyClass) {
if let index = find(arr, obj) {
arr.removeAtIndex(index)
}
}
removeObjMyClass(&removeFromList, objectIds[indexPath.row])