UITableView Section Titles - ios

I am attempting to create a UITableView with sections from my Parse objects. I have the Parse objects successfully. However, I am not sure how or what the best way to get the dates out of the objects to create my sections titles would be.
If I have an array of the objects should I just for through and create a date list? How would I relate that back to the date and that the object should be under that date
I was thinking of using a struct with Date and Item vars to hold the data cleaner, but I don't know I can do it that way either
Any suggestions / examples of what is the best way to do this?
EDIT:
func getExpenses() {
let query = ExpenseItem.query()
query?.order(byDescending: "expenseDate")
query?.findObjectsInBackground(block: { (expenseResult, error) in
if error == nil {
var sectionArray = [ExpenseItem]()
for object in expenseResult as! [ExpenseItem] {
if let firstObject = sectionArray.first {
if Calendar.current.compare(firstObject.expenseDate, to: object.expenseDate, toGranularity: .day) != .orderedSame {
self.tableData.append(sectionArray)
sectionArray = [ExpenseItem]()
}
} else {
sectionArray.append(object)
}
}
if sectionArray.count != 0 {
self.tableData.append(sectionArray)
}
}
})
}

Sort your array data from Parse into ascending order by date.
Declare an array of arrays of PFObject (or your PFObject subclass if you have one): var tableData: [[PFObject]]()
Loop through your Parse data, splitting it by date:
var sectionArray = [PFObject]()
// pfObjects contains the objects you retrieved from Parse, ordered by date
for object in pfObjects {
if let firstObject = sectionArray.first {
if object["dateField"] != firstObject["dateField"] {
self.tableData.append(sectionArray)
sectionArray = [PFObject]()
}
}
sectionArray.append(object)
}
if sectionArray.count != 0 {
self.tableData.append(sectionArray)
}
Now you have the data structure you need. The number of sections is the count of the outer array and the number of items in each section is the number of items in each inner array:
override func numberOfSections(in tableView: UITableView) -> Int {
return self.tableData.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableData[section].count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = self.tableData[indexPath.section][indexPath.row]
// deque a cell and configure it with item
}
To get the section header for each section, you can just use the date of the first object in that section's array, since all objects in the section have the same date.

Related

How do I reorder UITableView cells in Realm Swift?

