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:
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:
And a link to the Xcode project: https://static.realm.io/debug/RealmContacts.tgz
Related Topics
Uicollectionview Adding Image to a Cell
Autoshrink on a Uilabel with Multiple Lines
Scrollview Not Scrolling When Dragging on Buttons
Get the Value of Url Parameters
Differencebetween a Property and a Variable in Swift
Exc_Bad_Access When Building Nspredicate
What Is Export * in Module.Modulemap File Inside Each Framework
Updating iOS Badge Without Push Notifications
Force Landscape Viewcontroller in iOS 7
Trigger Local Notifications Automatically Daily on Dynamic Time Given in Arrays Objective C iOS
iOS Swiftui: Pop or Dismiss View Programmatically
Xcode 6 - Launch Simulator from Command Line
Get Safe Area Inset Top and Bottom Heights
How to Remove HTML Tags from Nsstring in Iphone