Indicate when tableView indexPath is last one - swift

I am implementing showing post part, I want it to do pagination, I got Query part. What I understand is I set limited number of posts when user reached last post that updated in tableView, query fetches more objects by next set limit.
For these, I need something indicates when tableView indexPath is reached at last one or before last one.
I searched some, I think this is good one, but I don't know what does mean.
Get notified when UITableView has finished asking for data?
Could anyone explain me what does mean(how it works) and how to subclass in UITableView's reloadData for this?
fun reloadData() {
print("begin reload")
super.reloadData()
print("end reload")
}
I add my fetching code, I don't think it is working.
var limit = 10
var skip = 0
func fetchAllObjectsFromParse() {
//empty postArray
postsArray = []
//bring data from parse
let query = PFQuery(className: "Posts")
query.limit = limit
query.orderByDescending("createdAt")
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error) -> Void in
if error == nil && objects != nil{
for object in objects! {
self.postsArray.append(object)
}
if (objects!.count == self.limit){
let query = PFQuery(className: "Posts")
self.skip += self.limit
query.skip = self.skip
query.limit = self.limit
print(self.limit)
print(self.skip)
query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if error == nil && objects != nil {
for object in objects! {
self.postsArray.append(object)
print(objects?.count)
}
}
})
dispatch_async(dispatch_get_main_queue(),{
self.tableView.reloadData()
})
}
}else{
print(error?.localizedDescription)
}
}
}

