I have a main view controller showing on the screen and a class (delegate) running in the background getting positions. When I get the result I call the delegate method in the view controller to update the label on the screen. I tried several ways but still no update to the screen.
Here is the method:
-(void) updateDisplay
{
NSLog(#"%#",myPosition.currentArea);
_area.text = [NSString stringWithFormat:#"area = %#",myPosition.currentShopArea];
//[self.view performSelectorOnMainThread:#selector(setNeedsDisplay) withObject:nil waitUntilDone:YES];
[self.view setNeedsDisplay];
//[self.view setNeedsLayout];
}
I check the console, it displays the correct area but on the screen still showing null. I tried all three ways (commented out above) but none of those update the screen.
You could try 2 things:
calling updateDisplay on the main thread:
[delegate performSelectorOnMainThread:#selector(updateDisplay) withObject:nil waitUntilDone:YES];
in the end, it is accessing UIKit, so better doing it right;
if that does not help, try with:
[_area setNeedsDisplay];
In this case also, if would be advisable to call the method on the main thread.
Related
I have a method that programmatically will take a screenshot [self makeScreenshot]. But sometimes, when a rotation happens for example, the result can be very ugly with black parts in it. So I’m trying to make a method with a completion that will wait for the view controllers animations to finish, so that the screenshots can be made safely and always look nice. The call would maybe look something like this:
[self methodWithCompletionWhenAnimationsIsDone:^(BOOL finished) {
// now it's safe to make the screenshot.
UIImage *myScreenshot = [self makeScreenshot];
}];
But I cant figure out how the code for such a method would look like. Any suggestions?
I’ve tried to place screenshot code the - (void)viewDidLayoutSubviews-method and it didn't work. And I don't want to use any rotation callback methods either, because the problem occurs at other occasions as well.
I don't know which method for animation you are using, but I will give you an example of the default UIView animation with completion:
[UIView animateWithDuration:1.0 animations:^{
/* Some animation here */
} completion:^(BOOL finished) {
UIImage *myScreenshot = [self makeScreenshot];
}];
Maybe you try to perform it in background thread
[self methodWithCompletionWhenAnimationsIsDone:^(BOOL finished) {
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *myScreenshot = [self makeScreenshot];
});
}];
I alloc and show an UIAlertView ( standard one button ). On
- (void)willPresentAlertView:(UIAlertView *)alertView{
NSArray *a = [self.view subviews];
}
I get one element. The view that was in effect when I showed the Alert. On
- (void)didPresentAlertView:(UIAlertView *)alertView{
NSArray *a = [self.view subviews];
}
I get 4 elements(??). UIAlertView somehow brings to life views that I previously have removed from their superview. Why??
Thanks, David
Showing an alert is an activity related to UI and it's generally good practice to perform all the UI related tasks on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
});
Inside the dispatch block, you should display your alert.
Hope this helps..
I am using setNeedsDisplay on my GUI, but there update is sometimes not done. I am using UIPageControllView, each page has UIScrollView with UIView inside.
I have the following pipeline:
1) application comes from background - called applicationWillEnterForeground
2) start data download from server
2.1) after data download is finished, trigger selector
3) use dispatch_async with dispatch_get_main_queue() to fill labels, images etc. with new data
3.1) call setNeedsDisplay on view (also tried on scroll view and page controller)
Problem is, that step 3.1 is called, but changes apper only from time to time. If I swap pages, the refresh is done and I can see new data (so download works correctly). But without manual page turn, there is no update.
Any help ?
Edit: code from step 3 and 3.1 (removed _needRefresh variables pointed in comments)
-(void)FillData {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *stateID = [DataManager ConvertStateToStringFromID:_activeCity.actual_weather.state];
if ([_activeCity.actual_weather.is_night boolValue] == YES)
{
self.contentBgImage.image = [UIImage imageNamed:[NSString stringWithFormat:#"bg_%#_noc", [_bgs objectForKey:stateID]]];
if (_isNight == NO)
{
_bgTransparencyInited = NO;
}
_isNight = YES;
}
else
{
self.contentBgImage.image = [UIImage imageNamed:[NSString stringWithFormat:#"bg_%#", [_bgs objectForKey:stateID]]];
if (_isNight == YES)
{
_bgTransparencyInited = NO;
}
_isNight = NO;
}
[self.contentBgImage setNeedsDisplay]; //refresh background image
[self CreateBackgroundTransparency]; //create transparent background if colors changed - only from time to time
self.contentView.parentController = self;
[self.contentView FillData]; //Fill UIView with data - set labels texts to new ones
//_needRefresh is set to YES after application comes from background
[self.contentView setNeedsDisplay]; //This do nothing ?
[_grad display]; //refresh gradient
});
}
And here is selector called after data download (in MainViewController)
-(void)FinishDownload:(NSNotification *)notification
{
dispatch_async(dispatch_get_main_queue(), ^{
[_activeViewController FillData]; //call method shown before
//try call some more refresh - also useless
[self.pageControl setNeedsDisplay];
//[self reloadInputViews];
[self.view setNeedsDisplay];
});
}
In AppDelegate I have this for application comes from background:
-(void)applicationWillEnterForeground:(UIApplication *)application
{
MainViewController *main = (MainViewController *)[(SWRevealViewController *)self.window.rootViewController frontViewController];
[main UpdateData];
}
In MainViewController
-(void)UpdateData
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(FinishForecastDownload:) name:#"FinishDownload" object:nil]; //create selector
[[DataManager SharedManager] DownloadForecastDataWithAfterSelector:#"FinishDownload"]; //trigger download
}
try this:
[self.view performSelectorOnMainThread:#selector(setNeedsLayout) withObject:nil waitUntilDone:NO];
or check this link:
http://blackpixel.com/blog/2013/11/performselectoronmainthread-vs-dispatch-async.html
setNeedsDisplay triggers drawRect: and is used to "redraw the pixels" of the view , not to configure the view or its subviews.
You could override drawRect: and modify your labels, etc. there but that's not what it is made for and neither setNeedsLayout/layoutSubviews is.
You should create your own updateUI method where you use your fresh data to update the UI and not rely on specialized system calls meant for redrawing pixels (setNeedsDisplay) or adjusting subviews' frames (drawRect:).
You should set all your label.text's, imageView.image's, etc in the updateUI method. Also it is a good idea to try to only set those values through this method and not directly from any method.
None of proposed solutions worked. So at the end, I have simply remove currently showed screen from UIPageControllView and add this screen again. Something like changing the page there and back again programatically.
Its a bit slower, but works fine.
What i want to achieve :
Create processing screen with some custom icons, label etc. with following behaviour.
Add view over window which will not allow user to touch anything in application until it is removed. (like processing/loading screen)
When this view is displayed all other operation like adding subview, performing segue etc should work as they work normally but below my loading view.
Want method showProcessingScreen to work on any thread (Whatever thread switching code etc should be in respective show/hide method).
It should be displayed/removed immediately in after calling respective methods.
Code :
-(void) showProcessingScreen
{
dispatch_async(dispatch_get_main_queue(),
^{
UIStoryboard *mystoryboard = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil];
processingScreen = [mystoryboard instantiateViewControllerWithIdentifier:#"loadingViewController"];
UIWindow* mainWindow = [[UIApplication sharedApplication] keyWindow];
[mainWindow addSubview: processingScreen.view];
[mainWindow bringSubviewToFront:processingScreen.view];
});
}
-(void) hideProcessingScreen
{
dispatch_async(dispatch_get_main_queue(),
^{
[processingScreen.view removeFromSuperview];
});
}
Issue:
I want code above to work with showing/hiding loading screen immediately.
- (IBAction)proceedBtnPressed:(id)sender
{
[[GUIUtilities sharedObj] showProcessingScreen];
//Some other code here
}
When i call showProcessingScreen like above processing screen takes around 2-3 sec to show.
But when i remove other code below it (//Some other code) it shows screen immediately.
What i have tried:
Putting code in showProcessingScreen in other method and calling that on main thread using performSelectorOnMainThread.
Calling showProcessingScreen on background and executing show code on main thread using performSelector.
This works
//code
-(IBAction)proceedBtnPressed:(id)sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
^{
[[GUIUtilities sharedObj] showProcessingScreen];
//Some other code here
});
}
But i don't want any thread switching mechanism outside of showProcessingScreen.
This is common screen almost used in every application. I used similar codes with xib, custom views in my previous apps which were not using storyboard etc.,
I know this is related to threading, what i am doing wrong here ? what is best practice to achieve this ?
Any help will be aprreciatead.
The problem is that when you're dispatching, you put the creation of your loading view at the end of the run loop. Whatever your "other code" is is blocking the main thread for a few seconds.
If you move this "other code" to a different thread, this will resolve your issue. (This solution is ideal.)
You could also only switch to the main thread conditionally, which would resolve the issue if you call the loading view from the main thread:
-(void) showProcessingScreen
{
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(showProcessingScreen) withObject:nil waitUntilDone:FALSE];
return;
}
UIStoryboard *mystoryboard = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil];
processingScreen = [mystoryboard instantiateViewControllerWithIdentifier:#"loadingViewController"];
UIWindow* mainWindow = [[UIApplication sharedApplication] keyWindow];
[mainWindow addSubview: processingScreen.view];
[mainWindow bringSubviewToFront:processingScreen.view];
}
Instead of adding an overlay view that intercepts touch events, you can just call
– beginIgnoringInteractionEvents
– endIgnoringInteractionEvents
You could try with this:
- (void)doSomeOtherCode {
//Some other code here
}
- (IBAction) showProcessingScreenAndDoSomeOtherCode:(id)sender
{
[[GUIUtilities sharedObj] showProcessingScreen];
[self performSelector:#selector(doSomeOtherCode) withObject:nil afterDelay:0.0];
}
- (IBAction)proceedBtnPressed:(id)sender
{
[self showProcessingScreenAndDoSomeOtherCode:self];
}
and it will work. About your remark:
But i don't want any thread switching mechanism outside of showProcessingScreen.
The above solution will not cause any thread switching outside of showProcessingScreen. performSelector will just add an entry in the event loop queue.
EDIT:
If you are using blocks, the same result can be achieved through:
dispatch_async(dispatch_get_main_queue(),
^{
[[GUIUtilities sharedObj] showProcessingScreen];
dispatch_async(dispatch_get_main_queue(), ^{
//Some other code here
});
});
or:
dispatch_async(dispatch_get_main_queue(),
^{
[[GUIUtilities sharedObj] showProcessingScreen];
});
dispatch_async(dispatch_get_main_queue(), ^{
//Some other code here
});
Since the main queue is a serial queue, showProcessingScreen and "Some other code here" would be executed serially.
I have already posted " HERe" .
and Here actually What I need to Deserve
If I put this part of code [self loadLocationView] in UIButton and ViewDidLoad method , It works fine here.
but whenever I choose a location from TableVeiw and calling from a configureViewSlider method , It gets called to the method loadLocationView but Not updating UI.
Here I have tried so far.
-(void) configureViewSlider
{
if (_detailItem) {
Location=[_detailItem description];
//[self performSelectorOnMainThread:#selector(btnLocations:) withObject:nil waitUntilDone:false];
//[btnLocations sendActionsForControlEvents:UIControlEventTouchDown];
// [self loadLocationView];
[self performSelectorOnMainThread:#selector(loadLocationView) withObject:nil waitUntilDone:NO];
//[self loadView];
NSLog(#"Location %#", Location);
}
}
-(void) loadLocationView
{
_viewLocBangalore.frame= CGRectMake(10, 10, 303,255);
[self.view addSubview:_viewLocBangalore];
// [self.View bringSubviewToFront:_viewlocBangalore];
//[_viewLocBangalore setNeedsDisplay];
}
What else I have to do to achieve it . I have tried in all possible ways . but No use .
Please do some suggestion at least, I'm ready to approach for any way which is ready deserve my task (i..e.. I have added a UITableVieContrller which has locations , when user touches the Location , the UIView should update ).
Why u called configureViewSlider method then loadlocationview method directly location selection tableview method add this view:
_viewLocBangalore.frame= CGRectMake(10, 10, 303,255);
[self.view addSubview:_viewLocBangalore];