Uitableview with Multiple Sections Using Realm and Swift

UITableView with Multiple Sections in Alphabetical Order using Realm and Swift, with multiple labels connected with same data

There are a number of issues with the code in the question and instead of trying to dissect them, let me provide an example that may help.

Suppose we have a bunch of fruits stored in Realm. We want to display those in alphabetical order in a tableview using sections

A
Apple
B
Banana

etc

This first piece of code is a structure to hold a section title and then an array of the fruits that go in that section. Also, a class fruitNameArray which will act as our tableView DataSource that holds all of the FruitStructs.

struct FruitStruct {
var sectionTitle = ""
var fruitNameArray = [String]()
}

var fruitDataSource = [FruitStruct]()

I am using an array to hold the initial data but in your case it could be a Realm results object populated from a Realm. This is called from viewDidLoad to organize our data into the dataSource array

func setupDataSourceData() {
let fruitArray = ["Apple", "Pear", "Banana", "Bing Cherry", "Grape", "Orange", "Plum", "Watermelon", "Cantelope"]

let allFirstChars = fruitArray.map { String($0.prefix(1)) } //get all first chars for section titles
let sectionTitles = Array(Set(allFirstChars)).sorted() //eliminate dups and sort

//iterate over the unique section titles and get the fruits that are in that section
// sort and then craft structs to hold the title and the associated fruits
sectionTitles.forEach { firstChar in
let results = fruitArray.filter { $0.prefix(1) == firstChar }
let sortedFruits = results.sorted()
let fruit = FruitStruct(sectionTitle: firstChar, fruitNameArray: sortedFruits)
fruitDataSource.append(fruit)
}

for fruitData in fruitDataSource {
print(fruitData.sectionTitle)
let fruits = fruitData.fruitNameArray
for fruitName in fruits {
print(" \(fruitName)")
}
}
}

Then, there are 4 functions a tableView needs to show the sections and then the rows in each section. You appear to have these 4 but an additional one sectionIndexTitles which may not be needed.

//
//handle sections
//
func numberOfSections(in tableView: UITableView) -> Int {
return self.fruitDataSource.count
}

func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let title = self.fruitDataSource[section].sectionTitle
return title
}

//
//handleTableView rows
//
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let rowsInSection = self.fruitDataSource[section].fruitNameArray.count
return rowsInSection
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellReuseIdentifier", for: indexPath)
let text = self.fruitDataSource[indexPath.section].fruitNameArray[indexPath.row]
cell.textLabel?.text = text
return cell
}

Note that I am not performing any filtering and any other stuff as the tableView is being populated. That section of code needs to be fast and lean to the UI is as responsive as possible.

I am just getting a string

let text = self.fruitDataSource[indexPath.section].fruitNameArray[indexPath.row]

but in your case, you may want to return an exersise object where you could then get the exerciseName and bodypartName to then assign those in the custom cellView

Best Practice Note: class properties should be lower cased - exerciseName, not uppercase, ExerciseName. Class and Struct names are typically capitalized.

UITableView with Multiple Sections using Realm and Swift

Here's some sample code that does exactly what you're looking for:

import UIKit
import RealmSwift

class Dog: Object {
dynamic var name = ""
dynamic var race = ""
dynamic var age = 0
dynamic var owner = ""
dynamic var dogID = ""

override static func primaryKey() -> String? {
return "dogID"
}

convenience init(name: String, race: String, dogID: String) {
self.init()
self.name = name
self.race = race
self.dogID = dogID
}
}

class TableViewController: UITableViewController {
let items = try! Realm().objects(Dog.self).sorted(["race", "name"])
var sectionNames: [String] {
return Set(items.valueForKeyPath("race") as! [String]).sort()
}

override func viewDidLoad() {
super.viewDidLoad()

tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")

let realm = try! Realm()
if realm.isEmpty {
try! realm.write {
realm.add(Dog(name: "Bailey", race: "Golden Retrievers", dogID: "0"))
realm.add(Dog(name: "Bella", race: "German Shepherds", dogID: "1"))
realm.add(Dog(name: "Max", race: "Bulldogs", dogID: "2"))
realm.add(Dog(name: "Lucy", race: "Yorkshire Terriers", dogID: "3"))
realm.add(Dog(name: "Charlie", race: "Bulldogs", dogID: "4"))
realm.add(Dog(name: "Molly", race: "German Shepherds", dogID: "5"))
realm.add(Dog(name: "Buddy", race: "German Shepherds", dogID: "6"))
realm.add(Dog(name: "Daisy", race: "Siberian Huskies", dogID: "7"))
}
}
}

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sectionNames.count
}

override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionNames[section]
}

override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return items.filter("race == %@", sectionNames[section]).count
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = items.filter("race == %@", sectionNames[indexPath.section])[indexPath.row].name
return cell
}
}

Which looks like this:

screenshot

Querying Realm to populate numberOfRowsInSection and cellForRowAt with Multiple Sections using Realm and Swift

Thank you for providing us with your models. As I see you already have a list of Workouts on your WeekDay elements, so your query to populate your Table View is simplified by this.

First things first. I recommend you to change your declaration of the results in your controller to use the following

class WorkoutsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, SwipeTableViewCellDelegate {
let realm = try! Realm()
var days : Results<WeekDays>!
// ... rest of the implementation
}

This way you can void the optional handling for the table view datasource and delegate methods.

That being said, you only need to query the WeekDay objects on the view controller. I usually do this on the viewDidLoad:

