CustomDelegate on viewDidLoad - ios

I'm calling a method which belongs to custom delegate class on viewDidLoad but it starts from [sampleProtocol startSampleProcess], starts from sleep(5), before it show me view controller and label1.
CustomDelegate *sampleProtocol = [[CustomDelegate alloc]init];
sampleProtocol.delegate = self;
[self.label1 setText:#"Processing..."];
[sampleProtocol startSampleProcess];
startSampleProcess method is below;
-(void)startSampleProcess{
sleep(5);
[self.delegate processCompleted];
}
processCompleted method is also below;
-(void)processCompleted{
[self.label1 setText:#"Process Completed"];
}
It just set a label on viewcontroller, go to another class and do something simple (etc: sleep) and come back to view controller and set label again. I didn't try custom delegate before so it would be great if you help me on what I'm missing.

The problem is that you are calling sleep on the main thread.
Here's how an iOS app works:
Wait until something interesting happens.
Process it.
Go back to step 1.
The app has something called a runloop that receives messages from the system about touches, timers, etc. Every time it gets a message, it runs some code, often provided by you. When you call the sleep function, it suspends the current thread. When the thread is suspended, the run loop can't process new events until the sleep is done.
When you change something onscreen, you add an event to the run loop that says the screen needs to be redrawn. So, this is what is happening in your application:
You change the label text. A redraw event is now added to the runloop.
You sleep for 5 seconds, meaning the runloop can't process new events.
5 seconds later, the thread wakes up and changes the label's text.
Control finally gets back to the run loop.
The run loop processes the redraw event, changing the label's text.
If the task needs to be a long-running task, you can do it in a background thread:
-(void)startSampleProcess {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_NORMAL, 0) ^{ //run this code in the background so it doesn't block the runloop
sleep(5);
dispatch_async(dispatch_get_main_thread(), ^{ //after the task is done, call the delegate function back on the main thread since UI updates need to be done on the main thread
[self.delegate processCompleted];
});
});
}

Related

Swift Xcode6 UIActivityIndicatorView Slow to display

