Table view from array index out of range - ios

I'm using an array to read data from a database, Currently I have 8 items in the array. I am trying to make a table where I have a section header. Currently I have 4 sections and I have set that properly and it works. It also works running the first time but when I try to scroll back I get an index out of range. I am using myarray[myindex] to set the cell data for each item and that is not working.
It seems that I need to break up my data into 4 sections that contains only the data for each section to let the table view control it properly. The data can contain any number of sections.
Is there a better way to do this?
I have attached a pic to describe the problem.
Thanks
Adding code on request.
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
print("Returning Sections - > \(sections)")
return sections //seems to work
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
print("Return number of rows in section -> \(noRowsInSection[section])")
return noRowsInSection[section] // seems to work
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionHeader[section] // seems to work
}
override func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
// Format for section Headers
let header:UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView
header.textLabel!.textColor = UIColor.blueColor()
UIColor.blueColor()
header.textLabel!.font = UIFont.boldSystemFontOfSize(12)
header.textLabel!.frame = header.frame
header.textLabel!.textAlignment = NSTextAlignment.Right
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("OurCell", forIndexPath: indexPath) as! OurTableViewCell
print("myindex - > \(myindex) row -> \(indexPath.row)")
cell.OurCellLabel.text = MyHouses[myindex].getAddressDetails() // End configure houses.cell
//cell.OurCellLabel.text = MyHouses[indexPath.row].getAddressDetails() // End configure houses.cell
myindex++ // PROBLEM HERE - GOES OUT OF RANGE
return cell
}
Here I am getting data from the sqlite DB
func GetListOfHousesFromDB() {
let docsDir = dirPaths[0]
let databasePath = docsDir.stringByAppendingString("/newdb.db")
if fileMgr.fileExistsAtPath(databasePath as String) {
let houseDB = FMDatabase(path: databasePath as String)
if houseDB.open() {
var noRows: Int = 0
var sql = "select count(Address) as cnt from Houses" // Define Query
houseDB.executeStatements(sql) // Execute Query
let results:FMResultSet? = houseDB.executeQuery(sql,withArgumentsInArray: nil) //Get results from Query
if results?.next() == true {
let cnt = (results?.stringForColumn("cnt"))! // Retrieve number of rows from DB
noRows = Int(cnt)!
}
var i = 0
sql = "SELECT Address, Street, City, State, Zip from Houses ORDER BY State, City, Street, Address" // Define Query
houseDB.executeStatements(sql) // Execute Query
let results2:FMResultSet? = houseDB.executeQuery(sql,withArgumentsInArray: nil) // Get results from Query
while results2?.next() == true {
MyHouses.append(newhouse())
MyHouses[i].address = (results2?.stringForColumn("Address"))!
MyHouses[i].street = (results2?.stringForColumn("Street"))!
MyHouses[i].city = (results2?.stringForColumn("City"))!
MyHouses[i].state = (results2?.stringForColumn("State"))!
MyHouses[i].zip = (results2?.stringForColumn("Zip"))!
print("Address -> \(i) \(MyHouses[i].getAddressDetails())")
i++
}
}
houseDB.close()
}
}

