Swift / Populate UITableView with Array.count of elements matching certain condition - ios

Currently I'm populating my UITableView by:
var eventDates: [NSMutableDictionary] = []
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventDates.count
}
Every eventDate within eventDates has a valueForKey("monthsYear").
Hardcoded I want now eventDates.count only return the eventDates with the valueForKey("monthsYear") == "September 2016".
In other words, to explain it one more time:
eventDates.count = 10. But only 5 of them match the condition valueForKey("monthsYear") == "September 2016".
How do I get only those 5 elements to populate the UITableView (with only 5 rows).
In theory something like this:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let returnValue = 0
for date in eventDates where date.valueForKey("monthsYear") == "September 2016" {
returnValue++
}
return returnValue
}
Help is very appreciated.

So what you need to do is a simple Filter
let sepDates = eventDates.filter() { $0.valueForKey("monthYear") == "September 2016" }
return sepDates.count
some not answer related stuff stuff
One Advice, try to use models to avoid using valueForKey, so you array would look like
var eventDates: [EventDate] = []
good luck and have fun ;)

Related

How do you make a UITableView with Sections using NSFetchedResultsController?

So we have to use CoreData and we have to use a UITableView, now I'm asking how does that work with a NSFetchedResultsController? We have something already set up for other basic sectioned views where we dont need a fetchresultscontroller and that was pretty easy, but this seems a little trickier. There is a little support out there for our old pal Objective-C that has been around for years but we are looking for a Swift solution. Swift 5 at the time of this post.
Have already implemented the plain list (which is fairly well documented) and now we just want to add some sections...
So we have a UITableView and a NSFetchedResultsController and we get a nice list of Entities. Now we want to add a few sections into the tableview.
No Problem so we update the sortDescriptors to include a section:
fetchRequest.sortDescriptors =
[NSSortDescriptor(key: "sectionLabel", ascending: false),
NSSortDescriptor(key: "unixTimestamp", ascending: false)]
and the entity to include the sectionLabel, this part is a bit more custom depending on what kind of sections you want.
Then we add a few section functions :
func numberOfSections(in tableView: UITableView) -> Int {
return self.fetchController?.sections?.count ?? 0
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard AllTheThings( fetchController -> sections -> entity -> cell )
if sections.count > 1 {
cell.configureCell(value: entity.sectionLabel ?? "myLabel")
return cell.contentView
} else { return UIView() }
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 20.0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let sections = fetchController?.sections else { return 0 }
return sections[section].numberOfObjects
}
So things are starting to shape up but theres still some work needed on the cellForRowAt indexPath and other standard cell functions. So in order to get an entity for the index to use in those we create a cool function:
func returnEntity(indexPath: IndexPath) -> MyEntity? {
guard let sections = fetchController.sections else {
return nil
}
guard let objects = sections[indexPath.section].objects else {
return nil
}
if objects.isEmpty {
return nil
}
guard let entity = objects[indexPath.row] as? MyEntity else {
return nil
}
return entity
}
Left the guards in this time ;)
Now all you have to do to get your entity is use returnEntity(indexPath: indexPath)

sectionForSectionIndexTitle retrieve previous section

I have a UITableView with sectionIndexTitles. Here's my data source :
let sSectionTitles = ["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 sectionTitles = [String]()
func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
return sSectionTitles
}
func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
var section = 0
if let selectedSection = sectionTitles.indexOf(title) {
section = selectedSection
} else {
section = index
}
return section
}
The variable sectionTitles is a similar array to sSectionTitles except that it only contains section indexes that are valid. For example, if I have no Contact with their name starting with the letter D, then "D" won't be in sectionTitles.
I'm trying to replicate the behavior in the Contact application :
If the user clicks on the Index title "D" and if there is at least one contact in the B section, then scroll to this section.
Else, scroll to the previous section available. (In this example, if there are no contacts for the B and C letter then scroll to A)
I've been stuck of this for many hours I still don't know how I could apply this logic. I thought about using a recursive function but I didn't manage to pull this off. Does someone has any lead on how this could be achieved?
Thanks.
I think you can do it by recursion. Use another helper function to retrieve the appropriate index and call it from tableview data source function. Example,
func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
var section = getSectionIndex(title)
return section
}
//recursive function to get section index
func getSectionIndex(title: String) -> Int {
let tmpIndex = sectionTitles.indexOf(title)
let mainIndex = sSectionTitles.indexOf(title)
if mainIndex == 0 {
return 0
}
if tmpIndex == nil {
let newTitle = sSectionTitles[mainIndex!-1]
return getSectionIndex(newTitle)
}
return tmpIndex!
}

Swift replacement for old style enums