days = realm.objects(WeekDays.self)

The associated workouts are loaded automatically and associated to each of the days you're getting from the database, on the days array.

Based on your requirement you can create the required number of sections for your table view like the following:

func numberOfSections(in tableView: UITableView) -> Int {
return self.days.count
}

That code will create as many section as the size of the days array. The next task is to provide the number of rows in the given section. This is almost immediate because we already have an array of the WorkOuts objects on the day:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let day = days[section]
return day.workouts.count
}

At this point we already have provided the number of sections in the table view (days) and the number of rows for each section (workouts associated to the corresponding day).

After this each cell can be configured based on the days array information (don't forget this is an array of WeekDays objects, each one containing an array of workouts.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! SwipeTableViewCell
cell.accessoryType = .disclosureIndicator
cell.delegate = self

// Populate with titles of workouts based on section/day of the week.
cell.textLabel?.text = days[indexPath.section].workouts[indexPath.row].name

return cell
}

The key here is you must obtain the WeekDays object (by getting the object at the index indexPath.section from the days array) and then getting the Workout details by getting the Workouts object at index indexPath.row from the weekday's array.

Hope this helps!

Swift: Load Data from Realm Duplicate Sections in TableView?

It was solved using this extension:

public enum SortingOrder {
case ascending
case descending
}

public extension Sequence {
func group<K: Hashable & Comparable>(
by keyForValue: (Element) -> K,
sortOrder: SortingOrder = .ascending) -> [[Element]] {
Dictionary(grouping: self, by: keyForValue)
.sorted { sortOrder == .ascending ? $0.key > $1.key : $0.key < $1.key }
.map { $0.value }
}
}

And adding...

contactArray = contacts.group(by: { $0.name.first ?? "-" }, sortOrder: .descending) to loadArtists()

Also, setupContacts() has been changed to:

contactArray = contacts.group(by: { $0.name.first ?? "-" }, sortOrder: .descending)

How can i get a right quantity of sections in tableview. Swift, realm

Your for loop can work like you want cause you continue iterate on item with same day property as some items before. And your numberOfRowsInSection method can't work properly cause you count on name property of section instead of items property. Although I don't have all informations on your model, that's how I would do: (Like say in another response, it's better to save sections and items instead of getting data at each call of table view data source and delegate)

typealias Sections = [(quantity: [Int], name: String, items: [Items])]
func getAllItems() -> [Int: [Items]] {
var array: [Int: [Items]] = [:]
let objects = realm.objects(Items.self)
for object in objects {
if Array(array.keys).contains(object.day) {
var items = array[object.day]
items.append(object)
array[object.day] = items
} else {
array[object.day] = [object]
}
}
return array
}
func getSections() -> Sections {
var sections: Sections = []
let data = getAllItems()

for (key, value) in data {
sections.append((quantity: value.count, name: "\(key)", items: value))
}

return sections
}

func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
RealmModel.shared.getSections()[section].name
}

func numberOfSections(in tableView: UITableView) -> Int {
RealmModel.shared.getSections().count
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return RealmModel.shared.getSections()[section].items.count
}

Use Realm Query Results as UITableView Section Headers

Try this:

private func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let nameArr = getCategoryNames()
return nameArr[section]
}

Function: func tableView(_ tableView: UITableView,
titleForHeaderInSection section: Int) -> String? so you need to return string for a particular section.

Creating an indexed tableview using Realm in swift

Here's a sample derived from https://stackoverflow.com/a/38797693/373262 for your models and specifying a hardcoded array of section titles as your comment above requested:

import UIKit
import RealmSwift

class ContactItem: Object {
dynamic var name = ""
dynamic var keyNumber = ""

convenience init(name: String, keyNumber: String) {
self.init()
self.name = name
self.keyNumber = keyNumber
}
}

let alphabet = (UnicodeScalar("A").value...UnicodeScalar("Z").value).flatMap(UnicodeScalar.init)

class ViewController: UITableViewController {
let items = try! Realm().objects(ContactItem.self).sorted(byProperty: "name")

override func viewDidLoad() {
super.viewDidLoad()

tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")

let realm = try! Realm()
if realm.isEmpty {
try! realm.write {
realm.add(ContactItem(name: "Bailey", keyNumber: "0"))
realm.add(ContactItem(name: "Bella", keyNumber: "1"))
realm.add(ContactItem(name: "Max", keyNumber:"2"))
realm.add(ContactItem(name: "Lucy", keyNumber: "3"))
realm.add(ContactItem(name: "Charlie", keyNumber:"4"))
realm.add(ContactItem(name: "Molly", keyNumber: "5"))
realm.add(ContactItem(name: "Buddy", keyNumber: "6"))
realm.add(ContactItem(name: "Daisy", keyNumber: "7"))
}
}
}

func items(forSection section: Int) -> Results<ContactItem> {
return items.filter("name BEGINSWITH %@", alphabet[section].description)
}

override func numberOfSections(in tableView: UITableView) -> Int {
return alphabet.count
}

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return alphabet[section].description
}

override func tableView(_ tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return items(forSection: section).count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let contactinfo = items(forSection: indexPath.section)[indexPath.row]
cell.textLabel?.text = contactinfo.name
cell.detailTextLabel?.text = contactinfo.keyNumber
return cell
}
}

Here's the end result:

screenshot

And a link to the Xcode project: https://static.realm.io/debug/RealmContacts.tgz



Related Topics



Leave a reply



Submit