Based on your other post, what you need is an updated House model and updated data structure for handling data for your table view.
House - Model class
struct House {
var address: String
var street: String
var city: String
var state: String
var zip: String
func getAddressDetails() -> String {
return "\(address) \(street) \(city) \(state) \(zip)"
}
func getCityState() -> String {
return "\(city) - \(state)"
}
}
Helper Class for loading data
class HouseDataHelper {
private static let _sharedInstance = HouseDataHelper()
var myHouses: Dictionary<String, [House]> = [:]
private init() {
loadHouseData()
}
static func sharedInstance() -> HouseDataHelper {
return _sharedInstance
}
private func loadHouseData() {
var houses = [House]()
//Populating your actual values here. GetListOfHousesFromDB()
//Loading dummy data for testing
var sectionHeader = ""
for i in 0...4 {
sectionHeader = "Header \(i)"
houses += [House(address: "Address1", street: "Street1", city: "City1", state: "State1", zip: "Zip1")]
houses += [House(address: "Address2", street: "Street2", city: "City2", state: "State2", zip: "Zip2")]
houses += [House(address: "Address3", street: "Street3", city: "City3", state: "State3", zip: "Zip3")]
houses += [House(address: "Address4", street: "Street4", city: "City4", state: "State4", zip: "Zip4")]
houses += [House(address: "Address5", street: "Street5", city: "City5", state: "State5", zip: "Zip5")]
myHouses.updateValue(houses, forKey: sectionHeader)
houses = []
}
}
}
Table View Controller
class TableViewController: UITableViewController {
var houses = HouseDataHelper.sharedInstance().myHouses
var sectionHeaders: [String] = []
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
sectionHeaders = Array(houses.keys.sort())
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return houses.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let rows = houses[sectionHeaders[section]] {
return rows.count
}
return 0
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionHeaders[section]
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//Populate cells based on "houses"
}
}

Related

Filtering TableView with array in array