Back in the old Objective-C days I would often use enums for things like tables with constant contents, segmented controls, etc - in situations where there was an enforced incremented list of integers starting at zero. I would also often add a ...count member at the end to give a count of the entries, useful for table section & rows. Trying to do this with swift enums is proving troublesome - lots of conversions to & from raw values and extra default clauses in switches to allow for the 'count' entry. Can anyone suggest a graceful method of dealing with these sorts of situations?
Automatic increment is still available in Swift.
enum Section: Int {
case A = 0
case B
case C
}
Section.C.rawValue // -> 2
As for count, you should implement it manually (as How do I get the count of a Swift enum?):
enum Section: Int {
case A = 0
case B
case C
static let count = C.rawValue + 1
}
As for "conversions to & from raw values and extra default clauses" problem, compare with enum instead of its rawValue.
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return Section.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch Section(rawValue: section)! {
case .A: return 1
case .B: return 2
case .C: return 5
}
}
If you want to do something like array[Section.A], you can easily implement it.
extension Array {
subscript(section: Section) -> T {
return self[section.rawValue]
}
}
extension NSIndexPath {
convenience init(forRow row: Int, inSection section: Section) {
self.init(forRow: row, inSection: section.rawValue)
}
}
let array = ["foo", "bar", "baz"]
array[.B] // -> "bar"
let indexPath = NSIndexPath(forRow: 20, inSection: .C)
indexPath.section // -> 2
indexPath.row // -> 20
And so on.. :)
Add a function "count" to each enum. For example
static func count() -> Int { return 3 }
Integer -> enum conversion is done by the init method.

UITableView with different optional sections?

I am looking for a "good" way to solve some special requirements:
I have an UITableView with different sections, for example:
Base Data
About me
Interests
Images
Base Data contains always values (but there is still an variable row count) - and all other "Categories" could contain rows, or still could be empty. If there is no data, the category should be not shown.
No my first idea to solve that is:
Create all possible categories (but that could be 20 or more) - and do something like that:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var count:Int = 0
switch (section) {
case 0:
count = baseHeaders.count
case 1:
if(mapItem.courses?.count > 0) {
count = mapItem.courses!.count
}
break;
default:
count = 0
}
return count
}
And ill check also with: titleForHeaderInSection if the count is null, and return then no header for the section.
BUT: is that a good way? My concern is about creating 20 sections and just 2 are used. Is there another, better way? Can i create sections manually? If yes, should i? So that only the base category is visible, and append everything else if there is data available.
This looks like my way of approaching such problems. I'm using enums (Obj-C & especially Swift) to handle and identify my Sections and I always return the full amount of potential sections:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return FormSection.count // enum function
}
In func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int, however, I switch the unused sections off by returning 0 rows.
The benefit I saw after struggling with your type of dynamic tables was that all sections are always at the same index which made cell management relatively easy:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let section:FormSection = FormSection(rawValue:indexPath.section)!
switch section {
case .Header:
//…
default:
//…
}
}
The same goes for section headers/footers:
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
switch section {
case FormSection.Header.rawValue:
return nil
case FormSection.RoomSetup.rawValue where foo == false:
return nil
default:
// return header with title = FormSection(rawValue: section)?.headerTitle()
// Swift enums ftw ;)
}
And the number of rows is calculated/fetched at runtime:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let section:FormSection = FormSection(rawValue:section)!
switch section {
case .Section1:
return fooExpanded ? (numberOfFoo) : 0
case .Section2:
return model.barCount()
default:
return 1
}
}

Return no cell based on nil variable

This is a bit of a continuation of this question, but basically I am trying to figure out how I can return no cell if the result of a function is nil.
This is my code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("rideCell", forIndexPath: indexPath) as! RideCell
var ride = DataManager.sharedInstance.getRideByName(favouritesArray[indexPath.row] as! String)
println(ride)
if ride != nil {
cell.rideNameLabel.text = ride!.name
var dateFormat = NSDateFormatter()
dateFormat.dateFormat = "h:mm a"
cell.updatedLabel.text = dateFormat.stringFromDate(ride!.updated!)
if ride!.waitTime! == "Closed" {
cell.waitTimeLabel.text = ride!.waitTime!
} else {
cell.waitTimeLabel.text = "\(ride!.waitTime!)m"
}
}
return cell
}
So at the moment everything works, however wherever ride does equal nil, I just get the prototype cell returned, whereas I would like it to return nothing.
I have tried hiding or changing the height of these cells to nil, but it just seems like messy solution. Anyone know a better one?
Thanks.
determine the number of rows by implementing tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) {
if section == 0 {
return arrayName.count
}
return 0
}
I figured out a solution. As a few people mentioned, I had to figure out what to display before dealing with cellForRowAtIndexPath.
Basically I added some code to figure out which favourites could be found in the array and put it in viewWillAppear.
for index in 0...favouritesArray.count - 1 {
var ride = DataManager.sharedInstance.getRideByName(favouritesArray[index] as! String)
if ride != nil {
favouritesFound.append(ride!.name!)
println(favouritesFound)
}
}
Works perfectly now! Thanks everyone.
It is assumed that you always know how many valid rows do you have and you publish this number by implementing UITableViewDataSource's tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int method. So if at some moment number of rows changes you should call UITableView.reloadData not to try to hide excess rows.
To get an array of valid rides try something like:
var newArray:[String] = []
for str in favouritesArray where DataManager.sharedInstance.getRideByName(str!) != nil
{
newArray += [str]
}

Resources