I am using the [performSelector:#selector(reloadData) withObject:nil afterDelay:0.01] inside IBAction of a UIButton, the reloadData method draw some subviews on the main view in a particular way, the issue is when I tap the button quickly and repeatedly the selector "ReloadData" executed multiple times, event though I am canceling the all previous requests to that selector, and this results in duplication for the subviews in the main view
-(IBAction) myButtonIsTapped
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(reloadData) object:nil];
[self performSelector:#selector(reloadData) withObject:nil afterDelay:0.01];
}
and reload data method like the following:
-(void) reloadData
{
#synchronized(self){
// clear all subviews from the main view
// draw new subviews
}
}
What about this:
-(IBAction) myButtonIsTapped
{
[self.myButton setUserInteractionEnabled:NO];
[self performSelector:#selector(reloadData) withObject:nil afterDelay:0.01];
}
-(void) reloadData
{
// Long task...
// Enable the button again:
[self.myButton setUserInteractionEnabled:YES];
}
Sometimes is just easier to control what the user is doing (UI), than logically dealing with what he has done.
Do one Thing create one BOOL variable and set in viewdidload yes and check in function if it is yes then method call and also make it no in buttonmake method.
Related
I have an NSOperation subclass that does some heavy calculations. It has a delegate as well.
If the user does not provide correct input, I validate it in the main method and through performSelectorOnMainThreadI create and show an alert (from my NSOperation subclass) and then I call the delegate method as such:
-(void) main{
[self performSelectorOnMainThread:#selector(showAnAlert)
withObject:nil
waitUntilDone:YES];
[self cancel]; //I need to cancel the operation
return; //don't want to finish main running.
}
- (void) showAnAlert{
//Create an alert here
[alert show];
}
And in my VC I have this:
- (void) aDelegateMethodFromMyOperation{
[self.textField setEndEditing:YES];
}
Now the problem is, once I dismiss the alert, I can't input any text in my textField.... It will show the keyboard on tap... but it won't accept my input... why is that?
Perhaps try:
[textField becomeFirstResponder];
Because you're switching through multiple UI elements in the NSOperation, it is possible that full control is not being regained by the text field.
I am using tab bar based iPhone application.I have set NSTimer in first two tabs.i want to invalidate this timer. so i am invalidating in viewDidDisappear. But when i am clicking on different tab, it will never call viewDidDisappear. I don't know how to call this method?please help me...thanking you in advance...
are you put [super viewWillDisappear:animated]; in viewWillDisappear some time it happaned becouse we forget to put this line in to viewWillDisappear
- (void) viewWillDisappear:(BOOL) animated {
[super viewWillDisappear:animated];
}
set time invalid in viewWillDisappear: method , like bellow..
-(void)viewWillDisappear:(BOOL)animated
{
[yourTimer invalidate];
yourTimer = nil;
}
In loadDidLoad controller call method with thread, like that:
- (void)viewDidLoad {
[super viewDidLoad];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(loadImages) object:nil];
[self.operationQueue addOperation:operation];
[operation release];
}
loadImages method downloading image and show it:
- (void) loadImages {
#synchronized (self) {
// download image...
// ...
// and next:
UIImageView *imageView = [imageViews objectAtIndex:currentImageIndex];
[imageView performSelectorOnMainThread:#selector(setNeedsDisplay) withObject:nil waitUntilDone:YES];
[imageView performSelectorOnMainThread:#selector(setImage:) withObject:[UIImage imageWithData:imageData] waitUntilDone:YES];
}
//...
}
that work if show view first time, next time i must rotate iphone to show images.
why view doesn't redraw?
i tried this code according to advices:
// imageButton is a UIView with button, image and label
NSData *imageData = [[HttpClient sharedInstance] getResourceWithCache:thumbPath];
[imageButton performSelectorOnMainThread:#selector(setImage:) withObject:[UIImage imageWithData:imageData] waitUntilDone:YES];
[imageButton performSelectorOnMainThread:#selector(setNeedsDisplay) withObject:nil waitUntilDone:YES];
[imageButton drawRect:imageButton.frame];
but do not work.
your problem is in the line
[imageView performSelectorOnMainThread:#selector(setNeedsDisplay) withObject:nil waitUntilDone:YES];
You would need to call setNeedsDisplay in self.view and not on self, as thats a method for UIView and not UIViewController.
Double check this by subclassing your view and putting a break point in drawRect: , as setNeedsDisplay must call drawRect.
I'm assuming that loadDidLoad is a typo, and that you meant viewDidLoad.
The viewDidLoad method is called when the view controller is done loading. It only does this once, unless the app receives a memory warning and the view controller gets unloaded.
If you're retaining the view controller and later showing the same view controller object again, the viewDidLoad method does not get called again.
If you need to perform an action every time the view controller gets pushed onto the view stack, you can use viewWillAppear: or viewDidAppear:.
I have a tableView that loads an XML feed as follows:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if ([stories count] == 0) {
NSString *path = #"http://myurl.com/file.xml";
[self parseXMLFileAtURL:path];
}
}
I'd like to have the spinner to show on the top bar at the app launch and dissapear once the data is displayed on my tableView.
I thought that putting the beginning at viewDidAppear and the end at -(void)parserDidEndDocument:(NSXMLParser *)parser but it didn't work.
I'd appreciate a good explained solution on how to implement this solution.
Here's the problem: NSXMLParser is a synchronous API. That means that as soon as you call parse on your NSXMLParser, that thread is going to be totally stuck parsing xml, which means no UI updates.
Here's how I usually solve this:
- (void) startThingsUp {
//put the spinner onto the screen
//start the spinner animating
NSString *path = #"http://myurl.com/file.xml";
[self performSelectorInBackground:#selector(parseXMLFileAtURL:) withObject:path];
}
- (void) parseXMLFileAtURL:(NSString *)path {
//do stuff
[xmlParser parse];
[self performSelectorOnMainThread:#selector(doneParsing) withObject:nil waitUntilDone:NO];
}
- (void) doneParsing {
//stop the spinner
//remove it from the screen
}
I've used this method many times, and it works beautifully.
Starting a new thread can be overkilling and a source of complexity if you want to do things that are supposed to start on the main thread.
In my own code, I need to start a MailComposer by pushing a button but it can take some time to appear and I want to make sure the UIActivityIndicator is spinning meanwhile.
This is what I do :
-(void)submit_Clicked:(id)event
{
[self.spinner startAnimating];
[self performSelector:#selector(displayComposerSheet) withObject:nil afterDelay:0];
}
It will queue displayComposerSheet instead of executing it straight away. Enough for the spinner to start animating !
I typically implement an NSTimer that will call my spinner method, which I fire off right before I go into doing the heavy work (the work that will typically block the main thread).
The NSTimer fires and my spinner method is called. When the main work is finished, I disable the spinner.
Code for that is like:
IBOutlet UIActiviyIndicatorView *loginIndicator;
{
...
[loginIndicator startAnimating];
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(executeAuthenticationRequest)
userInfo:nil repeats:NO];
...
}
- (void) executeAuthenticationRequest
{
/* Simulate doing a network call. */
sleep(3);
[loginIndicator stopAnimating];
...
}
You can also do:
IBOutlet NSProgressIndicator *pIndicator;
Start:
[pIndicator startAnimation:self];
[pIndicator setHidden:NO];
And Stop:
[pIndicator stopAnimation:self];
[pIndicator setHidden:YES];
In Cocoa (and most other app frameworks) the user interface is updated by the main thread. When you manipulate views, they are typically not redrawn until control returns to the run loop and the screen is updated.
Because you are parsing the XML in the main thread, you are not allowing the screen to update, and that is why your activity indicator is not appearing.
You should be able to fix it by doing the following:
In viewDidAppear, show/animate the spinner and then call
[self performSelector:#selector(myXMLParsingMethod) withObject:nil afterDelay:0];
In myXMLParsingMethod, parse your XML, then hide/stop the spinner.
This way, control will return to the run loop before parsing begins, to allow the spinner to begin animating.
I have an application that has a UITableView. This UITableView is populated by an NSMutableArray being held (as a property) in the appDelegate. You can think of this as an email window. It lists messages in a subclassed UITableViewCell. When a new message appears, I have all the code done which downloads the message, adds the data to the appDelegate's NSMutableArray which holds all of the messages. This code is working fine.
Now, once the new message is downloaded and added to the array, I am trying to update my UITableView using the following code, however - the UITableView's delegate functions do not get called.
The odd thing is when I scroll my UITableView up and down, the delegate methods finally get called and my section headers DO change (they show the message count for that section). Shoudn't they update in real-time and not wait for my scrolling to trigger the refresh? Also, the new cell is never added in the section!!
Please Help!!
APPDELEGATE CODE:
[self refreshMessagesDisplay]; //This is a call placed in the msg download method
-(void)refreshMessagesDisplay{
[self performSelectorOnMainThread:#selector(performMessageDisplay) withObject:nil waitUntilDone:NO];
}
-(void)performMessageDisplay{
[myMessagesView refresh];
}
UITableViewController Code:
-(void) refresh{
iPhone_PNPAppDelegate *mainDelegate = (iPhone_PNPAppDelegate *)[[UIApplication sharedApplication] delegate];
//self.messages is copied from appDelegate to get (old and) new messages.
self.messages=mainDelegate.messages;
//Some array manipulation takes place here.
[theTable reloadData];
[theTable setNeedsLayout]; //added out of desperation
[theTable setNeedsDisplay]; //added out of desperation
}
As a sanity check, have you verified that theTable is not nil at that point?
You could try putting a delay on the reloadData call - I had a similar problem when I was trying to get my tableview to update when reordering cells, except that the app crashed if I called reloadData during it.
So something like this might be worth a try:
Refresh method:
- (void)refreshDisplay:(UITableView *)tableView {
[tableView reloadData];
}
and then call it with (say) a 0.5 second delay:
[self performSelector:(#selector(refreshDisplay:)) withObject:(tableView) afterDelay:0.5];
Hope it works...
If you call reloadData from within a dispatched method, make sure to execute it on the main queue.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0), ^(void) {
// hard work/updating here
// when finished ...
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self.myTableView reloadData];
});
});
..same in method form:
-(void)updateDataInBackground {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0), ^(void) {
// hard work/updating here
// when finished ...
[self reloadTable];
});
}
-(void)reloadTable {
dispatch_async(dispatch_get_main_queue(), ^(void) {
[myTableView reloadData];
});
}
Have you tried setting a breakpoint in your refresh method just to be sure your messages array has the correct content before calling reloadData?