So i am showing a model controller on top of a view controller. And i have texts in the model controller, but somehow the texts are not visible. I tried everything but somehow labels are not visible. But of you stay on the page for like 30 -40 sec the text shows up. Also this model controller is called from main view controller after a successful service(REST) call. If i call the model without making the service call then labels are visible in simulator/iPad both. But if i call it after service call inside success block then labels are not visible. I tried adding the text programmatically but still same issue. I tried debugging using Color blended layers, but the label is not at all visible in the view somehow. :(
[self.serviceManager getCustDetails:account successBlock:^(NSDictionary * successDict) {
[self hideLoadingAnimation];
NSDictionary *custData = [[successDict objectForKey:#"txnData"] objectForKey:#"custData"];
self.showCurrYear = [iraContribData objectForKey:#"showCurrYear"];
if ([self.showCurrYear isEqual: #"true"]){
[self performSegueWithIdentifier:#"CSegue" sender:self];
}
} failureBlock:^(NSDictionary * failureDict) {
[self hideLoadingAnimation];
NSLog(#"Failiure Dict %#",failureDict);
}];
And this prepareForSegue method, -
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"CSegue"]) {
CustViewController *cVC = segue.destinationViewController;
cVC.delegate = self;
[cVC setModalPresentationStyle:UIModalPresentationFormSheet];
cVC.preferredContentSize = CGSizeMake(800,750);
}
}
Below is my screen in storyboard, but in simulator the label is not visible, only continue and close button is visible.
Please help!, any suggestions are most welcome. Thanks!
It is possible that the delay is due to a user interface update not made on the main thread.
Try to make sure that your code is executed on the main thread using dispatch_async like this :
[self.serviceManager getCustDetails:account successBlock:^(NSDictionary * successDict) {
dispatch_async(dispatch_get_main_queue(), ^{
[self hideLoadingAnimation];
NSDictionary *custData = [[successDict objectForKey:#"txnData"] objectForKey:#"custData"];
self.showCurrYear = [iraContribData objectForKey:#"showCurrYear"];
if ([self.showCurrYear isEqualToString:#"true"]){
[self performSegueWithIdentifier:#"CSegue" sender:self];
}
});
} failureBlock:^(NSDictionary * failureDict) {
dispatch_async(dispatch_get_main_queue(), ^{
[self hideLoadingAnimation];
NSLog(#"Failiure Dict %#",failureDict);
});
}];
Related
Maybe this is purely simulator related. I have not tried it on an actual device yet.
I'm on the latest greatest MacBook with a 1TB flash drive, and 95% free processor, and less than full memory consumption.
I have a UIPopoverController with 4 items in it, sized to those items.
There's nothing complicated or multi-threaded or long running in any way associated in the UIPopoverController in question.
I've set the appear and dismiss animation at 0, yet when I tap on an item in the list, there seems to be an random indeterminate delay between 0 and .4 seconds in the popover disappearing. Of course the 0 is expected, but the times when it's nearly a half second is very noticeably longer and disconcerting.
Any idea what may be causing that?
Code that shows the popover...
-(IBAction)theLandImpsButtonPressed:(UIButton *)sender
{
iRpNameValuePopover *thePopoverContent = [[iRpNameValuePopover alloc] init];
thePopoverContent.theTableValues = [self getLandImpsChoicesList];
impsLandPopover = [[UIPopoverController alloc] initWithContentViewController:thePopoverContent];
thePopoverContent.thePopoverController = impsLandPopover;
impsLandPopover.popoverContentSize = [iRpUIHelper sizeForPopoverThatHasTitle:NO andListContent:thePopoverContent.theTableValues];
impsLandPopover.delegate = self;
[impsLandPopover presentPopoverFromRect:self.theLandImpsButton.bounds inView:self.theLandImpsButton permittedArrowDirections:UIPopoverArrowDirectionAny animated:NO];
}
Code that dismisses the popover...
BTW, there is no evaluation time incurred here [self userChoiceIsValid] because it simply returns YES right now.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
_theChosenNameValueItem = [self.theTableValues objectAtIndex:indexPath.row];
[self acceptUserChoiceAndClose];
}
// This contentViewController is encapsulated INSIDE a UIPopoverViewController, and this class cannot itself
// close the popover which contains it, hence the need for the reference to the popover controller
// It is the popover's delegate... the one that created the popover, that is able to close it.
-(void)acceptUserChoiceAndClose
{
_theUserChoseAValue = NO; // Start by assuming they didn't chose a valid value.
if ([self userChoiceIsValid])
{
// Set variable that indicates the user chose a value which can be saved to core data, and/or presented on screen.
_theUserChoseAValue = YES;
// Close the popover.
[_thePopoverController dismissPopoverAnimated:NO];
// Notify the class that presented the popover that the popover has been dismissed.
// It will still be available to the dismissal method where code can retrieve the user's choice, and set the popover to nil.
if (_thePopoverController.delegate && [_thePopoverController.delegate respondsToSelector:#selector(popoverControllerDidDismissPopover:)])
{
[_thePopoverController.delegate popoverControllerDidDismissPopover:_thePopoverController];
}
}
else
{
[self showValidationFailureMessageToUser];
}
}
Dismissing the viewController in main thread will solve the issue.
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES completion:nil];
});
I would check it out in the profiler and see what the time is being spent on.
There's a good tutorial here.
UIPopoverPresentationController *popOverView;
//////
Dismiss it...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[popOverView.presentedViewController dismissViewControllerAnimated:NO completion:nil];
popOverView = nil;
});
});
I am developing an app where the user will get to confirm some action via UIAlertView, if he confirms, I call a method that handles the operation, then I prepare to pop the view I am in to go back to another view after the method has been called.
I want to show UIActivityIndicatorView if the user presses confirm for as long as it takes to execute the method and go to that other view. I used startAnimating and stopAnimating in the proper location, but i never get to see the UI UIActivityIndicatorView shown, not for a sec.
I guess its related to some UI issues due to UIAlertView, not sure if I am correct though. I just need a clue on how to use UIActivityIndicatorView properly for a method execution time.
My code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.activityIndicator.alpha = 1.0;
self.activityIndicator.hidesWhenStopped = YES;
self.activityIndicator.center = self.view.center;
[self.view addSubview:self.activityIndicator];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if(buttonIndex == 1) {
[self.activityIndicator startAnimating];
ContactsTableViewController *contactTableView = [self getContactsTVC];
[contactTableView applyActionOnCells];
// doing some setup before poping off to the root view controller of my nav controller
[self.activityIndicator stopAnimating];
// then go to rootViewController
[self.navigationController popToRootViewControllerAnimated:YES];
}
}
I'm not 100% certain, but try to comment out the stopAnimating call and see if it shows up.
If that helps, applyActionOnCells probably blocks your main thread (where all UI stuff also happens) and the indicator never has a chance to show up before you hide it again.
In that case, try do the applyActionOnCells call in the background:
if(buttonIndex == 1) {
[self.activityIndicator startAnimating];
__block typeof(self) bself = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
ContactsTableViewController *contactTableView = [bself getContactsTVC];
[contactTableView applyActionOnCells];
dispatch_async(dispatch_get_main_queue(), ^{
[bself.activityIndicator stopAnimating];
// then go to rootViewController
[bself.navigationController popToRootViewControllerAnimated:YES];
});
});
}
Edit: see also an earlier question.
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.
I receive data async via a completion method.
When the completion get fired, I passing the data to a new tableviewcontroller.
But the table view controller takes too much time to load. The data is an array with up to 25 objects. So not that much.
To move to the new controller, I use a segue I created in storyboard.
This is the completion method:
{
if(receivedArray != nil)
{
searchResult = receivedArray;
[self performSegueWithIdentifier:#"searchToDisplay" sender:self];
}
}
When I debug, and set a breakpoint to the prepareForSegue:sender: method, I get there after less than one second.
here the method:
if([segue.identifier isEqualToString:#"searchToDisplay"])
{
BookingDisplayTableViewController *bookingVC = (BookingDisplayTableViewController *)segue.destinationViewController;
bookingVC.bookings = [NSArray arrayWithArray:searchResult];
}
Additionally here the viewDidLoad method of the BookingDisplayController. The viewWillAppear isn't overrided.
- (void)viewDidLoad
{
[super viewDidLoad];
UINib *nib = [UINib nibWithNibName:#"BookingCellDesign" bundle:nil];
[self.tableView registerNib:nib forCellReuseIdentifier:#"BookingCell"];
self.tableView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"tableBackground.png"]];
self.clearsSelectionOnViewWillAppear = NO;
}
And I can't get the performance problem here...
I need help. :)
Thanks!
UPDATE
So i troubleshooted the problem. It was the completion method.
I got the date via dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)... And a synchronous NSMutableRequest.
I fired the completion After I send the synchronous request. Now I put this code inside the dispath block
dispatch_async(dispatch_get_main_queue(),
^{
if(response)
{
[self removeLoadingView];
if(bookingTemp.count > 0)
completion(bookingTemp);
else
completion(nil);
}
});
And now it works great.
What I don't understand before I did this, I debugged and reached the completion block like now. But only the segue didn't start.
Thanks for help!
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.