I made alert informing user about saving action, i add it to view, save some image and dismiss alert. However it's not working in the way i hoped it would. Looking at code below ofc firstly in console i get "saved.." then "dispath". I would like to get opposite effect firstly get "dispath" then "saved.." (so write alert on screen, then save in background and finally dismiss alert). But i change image of imageView1, so i cant move merging out of dispath_async because its an UI action.. how to do it then..? I need to firstly merge images and after it to save them and all of this calculating time to keep alert in front.
//adding alert to view
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
dispatch_async(dispatch_get_main_queue(), ^{
//i want this to complete->
imageView1.image = [self merge:imageView1.image and:imageView2.image];
NSLog(#"dispatch");
});
//and then to do this action->
UIImageWriteToSavedPhotosAlbum(imageView1.image, self, #selector(imageSavedToPhotosAlbum:didFinishSavingWithError:contextInfo:), nil);
NSLog(#"saved..");
dispatch_async(dispatch_get_main_queue(), ^{
[alert dismissWithClickedButtonIndex:0 animated:YES];
});
});
You should simply use dispatch_sync instead of dispatch_async. That function will not return until the block has executed on the main thread.
Related
I am creating a UIActionSheet on actionSheet:clickedButtonAtIndex delegate method.
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
if(buttonIndex == 1){
[self.myFirstView removeFromSuperview];
if (!self.mySecondView) {
[[NSBundle mainBundle] loadNibNamed:#"MySecondView" owner:self options:nil];
}
[self.mySecondView setFrame:CGRectMake(0, 0, 320, 480)];
[[UIApplication sharedApplication].keyWindow addSubview: self.mySecondView];
UIActionSheet * action = [[UIActionSheet alloc]initWithTitle:#""
delegate:self
cancelButtonTitle: nil
destructiveButtonTitle: deleteContacts
otherButtonTitles: cancel, nil];
action.tag = 102;
[action showInView:self.view];
[action release];
}
I handle the click event of this UIActionSheet in the exact same method as above.
if(actionSheet.tag == 102){
if(buttonIndex == 0){
if([[NSBundle mainBundle] loadNibNamed:#"MyThirdView" owner:self options:nil]) {
[self.myThirdView setFrame:CGRectMake(0, 0, 320, 480)];
[[UIApplication sharedApplication].keyWindow addSubview:self.myThirdView];
}
[self.mySecondView removeFromSuperview];
[self.doneButton.target performSelector:self.doneButton.action withObject:self.doneButton.target];
[self performSelector:#selector(RemoveView) withObject:self afterDelay:3.0];
}
}
The problem I am facing is that, the UIActionSheet takes too much time to respond. When I click on the UIActionSheet button, its in a frozen state for 2 or 3 seconds, before myThirdView loads. I am not able to understand, whats the response delay in this case as the first thing I do in the UIActionSheet button click event method is to load myThirdView. The rest of the code is executed only after the code to load the myThirdView. But even the first line of code seems to execute after a delay.
Any suggestions?
this is perhaps due to this
[self performSelector:#selector(RemoveView) withObject:self afterDelay:3.0];
make an other methods and do this in that method. like this
[self viewRemover];
and in viewRemover
-(void) viewRemover
{
[self performSelector:#selector(RemoveView) withObject:self afterDelay:3.0];
}
so your code will be like this now
if(actionSheet.tag == 102){
if(buttonIndex == 0){
if([[NSBundle mainBundle] loadNibNamed:#"MyThirdView" owner:self options:nil]) {
[self.myThirdView setFrame:CGRectMake(0, 0, 320, 480)];
[[UIApplication sharedApplication].keyWindow addSubview:self.myThirdView];
}
[self.mySecondView removeFromSuperview];
[self.doneButton.target performSelector:self.doneButton.action withObject:self.doneButton.target];
[self performSelectorInBackground:#selector(viewRemover) withObject:nil];
}
}
User interface actions run in the main thread and only occur when your method ends. So, MyThirdView will not appear until the other instructions have finished. The only thing I can figure is delaying that is:
[self.doneButton.target performSelector:self.doneButton.action withObject:self.doneButton.target];
If you are doing any heavy calculation or net conection, for sure that is the reason.
OTOH, I think you'd better modify that line:
[self.doneButton.target performSelector:self.doneButton.action withObject:self.doneButton];
if you want to simulate a button touch action.
Question. Does the UIActionSheet freeze, or does it disappear and the 3rd view isn't visible for 2-3 seconds?
This could be due to 1 of 2 problems.
If the entire action sheet freezes, then you are doing some heavy lifting when you init that 3rd view, you are loading some core data, or a lot of assets, or something that is taking a long time. If this is the case, you'll need to reformat HOW you load that 3rd view. I'd suggest pushing any heavy loading to the background (this means if you have a lot of images in your xib, you may need to load them in code).
The other possibility, is you are adding the 3rd view BELOW the 2nd view, and then not hiding the 2nd view for 3 seconds (done by performing the selector with a delay). If this is the case, simply remove the delay.
I made a couple of classes to help me time executions and find the bottlenecks in my code, it seems like they might help you now.
http://forrst.com/posts/Code_Execution_Timer_for_iOS_Development-dSJ
How big is your third view. if the nib file needs to load too much, you may be waiting for a lot to happen, also if you have to change some UI elements and your blocking the UI thread, you will hault your app until a timeout occurs and the app will shift some things to compensate..
the route I take with this is dispatch_async and dispatch_sync
// This will release the UI thread which may be blocking your calls.
// You can use any of the DISPATCH_QUEUE_PRIORITY_.... values to set how important this block is.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// this command will get added to the stack and return without a hitch.
// the block will be run the next time the main runloop ends.
dispatch_async(dispatch_get_main_queue(), ^{
// do some UI work that can happen without a hitch, and does not impact the calling block
});
// this command will act much the same. but will pause the background execution
// of this block until the runloop has executed this and returned.
dispatch_sync(dispatch_get_main_queue(), ^{
// do some UI work that must be completed to continue.
});
});
doing too much in the UI thread will pause the execution of things that get stacked on the queue. Shipping all code to the background thread and only skipping to the UI thread when you need to alter the UI is a better and more responsive way to code your iphone app.
I hope this helps :)
in Swift 4:
I wrapped the code with this block
DispatchQueue.main.async {
// your code to show action sheet.
}
for example
DispatchQueue.main.async {
let alert = UIAlertController(title: "Options", message: String("What do want to do?"), preferredStyle: UIAlertController.Style.actionSheet)
alert.addAction(UIAlertAction(title: "Open", style: UIAlertAction.Style.default, handler: {(action:UIAlertAction!) in
self.myOpen(code: self.codes[indexPath.row])
}))
alert.addAction(UIAlertAction(title: "Delete", style: UIAlertAction.Style.default, handler: {(action:UIAlertAction!) in
self.myDelete(indexPath: indexPath)
}))
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertAction.Style.default, handler: {(action:UIAlertAction!) in
print("Cancel")
}))
self.present(alert, animated: true, completion: nil)
}
I'm displaying a long list of items that I need to sort with 3 index (name, date, id). I then display the list with a UISegmentControl in self.navigationItem.titleView. The user can use that control to select which sort order he wants to display.
The user then have the option to filter that list based on some categories. I then have to sort again the list because it is possible that with the new filtered list, there will be only one name displayed so I don't want to display the sort order by name. So I need to sort just to be able to reset the UISegmentControl.
Since the sort is taking a few seconds, I pushed it in another thread. Here is my code:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"value"]) {
//The filter value has changed, so I have to redo the sort
[[self navigationItem] setTitleView:NULL];
[[self navigationItem] setTitle:#""];
self.navigationItem.rightBarButtonItem.enabled = false;
[self.tableView reloadData]; //I reload here because I want to display Loading... on my table (another var has been set before hands so the tableview will display that)
dispatch_async(backgroundQueue, ^(void) {
[self displayAfterFilterChanged];
});
}
}
-(void) displayAfterFilterChanged
{
displayState = DISPLAYRUNNING;
[self setupSort]; //<<< this is where the 3 sort index are setup based on the filter
dispatch_async(dispatch_get_main_queue(), ^(void) { //<<< I call all the UI stuff in the main_queue. (remember that this method has been call from dispatch_async)
[self.tableView reloadData]; //<< the table will display with the sorted and filtered data
self.navigationItem.rightBarButtonItem.enabled = true;
if (!self.leSelecteur) { //<<< if I don't have a UISegmentControl I display a title text
[[self navigationItem] setTitle:#"atitle"];
} else { // I have a UISegmentControl, so I use it.
[[self navigationItem] setTitleView:self.leSelecteur];
NSLog(#"before setneedsdisplay");
[self.navigationItem.titleView setNeedsDisplay];
NSLog(#"after setneedsdisplay");
}
});
}
NOW, THE PROBLEM:
The table is redisplaying itself right away, the rightbarbuttonitem is enabled right away.
But the navigationItem titleView takes 5-10 secondes before it display itself.
Looks like it is skipping a display cycle and catch the next one.
Is there a way I could speed it up ?
I can see that "before setNeedsdisplay" and "after setneedsdisplay" are displayed on the log right away. But the actual refresh occurs 5-10 sec later.
Try changing dispatch_async(dispatch_get_main_queue(), ^(void) { in your displayAfterFilterChanged to dispatch_sync(dispatch_get_main_queue(), ^(void) { this is the only thing about your code that seems off to me. Because your call to displayAfterFilterChanged is already running asynchronously I assume you want [self setupSort]; to block and then all of your UI code to run synchronously on the UI thread.
Your UI code should run on the main thread. Don't create a separate thread and perform tasks on the UI.
ok, got it. I was actually building my UISegmentControl in the thread. So I was playing with the UI not in the main thread (bad boy... slap, slap ;) ). I moved all the code to create the control in the block called in the main thread, and voila, it worked. thank you all
When a UIButton is triggered, a UIActivityIndicator is started, and then stopped when implementLogin finishes:
-(IBAction)loginButton {
NSLog(#"loginButton triggered");
// Checks the Fields are not empty
if ([sessionIdField.text length] != 0 && [usernameField.text length] != 0 ) {
NSLog(#"Beginning Spinner");
// Displays spinner
[activitySpinner startAnimating];
[self implementLogin];
// Displays spinner
[activitySpinner stopAnimating];
}
}
However at runtime, the spinner doesn't appear! I have set the spinner to 'hide when stopped'.
When I set the activity indicator to animate before the view loads, it appears as it should, so I'm guessing it has a problem with the UIButton... (Also, I'm using 'Touch Up Inside' for the button.)
It's a simple problem... Can anyone help?
Thanks
Whatever implementLogin is doing (making a network request, perhaps?), it's doing it on the main thread, which is likely blocking UI updates like spinner animation.
You could recode something like this:
[activitySpinner startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAULT, 0), ^{
[self implementLogin];
dispatch_async(dispatch_get_main_queue(), ^{
// Stops spinner
[activitySpinner stopAnimating];
}
}
[Code is untested, but you get the idea.]
What is happening here is that you are dispatching the login task to the background, and the last thing that block will do is stop the spinner on the main thread (as a separate task).
Without more code, one can only guess the reason why it doesn't work.
I assume 'Beginning Spinner' output correctly(?)
If so, you probably didn't properly initialize the UIActivityIndicatorView.
Does it look like this?
activitySpinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activitySpinner.hidesWhenStopped = YES;
[view addSubview:activitySpinner];
I have an app that is continuously taking in images from the video buffer (using the process described here: http://www.benjaminloulier.com/articles/ios4-and-direct-access-to-the-camera) and performing various processing on the most recent image in the buffer. For my particular application, when something noteworthy is found in the image, I want to display this information to the user and have the user decide whether the information is correct or not.
I want to display 2 UIButtons on the screen when this information is returned and it is at this point that I wish the code to "pause" (like a runtime breakpoint) and wait to see which button the user clicks before resuming. After clicking a button, the buttons will disappear.
The reason for this is I can't have the camera continue to acquire images and process them while I am waiting for the user input.
Thanks!
EDIT:
Here is what my code basically looks like:
if (continueRunningScript == YES) {
NSString *results = [self processImage];
[self displayResults: results];
// Show pause button
dispatch_async(dispatch_get_main_queue(), ^{
[pauseButton setHidden: NO];
});
}
and the pause button code:
- (UIAction) pauseButtonPress:(id) sender {
[pauseButton setHidden: YES];
[playButton setHidden: NO];
continueRunningScript = NO;
}
and the play button code:
- (UIAction) playButtonPress:(id) sender {
[playButton setHidden:YES];
continueRunningScript = YES;
}
Where could I add more boolean to handle the delay?
I'm having a hard time figuring out how to put this all together.
I have a puzzle solving app on the mac.
You enter the puzzle, press a button, and while it's trying to find the number of solutions,
min moves and such I would like to keep the UI updated.
Then once it's finished calculating, re-enable the button and change the title.
Below is some sample code from the button selector, and the solving function:
( Please keep in mind I copy/paste from Xcode so there might be some missing {} or
some other typos.. but it should give you an idea what I'm trying to do.
Basicly, user presses a button, that button is ENABLED=NO, Function called to calculate puzzle. While it's calculating, keep the UI Labels updated with moves/solution data.
Then once it's finished calculating the puzzle, Button is ENABLED=YES;
Called when button is pressed:
- (void) solvePuzzle:(id)sender{
solveButton.enabled = NO;
solveButton.title = #"Working . . . .";
// I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state.
[self performSelectorInBackground:#selector(createTreeFromNode:) withObject:rootNode];
// I've tried to use GCD but similar issue and can't get UI updated.
//dispatch_queue_t queue = dispatch_queue_create("com.gamesbychris.createTree", 0);
//dispatch_sync(queue, ^{[self createTreeFromNode:rootNode];});
}
// Need to wait here until createTreeFromNode is finished.
solveButton.enabled=YES;
if (numSolutions == 0) {
solveButton.title = #"Not Solvable";
} else {
solveButton.title = #"Solve Puzzle";
}
}
Needs to run in background so UI can be updated:
-(void)createTreeFromNode:(TreeNode *)node
{
// Tried using GCD
dispatch_queue_t main_queue = dispatch_get_main_queue();
...Create Tree Node and find Children Code...
if (!solutionFound){
// Solution not found yet so check other children by recursion.
[self createTreeFromNode:newChild];
} else {
// Solution found.
numSolutions ++;
if (maxMoves < newChild.numberOfMoves) {
maxMoves = newChild.numberOfMoves;
}
if (minMoves < 1 || minMoves > newChild.numberOfMoves) {
solutionNode = newChild;
minMoves = newChild.numberOfMoves;
// Update UI on main Thread
dispatch_async(main_queue, ^{
minMovesLabel.stringValue = [NSString stringWithFormat:#"%d",minMoves];
numSolutionsLabel.stringValue = [NSString stringWithFormat:#"%d",numSolutions];
maxMovesLabel.stringValue = [NSString stringWithFormat:#"%d",maxMoves];
});
}
GCD and performSelectorInBackground samples below. But first, let's look at your code.
You cannot wait where you want to in the code above.
Here's the code you had. Where you say wait in the comment is incorrect. See where I added NO.
- (void) solvePuzzle:(id)sender{
solveButton.enabled = NO;
solveButton.title = #"Working . . . .";
// I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state.
[self performSelectorInBackground:#selector(createTreeFromNode:) withObject:rootNode];
// NO - do not wait or enable here.
// Need to wait here until createTreeFromNode is finished.
solveButton.enabled=YES;
}
A UI message loop is running on the main thread which keeps the UI running. solvePuzzle is getting called on the main thread so you can't wait - it will block the UI. It also can't set the button back to enabled - the work hasn't been done yet.
It is the worker function's job on the background thread to do the work and then when it's done to then update the UI. But you cannot update the UI from a background thread. If you're not using blocks and using performSelectInBackground, then when you're done, call performSelectorOnMainThread which calls a selector to update your UI.
performSelectorInBackground Sample:
In this snippet, I have a button which invokes the long running work, a status label, and I added a slider to show I can move the slider while the bg work is done.
// on click of button
- (IBAction)doWork:(id)sender
{
[[self feedbackLabel] setText:#"Working ..."];
[[self doWorkButton] setEnabled:NO];
[self performSelectorInBackground:#selector(performLongRunningWork:) withObject:nil];
}
- (void)performLongRunningWork:(id)obj
{
// simulate 5 seconds of work
// I added a slider to the form - I can slide it back and forth during the 5 sec.
sleep(5);
[self performSelectorOnMainThread:#selector(workDone:) withObject:nil waitUntilDone:YES];
}
- (void)workDone:(id)obj
{
[[self feedbackLabel] setText:#"Done ..."];
[[self doWorkButton] setEnabled:YES];
}
GCD Sample:
// on click of button
- (IBAction)doWork:(id)sender
{
[[self feedbackLabel] setText:#"Working ..."];
[[self doWorkButton] setEnabled:NO];
// async queue for bg work
// main queue for updating ui on main thread
dispatch_queue_t queue = dispatch_queue_create("com.sample", 0);
dispatch_queue_t main = dispatch_get_main_queue();
// do the long running work in bg async queue
// within that, call to update UI on main thread.
dispatch_async(queue,
^{
[self performLongRunningWork];
dispatch_async(main, ^{ [self workDone]; });
});
}
- (void)performLongRunningWork
{
// simulate 5 seconds of work
// I added a slider to the form - I can slide it back and forth during the 5 sec.
sleep(5);
}
- (void)workDone
{
[[self feedbackLabel] setText:#"Done ..."];
[[self doWorkButton] setEnabled:YES];
}
dispatch_queue_t backgroundQueue;
backgroundQueue = dispatch_queue_create("com.images.bgqueue", NULL);
- (void)process {
dispatch_async(backgroundQueue, ^(void){
//background task
[self processHtml];
dispatch_async(main, ^{
// UI updates in main queue
[self workDone];
});
});
});
}
By and large, any work to be submitted to a background queue needs to follow this pattern of code:
dispatch_queue_t queue = dispatch_queue_create("com.myappname", 0);
__weak MyClass *weakSelf = self; //must be weak to avoid retain cycle
//Assign async work
dispatch_async(queue,
^{
[weakSelf doWork];
dispatch_async(dispatch_get_main_queue(),
^{
[weakSelf workDone];
});
});
queue = nil; //Using ARC, we nil out. Block always retains the queue.
Never Forget:
1 - queue variable above is a reference counted object, because it is a private queue, not a global one. So it is retained by the block which is executing inside that queue. Until this task is complete, it is not released.
2 - Every queue got its own stack which will be allocated / deallocated as part of recursive operation. You only need to worry about class member variables which are reference counted (strong, retain etc.) which are accessed as part of doWork above.
3 - While accessing those reference counted vars inside background queue operation, you need to make them thread-safe, depending on use cases in your app. Examples include writes to objects such as strings, arrays etc. Those writes should be encapsulated inside #synchronized keyword to ensure thread-safe access.
#synchronized ensures no another thread can get access to the resource it protects, during the time the block it encapsulates gets executed.
#synchronized(myMutableArray)
{
//operation
}
In the above code block, no alterations are allowed to myMutableArray inside the #synchronized block by any other thread.