First let's tackle pagination. Now pagination isn't undoable but it does require your server to accept pages so that it knows which set of results to return. For example if you have 100 results in group sizes of twenty, in your url request you'd have to specify if you wanted page 0 - 4.
I would personally recommend using an open-source library to take care of pagination for you. Take a look at MMRecord, it does have some pagination support for you. If you are feeling ambitious and you want to implement it on your own, you'll need to build some networking service, best built on something like AFNetworking that keeps track of the last requested page.
In swift, that would look something like this:
NetworkManager.GET('http://someURI', parameters: [:], page: 0)
Somehow you have to keep the state of which page is next around. A better design, as suggested here, is to use protocols and some sort of datasource wrapper that abstracts the mechanism from the client.
Now you also asked how to detect when a table view scrolls to the end so you can fire off a network request. One way to do it is simple observe scrollViewDidScroll (UITableView inherits from UIScrollView) and check to see if the contentOffset is >= self.tableView.contentSize - self.tableView.frame.size.height (which will be the content offset of when the table is all the way scrolled). However you want the user of your application to be able to keep scrolling without having to scroll to the bottom, waiting for it to load, then continue scrolling so what I would is fire the request when they've scrolled 3/4 down the page or even halfway if you know the request will take a long time. That way by the time they get to the bottom, it'll already have loaded the data and the user will never know the difference.
EDIT:
I would add a few suggestions for you apart from my answer. 1) Take a look at this: http://artsy.github.io/blog/2015/09/24/mvvm-in-swift/
It talks about the MVVM design pattern which is much better for holding state instead of having state variables in your view controller.
2) your function, fetchAllObjectsFromParse is a bad name for a couple of reasons. First, the name implies that the function fetches all the objects at once, which isn't the case if you want it paginated.
Maybe something like this:
func fetchObjectsForModel(model: String, atPage page: Int, withBlock block: ([PFObejct], NSError?) -> Void {
}
And then make another function which will generate your query:
func generateQueryWithSkips(skip: Int, limit: Int) -> PFQuery {}

Related

Nill value when passing data beetween related entities in Core Data

I'm trying for some days now (without success) saving on Core Data some properties of an entity ("Alumno") just after saving on its "children" viewController ("Especialidad") its related value.
Mine is a music teaching app, just as a contacts one. I pass the selected "Especialidad" (the played instrument in this case), after saving it to Core Data, for the correspondent "segue" to be performed, from its table view
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
especialidadSeleccionada = especialidades[indexPath.row]
performSegue(withIdentifier: "GuardarEspecialidad", sender: self)
}
to its related "parent", the complete pupil's data, containing this "Especialidad", among others properties.
Next, I try to save every pupil's data on Core Data:
if nombreField.text != "" && especialidadField.text != "" {
let context = (UIApplication.shared.delegate as!
AppDelegate).persistentContainer.viewContext
let nuevoAlumno = Alumno(context: context)
nuevoAlumno.nombre = nombreField.text!
print("Data before: \(especialidadSeleccionada)")
nuevoAlumno.especialidadRelacionada?.nombre = especialidadSeleccionada?.nombre
print("Data after: \(nuevoAlumno.especialidadRelacionada?.nombre)")
//... and so on with the rest of properties
do {
try context.save()
} catch {
print("error")
}
But it saves every property EXCEPT "nuevoAlumno.especialidadRelacionada?.nombre", marked as nill. As you can see, for debugging purposes, I print its value BEFORE the equality, obtaining a non-optional value, and AFTER it, getting... an optional value!!
I'm almost sure that it has to be something about their relationship. I've double-checked that "Especialidades" exact data is correct, but I really can't find the way to save "especialidad.nombre" in this "Alumno" entity...
An idea: maybe using "didSet" for that property of "Alumnos" could help? I've tried, but don't know how.
Any help? Thanks in advance!
I have reached to a solution. I was just using an incorrect property syntax:
nuevoAlumno.especialidadRelacionada = especialidadSeleccionada
Now it works.
Looks to me like nuevoAlumno.especialidadRelacionada might be nil. Note that you're printing two different things in your before/after. I'd say set a breakpoint on this line:
nuevoAlumno.especialidadRelacionada?.nombre = especialidadSeleccionada?.nombre
and inspect all the values there.

CollectionView.cellForItem returning nil

I've created a collectionView in Storyboard and then put the Delegate and DataSource methods in an extension to the ViewController which manages that screen.
The collectionView uses a layoutDelegate to show a four-by-four grid of images. All cells are shown in the grid, so a cell not being visible isn't a problem and they are all instances of the class imageCVC, a subclass of UICollectionViewCell
This all loads without a problem, but I now want to manipulate four random images before passing control to the user. Mindful that the collectionView may not have fully loaded by the end of viewDidLoad, I call the routine that chooses which image to manipulate, changeImages() in the viewDidLayoutSubviews method. The function is as follows:
func changeImages() {
collectionView.layoutIfNeeded()
let maxChanges = 30
var imageIndex = 0
var imageChanges 0
while imageChanges < maxChanges {
imageIndex = Int.random(in: 0..<(collectionView.numberOfItems(inSection: 0)))
if let cell = collectionView.cellForItem(as: IndexPath(row: imageIndex, section: 0)) as? imageCVC {
changeCell(cell)
imagesChanges += 1
}
}
}
(EDIT: Incorporated Sam's suggestion (below), but it still always returns nil!)
Unfortunately, whilst the imageIndex gets set correctly (so the collection knows how many elements it has), the cellForItem call always returns nil. I've forced the layout at the beginning of the function, but it has no effect.
Please could someone let me know what I'm doing wrong? Many thanks in advance.
In the following line:
imageIndex = Int.random(in: 0...(collectionView.numberOfItems(inSection: 0)))
The code starts from 0 and goes all the way to the collection view items count, so if the count is 10, the code goes from 0 to 10 including 10 which is 11 items in total. This is probably what is causing the crash since there are only 10 items and we try to access 11 items.
Just change:
0...(collectionView.numberOfItems(inSection: 0)
To
0..<(collectionView.numberOfItems(inSection: 0)
After further investigation, it appears that the collectionView data is not being loaded until after the viewDidLayoutSubviews - which seems a little contradictory to me, but hey, I'm sure there's a good reason... - and so I have implemented what I consider to be a work-around.
I've taken the call to changeImages() out from the viewDidLayoutSubviews and put it into the completion segment of a DispatchQueue.main..., written in the viewDidLoad, as follows:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
self.changeImages()
})
Essentially, I'm giving the system time (0.3 seconds) to complete it's full loading of the subviews, rather than actually placing my code at the correct part of the cycle when I know that the views have been fully loaded. A solution but, I suspect, an inelegant one.
If anyone knows how I should be approaching it, I'd be very interested to hear. Thanks.

Impossible Index of of Range crash

In application I am working on, I have to take one element of the array by specifying element's index in the array, like this array[index].
It's a simple UICollectionView which is populated with items I am getting from the array.
In order to guard against Index out of range exception, I am doing this:
guard index < array.count else { return }
return array[index]
Even though I have this guard, I got an Index out of range exception on the array[index] line (but not always).
I don't know how this can happen.
I have even added another check:
extension Collection where Indices.Iterator.Element == Index {
subscript (optional index: Index) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
So I am doing this (this is the actual code snippet from the application):
...
guard let section = chatSections[optional: indexPath.section] else {
return nil
}
guard indexPath.item < section.itemViewModels.count else {
return nil
}
return section.itemViewModels[optional: indexPath.item]
It doesn't always happen, but sometimes I get the Index out of Range exception there.
I was debugging most of the day, trying to figure out conditions when crash happens so I might be able to figure out why, but it seems to happen randomly.
Does anyone have any idea how is this possible? Did anyone encounter this kind of issue?
Are you, by any chance, doing any of your updating from a background thread/queue? If so, make sure your UI interactions are done on the main thread/queue and that you aren't changing the array contents behind your UI's back.
That is, if you're changing the array contents in one queue and trying to update your UI while this is happening, your guard statement could be passing just before the array is modified elsewhere, then by the time the rest of your UI-interacting code executes, the index my no longer be valid.
Without a more complete picture, it's hard to say what's going on, but all these bounds checks you're adding in order to guard against mysteriously-changing array indexes are a big clue to multithreading shenanigans.
I believe you have multiple access to the your data source array (multiple thread try to access add/remove from the array).
to overcome this you should use something to enforce synchronization while accessing your array, there is multiple approce using semaphore or DispatchGroup.
I would recommend to use semaphore since the array is considered as shared resource, example:
private let semaphore = DispatchSemaphore(value: 1)
private var _array:[Item] = []
var array:[Item] {
get {
semaphore.wait()
let result = self._allMessages
defer {
semaphore.signal()
}
return result
}
set {
semaphore.wait()
self. _array = newValue
semaphore.signal()
}
}
and use the array variable to access the array data source not the private _array.

Detect active NSSplitViewItem

I have a SplitView with (several) SplitViewItems. Echt SplitViewItem has a ViewController with multiple views in it.
I need to detect which of the SplitViewItems has the focus of the user.
For example: if the user clicks on any control/view (or navigates to it in any other way), the background of the SplitViewItem that contains that view item should change. As I do not know which/how many views will be enclosed in the ViewController in the SplitViewItem, I'd prefer to detect which SplitViewItem is the 'active' one in the SplitViewController.
I have been searching for a solution all day long. I could not find any kind of notification, nor found a way to solve this managing the responder chain.
Can anybody please point me in the right direction? A (swift) code example would be highly appreciated.
Thanks!
It took me quite some research, but I found a working solution. Not the most elegant, but working.
I found the best approach to be to add an event monitor to the SplitViewController.
In viewDidLoad() add the following code:
NSEvent.addLocalMonitorForEvents(matching: [.keyDown, .leftMouseDown, .flagsChanged]) { [unowned self] (theEvent) -> NSEvent? in
let eventLocation = theEvent.locationInWindow
let numberOfSplitViews = self.splitViewItems.count
var activeIndex: Int?
for index in 0..<numberOfSplitViews {
let view = self.splitViewItems[index].viewController.view
let locationInView = view.convert(eventLocation, from: nil)
if ((locationInView.x > 0) && (locationInView.x < view.bounds.maxX) && (locationInView.y > 0) && (locationInView.y < view.bounds.maxY)) {
activeIndex = index
break
}
}
switch theEvent.type {
case .keyDown:
print("key down in pane \(activeIndex)")
self.keyDown(with: theEvent)
case .leftMouseDown, .rightMouseDown:
print("mouse down in pane \(activeIndex)")
self.mouseDown(with: theEvent)
case .flagsChanged:
print("flags changed in pane \(activeIndex)")
self.flagsChanged(with: theEvent)
default:
print("captured some unhandled event in pane \(activeIndex)")
}
return theEvent
}
(you may have to tweak the relevant events to your own liking. Also, you may need to remover the monitor using NSEvent.removeMonitor(_:)).
In addition (out of scope of this question), you might also want to consider making the variable activeIndex an observable class variable (I did this using RxSwift), allowing you to easily react on any change happening inside the 'active pane'.
Any more elegant/simple solutions are welcome!

How to make a serial queue with GCD

I tried to make a serial queue for network operations with GCD like this:
let mySerialQueue = dispatch_queue_create("com.myApp.mySerialQueue", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0))
func myFunc() {
dispatch_async(mySerialQueue) {
do {
// Get object from the database if it exists
let query = PFQuery(className: aClass)
query.whereKey(user, equalTo: currentUser)
let result = try? query.getFirstObject()
// Use existing object or create a new one
let object = result ?? PFObject(className: aClass)
object.setObject(currentUser, forKey: user)
try object.save()
} catch {
print(error)
}
}
}
The code first looks for an existing object in the database.
If it finds one, it updates it. If it doesn't find one, it creates a new one. This is using the Parse SDK and only synchronous network functions (.getFirstObject, .save).
For some reason it seems that this is not executed serially, because a new object is sometimes written into the database, although one existed already that should have been updated only.
Am I missing something about the GCD?
From the documentation on dispatch_queue_attr_make_with_qos_class:
relative_priority: A negative offset from the maximum supported scheduler priority for the given quality-of-service class. This value must be less than 0 and greater than MIN_QOS_CLASS_PRIORITY
Therefore you should be passing in a value less than 0 for this.
However, if you have no need for a priority, you can simply pass DISPATCH_QUEUE_SERIAL into the attr argument when you create your queue. For example:
let mySerialQueue = dispatch_queue_create("com.myApp.mySerialQueue", DISPATCH_QUEUE_SERIAL)