In iOS/Swift I've created an indexed "client" UITableView based on the clientName property in my Client class. I created a dictionary with A to Z as the sections. The indexed tableview works great. However, I'm trying to figure out a way to determine which row it is in the original source array, when the user selects a row. I was thinking of building some type of cross reference array, except that the dictionary ends up sorted to match the sections, so I don't know which section/row combo matches which original array entry. Is there a common approach to handling this issue?
In an attempt to clarify...
class Client {
var clientId : Int!
var firstName : String!
var lastName : String!
var email : String!
var phone : String!
...
init() {
}
}
var clients: [Client] = []
// clients array loaded from web service
...
// Create dictionary to be source for indexed tableview
func createClientDict() {
clientDict = [String: [String]]()
clientSectionTitles = [String]()
var clientNames:[String] = []
for i in 0..<clients.count {
let client = clients[i]
let clientName = "\(client.lastName), \(client.firstName)"
clientNames.append(clientName)
}
for name in clientNames {
var client: Client = Client()
// Get the first letter of the name and build the dictionary
let clientKey = name.substringToIndex(name.startIndex.advancedBy(1))
if var clientValues = clientDict[clientKey] {
clientValues.append(name)
clientDict[clientKey] = clientValues
} else {
clientDict[clientKey] = [name]
}
}
// Get the section titles from the dictionary's keys and sort them in ascending order
clientSectionTitles = [String](clientDict.keys)
clientSectionTitles = clientSectionTitles.sort { $0 < $1 }
}
Now, when the user taps a row in the tableview, I can get the section and row (indexPath). However, how can I determine which row in the clients array is the match, assuming there could be duplicate names? Is there some way to create a cross reference of indexed section/row mapped to row in source array on the fly? I was going to try to do that while building the dictionary, except that the dictionary gets sorted after, so nothing would match up. Maybe I should somehow be including the source row number in/with the dictionary??
Here is the tableview code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! ClientCell
let clientKey = clientSectionTitles[indexPath.section]
if let clientValues = clientDict[clientKey] {
cell.clientName.text = clientValues[indexPath.row]
}
return cell
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return clientSectionTitles.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let clientKey = clientSectionTitles[section]
if let clientValues = clientDict[clientKey] {
return clientValues.count
}
return 0
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return clientSectionTitles[section]
}
func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
return clientIndexTitles
}
func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
guard let index = clientSectionTitles.indexOf(title) else {
return -1
}
return index
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 20
}
func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let headerView = view as! UITableViewHeaderFooterView
headerView.contentView.backgroundColor = UIColor ( red: 0.0, green: 0.3294, blue: 0.6392, alpha: 1.0 )
headerView.textLabel?.textColor = UIColor.greenColor()
headerView.textLabel?.font = UIFont(name: "Noteworthy-bold", size: 15.0)
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedIndex = indexPath
// In the following prepare for segue, I need to somehow use the selected indexpath to find the correct entry
// in the clients array and pass it along.
performSegueWithIdentifier("clientDetailSegue", sender: self)
}
I figured it out. I didn't realize (until I recently tried it) that you could nest an array of any class in a dictionary. When I changed my dictionary to have my clients array nested in it, all was solved. I changed my function as shown below.
func createClientDict() {
// Declared for view controller. Re-initialized here.
clientDict = [String: [Client]]()
clientSectionTitles = [String]()
clients.sortInPlace ({ $0.lastName < $1.lastName })
for c in clients {
let clientName = "\(c.lastName), \(c.firstName)"
// Get the first letter of the name and build the dictionary
let clientKey = clientName!.substringToIndex(clientName!.startIndex.advancedBy(1))
if var clientValues = clientDict[clientKey] {
clientValues.append(c)
clientDict[clientKey] = clientValues
} else {
clientDict[clientKey] = [c]
}
}
// Get the section titles from the dictionary's keys and sort them in ascending order
clientSectionTitles = [String](clientDict.keys)
clientSectionTitles = clientSectionTitles.sort { $0 < $1 }
}
However, this line was the key to the solution:
let clientDict = [String: [Client]]()
Related
I have object named myArray in firebase database structure and it has a multiple lines of strings as [a , b and c ] like this how can I display this object in my Table View ,When I run it its coming in My Debug like this but its not appears in my simulator its appease like this ,I am using swift 3
This is My Code :
import UIKit
import Firebase
class TestTable: UITableViewController {
var arrayList = [test]()
var Ref = FIRDatabaseReference()
override func viewDidLoad() {
super.viewDidLoad()
Ref=FIRDatabase.database().reference()
Ref.child("Users").child("Test").observe(.childAdded ,with: { (snapshot) in
if let User = snapshot.value as? [String : AnyObject]{
let Add = test()
Add.setValuesForKeys(User)
self.arrayList.append(Add)
print("##############################\(snapshot.value)")
self.tableView.reloadData()
}
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return arrayList.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return arrayList[section].myArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as!TestTableCell
let hh = indexPath.section
cell.myTestView.text = (arrayList[hh].myArray as AnyObject) as? String
cell.titleText.text = arrayList[hh].title
return cell
}
}
and this is My Class :
class test : NSObject {
var myArray : NSArray = []
var title : String?
}
Your myArray is not array of string, it's dictionary. So if you want value of a,b and c in cell.myTestView.text. Then you need to run loop to get all a,b and c in cell.myTestView.text.
var string = ""
for (key, value) in arrayList[hh].myArray
{
print("\(key) -> \(value)")
string = string + "\n\(key) -> \(value)"
}
cell.myTestView.text = string
update your line cell.myTestView.text = (arrayList[hh].myArray as AnyObject) as? String with above code.
Problem Statement:
I want to display text separated by "," on new custom UITableViewCell.
Problem: It displays all data in single custom cell only with multi-line property, as shown in below.
I want to display tableView like this way.
Now I'm trying to display above data separated by "," on each new custom cell, as shown in above screenshot, but it displays only 1st data and skip remaining data, as per my code.
let Meaning :String = "Aback,Abacus,Abandon,Able,Aboard"
let Smeaning :String = "Fabric,Habit,keen,Pace"
func tableView(tableViewData: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableViewData.dequeueReusableCellWithIdentifier("cell")! as! StudentCell
let fmeaning = Mmeaning.characters.split{$0 == ","}.map(String.init)
let smeaning = Hmeaning.characters.split{$0 == ","}.map(String.init)
for var i = 0; i < fmeaning.count; i += 1{
print(fmeaning[i])
//Here it prints all values perfectly
}
for var i = 0; i < smeaning.count; i += 1{
print(smeaning[i])
//Here it prints all values perfectly
}
Problem occurs here below two statements: display only 1st value in UITableView
cell.lblMeaning1.text = fmeaning[indexPath.row]
cell.lblMeaning2.text = smeaning[indexPath.row]
return cell;
}
How should I assign an Array to these two custom cells, so that it will display data on separate new custom cell?
For that you need to use section table with your tableView, also you need to make this calculation in viewDidLoad and then reload the tableView, For that declare your two instance of array like this in your viewController.
var languageDic = [String: [String]]()
var allLanguage = [String]()
override func viewDidLoad() {
super.viewDidLoad()
let mMeaningArray = Mmeaning.characters.split{$0 == ","}.map(String.init)
let hMeaningArray = Hmeaning.characters.split{$0 == ","}.map(String.init)
self.languageDic = ["Marathi Meaning": mMeaningArray,"Hindi Meaning": hMeaningArray] // You can add other langaue in the dic sam way I have added this two
self.allLanguage = Array(self.languageDic.keys) as [String]
self.tableView.reloadData()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.allLanguage.count
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String {
return self.allLanguage[indexPath.section]
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.languageDic[self.allLanguage[section]].count
}
func tableView(tableViewData: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableViewData.dequeueReusableCellWithIdentifier("cell")! as! StudentCell
let str = self.languageDic[self.allLanguage[indexPath.section]][indexPath.row]
cell.lblMeaning1.text = str
return cell
}
Note: In cellForRowAtIndexPath you need only one label.
Edit: As of you want return the count of array that have more element you can try like this. F
var mMeaningArray = [String]()
var hMeaningArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.mMeaningArray = Mmeaning.characters.split{$0 == ","}.map(String.init)
self.hMeaningArray = Hmeaning.characters.split{$0 == ","}.map(String.init)
self.tableView.reloadData()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (self.mMeaningArray.count > self.hMeaningArray.count)? self.mMeaningArray.count : self.hMeaningArray.count
}
func tableView(tableViewData: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableViewData.dequeueReusableCellWithIdentifier("cell")! as! StudentCell
if indexPath.row < self.mMeaningArray.count {
cell.lblMeaning1.text = self.mMeaningArray[indexPath.row]
}
else {
cell.lblMeaning1.text = ""
}
if indexPath.row < self.hMeaningArray.count {
cell.lblMeaning2.text = self.hMeaningArray[indexPath.row]
}
else {
cell.lblMeaning2.text = ""
}
return cell
}
the numberOfRowsInSection() function should return fmeaning.count and smeaning.count... put an if to determine which table you want to have the correct row number.
also you cand display the fmeaning as a section in your tableview, and the smeaning as cells.
I am trying to retrive each row from database and keep value in dictionary. After that I have created a array of dictionary. But when i am trying to show each value of dictionary in table cell, i can't . Here is my code
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
var borrowerInformation:NSMutableDictionary? = NSMutableDictionary();
var borrowerName:String = ""
var givenDate:String = ""
var borrower:NSMutableArray = []
override func viewDidLoad() {
super.viewDidLoad()
dataQuery()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return self.borrower.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! BorrowerLanderTableViewCell
cell.borrowerName.text = self.borrower[indexPath.row] as? String
return cell
}
func dataQuery(){
let queryBorrower = PFQuery(className:"BorrowerLander")
queryBorrower.whereKey("borrowerLander", equalTo: 0)
queryBorrower.whereKey("userId", equalTo: (PFUser.currentUser()?.objectId)!)
queryBorrower.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
self.borrower = NSMutableArray()
if error == nil {
if let borrowerData = objects {
for object in borrowerData {
self.borrowerName = object["name"] as! String
self.givenDate = object["givenDate"] as! String
self.borrowerInformation = [ "borrowerName": self.borrowerName,"givenDate":"self.givenDate"]
self.borrower.addObject(self.borrowerInformation!)
}
}
}
self.tableview.reloadData()
}
}
}
Here, i have just added main part of the code. Please tell me how can i show value of array of the dictionary in table view cell?
I don't know how you did the setup but an issue could be that you have not set your tableviews delegate and datasource to the controller you are working with.
self.tableView.dataSource = self
self.tableView.delegate = self
I'm also wondering if you did implement the numberOfRows and numberOfSections delegate methods.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return borrower.count
}
and
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
Also when setting the text you need to actually use the name string you added to the dictionary
let borrower = self.borrower[indexPath.row]
if let name = borrower["borrowerName"] as? String{
cell.borrowerName.text = name
} esle {
cell.borrowerName.text = "error"
}
Also declare your data array as
var borrower:[[String: AnyObject]] = []
Extra edit:
I do want to say that I really think you should use a custom object here instead of an dictionary
class Borrower {
var name: String
var givenDate: String
//etc.
init(name: String, givenDate: String) {
self.name = name
self.givenDate = givenDate
}
}
enabling the following
var borrower:[Borrower] = []
and in the query:
for object in borrowerData {
if let name = object["name"] as? String, givenDate = object["givenDate"] as? String {
self.borrower.append(Borrower(name: name, givenDate: giverDate))
}
}
First, make sure you've set the dataSource and delegate for your table view to your view controller. Then, the function tableView:cellForRowAtIndexPath: will never get called unless you implement tableView:numberOfRowsInSection. The default implementation of that function returns 0. Try implementing this:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return borrower.count
}
Also, if the code inside of queryBorrower.findObjectsInBackgroundWithBlock { is actually in the background, you need to dispatch your table view reload to the main thread. Like this:
dispatch_async(dispatch_get_main_queue(), { [weak self] () -> Void in
self?.tableView.reloadData()
})
You need to implement numberOf... dataSource delegate first:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
I am trying to populate my feed of images and text users post. The code compiles and runs but I get empty cells.
var titles = [String]()
var locations = [String]()
var dates = [String]()
var imageFiles = [PFFile]()
override func viewDidLoad() {
super.viewDidLoad()
var privacySettingQuery = PFQuery(className: "Posts")
privacySettingQuery.whereKey("privacy", equalTo: true)
privacySettingQuery.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if let objects = objects {
for object in objects {
self.titles.append(object["name"] as! String)
self.dates.append(object["dateTime"] as! String)
self.locations.append(object["location"] as! String)
self.imageFiles.append(object["imageFile"] as! PFFile)
}
}
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return titles.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath(indexPath: NSIndexPath) -> UITableViewCell {
let postsCell = tableView.dequeueReusableCellWithIdentifier("PostsCell", forIndexPath: indexPath) as! PostsCell
postsCell.postImage.image = UIImage(named: "Post-Image- Placeholder-1.png")
postsCell.postTitle.text = titles[indexPath.row]
postsCell.postLocation.text = locations[indexPath.row]
postsCell.postDate.text = dates[indexPath.row]
return postsCell
}
Any help would be appreciated. Also is there a better way to do this other than using arrays?
To solve the empty issue.You need call tableView.reloadData() after you got the objects.
Model class like
class Post: NSObject {
var title: String
var location: String
var date: NSDate
var imageFile: PFFile
}
And in viewController use
var posts: [Post]?
instead of the 4 arrays
When this code is being called
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return titles.count
}
The value for titles.count is still 0. After you finish fetching the info from parse and you have values for the arrays you must reload the UITableView. To do this make an outlet for the UITableView and call it tableView. Then call this code tableView.reloadData()
Hope this helps!!
I'm a complete rookie at Swift and iOS programming so you'll have to forgive the perhaps simple question.
I've created a tableView which displays the contents of an array (strings) at the press of a button.
Now, I'd like to "group" these strings in tableView sections, sorted by date.
In more detail: When the user taps the button, the string should be inserted at index 0 of the array and be displayed in a section with a header of todays date. If there's values older than today's date in the array, these should be displayed in a separate section for that date. Each section should correspond to a 24 hour day and display all the strings added during that day.
Here's some sample code of what I've achieved so far:
var testArray[String]()
var sectionsInTable[String]()
#IBOutlet weak var testTable: UITableView!
#IBAction func saveButton(sender: AnyObject) {
testArray.insert("\(strTest)", atIndex: 0)
testTable.reloaddata()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sectionsInTable.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return testArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
cell.textLabel.text = String(testArray[indexPath.row])
return cell
}
I really don't know how to manage the sections part. Hopefully someone can point me in the right direction. Thanks!
I was in need for something similar, and while Ron Fessler's solution works, when there's a lot of sections/rows, it took a very long time for table to load data, and even after that it wasn't much responsive. Main issue there I think is getSectionItems function as it will always go through all of items...
My solution:
struct TableItem {
let title: String
let creationDate: NSDate
}
var sections = Dictionary<String, Array<TableItem>>()
var sortedSections = [String]()
#IBAction func saveButton(sender: AnyObject) {
let date:String = "your date in string..."
//if we don't have section for particular date, create new one, otherwise we'll just add item to existing section
if self.sections.indexForKey(date) == nil {
self.sections[date] = [TableItem(title: name, creationDate: date)]
}
else {
self.sections[date]!.append(TableItem(title: name, creationDate: date))
}
//we are storing our sections in dictionary, so we need to sort it
self.sortedSections = self.sections.keys.array.sorted(>)
self.tableView.reloadData()
}
tableView dataSource methods:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sections.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[sortedSections[section]]!.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell")
let tableSection = sections[sortedSections[indexPath.section]]
let tableItem = tableSection![indexPath.row]
cell.titleLabel?.text = tableItem.title
return cell
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sortedSections[section]
}
I would usually do this with Core Data and NSFetchedResultsController since it has built-in methods for getting sections.
However, I'll answer the question without using Core Data. The code is a little messier but here we go...
First, you have to create an object that will store both the date and the text. The testArray will be an array of these objects, instead of a String array. For example:
class DateTextItem: NSObject {
var text: String = ""
var insertDate: NSDate = NSDate()
}
var testArray = [DateTextItem]()
Then when the saveButton is hit, we'll create and add the DateTextItem object. We'll also add the date to the sectionsInTable if it doesn't already exist.
#IBAction func saveButton(sender: AnyObject) {
let newItem = DateTextItem()
newItem.text = "Test \(testArray.count)"
// this is for development only
// increment the date after 2 records so we can test grouping by date
if testArray.count >= (testArray.count/2) {
let incrementDate = NSTimeInterval(86400*(testArray.count/2))
newItem.insertDate = NSDate(timeIntervalSinceNow:incrementDate)
}
testArray.append(newItem)
// this next bit will create a date string and check if it's in the sectionInTable
let df = NSDateFormatter()
df.dateFormat = "MM/dd/yyyy"
let dateString = df.stringFromDate(newItem.insertDate)
// create sections NSSet so we can use 'containsObject'
let sections: NSSet = NSSet(array: sectionsInTable)
// if sectionsInTable doesn't contain the dateString, then add it
if !sections.containsObject(dateString) {
sectionsInTable.append(dateString)
}
self.tableView.reloadData()
}
Next, I created a function to get the items in a section since we need it in a couple places.
func getSectionItems(section: Int) -> [DateTextItem] {
var sectionItems = [DateTextItem]()
// loop through the testArray to get the items for this sections's date
for item in testArray {
let dateTextItem = item as DateTextItem
let df = NSDateFormatter()
df.dateFormat = "MM/dd/yyyy"
let dateString = df.stringFromDate(dateTextItem.insertDate)
// if the item's date equals the section's date then add it
if dateString == sectionsInTable[section] as NSString {
sectionItems.append(dateTextItem)
}
}
return sectionItems
}
Finally, here is what the Table View Data Source methods look like
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sectionsInTable.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.getSectionItems(section).count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Configure the cell...
var cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
// get the items in this section
let sectionItems = self.getSectionItems(indexPath.section)
// get the item for the row in this section
let dateTextItem = sectionItems[indexPath.row]
cell.textLabel.text = dateTextItem.text
return cell
}
// print the date as the section header title
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionsInTable[section]
}
You have to make an array for each day (called dayArray[] for example) and add it to sectionInTable[] and do something like that :
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sectionsInTable.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return sectionsInTable.objectAtIndex(section).count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
cell.textLabel.text = String(sectionInTable.objectAtIndex(indexPath.section).objectAtIndex(indexPath.row))
return cell
}
Sorry if I did mistakes, I'm not familiar with swift but I think the idea can help.
I implemented generic algorithm to sort out any objects which can by identified by some day. I guess would be helpful in such cases:
protocol DayCategorizable {
var identifierDate: Date { get }
}
extension Array where Element: DayCategorizable {
var daySorted: [Date: [Element]] {
var result: [Date: [Element]] = [:]
let calendar = Calendar.current
self.forEach { item in
let i = calendar.startOfDay(for: item.identifierDate)
if result.keys.contains(i) {
result[i]?.append(item)
} else {
result[i] = [item]
}
}
return result
}
}