How do I get the UIActivityIndicatorView to display first, then execute other code?
I've experimented with using sleep, and it works but it doesn't "feel" right and adds an extra second to processing a bunch of core data stuff. I've also tried dispatching it to the main thread which only works some of the time. (I'm guessing when the rest of the block is executed outside of the main thread).
Ideally as soon as a user touches the button the instance of the UIActivityIndicatorView would display (which seems to happen where I've used it in other apps by itself or with other minimal processing).
Details: I have an IBAction connected to a button that executes a bunch of core data stuff, sometimes including images, that takes between 1 - 3 seconds to finish. When it finishes it dismisses the view controller. The view controller where this is executed is presented as a modal over current context.
Here is a code snippet:
// get the background queue
let bg_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(bg_queue, {
// long running code here...
dispatch_async(dispatch_get_main_queue(), {
self.activityIndicator.stopAnimating()
})
})

Fastest manner of altering viewcontroller layout on main queue?

I have a dataTask making a webservice API call, and in the completion block for that dataTask I use the returned info to build a UIImageView and add it as a subview of the parent UIViewController. I have tried it with two API calls and I would like to know why one causes the UIImageView to be displayed so much slower than the other. Inside of the completion block, the fast call, which takes less than 1 second, is:
dispatch_async(dispatch_get_main_queue(), {
(self.delegate as TBGQRCodeViewController).displayQRCode(receiveAddr, withAmountInBTC:amountBTC)
});
while the slower call, which takes roughly 13 seconds, is:
(self.delegate as TBGQRCodeViewController).displayQRCode(receiveAddr, withAmountInBTC:amountBTC)
The first clearly runs on the main queue, but the second one I'm not so sure, since it's running directly in the completion block of the dataTask.
Can someone explain in detail why these two calls of the same function have such markedly different run lengths?
As these calls update the UI ,your first call is on main thread which updates your imageView immediately.But your second call directly within dataTask completion handler which generally runs on secondary thread run loops so this call
(self.delegate as TBGQRCodeViewController).displayQRCode(receiveAddr, withAmountInBTC:amountBTC)
run on secondary thread runloop so it will not able to update UI. While some other call or some other event reloads the UIImageView which display the your computed image to display on UIImageView.
So as dataTask fetch data on secondary thread if your call is directly within completion handler than it is updating your imageView on secondary thread which should not be done as all UI must be update on main thread.Put your imageView updation on main thread.

MBProgressHUD won't display the label

I'm using MBProgressBar in my app to display feedback whenever there is a call to a certain webService.
To do so, in the method "requestStarted" of ASIHTTPRequest, I call:
[NSThread detachNewThreadSelector:#selector(startLoader) toTarget:self];
Where startLoader is the method that pops the HUD.
Now, the thing is that whenever I call startLoader directly, the HUD gets displayed with no problem, but when I call the method using the detachNewThreadSelector thing (which is needed), the HUD is displayed but with no text label.
If I had to guess, I would say I need to force-refresh the component, but I don't know how to do that.
Anything having to do with the HUD will need to be done on the main/UI thread. If you are detach and put on a background thread, the HUD will likely never get those updates because your request will finish before getting back around to the main thread.

Show a subview immediately / wait until a view is visible before continuing

I have an app that launches to a tab bar controller. When the app either starts up or returns from the background it checks a server for updates to its data. If updates are available, it can take several seconds to get the data and update it.
I would like to simply present an overlay view saying to the user that the app's data is updating and to please wait for a few seconds. The way that I am trying to do this is as follows: in my class that takes care of the updates I have:
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
[delegate.tabBarController.selectedViewController.view addSubview:updatingDataView];
[self runUpdateMethods];
The problem is that the updatingDataView appears on screen only after the update methods have completed. How can I get it to appear before the updating methods start?
It looks like you are running [self runUpdateMethods]; on the main thread (This will block your UI from updating). You would want to run this on a background thread. Something like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
^{
[self runUpdateMethods];
});
or
[self performSelectorInBackground:#selector(runUpdateMethods) withObject:nil];
Update
Since you want to do something after [self runUpdateMethods]; completes you would want to do something like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
^{
[self runUpdateMethods];
dispatch_async(dispatch_get_main_queue(), ^{
[self doSomethingAfterUpdate];
});
});
Doing it this way would still give you the ability to know when runUpdateMethods returns and will not hang the UI.
Your update method (the connection) is probably being executed on the Main Thread, what blocks UI updates. You should use async methods (gcd, NSThread, NSOperationQueue, etc) to run your update.
You need to empty into your run loop so that the views you've added will get drawn on the screen. Drawing on iOs is not done real time. You basically set up your drawing and exit your method, and the run loop actually draws it. So what you need to do is delay the execution of runUpdateMethods until after you've exited your routine. Try instead:
[self performSelector:#selector(runUpdateMethods) withObject:nil afterDelay:0.0];

ios UI unresponsive during viewdidappear

I have a table view, and when the user selects a row, i push them to a new ViewController. At first, I initialized all my view objects in the ViewDidLoad method (involving web service calls) but I saw that it made the transition from my tableview to my new viewcontroller very long.
Instead, I moved most of my UI initialization in the ViewDidAppear method, and I like that it sped up my transition from tableview to new viewcontroller.
However, I cannot press any buttons in my NavigationBar at the top of the screen (like the back button) until my ViewDidAppear method completes and the UI is loaded.
What's the solution for this? Is there another way for me to load my UI without it preventing the user from interacting with the buttons in my NavigationBar?
Thanks!!
you do too much on the main thread. off load your longer operations like IO or longer computations BUT take care to not mess with the UI in the background thread.
Only touch the UI on the main thread. (Note sometimes it might seem safe, but in the long run it always end up producing weird issues)
one easy way is to use GCD:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
//insert web service requests / computations / IO here
dispatch_async(dispatch_get_main_queue(),^{
//back to the main thread for UI Work
});
});
You could use grand central dispatch to make your web service calls asynchronously, which will keep the UI on the main thread responsive.
//create new queue
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.siteName.projectName.bgqueue", NULL);
//run requests in background on new queue
dispatch_async(backgroundQueue, ^{
//insert web service requests here
});
Here's a more in-depth tutorial:
http://www.raywenderlich.com/4295/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial
Try to initialize your UI in the background by using the following method
[self performSelectorInBackground:#selector(initYourUI) withObject:yourObj];
You can call this in the ViewDidLoad

Resources