I have implemented a client for a paginated REST service where I populate the whole table with empty entries and then load them in the background using pagination when the user starts scrolling the table.
This seems to work very well except for a little detail: I am calling reloadRowsAtIndexPaths: in the main UI thread (because UIKit requires so), and I feel the animation sometimes makes me lose input touches.
This is, the user starts scrolling through the list and items start to load. Sometimes a reload animation happens at the same time the user is just touching the screen again, and this touch is not recognized. The scrolling then doesn't continue and stops. It doesn't happen all the time, and I guess it is an infrequent user case (the user is expected to read each row of results before scrolling down) but it annoys me to no end.
I'm doing most of my animations with blocks and the flag UIViewAnimationOptionAllowUserInteraction is really helpful, so is there a block version of reloadRowsAtIndexPaths where the reload animation would not block the user interaction with the scrolling?
I think the solution you seek is to background-load your data in a thread (as you describe) and only call reloadRowsAtIndexPaths: once the data is received and ready to display. You should also probably redisplay the data NOT animated.
I don't think there's anything you can do to get around the missed touches with your current scheme. My suggestion is to "do what iOS wants" rather than "try to cajole iOS to do what you want." Yes, probably not the answer you wanted to hear, but I think that's just the way it works.
(Btw, you can always use bugreporter.apple.com to make an enhancement request! :)
Related
I am attempting to run a database fetch process in the background without locking the user interface.
Currently I have a button that does this, but I would like it to be automatic so that it can get more results as user is browsing current results.
Here is the code that the button does, I would like to make this automatic and not lock the UI. Also if there is a way to pause the process, but continue where it left off if user goes to another screen that would also be very useful.
Thanks in advance!
-(IBAction)continueUpdatingResultsButtonPressed:(UIButton*)sender{
[findMoreButton removeFromSuperview];
[self continueFindingMoreRecipes]; //(do this in background without locking screen)
[self loadRefreshButton];//At completion load this button (a blinking button) to refresh the cells with new results
}
A typical pattern you can use is something like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// perform data processing here (done in the background)
dispatch_async(dispatch_get_main_queue(), ^{
// update user interface here (done on the main thread)
});
});
You could do batch requests where you cache the next X amount of answers every time your UI got with in Y of the current end. Depending on what you are using a lot of databases have protocols that can help you batch cache easily. Good luck!
Use grand central dispatch. Create a new queue, dispatch it with a block and when you need to update, call dispatch get main queue. There is no way to pause this once the queue has been dispatched though. Maybe load recipes into intermediary then update as needed.
Look for some gcd tutorials there are a few decent ones there.
Would give you more code but I'm typing on iPhone.
It strikes me (especially since you are, by your own admission, "very new to programming") that it might not be prudent to pursue GCD to prefetch data in a background queue, make sure you coordinate the background queue's database operations with the foreground's database operations (possibly via something like FMDB's FMDatabaseQueue or something equivalent), gracefully handle pausing this operation as you go to other screens (as you indicated in your question) and making sure you don't cause retain cycles in the process.
To make matters worse, I gather that this is all in pursuit of a possibly questionable goal, if I understand you correctly, to retrieve 10,000 recipes in the background. Don't get me wrong. You certainly can design all of the background operations like we've outlined, but I think you should stop and ask yourself whether that's the right design for your business problem.
I might suggest an infinitely easier solution. Just load your tableview with the first x recipes, and as the user scrolls down, when you start to approach the end of the tableview, detect that fact and retrieve the next x records and add them to the tableview. But any user interface that is expecting the user to flip through 10,000 entries doesn't pass the smell test. When I think of a database with 10,000 entries, I think of something more like a imdb/wikipedia/google/facebook-like user interface rather than a contacts-style user interface.
Regardless, you almost certainly don't want your app just endlessly retrieving recipes in the background. You can solve your UI performance issue, but maybe replace it with memory management issues. And you're going to design a complicated system architecture when it's not entirely clear whether your recipe app requires that.
i am developing a book reader on ipad. i am using the swipe gesture to navigate to the next page. But my app is getting hanged on crashed when i try to navigate (swipe) at a fast pace. So, is there any way to restrict the number of continuous or consecutive swipes…???
Please help with me with your inputs. They will help me go a long way in my project. Thankyou
You could throttle the number of swipes you are handling by skipping say every third swipe or so). However, that would just add frustration for the user, as they'll be swiping and nothing would be happening. And if you actually go that route, at least add a wait cursor, so that the user is aware the app is busy.
However, you really should figure out why the app is crashing (or hanging - not clear from your question) and fix it. If the user wants to leaf quickly through the pages, you should let them.
Use a flag that keeps track of when you are paging. When you swipe, only trigger the paging mechanism if the flag is NO, and immediately set it to YES when paging. When your paging mechanism finishes, set it back to NO.
Deleting or moving rows for a UITableView that is backed with local data (e.g., NSArray) is easy and instantaneous:
Remove the value from the array.
Call deleteRowsAtIndexPaths:withRowAnimation:.
Profit!
But my table view communicates with a web service, which means once the "Delete" button on that row gets tapped, I have to forward a request on to the server (via ASIHTTPRequest, of course), get the response, and then tell the table view to run its little delete-row animation, all with a few seconds of latency in between.
From a high-level, what's the best way to do that? Throw some callback selector into ASIHTTPRequest's userInfo dictionary? KVO?
Bonus points for some nice UI touch, like some kind of spinner on the soon-to-be-deleted cell.
The most elegant way I know of is to use Core Data for the local data, and to have your UITableView listening for change notifications from the Core Data store. This maintains model/view separation, and yet you can still get the eye candy of a row-deletion animation. It goes like this:
User says to delete a row.
Your code sends off an ASIHTTPRequest, and registers a selector to be called back when the request succeeds.
When it's done, the callback selector modifies the model that is in your Core Data store.
This automatically causes your UITableView to be notified that a row was deleted, so it can animate the deletion of the row.
This doesn't get the bonus points, because it doesn't give you any sort of UI indication that the cell will be deleted soon. One thing I've considered doing sometimes, but haven't been brave enough to try, is to optimistically code things on the assumption that the web-service call will almost certainly succeed, so go ahead and delete the row immediately, before the ASIHTTPRequest indicates success/failure; but then, if it does fail, roll back, and make the row re-appear. This has the advantage of making the app much faster in the common case where the web-service call succeeds. Core Data gives you a couple of ways to do this kind of "roll-back" stuff, but still, it definitely gets tricky, so it may not be worth it.
If you don't want to use Core Data, then to me KVO is the second-best solution.
EDIT: Thinking about it a bit more, in order to get a visual indicator that the row is about to be deleted, your Core Data model could have a flag on each row, "markedForDeletion" or something; when you fire off the ASIHTTPRequest, you would also modify the model to set markedForDeletion to true. The UITableView would be notified of the change (because it has registered for notifications); so it would then do whatever UI you want on top of the row, e.g. a spinner or something, to indicate that it's being deleted. Then, if the ASIHTTPRequest succeeds, it just deletes the row; but if it fails, it sets the rows "markedForDeletion" flag back to false, which causes the UITableView to turn off the spinner.
I have disabled interactions in my primary view (which contains some subviews I use as buttons).
I keep this disabled while I have a secondary view up indicating network activity (loading data). When it is finished, I re-enable interactions in the primary view.
This is so the user isn't tapping those buttons while the network operation takes place.
Anyway, all seems well, but the if the user starts tapping buttons in the primary view, once the re-enable occurs those touch events (from the past few seconds) actually trigger. Not the touches-began, which highlights the buttons, but the functions called on the touches-ended. Its like its queued the whole time it was disabled and "races to catch up".
It is very bizarre, why would touch events be queued while the view has its user interaction disabled?
It's hard to be confident of an answer here without seeing the code, but here is one idea:
If your network activity is synchronous, aka blocking, then it could be the case that the user's touches are queued before they get a chance to hit any of your code. If that's the case, then those touches won't be sent into your responder chain until the network activity finishes, and they'd never get a chance to "know" that you'd disabled interaction with those controls.
Here's one way you could help debug your situation: Add some debug NSLog statements in the top layer (what you call the secondary view) that's indicating network activity. Make sure the secondary view's frame is big enough to include those touches, and see if it logs the touches as soon as they happen. If not, my guess might be correct. If yes, well, you still get useful information - and you might be able to simply capture the touches at this level, instead of allowing them to queue up.
If this guess is correct, the least hacky fix I can think of would be make your network operations asynchronous.
If you don't want to do that, you could also try using an NSTimer to leave the network activity indicator up for a split second after the synchronous call completes. This way, your responder chain could clear out the queue of incoming touches, and ignore them if that's the desired behavior.
We have an app in AppStore Bust~A~Spook we had an issue with. When you tap the screen we use CALayer to find the position of all the views during their animation and if you hit one we start a die sequence. However, there is a noticeable delay, it appears as if the touches are buffered and we we receive the event to late. Is there a way to poll or any better way to respond to touches to avoid this lag time?
This is in a UIView not a UIScrollView
Are you using a UIScrollView to host all this? There's a property of that called delaysContentTouches. This defaults to YES, which means the view tries to ascertain whether a touch is a scroll gesture or not, before passing it on. You might try setting this to NO and seeing if that helps.
This is a pretty old post about a seasonal app, so the OP probably isn't still working on this problem, but in case others come across this same problem and find this useful.
I agree with Kriem that CPU overload is a common cause of significant delay in touch processing, though there is a lot of optimization one can do before having to pull out OpenGL. CALayer is quite well optimized for the kinds of problems you're describing here.
We should first check the basics:
CALayers added to the main view's layer
touchesBegan:withEvent: implemented in the main view
When the phase is UITouchPhaseBegan, you call hitTest: on the main view's layer to find the appropriate sub-layer
Die sequence starts on the relevant model object, updating the layer.
Then, we can check performance using Instruments. Make sure your CPU isn't overloaded. Does everything run fine in simulator but have trouble on the device?
The problem you're trying to solve is very common, so you should not expect a complex or tricky solution to be required. It is most likely that the design or implementation has a basic flaw and just needs troubleshooting.
Delayed touches usually indicates a CPU overload. Using a NSTimer for frame-to-frame based action is prone to interfering with the touch handling.
If that's the case for your app, then my advice is very simple: OpenGL.
If you're doing any sort of core-animation animation of the CALayers at the same time as you're hit-testing, you must get the presentationLayer before calling hitTest:, as the positions of the model layers do not reflect what might be on screen, but the positions to which the layers are animating.
Hope that helps.