I'm using Realm for my Note-taking-app.
I used core data before and I am now migrating core data to realm, but i got trouble! reordering objects like this causes error.
do {
let realm = try Realm()
let source = myNote[sour!.row]
try realm.write() {
myNote.remove(objectAtIndex: sourceIndexPath!.row)
myNote.insert(source, at: destensionIndexPath!.row)
}
}
catch {
print("handle error")
}
So I added
orderPosition property to my object
dynamic var orderPosition: Int = 0
and changed tableView moveRowAtIndexPath to this
ReorderingRealmResultsInTableView.swift
but it didn't helped a lot.
so How can I reorder objects in realm?
I'd encourage you to store ordered items in a List rather than sorting based on an orderPosition property.
Storing the index manually will be much less performant when moving an item, because all objects between the "old index" and "new index" will need to be mutated to account for the change.
You can then use List.move(from:to:) to move an object from one index to another, which should correspond directly to the indices in the table view you're reordering.
Here's a tutorial you can follow guides you through building a task management app, including support for reordering tasks: https://realm.io/docs/realm-mobile-platform/example-app/cocoa/
A List is certainly efficient and clean, though I wasn't sure how I would sync it through a server. So in my case I'm using orderPosition: Double, and the value is calculated as the middle of the two existing orderPositions that the object is inserted between. Also keep in mind that you can perform the write without updating the tableView from the notification: try! list.realm?.commitWrite(withoutNotifying: [notificationToken!]).
As others suggested, List is the solution. Here is the example to implement the approach on Swift 5:
import UIKit
import RealmSwift
// The master list of `Item`s stored in realm
class Items: Object {
#objc dynamic var id: Int = 0
let items = List<Item>()
override static func primaryKey() -> String? {
return "id"
}
}
class Item: Object {
#objc dynamic var id: String = UUID().uuidString
#objc dynamic var name = ""
}
class ViewController: UITableViewController {
let realm = try! Realm()
var items = RealmSwift.List<Item>()
override func viewDidLoad() {
super.viewDidLoad()
// initialize database
var itemsData = realm.object(ofType: Items.self, forPrimaryKey: 0)
if itemsData == nil {
itemsData = try! realm.write { realm.create(Items.self, value: []) }
}
items = itemsData!.items
// temporarily add new items
let newItem1 = Item()
newItem1.name = "Item 1"
let newItem2 = Item()
newItem2.name = "Item 2"
try! realm.write {
items.append(newItem1)
items.append(newItem2)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
...
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
try! items.realm?.write {
items.move(from: sourceIndexPath.row, to: destinationIndexPath.row)
}
}
}
You can find example applications for both iOS and macOS in GitHub Repo (https://github.com/realm/realm-cocoa) under examples/, demonstrating how to use many features of Realm like migrations, how to use it with UITableViewControllers, encryption, command-line tools and much more.

Proper way to load data to TableView

I have an application that loads list of questions from JSON data and shows them on TableView.
Everything is working fine most of the time but it seems to be that I am doing something wrong and that is why - app crashes.
It happens rarely so it is hard to detect but I am sure that there must a problem with the logic.
So I have model class for question and array for question items :
class questionItem {
var id = 0
var title : String = ""
var question : String = ""
}
var questions = [questionItem]()
Inside my ViewController I have IBOutlet for TableView and I placed data loading inside viewDidLoad
class QuestionsListVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var questionsTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
get_questions()
}
func get_questions()
{
let request = NSMutableURLRequest(URL:myURL!)
request.HTTPMethod = "POST"
let postString = ""
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{
data, response, error in
if error != nil {
print("error=\(error)")
return
}
//clearing array for new items
questions.removeAll(keepCapacity: false)
dispatch_async(dispatch_get_main_queue(),{
var json = JSON(data: data!)
if let items = json["questions"].array {
for item in items {
let question = questionItem()
question.id = item["id"].int!
question.title = item["title"].string!;
question.question = item["question"].string!
questions.append(question)
}
}
self.questionsTableView.reloadData()
});
}
task.resume()
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return questions.count
}
Error is shown inside cellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : QuestionsListCell = self.questionsTableView.dequeueReusableCellWithIdentifier("QuestionsListCell") as! QuestionsListCell
//error happens here - Index out of range
print(questions[indexPath.row].title)
It happens one time in six cases and there is no errors in other 5 of 6 tests - but I don't understand why.
This points to a problem with the
numberOfSectionsInTableView
and/or
numberOfRowsInSection
Can you post your current implementation of these?
If you only displaying one continuous list, the numberOfSectionsInTableView should always return 1, and you need to check numberOfRowsInSection is accurately returning the number of items in the datasource.
Edit:
Can you try clearing the existing datasource on the main thread immediately before updating with the new items as in the code below:
dispatch_async(dispatch_get_main_queue(),{
questions.removeAll(keepCapacity: false)
var json = JSON(data: data!)
if let items = json["questions"].array {
for item in items {
let question = questionItem()
question.id = item["id"].int!
question.title = item["title"].string!;
question.question = item["question"].string!
questions.append(question)
}
}
self.questionsTableView.reloadData()
});
The call to questions.removeAll in your code makes the following sequence of events possible:
numberOfRowsInSection is called before questions.removeAll, returning the old non-zero capacity
questions.removeAll clears questions
cellForRowAtIndexPath is called before questions are re-populated, causing index out of range exception
One way to fix is is relatively straightforward: make a newQuestions array, populate it in get_questions, and swap it in when numberOfRowsInSection is called:
// Add this to your class
var newQuestions : [questionItem]
// Change get_questions:
dispatch_async(dispatch_get_main_queue(), {
var json = JSON(data: data!)
if let items = json["questions"].array {
var tmpQuestions = [questionItem]()
for item in items {
let question = questionItem()
question.id = item["id"].int!
question.title = item["title"].string!;
question.question = item["question"].string!
tmpQuestions.append(question)
}
newQuestions = tmpQuestions
self.questionsTableView.reloadData()
}
});
// Change numberOfRowsInSection
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
if newQuestions != nil {
questions = newQuestions
newQuestions = nil
}
return questions.count
}
Note how get_questions does not populate newQuestions directly. Instead, it builds tmpQuestions, and sets it to newQuestions only when it is fully built.
Try with below code,
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell : QuestionsListCell = self.questionsTableView.dequeueReusableCellWithIdentifier("QuestionsListCell") as! QuestionsListCell
if let objModel : questionItem = questions[indexPath.row] as? questionItem
{
print(objModel.title)
}
}
Change your cellForRow with this method. Hope this helps you.
Just to avoid crash, I would have added following three safe checks.
1. Check Array count before clearing out.
if questions.count > 0
{
questions.removeAll()
}
2. Check array count before table Reload
if questions.count > 0
{
self.questionsTableView.reloadData()
}
3. In cellForRowAtIndex method Check for value in array of object, before putting on Cell
if let objModel : questionItem = questions[indexPath.row] as? questionItem
{
print(" title is \(objModel.title)")
}