I have 2 methods:
class Sign {
let code: String
let name: String
let description: String
let picture: String
init(code: String, name: String, description: String, picture: String) {
self.code = code
self.name = name
self.description = description
self.picture = picture
}
}
class Category {
let name: String
let sign: [Sign]
init(name: String, sign: [Sign]) {
self.name = name
self.sign = sign
}
}
Both are used to be in TableView - Category as section title and Sign as section row. I tried to implement searchBar above tableView, but when I start type keyword I see only Category names filtered. Do you have any idea how to figure out that?
Eg.
var categories: [Category] = [
Category(name: "X", sign: [Sign(code: "X-1", name: "***"),
Category(name: "Y", sign: [Sign(code: "Y-1", name: "Yyy"),
After typing "yy" || "y" in search bar I need my tableView shows only Sign which contains "yy".
My current TableView configuration:
override func numberOfSections(in tableView: UITableView) -> Int {
return filteredCategories.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredCategories[section].sign.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch section {
case 0:
return filteredCategories[0].name
case 1:
return filteredCategories[1].name
case 2:
return filteredCategories[2].name
case 3:
return filteredCategories[3].name
default:
return "Error"
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: PropertyKeys.categorieCell, for: indexPath) as! SignTableViewCell
let categorie = filteredCategories[indexPath.section]
let sign = categorie.sign[indexPath.row]
cell.signImagemin.image = UIImage(named: sign.picture)
cell.signCodeMin.text = sign.code
cell.signDescriptionMin.text = sign.name
return cell
}
My current searchBar func:
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredCategories = []
let text = searchText.lowercased()
if searchText == "" {
filteredCategories = categories
} else {
for categorie in categories {
if categorie.name.lowercased().contains(searchText.lowercased()) {
let signs = categorie.sign.filter() { $0.name.contains(searchText) }
let newCat = categorie
newCat.sign = signs
filteredCategories.append(newCat)
}
}
}
self.tableView.reloadData()
}
Thank you!
Eg.: In this tableView clean
Want to see only signs which contain "A1" and Cat A above as section name
result
You just control the categorie.name contains the giving search keyword.You must add also this categorie.sign.filter({$0.name.contains(searchText)}).count != 0 to your if condition. It will be like
if categorie.sign.filter({$0.name.contains(searchText)}).count != 0{
// codes here
}
Also you declared sign in Category class with let so
let newCat = categorie
newCat.sign = signs
Above code is illegal You need to change let with var in class
Category
var sign: [Sign]

Is there a selector that matches CNContactSortOrder.userDefault when using UILocalizedIndexedCollation to create section indexes in Swift?

I'm trying to build an app modeled off of iPhone's Contacts app. When I fetch records from the CNContactStore. I can specify CNContactSortOrder.userDefault.
let fetchRequest = CNContactFetchRequest(keysToFetch:
[CNContactFormatter.descriptorForRequiredKeys(for: .fullName)])
fetchRequest.sortOrder = CNContactSortOrder.userDefault
I would like to replicate this sort order when creating section indexes. I can create a selector for CNContact.familyName. I need something like .userDefault because it picks up other fields like nickname when familyName is nil and includes it in the sorted results correctly.
import UIKit
import Contacts
class SectionIndexesTableViewController: UITableViewController {
let collation = UILocalizedIndexedCollation.current()
var sections: [[CNContact]] = []
var sectionTitles: [String] = []
var contacts: [CNContact] = [] {
didSet {
sectionTitles.removeAll()
sections.removeAll()
// let selector: Selector = #selector(getter: CNContact.familyName)
let selector: Selector = #selector(getter: CNContact.comparator(forNameSortOrder: .userDefault))
var sectionsAll = Array(repeating: [], count: collation.sectionTitles.count)
let sortedContacts = collation.sortedArray(from: contacts, collationStringSelector: selector)
for contact in sortedContacts {
let sectionNumber = collation.section(for: contact, collationStringSelector: selector)
sectionsAll[sectionNumber].append(contact as! CNContact)
}
for index in 0 ..< sectionsAll.count {
if sectionsAll[index].count > 0 {
sectionTitles.append(collation.sectionTitles[index])
sections.append(sectionsAll[index] as! [CNContact])
}
}
self.tableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
let fetchRequest = CNContactFetchRequest(keysToFetch:
[CNContactFormatter.descriptorForRequiredKeys(for: .fullName)])
fetchRequest.sortOrder = CNContactSortOrder.userDefault
let store = CNContactStore()
do {
try store.enumerateContacts(with: fetchRequest, usingBlock: { (contact, stop) -> Void in
self.contacts.append(contact)
})
}
catch let error as NSError {
print(error.localizedDescription)
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TVCellSectionIndexes", for: indexPath)
let contact = sections[indexPath.section][indexPath.row]
let formatter = CNContactFormatter()
cell.textLabel?.text = formatter.string(from: contact)
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionTitles[section]
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return collation.sectionIndexTitles
}
override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
return collation.section(forSectionIndexTitle: index)
}
}
The compile error I receive is: Argument of '#selector' does not refer to an '#objc' method, property, or initializer
How do I expose .userDefault to #objc?
Apple's Contacts App with added records

Firebase Data is not loaded into tableview data model

The problem in the given code is, that the Firebase Data is not loaded into the Arrays before they get returned. Since I am new to swift I am really satisfied with my data model set up to load multiple sections into the tableview and don't want to change everything. The only problem is as I said, that the Firebase data collectors need some kind of a completion handler, but I don't really know how to apply them and I have no idea how to change the following code as well...
This is the class which defines an object:
class Court {
var AnzahlToreKoerbe: String?
var Breitengrad: String?
var Groesse: String?
var Hochgeladen: String?
var Laengengrad: String?
var Stadt: String?
var Stadtteil: String?
var Strasse: String?
var Untergrund: String?
var Upload_Zeitpunkt: Int?
var Platzart: String?
init(AnzahlToreKoerbeString: String, BreitengradString: String, GroesseString: String, HochgeladenString: String, LaengengradString: String, StadtString: String, StadtteilString: String, StrasseString: String, UntergrundString: String, UploadTime: Int, PlatzartString: String) {
AnzahlToreKoerbe = AnzahlToreKoerbeString
Breitengrad = BreitengradString
Groesse = GroesseString
Hochgeladen = HochgeladenString
Laengengrad = LaengengradString
Stadt = StadtString
Stadtteil = StadtteilString
Strasse = StrasseString
Untergrund = UntergrundString
Upload_Zeitpunkt = UploadTime
Platzart = PlatzartString
}
}
This is the class which collects the objects and load them into multiple arrays, which are then called with the getCOurts function in the tableViewController:
class Platzart
{
var Courts: [Court]
var name: String
init(name: String, Courttypes: [Court]) {
Courts = Courttypes
self.name = name
}
class func getCourts() -> [Platzart]
{
return [self.AllSoccer(), self.AllBasketball(), self.Test(), self.Test2()]
}
This is an example private class function which loads the data:
private class func AllSoccer() -> Platzart {
var allSoccer = [Court]()
let databaseref = FIRDatabase.database().reference()
databaseref.child("Court").child("Fußball").observe(.childAdded, with: { (snapshot) in
if let Courtdictionary = snapshot.value as? [String : Any] {
let city = Courtdictionary["Stadt"] as? String
let district = Courtdictionary["Stadtteil"] as? String
let street = Courtdictionary["Strasse"] as? String
let surface = Courtdictionary["Untergrund"] as? String
let latitude = Courtdictionary["Breitengrad"] as? String
let longitude = Courtdictionary["Laengengrad"] as? String
let size = Courtdictionary["Groesse"] as? String
let Userupload = Courtdictionary["Hochgeladen"] as? String
let timestamp = Courtdictionary["Upload_Zeitpunkt"] as? Int
let numberofHoops = Courtdictionary["AnzahlToreKoerbe"] as? String
let courttype = Courtdictionary["Platzart"] as? String
allSoccer.append(Court(AnzahlToreKoerbeString: numberofHoops!, BreitengradString: latitude!, GroesseString: size!, HochgeladenString: Userupload!, LaengengradString: longitude!, StadtString: city!, StadtteilString: district!, StrasseString: street!, UntergrundString: surface!, UploadTime: timestamp!, PlatzartString: courttype!))
print(allSoccer)
}
})
return Platzart(name: "Fußballplatz", Courttypes: allSoccer)
}
The data is then loaded into the tableview:
class CourtlistViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
lazy var Courtarrays: [Platzart] = {
return Platzart.getCourts()
}()
#IBOutlet weak var CourtlisttableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
CourtlisttableView.delegate = self
CourtlisttableView.dataSource = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
CourtlisttableView.reloadData()
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let Courttype = Courtarrays[section]
return Courttype.name
}
func numberOfSections(in tableView: UITableView) -> Int {
return Courtarrays.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Courtarrays[section].Courts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = CourtlisttableView.dequeueReusableCell(withIdentifier: "Court Cell", for: indexPath) as! CourtCell
let Platzarray = Courtarrays[indexPath.section]
let Sportplatz = Platzarray.Courts[indexPath.row]
cell.updateUI(Sportplatz)
return cell
}
}
This procedure works well with data which I append to an array manually. For the Firebase Sections (AllSoccer() and AllBasketball()) only the headers are loaded and displayed.

SWIFT: Dictionary not displaying in tableView

I'm attempting to display a dictionary in the following format in a tableView (storyboard shown below):
var guestList = ["Event 1": ["Guest 1", "Guest 2", "Guest 3"], "Event 2": ["Guest 1", "Guest 2"], "Event 3": ["Guest 1", "Guest 2"], "Event 4": ["Guest 1", "Guest 2"]]
The data gets added to the objectArray variable successfully, but for some reason, none of the guests are showing in the table (see screenshot below):
import UIKit
import Parse
class RSVPTableViewController: UITableViewController {
var guestList = [String : [String]]()
let eventList = ["Hindu", "Reception", "Sangeet", "Tibetan"]
struct Objects {
var sectionName : String!
var sectionObjects : [String]!
}
var objectArray = [Objects]()
override func viewDidLoad() {
super.viewDidLoad()
for event in eventList { guestList[event] = [String]() }
let query = PFQuery(className:"GuestList")
query.whereKey("Family", equalTo: "Family1")
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let family = objects {
print(family)
for guest in family {
for event in self.eventList {
if let invited = guest.valueForKey("\(event)Invite") as? Bool {
if invited {
self.guestList[event]!.append(guest.valueForKey("GuestName") as! String)
}
}
}
}
print(self.guestList)
for (key, value) in self.guestList {
print("\(key) -> \(value)")
self.objectArray.append(Objects(sectionName: key, sectionObjects: value))
self.tableView.reloadData()
}
print(self.objectArray)
}
} else {
print(error)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return objectArray.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return objectArray[section].sectionObjects.count
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return objectArray[section].sectionName
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = objectArray[indexPath.section].sectionObjects[indexPath.row]
return cell
}
}
SOLUTION:
I added self.tableView.reloadData() in my viewDidLoad and it worked.

Sections by month in UITableView

I've created a tableView which displays my array, it's an historic of my performances.
I want to "group" those performances in sections by month and in my array i got the creation date of each performance.
But i'm kind of lost on how to proceed. Here is the code of my tableview.
var historic: Historic!
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.historic == nil {
return 0
}
return self.historic.performances.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("HistoricTableViewCellOne", forIndexPath: indexPath) as! HistoricTableViewCell
// Configure the cell...
if self.historic != nil {
let perf = self.historic.performances[indexPath.row]
cell.configureWithPerformance(perf)
}
return cell
}
And here are my objects
class Historic: NSObject {
var totalDistance: CLLocationDistance = 0.0
var performances: [Performance] = [Performance]()
override init() {
super.init()
}
}
class Performance: NSObject {
var urlCover: NSURL
var duration: Double = 0
var length: Double = 0
var id: UInt = 0
var publicationStatus: PerformancePublicationStatus?
var creationDate: NSDate?
init(urlCover: NSURL, creationDate: NSDate){
self.urlCover = urlCover
self.creationDate = creationDate
}
}
Final Edit: It's working ! I'm putting the code here in case someone is interest.
var perfSections = [String]()
var tableViewCellsForSection = [Performance]()
var perfCells = Dictionary<String, [Performance]>()
// MARK: - Setup Data Source
private func setupDataSource() {
var calendar = NSCalendar.currentCalendar()
var dateFormatter = NSDateFormatter()
dateFormatter.locale = NSLocale.currentLocale()
dateFormatter.timeZone = calendar.timeZone
dateFormatter.setLocalizedDateFormatFromTemplate("MMMM YYYY")
let dateComponents: NSCalendarUnit = .CalendarUnitYear | .CalendarUnitMonth
var previousYear: Int = -1
var previousMonth: Int = -1
for performance in historic.performances {
var components: NSDateComponents = calendar.components(dateComponents, fromDate: performance.creationDate!)
var year: Int = components.year
var month: Int = components.month
if (year == previousYear && month == previousMonth) {
self.tableViewCellsForSection.append(performance)
previousYear = year
previousMonth = month
}
if (year != previousYear || month != previousMonth) {
if !self.tableViewCellsForSection.isEmpty {
var sectionHeading: String = dateFormatter.stringFromDate(self.tableViewCellsForSection.last!.creationDate!)
self.perfSections.append(sectionHeading)
self.perfCells[sectionHeading] = self.tableViewCellsForSection
self.tableViewCellsForSection = []
}
self.tableViewCellsForSection.append(performance)
previousYear = year
previousMonth = month
}
}
var sectionHeading: String = dateFormatter.stringFromDate(self.tableViewCellsForSection.last!.creationDate!)
self.perfSections.append(sectionHeading)
self.perfCells[sectionHeading] = self.tableViewCellsForSection
self.tableViewCellsForSection = []
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.perfSections.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.historic == nil {
return 0
}
var key = self.perfSections[section]
self.tableViewCellsForSection = self.perfCells[key]!
return self.tableViewCellsForSection.count
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.perfSections[section]
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("HistoricTableViewCellOne", forIndexPath: indexPath) as! HistoricTableViewCell
// Configure the cell...
if self.historic != nil {
var key = self.perfSections[indexPath.section]
self.tableViewCellsForSection = self.perfCells[key]!
let perf = self.tableViewCellsForSection[indexPath.row]
cell.configureWithPerformance(perf)
}
return cell
}
The problem is that your data model (self.historic.performances) is too simple-minded. You need sections and rows, but your data model expresses only rows. You will need a data model that structures your performances into sections. A typical approach is to use an array of arrays — each outer array is a section, and each inner array is the rows of that section. But of course many other approaches are possible.

Resources