EGORefreshTableHeaderView checking connection - iphone

I use EGORefreshTableHeaderView to update my tableview.
Here I call my method to download the database:
- (void)reloadTableViewDataSource
{
[self downloadNewsDatabase];
_reloading = YES;
}
In this method (downloadNewsDatabase) I check internet connection. If the device is connected, I download my xml, I saved datas, ecc. Then I call this method
[self performSelector:#selector(doneLoadingTableViewData)];
And this works!
The problem is if the device is not connected. In fact, in that case I call immediately the doneLoadingTableViewData method but when I try to update one more time the tableview the EGOOPullRefreshLoading state is not set.
I think there is a problem with the contentOffset.y of the tableview (in egoRefreshScrollViewDidEndDragging the scrollView.contentOffSet is now 0.0 so the condition scrollView.contentOffset.y <= - 65.0f doesn't work anymore) but... what is wrong in my simple code?

just add a delay on it, like:
[self performSelector:#selector(doneLoadingTableViewData) withObject:nil afterDelay:1.0];
Then it will work.

Related

Using Cocos2D scheduleUpdate to delay loading

I have an algorithm that takes a few seconds to load some stuff, and I want to first set the string on a label to say "loading" before the actual loading begins. This is all within the same layer, this is not switching between scenes.
I thought I could simply do this:
-(void)startLoading{
[self unscheduleAllSelectors];//just in case the update is already scheduled
[self.loadingLabel setString:#"Loading...."];
[self scheduleUpdate];
}
Then, I have this:
-(void)update:(ccTime)delta{
[self unscheduleUpdate];
[self beginLoading];//another method that loads all the stuff
}
My understanding is that my method beginLoading should not run until the next frame. Thus my label should get updated correctly. However this is not happening. There is a slight freeze while all my assets get loaded and my label never gets updated before the loading begins.
Am I missing a step?
Nope ure not missing anything. I stopped fighting this and now use this kind of 'delayed' task catapult. It should make certain you will get a draw in the transition from the first to the second tick:
-(void) startLoading{
_loadTicker=0; // an NSUInteger iVar declared in the .h
[self schedule:#selector(tickOffLoading:)];
}
-(void) tickOffLoading:(ccTime) dt{
_loadTicker++;
if(_loadTicker==1) {
[self.loadingLabel setString:#"Loading...."];
} else {
[self unschedule:#selector(tickOffLoading:)];
[self beginLoading];
}
}

how to display UIActivityIndicatorView BEFORE rotation begins

I'd like to display an activity indicator BEFORE the work undertaken by willAnimateRotationToInterfaceOrientation:duration: begins. Most of the time in my app, this work is quickly completed and there would be no need for an activity indicator, but occasionally (first rotation, i.e. before I have cached data, when working with a large file) there can be a noticeable delay. Rather than re-architect my app to cope with this uncommon case, I'd rather just show the UIActivityIndicatorView while the app generates a cache and updates the display.
The problem is (or seems to be) that the display is not updated between the willRotateToInterfaceOrientation:duration and the willAnimateRotationToInterfaceOrientation:duration: method. So asking iOS to show UIActivityIndicator view in willRotate method doesn't actually affect the display until after the willAnimateRotation method.
The following code illustrates the issue. When run, the activity indicator appears only very briefly and AFTER the simulateHardWorkNeededToGetDisplayInShapeBeforeRotation method has completed.
Am I missing something obvious? And if not, any smart ideas as to how I could work around this issue?
Update: While suggestions about farming the heavy lifting off to another thread etc. are generally helpful, in my particular case I kind of do want to block the main thread to do my lifting. In the app, I have a tableView all of whose heights need to be recalculated. When - which is not a very common use case or I wouldn't even be considering this approach - there are very many rows, all the new heights are calculated (and then cached) during a [tableView reloadData]. If I farm the lifting off and let the rotate proceed, then after the rotate and before the lifting, my tableView hasn't been re-loaded. In the portrait to landscape case, for example, it doesn't occupy the full width. Of course, there are other workarounds, e.g. building a tableView with just a few rows prior to the rotate and then reloading the real one over that etc.
Example code to illustrate the issue:
#implementation ActivityIndicatorViewController
#synthesize activityIndicatorView = _pgActivityIndicatorView;
#synthesize label = _pgLabel;
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
{
NSLog(#"willRotate");
[self showActivityIndicatorView];
}
- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
{
NSLog(#"willAnimateRotation");
[self simulateHardWorkNeededToGetDisplayInShapeBeforeRotation];
}
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
{
NSLog(#"didRotate");
[self hideActivityIndicatorView];
}
- (void) simulateHardWorkNeededToGetDisplayInShapeBeforeRotation;
{
NSLog(#"Starting simulated work");
NSDate* date = [NSDate date];
while (fabs([date timeIntervalSinceNow]) < 2.0)
{
//
}
NSLog(#"Finished simulated work");
}
- (void) showActivityIndicatorView;
{
NSLog(#"showActivity");
if (![self activityIndicatorView])
{
UIActivityIndicatorView* activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[self setActivityIndicatorView:activityIndicatorView];
[[self activityIndicatorView] setCenter:[[self view] center]];
[[self activityIndicatorView] startAnimating];
[[self view] addSubview: [self activityIndicatorView]];
}
// in shipping code, an animation with delay would be used to ensure no indicator would show in the good cases
[[self activityIndicatorView] setHidden:NO];
}
- (void) hideActivityIndicatorView;
{
NSLog(#"hideActivity");
[[self activityIndicatorView] setHidden:YES];
}
- (void) dealloc;
{
[_pgActivityIndicatorView release];
[super dealloc];
}
- (void) viewDidLoad;
{
UILabel* label = [[UILabel alloc] initWithFrame:CGRectMake(50.0, 50.0, 0.0, 0.0)];
[label setText:#"Activity Indicator and Rotate"];
[label setTextAlignment: UITextAlignmentCenter];
[label sizeToFit];
[[self view] addSubview:label];
[self setLabel:label];
[label release];
}
#end
The app doesn't update the screen to show the UIActivityIndicatorView until the main run loop regains control. When a rotation event happens, the willRotate... and willAnimateRotation... methods are called in one pass through the main run loop. So you block on the hard work method before displaying the activity indicator.
To make this work, you need to push the hard work over to another thread. I would put the call to the hard work method in the willRotate... method. That method would call back to this view controller when the work is completed so the view can be updated. I would put show the activity indicator in the willAnimateRotation... method. I wouldn't bother with a didRotateFrom... method. I recommend reading the Threaded Programming Guide.
Edit in response to a comment: You can effectively block user interaction by having the willAnimateRotation... method put a non functioning interface on screen such as a view displaying a dark overlay over and the UIActivityIndicatorView. Then when the heavy lifting is done, this overlay is removed, and the interface becomes active again. Then the drawing code will have the opportunity to properly add and animate the activity indicator.
More digging (first in Matt Neuberg's Programming iPhone 4) and then this helpful question on forcing Core Animation to run its thread from stackoverflow and I have a solution that seems to be working well. Both Neuberg and Apple issue strong caution about this approach because of the potential for unwelcome side effects. In testing so far, it seems to be OK for my particular case.
Changing the code above as follows implements the change. The key addition is [CATransaction flush], forcing the UIActivityIndicatorView to start displaying even though the run loop won't be ended until after the willAnimateRotationToInterfaceOrientation:duration method completes.
- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
{
NSLog(#"willRotate");
[self showActivityIndicatorView];
[CATransaction flush]; // this starts the animation right away, w/o waiting for end of the run loop
}
- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
{
NSLog(#"willAnimateRotation");
[self simulateHardWorkNeededToGetDisplayInShapeBeforeRotation];
[self hideActivityIndicatorView];
}
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
{
NSLog(#"didRotate");
}
Try performing you work on a second thread after showing the activity view.
[self showActivityIndicatorView];
[self performSelector:#selector(simulateHardWorkNeededToGetDisplayInShapeBeforeRotation) withObject:nil afterDelay:0.01];
Either execute the heavy lifting in a background thread and post the results in the foreground thread to update the UI (UIKit is only thread safe since iOS 4.0):
[self performSelectorInBackground:#selector(simulateHardWorkNeededToGetDisplayInShapeBeforeRotation) withObject:nil]
Or you can schedule the heavy lifting method to be executed after the rotation took place:
[self performSelector:#selector(simulateHardWorkNeededToGetDisplayInShapeBeforeRotation) withObject:nil afterDelay:0.4]
But these are only hacks and the real solution is to have proper background processing if your UI needs heavy processing to get updated, may it be in portrait or landscape. NSOperation and NSOperationQueue is a good place to start.

Can't connect UIButton to Method on iPhone

Ok, so this has been driving me mad for a while, but it occasionally self corrects so I've dealt with it, but it's time to fix it!
Situation is as follows:
View created in Interface Builder for iOS. Contains a UIButton referenced as 'banana015'
Method in my implementation .m as follows:
- (IBAction)draggedOut: (id)sender withEvent: (UIEvent *) event {
if ([[NSUserDefaults standardUserDefaults] integerForKey:#"edittingmode"] == 3) {
UIButton *selected = (UIButton *)sender;
selected.center = [[[event allTouches] anyObject] locationInView:self.view];
if ([[NSUserDefaults standardUserDefaults] integerForKey:#"changeskin"] == 1) {
[self scifiTempSave];
}
else if ([[NSUserDefaults standardUserDefaults] integerForKey:#"changeskin"] == 2) {
[self bananaTempSave];
}
else if ([[NSUserDefaults standardUserDefaults] integerForKey:#"changeskin"] == 3) {
[self orangeTempSave];
}
}
else{}
}
And declared in my .h Header as
-(IBAction)draggedOut:(id)sender;
Now, when I connect the button to the 'draggedOut:' method in Interface Builder using the Touch Drag Inside event, I can drag the button around when I build and run the project (expected behaviour) however when I release the app crashes.
In the past I've been able to connect to a method that appears as "draggedout: withevent" which works beautifully. This evening I've connected some 50 items up to this 'draggedout: withevent' option and it works beautifully for each one - I have however got two UIButtons (identical in almost every way (different referencing outlets) that I have yet to connect to it, and I'll be damned if it'll let me.
Can anyone help? This has been infuriating for some time. The option disappears, yet all those that I previously connected to it still work perfectly, I just can't hook anything new up to it...or the old ones if I disconnect them.
The one problem I can see is that your method declaration in the header is not the same as your implementation. The header declaration should be the first line of the method in the implementation plus a semicolon:
- (IBAction)draggedOut: (id)sender withEvent: (UIEvent *) event;
To get more info on the crash, you should try setting NSZombieEnabled to true.
To answer your question in the comment, I don't know why you would get the option of a draggedout: method in addition to the one including withEvent if it's not declared in your code.

Cocos2d-iPhone: "update" called after dealloc

So I have a subclass of a CCSprite object, and in its init method, I call:
[self scheduleUpdate]
I later release this object from its parent CCNode like so:
[self removeChild:sprite cleanup:YES];
In addition, I call [self unscheduleUpdate] in the sprite's dealloc method.
However, I'm getting a bad memory access, so it appears that the update method is still attempted after the object is released (I've narrowed it down to this, as it works perfectly if I comment out the [self scheduleUpdate] line.
Any ideas?
Found this post in an attempt to ask the same question. I tried unschedule update (within my init method as well) with no luck, but then realized that by moving the [self unscheduleUpdate]; to the actual update method (which is running continuously, unlike the init method) based on a condition it worked!
So, for those looking for copy paste, here's a progress bar example that I'm implementing from http://www.ccsprite.com/cocos2d/using-ccprogresstimer-cocos2d-example.html#HCB_comment_box
-(id) init
{
//initialize progress bar, make sure to add a file named green_health_bar.png to your
//resource folder
timer = [CCProgressTimer progressWithFile:#"green_health_bar.png"];
//set the progress bar type to horizontal from left to right
timer.type = kCCProgressTimerTypeHorizontalBarRL;
//initialize the progress bar to zero
timer.percentage = 0;
//add the CCProgressTimer to our layer and set its position
[self addChild:timer z:1 tag:20];
[timer setPosition:ccp(100, 280)];
[self scheduleUpdate];
}
and in your update method:
-(void)update:(ccTime)dt
{
//get progress bar
CCNode* node = [self getChildByTag:20];
timer.percentage += dt * 10;
if (timer.percentage >= 100)
{
[self gameOver]; //used to stop parallax and show gameover menu
[self unscheduleUpdate];
}
}
I usually don't allow forum reply emails, but feel free to ask questions via #russ152!
Hmm.. try not to use scheduleUpdate? I tried looking for self unscheduleUpdate but there is not such function in a CCNode.. You can try [self unscheduleAllselectors], which stops all selectors of the object , including the update selector, if you are not using the object anymore.. Or use custom selectors instead..

connectionDidFinishLoading - how to force update UIView?

I am able to download a ZIP file from the internet. Post processing is done in connectionDidFinishLoading and works OK except no UIView elements are updated. For example, I set statusUpdate.text = #"Uncompressing file" but that change does not appear until after connectionDidFinishLoading has completed. Similarly, the UIProgressView and UIActivityIndicatorView objects are not updated until this method ends.
Is there any way to force an update of the UIView from within this method? I tried setting [self.view setNeedsDisplay] but that didn't work. It appears to be running in the main thread. All other commands here work just fine - the only problem is updating the UI.
Thanks!
Update: here is the code that is NOT updating the UIVIEW:
-(void)viewWillAppear:(BOOL)animated {
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(processUpdate:) userInfo:nil repeats:YES];
downloadComplete = NO;
statusText.text = #"";
}
-(void)processUpdate:(NSTimer *)theTimer {
if (! downloadComplete) {
return;
}
[timer invalidate];
statusText.text = #"Processing update file.";
progress.progress = 0.0;
totalFiles = [newFiles count];
for (id fileName in newFiles) {
count++;
progress.progress = (float)count / (float)totalFiles;
// ... process code goes here ...
}
}
At then end of processUpdate, I set downloadComplete = YES. This builds & runs without errors and works as intended except nothing updates in the UIVIEW until after processUpdate completes, then everything updates at once.
Thanks for your help so far!
As Niels said, you must return control to the run loop if you want to see views update. But don't start detaching new threads unless you really need to. I recommend this approach:
- (void)connectionDidFinishLoading:(NSConnection *)connection {
statusUpdate.text = #"Uncompressing file";
[self performSelector:#selector(doUncompress) withObject:nil afterDelay:0];
}
- (void)doUncompress {
// Do work in 100 ms chunks
BOOL isFinished = NO;
NSDate *breakTime = [NSDate dateWithTimeIntervalSinceNow:100];
while (!isFinished && [breakTime timeIntervalSinceNow] > 0) {
// do some work
}
if (! isFinished) {
statusUpdate.text = // here you could update with % complete
// better yet, update a progress bar
[self performSelector:#selector(doUncompress) withObject:nil afterDelay:0];
} else {
statusUpdate.text = #"Done!";
// clean up
}
}
The basic idea is that you do work in small chunks. You return from your method to allow the run loop to execute periodically. The calls to performSelector: will ensure that control eventually comes back to your object.
Note that a risk of doing this is that a user could press a button or interact with the UI in some way that you might not expect. It may be helpful to call UIApplication's beginIgnoringInteractionEvents to ignore input while you're working... unless you want to be really nice and offer a cancel button that sets a flag that you check in your doUncompress method...
You could also try running the run loop yourself, calling [[NSRunLoop currentRunLoop] runUntilDate:...] every so often, but I've never tried that in my own code.
While you are in connectionDidFinishLoading nothing else happens in the application run loop. Control needs to be passed back to the run loop so it can orchestrate the UI updating.
Just flag the data transfer as complete and the views for updating. Defer any heavy processing of the downloaded data to it's own thread.
The application will call your views back letting them refresh their contents later in the run loop. Implement drawRect on your own custom views as appropriate.
If you're receiving connectionDidFinishLoading in the main thread, you're out of luck. Unless you return from this method, nothing will be refreshed in the UI.
On the other hand, if you run the connection in a separate thread, then you can safely update the UI using the following code:
UIProgressView *prog = ... <your progress view reference> ...
[prog performSelectorOnMainThread:#selector(setProgress:)
withObject:[NSNumber numberWithFloat:0.5f]
waitUntilDone:NO];
Be careful not to update the UI from a non-main thread - always use the performSelectorOnMainThread method!
Do exactly what you're doing with the timer, just dispatch your processing code to a new thread with ConnectionDidFinish:. Timers can update the UI since they're run from the main thread.
The problem turned out to that the UI isn't updated in a for() loop. See the answer in this thread for a simple solution!