I would like show my view immediately when I call it. I don't know how to make the view show.
-(IBAction) showProgress: (id) sender {
progressViewController *progress = [[progressViewController alloc] initWithNibName:#"progressViewController" bundle:NULL];
[self.view addSubview:progress.view];
[self someFunctionWhichTakesAgesToBeDone];
}
It's called from current UIViewController. And the view appears after the long function. How can I show it before the long funcion? Thanks for answer.
Use GCD (Grand Central Dispatch) which is the simplest way (and recommended by Apple), the code will then be:
-(IBAction) showProgress: (id) sender {
progressViewController *progress = [[progressViewController alloc] initWithNibName:#"progressViewController" bundle:NULL];
[self.view addSubview:progress.view];
// Heavy work dispatched to a separate thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"dispatched");
// Do heavy or time consuming work
[self someFunctionWhichTakesAgesToBeDone];
// When finished call back on the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
// Return data and update on the main thread
});
});
}
It´s two blocks. The first one does the heavy work on a separate thread and then a second block is called when the heavy work is finished so that changes and UI updates are done on the main thread, if needed.
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsobject_Class/Reference/Reference.html
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait`
use
[self.view performSelectorOnMainThread:#selector(addSubview:) withObject:progress.view waitUntilDone:YES]
or put your Sleep() function (i hope it's anything else, Sleep() func is really bad, as its been told) into another function MySleepFunc and call
[self performSelector:#selector(MySleepFunc) withObject:nil afterDelay:0.003]
instead of Sleep(3).
Read about multi-threading. In short, there's single UI thread that does drawing, accepting user events and so on. If you pause it with sleep() or any other blocking method, nothing will be shown/redrawn and no events will be processed. You have to make your HTTP request from background thread.
Related
The WatchApp receives data from the iPhone.
I refresh the label text with the data received, nothing happens, the UI is not refreshing.
Other threads suggested pushing it to the main thread and that seems to do nothing either.
Any thoughts most welcome.
-(void)session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary *)applicationContext
{
dispatch_async(dispatch_get_main_queue(), ^{
[self.lblTitleBorH setText:#"test"];
});
}
Are you using
[*your session* updateApplicationContext:*your dictionary* error:nil];
correctly?
try putting a NSLog inside your above didReceiveApplicationContext code and see if it is printing anything out.
In my case, when I tried to refresh the UI, I found that the outlet references were nil. The problem was caused by two interfaces on the storyboard, belonging to the same WKInterfaceController class.
When I assigned the second screen interface to another WKInterfaceController class it worked fine.
remember to call the UI objects from the main thread by using
dispatch_async(dispatch_get_main_queue(), ^{
...
});
or by using methods like performSelectorOnMainThread: withObject: waitUntilDone:
I am trying to understand the run loop. Which is the best way to code this?
for (UIView *view in self.viewSet) {
dispatch_async(dispatch_get_main_queue(), ^{
[view removeFromSuperview];
});
}
or
dispatch_async(dispatch_get_main_queue(), ^{
for (UIView *view in self.viewSet) {
[view removeFromSuperview];
}
});
To answer the question specifically, it breaks down into the size of units of work. If you dispatch a really large chunk of work to the main thread, then that large chunk will block the main thread for the entire time it is processing. If you dispatch a bunch really small chunks of work to the main thread, then the main thread will interleave processing of the chunks of work with processing of other events which will make your app slightly less efficient, but overall more responsive.
However, in the context of the question asked, neither dispatch_async() pattern is likely to be what you really want. You really shouldn't be removing a ton of views from any superview, whether it be all at once or interleaved on the main queue.
Instead, you should have a container view that contains the views that need to be swapped out-- potentially managed by a view controller-- and you should have one removeFromSuperview that removes that container. Once removed, then there really shouldn't be a need to manually remove any of the container views as you should have defined the retain/weak ownership rules such that you don't have cycles through view -> subview relationships.
Or, succinctly:
dispatch_async(.. main queue .., ^{
[_myContainerView removeFromSuperview];
});
In an ideal world, that removeFromSuperview would remove the last reference to _myContainerView and then it'd automatically tear down the hierarchy of subviews as the UIKit sees fit.
The first one
for (UIView *view in self.viewSet) {
dispatch_async(dispatch_get_main_queue(), ^{
[view removeFromSuperview];
});
}
queues up a bunch of [view removeFromSuperview] calls on the main thread. After the loop is over, they are executed one at a time. Each call is stuck waiting until the main queue clears, like a train waiting to pull into a station.
The second one
dispatch_async(dispatch_get_main_queue(), ^{
for (UIView *view in self.viewSet) {
[view removeFromSuperview];
}
});
queues just one thing onto the main thread, namely the whole loop. As soon as the main thread is free, a single piece of code runs, in which all the views are removed, kaboom.
i have this code to wait for a loading task, showing a activityIndicator view
if (isLoading) {
self.tipView = [[BBTipsView alloc] initWithMessage:#"loading..." showLoading:YES parentView:self.view autoClose:NO];
self.tipView.needsMask = YES;
[self.tipView show];
while (isLoading) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[self.tipView close];
}
the loading view will animate until the isLoading become false.here is my problem:
running a runloop in main thread will block the main thread until there is a source event comes or timer fire. but why the loading view keep animating while the main runloop didn't return?
-----edit by bupo----
I found that when timer fire the runloop won't return. This will make sense that the animation refresh ui by CADisplayLink timer fire.
Note that from the perspective of NSRunloop, NSTimer objects are not "input"—they are a special type, and one of the things that means is that they do not cause the run loop to return when they fire.
The NSRunLoop method runMode:beforeDate: runs until the given date OR until it finds a single event to deal with - after which the call returns. You're calling it on the main run loop ([NSRunLook currentRunLoop]). Hence, even though you think you're blocking the main run loop, you're not - you're causing events to be serviced. Hence, the animation timer can function, even though you might think you're 'blocking' the main run loop.
To confirm this, comment out the call to runMode:beforeDate: and you should see that the UI freezes until the operation completes.
Edit: see CodaFi's comment on your question. What actually happens if you comment out the call to runMode:beforeDate:, out of interest?
Original answer:
This style of code isn't recommended for starting and stopping UI animations. Don't mess around with run loops unless you have to. And having a tight loop that checks for boolean flag changing from elsewhere is often a code smell that means there's a better way.
Instead, do it asynchronously and without sitting on the main thread:
// on main thread
self.tipView = [[BBTipsView alloc] initWithMessage:#"loading..." showLoading:YES parentView:self.view autoClose:NO];
self.tipView.needsMask = YES;
[self.tipView show];
} // end of the method
- (void)loadingHasFinished {
// assuming this method called on main thread
[self.tipView close];
}
Obviously you'll have to ensure the loadingHasFinished is called as appropriate.
If loadingHasFinished is called on a background thread rather than the main thread, you'll want something like this:
- (void)loadingHasFinished {
dispatch_async(dispatch_get_main_queue(), ^{
[self.tipView close];
});
};
I have a really light ViewController, it does nothing in viewDidLoad. I'm pushing this view on top of a navigationController. The method who does this action is called from inside a block. After the call to showView I added an NSLog, and that log prints in the console really fast, but the view takes a lot to load... I really don't understand what maybe happening... any idea???
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
[self showView];
NSLog(#"EXECUTED");
});
- (void) showView{
TestViewController *test = [[TestViewController alloc]init];
[self.navigationController pushViewController:test animated:NO];
}
From the docs for ABAddressBookRequestAccessWithCompletion:
The completion handler is called on an arbitrary queue. If your app uses an address book throughout the app, you are responsible for ensuring that all usage of that address book is dispatched to a single queue to ensure correct thread-safe operation.
You should make sure your UI code is called on the main thread.
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error {
dispatch_async(dispatch_get_main_queue(), ^{
[self showView];
NSLog(#"EXECUTED");
});
});
This might not be the only problem, but according to the docs, the completion handler passed to ABAddressBookRequestAccessWithCompletion is called on an arbitrary queue. -showView should only be called on the main queue since it is dealing with UIViewController objects.
The other thing to ask is what else is happening on the main queue? Are there other long-running tasks that could be blocking UI updates?
// Method called when a button is clicked
- (void)handleClickEvent {
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self backgroundProcessing];
});
// Some code to update the UI of the view
....
[self updateUI];
....
}
1) handleClickEvent is called on the main thread when a button on the view is pressed.
2) I used dispatch_sync() because the following code that updates the UI of the view cannot be done until a variable in the backgroundProcessing method is calculated.
3) I used dispatch_get_global_queue in order to get the backgroundProcessing off the main thread. (following the rule: generally put background processing off main thread and generally put code that affect the UI on the main thread).
My question is: Does the backgroundProcessing method "hang" the main thread until it is complete since I am using dispatch_sync?
EDIT:
Based on the answer below i have implemented this solution:
- (void)handleClickEvent {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self backgroundProcessing];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUI];
});
});
}
solution from this link: Completion Callbaks
Yes, dispatch_sync will block until the task is complete. Use dispatch_async and when the job is complete have it post a block back to the main queue to update the view.