swift PFQueryTableViewController - remove duplicate objects before presenting cells

I have two Classes in Parse, one contains details of images that users have liked ("Liked"), the second class lists users that are following other users ("Follows").
In my PFQueryViewController I am able to create a query in queryForTable() that does the following:
//get username of users being followed
let query:PFQuery = PFQuery(className:"Follows")
query.whereKey("fromUser", equalTo: (PFUser.currentUser()?.username)!)
//get images of all followed users
let imageQuery:PFQuery = PFQuery(className: "Liked")
imageQuery.whereKey("username", matchesKey: "toUser", inQuery: query)
//get images current user liked
let userImagesQuery:PFQuery = PFQuery(className: "Liked")
userImagesQuery.whereKey("username", equalTo: (PFUser.currentUser()?.username)!)
//combine liked images of following and current user
let combinedQuery:PFQuery = PFQuery.orQueryWithSubqueries([imageQuery,userImagesQuery])
if(objects?.count == 0)
{
combinedQuery.cachePolicy = PFCachePolicy.CacheThenNetwork
}
combinedQuery.orderByDescending("createdAt")
return combinedQuery
This works where I am able to display the liked images of the user, and the users being followed, in cellForRowAtIndexPath by querying the imageID of the object for each cell. However, multiple users can like the same image, so if a user and someone they are following like the same image, the image will appear twice in the table.
Is there a way to force the query to ignore objects when it finds a duplicate based on the ImageID key?
I am aware this can be done by executing the query and iterating through the objects, however this wont work in this scenario as I need the duplicates to be removed before the query is returned in queryForTable().
I managed to solve this by using objectsDidLoad(). This captures the objects before they are sent to the table, therefore I was able to iterate through each object and delete any duplicates.
I store all the loaded objects into a new array and remove an object if a duplicate is found. I then make the object in cellForRowAtIndexPath the corresponding object in the array.
override func objectsDidLoad(error: NSError?) {
super.objectsDidLoad(error)
displayedImages.removeAll()
imageObjects = NSMutableArray(array: objects!)
for item in imageObjects {
if displayedImages.count == 0 {
let image = item["imageid"] as! NSNumber
displayedImages.insert(image, atIndex: 0)
}else{
var count = 0
let image = item["imageid"] as! NSNumber
for imageToCheck in displayedImages {
if imageToCheck != image {
count++
}
}
if count != displayedImages.count {
imageObjects.removeObject(item)
}else{
displayedImages.insert(image, atIndex: 0)
}
}
}
self.tableView.reloadData()
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return imageObjects.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! FeedTableViewCell
if let pfObject:PFObject = self.imageObjects.objectAtIndex(indexPath.row) as? PFObject {
//display images as before

Array has append, but no value inside

I've declared a String array in my Swift file.
var postTitleArray : [String] = []
In my viewDidLoad method, I've append values into my array...
RestApiManager.sharedInstance.makeGetRequest("/development/vw_posts?filter=parent_term_id%20%3D%20%22%5B82%5D%22", onCompletion: { json in
for result in json["record"].arrayValue
{
let postTitle = result["post_title"].stringValue
print(postTitle)
self.postTitleArray.append(postTitle)
}
})
Other than that, I've use the count of array into the numberOfRowsInSection to set the rows number of my tableView...
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return self.postTitleArray.count
}
However, the numberOfRowsInSection has return 0, why could it happened, I've assigned value into my postTitleArray, anyone can help me take a look on this? Appreciated...
Your API call is likely not finished before numberOfRowsInSection is called.
Try calling reloadData on your UITableView instance after you have populated your postTitleArray. (I'm assuming here you have a reference to your UITableView and it's called tableView)
RestApiManager.sharedInstance.makeGetRequest("/development/vw_posts?filter=parent_term_id%20%3D%20%22%5B82%5D%22", onCompletion: { json in
for result in json["record"].arrayValue
{
let postTitle = result["post_title"].stringValue
print(postTitle)
self.postTitleArray.append(postTitle)
}
self.tableView.reloadData()
})
You should check :
if let postTitle = result["post_title"]?.stringValue{
self.postTitleArray.append(postTitle)
self.tableView.reloadData()
}

Realm date-query

In my RealmSwift (0.92.3) under Xcode6.3, how would I
// the Realm Object Definition
import RealmSwift
class NameEntry: Object {
dynamic var player = ""
dynamic var gameCompleted = false
dynamic var nrOfFinishedGames = 0
dynamic var date = NSDate()
}
The current tableView finds the number of objects (i.e. currently all objects) like follows:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let cnt = RLM_array?.objects(NameEntry).count {
return Int(cnt)
}
else {
return 0
}
}
First question: How would I find the number of objects that have a date-entry after, let's say, the date of 15.06.2014 ?? (i.e. date-query above a particular date from a RealmSwift-Object - how does that work ?). Or in other words, how would the above method find the number of objects with the needed date-range ??
The successful filling of all Realm-Objects into a tableView looks as follows:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("NameCell") as! PlayersCustomTableViewCell
if let arry = RLM_array {
let entry = arry.objects(NameEntry)[indexPath.row] as NameEntry
cell.playerLabel.text = entry.player
cell.accessoryType = entry.gameCompleted ? .None : .None
return cell
}
else {
cell.textLabel!.text = ""
cell.accessoryType = .None
return cell
}
}
Second question: How would I fill into the tableView only the RealmSwift-Objects that have a particular date (i.e. for example filling only the objects that have again the date above 15.06.2014). Or in other words, how would the above method only fill into the tableView the objects with the needed date-range ??
You can query Realm with dates.
If you want to get objects after a date, use greater-than (>), for dates before, use less-than (<).
Using a predicate with a specific NSDate object will do what you want:
let realm = Realm()
let predicate = NSPredicate(format: "date > %#", specificNSDate)
let results = realm.objects(NameEntry).filter(predicate)
Question 1: For the number of objects, just call count: results.count
Question 2: results is an array of NameEntrys after specificNSDate, get the object at indexPath. Example, let nameEntry = results[indexPath.row]
For creating a specific NSDate object, try this answer: How do I create an NSDate for a specific date?

Resources