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
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]
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
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.
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.
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.