I have 4 tabs and all have UIWebView. I want that the first tab should load and displayed immediately without waiting for others to load. For which I did this in UITabBarController class:
for(UIViewController * viewController in self.viewControllers){
if(![[NSUserDefaults standardUserDefaults]boolForKey:#"isSessionExpired"])
{
if((int)[self.viewControllers indexOfObject:viewController] != 4)
{
viewController.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:viewController];
[viewController view];
}
}
}
But the main thread waits for all tabs to load.
I tried this with GCD using dispatch_async
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // 1
dispatch_async(dispatch_get_main_queue(), ^{
UIViewController *firstContrl = [self.viewControllers objectAtIndex:0];
firstContrl.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:firstContrl];
[firstContrl view];
dispatch_async(dispatch_get_main_queue(), ^{ // 2
UIViewController *secContrl = [self.viewControllers objectAtIndex:1];
secContrl.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:secContrl];
[secContrl view];
dispatch_async(dispatch_get_main_queue(), ^{ // 3
UIViewController *thirdContrl = [self.viewControllers objectAtIndex:2];
thirdContrl.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:thirdContrl];
[thirdContrl view];
dispatch_async(dispatch_get_main_queue(), ^{ // 4
UIViewController *fourthContrl = [self.viewControllers objectAtIndex:3];
fourthContrl.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:fourthContrl];
[fourthContrl view];
});
});
});
});
});
But this doesn't work either. It takes the same time to display the first tab.
How can this be fixed?
Ehm.. GCD and UIWebView's -loadRequest: doesn't work as you think. All the code above is pretty equal to:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
UIViewController *firstContrl = [self.viewControllers objectAtIndex:0];
firstContrl.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:firstContrl];
[firstContrl view];
UIViewController *secContrl = [self.viewControllers objectAtIndex:1];
secContrl.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:secContrl];
[secContrl view];
UIViewController *thirdContrl = [self.viewControllers objectAtIndex:2];
thirdContrl.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:thirdContrl];
[thirdContrl view];
UIViewController *fourthContrl = [self.viewControllers objectAtIndex:3];
fourthContrl.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:fourthContrl];
[fourthContrl view];
});
});
To solve your problem you need to implement queue depends on - (void)webViewDidFinishLoad:(UIWebView *)webView. The idea is:
First view controller's webView starts loading request
asynchronously;
When request is loaded (or failed), webView will notify your
delegate;
Notify all other view controllers with webView, to let them start
loading their requests;
If I were you, I would implement it in next way:
// FirstViewController.h
extern NSString * const FirstViewControllerDidLoadWebView;
// FirstViewController.m
NSString * const FirstViewControllerDidLoadWebView=#"FirstViewControllerDidLoadWebView";
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// notify other controllers, to let them load their web views:
[[NSNotificationCenter defaultCenter] postNotificationName:FirstViewControllerDidLoadWebView
object:nil];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
// loading failed. Try to load it few more times but anyway notify other controllers after:
[[NSNotificationCenter defaultCenter] postNotificationName:FirstViewControllerDidLoadWebView
object:nil];
}
After that, 'subscribe' all other(only tabs) view controllers for FirstViewControllerDidLoadWebView notification and load their WebView after it arrives.
For more details check NSNotificationCenter and UIWebViewDelegate
Related
I'm trying to understand how to minimize my transition time between my view controllers.
For some reason, segment switching to one of my controllers takes 1204.0 ms.
In instruments it is telling me that this method is taking 964.0 ms.
- (void)goToNextContentViewController
{
self.index = [NSNumber numberWithInt:1];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MusicPlayerQueue" bundle:nil];
SABaseContentViewController *queueViewController = [storyboard instantiateViewControllerWithIdentifier:#"Queue"];
queueViewController.rootViewController = self;
[self.pageViewController setViewControllers:#[queueViewController]
direction:UIPageViewControllerNavigationDirectionForward
animated:YES
completion:^(BOOL finished) {
}];
}
Specifically when I select a segmented control.
- (IBAction)segmentSwitch:(UISegmentedControl *)sender
{
UISegmentedControl *segmentedControl = (UISegmentedControl *) sender;
NSInteger selectedSegment = segmentedControl.selectedSegmentIndex;
if (selectedSegment == 0) {
[self goToPreviousContentViewController];
}
else{
[self goToNextContentViewController];
}
}
I am pushing to a tableViewController that is loading data that I am populating from a singleton. Any ideas why this is happening? My other segment tab loads the view controller at a much faster speed with the same method.
The strangest part is that the view loads pretty fast, ONLY when the tableView in the controller I am pushing to is populated with 3 or less objects from my SingletonArray. If the array is 3 or greater, it will load at a speed of 1000+ms....
Here is my viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.tableView setEditing:YES];
self.editSelected = NO;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemDidFinishPlaying:) name:#"songEnded" object:nil];
}
I have a today widget extension, and when I click a button it opens up the app. The first time I click the button and follow the code through, it uses a custom URL scheme to pass data through. That's parsed in the AppDelegate, and it figures out what data to populate the ViewController with. The ViewController is instantiated with a storyboard ID. Values are applied to one of the ViewController's properties, and then in the viewDidLoad the rest of the values are populated based on this passed in Value. This all works the first time.
However, if I hit the home button, open notification center, tap a button in my app and go through the whole process for a second time.. I step through the code as normal, all the values get set, but when the ViewController gets displayed, the values (such as UILabel) are the same as the first time, but they should have changed.
NSString *url = [NSString stringWithFormat:string ://%#", self.expanededTubeLine.lineName ];
NSExtensionContext *myExtension=[self extensionContext];
[myExtension openURL:[NSURL URLWithString:url] completionHandler:nil];
//
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
NSString *tubeLineName = [url host];
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.koc.extensiontest"];
if ([defaults objectForKey:#"weekendData"]) {
NSData *tubeData = [[defaults objectForKey:#"weekendData"] copy];
TFLParser *parser = [[TFLParser alloc] initWithData:tubeData];
[parser parseData];
for (int x = 0; x < parser.delayedTubeLines.count; x++) {
TubeLine *tl = [[TubeLine alloc] init];
tl = [parser.delayedTubeLines objectAtIndex:x];
if ([tl.lineName isEqualToString:tubeLineName]) {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
TubeLineViewController *tubeLineViewController = [storyboard instantiateViewControllerWithIdentifier:#"TubeLineViewController"];
tubeLineViewController.tubeLine = tl;
[self.window.rootViewController presentViewController:tubeLineViewController animated:YES completion:nil];
return YES;
}
}
}
}
//
- (void)viewDidLoad {
[super viewDidLoad];
self.tubeLineName.text = self.tubeLine.lineName;
self.tubeLineName.font = [UIFont openSansLightFontOfSize:18.0f];
self.tubeLineName.textColor = [UIColor whiteColor];
self.tubeLineName.backgroundColor = self.tubeLine.lineBackgroundUIColor;
self.tubeLineName.layer.cornerRadius = 5;
self.tubeLineName.clipsToBounds = YES;
self.tubeLineMessage.font = [UIFont openSansLightFontOfSize:18.0f];
self.tubeLineMessage.text = self.tubeLine.lineMessage;
self.tubeLineMessage.textColor = [UIColor darkGrayColor];
}
It sounds like the View is loaded already so pop all the view controllers before pushing the widget based controller in handleOpenURL: function.
[self.viewController.navController popToRootViewControllerAnimated:NO];
if ([self.viewController presentedViewController]) {
[self.viewController dismissViewControllerAnimated:NO completion:nil];
}
I am using the Storyboard, then in my AppDelegate has no navigation controller programming on hand (all normal so far).
Now i need to fire the "applicationDidEnterBackground" I would like my app point to the navigation controller to the first screen (ie) popToView or popToRoot.
I tried to use some means found as:
EDITED: - Insert the applicationDidEnterBackgoround Method
//*-- BEGIN
- (void)applicationDidEnterBackground:(UIApplication *)application
{
_mainMenuDelegate = [[UIStoryboard storyboardWithName: stb_name bundle: nil] instantiateViewControllerWithIdentifier: # "idMainMenu"];
[[_window.rootViewController navigationController] popToViewController: _mainMenuDelegate animated: NO];
OR
NSArray * = viewContrlls _window.subviews;
for (int i = 0; i <[viewContrlls count], i + +)
{
id obj = [viewContrlls objectAtIndex: i];
if ([obj isKindOfClass: [MainMenu class]])
{
[[_window.rootViewController navigationController] popToViewController: obj animated: YES];
return;
}
}
} //*-- END
But the first does not fire and, the second is just 1 result row and it is not the MainMenu, so do not jump to the ViewController.
My question is: How to do it, knowing that I am using Storyboard?
Thank you
Try that:
NSArray* viewControllers = [[_window.rootViewController navigationController] viewControllers];
for (int i = 0; i < [viewControllers count]; i++)
{
id obj = [viewControllers objectAtIndex: i];
if ([obj isKindOfClass: [MainMenu class]])
{
[[_window.rootViewController navigationController] popToViewController: obj
animated: YES];
return;
}
}
Hope it helps you.
I m using UIPageViewController on iPad where I need to show a firstviewController in the first page and ContentViewController in the next page in landscape.
If I set the NSArray with two viewControllers the app is crashes at [self.pagviewController setViewController:] with the following exception:
The number of provided view controllers (2) doesn't match the number required (1) for the requested spine location (UIPageViewControllerSpineLocationMin)
Below is the code:
#pragma mark - UIPageViewControllerDataSource Methods
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger currentIndex = [self.modelArray indexOfObject:[(ContentViewController *)viewController textContents]];
if(currentIndex == 0)
{
return nil;
}
ContentViewController *contentViewController = [[ContentViewController alloc] init];
contentViewController.textContents = [self.modelArray objectAtIndex:currentIndex - 1];
return contentViewController;
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController
{
NSUInteger currentIndex = [self.modelArray indexOfObject:[(ContentViewController *)viewController textContents]];
if(currentIndex == self.modelArray.count-1)
{
return nil;
}
ContentViewController *contentViewController = [[ContentViewController alloc] init];
contentViewController.textContents = [self.modelArray objectAtIndex:currentIndex + 1];
return contentViewController;
}
//#pragma mark - UIPageViewControllerDelegate Methods
- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
if(UIInterfaceOrientationIsPortrait(orientation))
{
//Set the array with only 1 view controller
UIViewController *currentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
NSArray *viewControllers = [NSArray arrayWithObject:currentViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
//Important- Set the doubleSided property to NO.
self.pageViewController.doubleSided = NO;
//Return the spine location
return UIPageViewControllerSpineLocationMin;
}
else
{
NSArray *viewControllers = nil;
ContentViewController *currentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
NSUInteger currentIndex = [self.modelArray indexOfObject:[(ContentViewController *)currentViewController textContents]];
if(currentIndex == 0 || currentIndex %2 == 0)
{
UIViewController *nextViewController = [self pageViewController:self.pageViewController viewControllerAfterViewController:currentViewController];
viewControllers = [NSArray arrayWithObjects:currentViewController, nextViewController, nil];
}
else
{
UIViewController *previousViewController = [self pageViewController:self.pageViewController viewControllerBeforeViewController:currentViewController];
viewControllers = [NSArray arrayWithObjects:previousViewController, currentViewController, nil];
}
//Now, set the viewControllers property of UIPageViewController
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
return UIPageViewControllerSpineLocationMid;
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
appDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
//Instantiate the model array
self.modelArray = [[NSMutableArray alloc] init];
self.vcs = [[NSMutableArray alloc]init];
for (int index = 1; index <= 2 ; index++)
{
[self.modelArray addObject:[NSString stringWithFormat:#"Page %d",index]];
}
//Step 1
//Instantiate the UIPageViewController.
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
//Step 2:
//Assign the delegate and datasource as self.
self.pageViewController.delegate = self;
self.pageViewController.dataSource = self;
//Step 3:
//Set the initial view controllers.
appDelegate.contentViewController.textContents = [self.modelArray objectAtIndex:0];
NSArray *viewControllers = [NSArray arrayWithObjects:appDelegate.firstViewController,appDelegate.contentViewController,nil];
[self.pageViewController setViewControllers:viewControllers
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
//Step 4:
//ViewController containment steps
//Add the pageViewController as the childViewController
[self addChildViewController:self.pageViewController];
//Add the view of the pageViewController to the current view
[self.view addSubview:self.pageViewController.view];
//Call didMoveToParentViewController: of the childViewController, the UIPageViewController instance in our case.
[self.pageViewController didMoveToParentViewController:self];
//Step 5:
// set the pageViewController's frame as an inset rect.
CGRect pageViewRect = self.view.bounds;
pageViewRect = CGRectInset(pageViewRect, 40.0, 40.0);
self.pageViewController.view.frame = pageViewRect;
//Step 6:
//Assign the gestureRecognizers property of our pageViewController to our view's gestureRecognizers property.
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
}
The problem is you passing an array containing two view controllers to the page view controller while it expects one at a time, change your array to be like this :
NSArray *viewControllers = #[appDelegate.firstViewController];
You will pass one of the views but viewControllerAfterViewController and viewControllerBeforeViewController will handle the rest.
Ah..Finally got solution for this same issue.., it may helps you..
When we set the spine location to UIPageViewControllerSpineLocationMid, the doubleSided property of the pageViewController is automatically set to YES. This means that the content on page front will not partially show through back. But when this property is set to NO, the content on page front will partially show through back, giving the page a translucent kind of effect. So, in the portrait orientation, we have to set the value to NO, otherwise it would result in an exception.
So in your UIPageviewcontroller delegate method, in else part add this doubleSided property as YES when you return spineLocation as UIPageViewControllerSpineLocationMid
self.pageViewController.doubleSided = YES;
return UIPageViewControllerSpineLocationMid;
self.pageViewController.doubleSided = NO;
return UIPageViewControllerSpineLocationMid;
This is the solution for the exception.
In xcode it self you could find this.
Go to the UIPageViewcontroller class there you could see the explanation for this like:
#property (nonatomic, readonly) UIPageViewControllerSpineLocation spineLocation; // If transition style is 'UIPageViewControllerTransitionStylePageCurl', default is 'UIPageViewControllerSpineLocationMin', otherwise 'UIPageViewControllerSpineLocationNone'.
// Whether client content appears on both sides of each page. If 'NO', content on page front will partially show through back.
// If 'UIPageViewControllerSpineLocationMid' is set, 'doubleSided' is set to 'YES'. Setting 'NO' when spine location is mid results in an exception.
#property (nonatomic, getter=isDoubleSided) BOOL doubleSided; // Default is 'NO'.
Instead of implementing a full data source, you can set the PageViewController with one view controller at a time each time the user pushes a next or back button, like
[pageViewController setViewControllers:#[contentViewController]
direction:UIPageViewControllerNavigationDirectionForward
animated:YES
completion:nil];
This will animate the page transition as you switch.
I am posting a notification from the UIPopoverController back to my main view, which then immediately calls dismissPopoverAnimated, and then goes about doing some fairly heavy work loading web views. All of this code works; the problem is that on some older ipads, the popover is not actually dismissed until after the cpu intensive work is completed (verified in debugger). This causes the popover to appear to hang for second after being dismissed. How can I ensure the popover is dismissed immediately instead of doing the intensive code first?
The method which responds to the notification is as follows:
- (void)changeDefaultView:(NSNotification *)note
{
[self closePopover];
int i;
for(i = 0; i < [arrWebViewControllers count]; i++)
{
WebViewController *wvc = [arrWebViewControllers objectAtIndex:i];
[[wvc webview] stopLoading];
[[wvc webview] removeFromSuperview];
[[wvc imageview] removeFromSuperview];
wvc = nil;
}
[arrWebViewControllers removeAllObjects];
[arrLinks removeAllObjects];
[arrImageViews removeAllObjects];
[self loadCategory:[note object]];
[self addWidgetsToView];
}
and closePopover is:
- (void)closePopover
{
if(popover != nil)
{
[popover dismissPopoverAnimated:YES];
popover = nil;
}
}
The animation of the disappearing popover happens on the main run loop; if you're doing heavy CPU work on the main thread, then the loop won't get a chance to run. So what you need to do is to perform your work on a background thread.
The currently-preferred way to do this is using Grand Central Dispatch, like so: (I'm assuming that loadCategory: is the CPU-intensive operation)
- (void)changeDefaultView:(NSNotification *)note
{
[self closePopover];
int i;
for(i = 0; i < [arrWebViewControllers count]; i++)
{
WebViewController *wvc = [arrWebViewControllers objectAtIndex:i];
[[wvc webview] stopLoading];
[[wvc webview] removeFromSuperview];
[[wvc imageview] removeFromSuperview];
wvc = nil;
}
[arrWebViewControllers removeAllObjects];
[arrLinks removeAllObjects];
[arrImageViews removeAllObjects];
// perform the expensive operation on a background thread
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self loadCategory:[note object]];
// now get back onto the main thread to perform our UI update
dispatch_async(dispatch_get_main_queue(), ^{
[self addWidgetsToView];
});
});
}