I encountered this problem when doing my MCQ Game app testing.
I'm using ARC & Storyboard custom segue and [self performSegueWithIdentifier:#"VC" sender:self]; to push from ViewController1 to ViewController2 but very seldom popping back the ViewController1 due to the naturals of the app, moving forward from question to question. the app will become very very sluggish after multiple times pushing. what is the best way to solve the problem?
Custom segue:
-(void)perform {
UIViewController *sourceVC = (UIViewController *) self.sourceViewController;
UIViewController *destinationVC = (UIViewController *) self.destinationViewController;
[UIView transitionWithView:sourceVC.navigationController.view duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
[sourceVC.navigationController pushViewController:destinationVC animated:NO];
}
completion:^(BOOL completed)
{
//[sourceVC.navigationController pushViewController:destinationVC animated:NO];
}
];
}
I suspect there is a strong reference cycle going on. Run Leaks to make sure that the previous view controller does not have a strong reference after you push the new one.
i changed the custom segue from push to pop, based on my view controller name, and it works :)
-(void)perform {
UIViewController *sourceVC = (UIViewController *) self.sourceViewController;
//UIViewController *destinationVC = (UIViewController *) self.destinationViewController;
NSInteger index = -1;
NSArray* arr = [[NSArray alloc] initWithArray:sourceVC.navigationController.viewControllers];
for(int i=0 ; i<[arr count] ; i++)
{
if([[arr objectAtIndex:i] isKindOfClass:NSClassFromString(#"mainViewController")])
{
index = i;
}
}
[UIView transitionWithView:sourceVC.navigationController.view duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
[sourceVC.navigationController popToViewController:[arr objectAtIndex:index] animated:NO];
}
completion:^(BOOL completed)
{
//[sourceVC.navigationController pushViewController:destinationVC animated:NO];
}
];
}
Related
Below is the code of ModelController.m:
- (id)init
{
self = [super init];
if (self) {
// Create the data model.
DataHandler *handler = [DataHandler defaultDataHandler];
[handler syncData];
NSInteger tempTotal=1;//[[DataHandler defaultDataHandler] getTotalContents];
NSMutableDictionary *favs=[Settings getSetting:kFavList];
if(favs!=nil){
tempTotal=[favs count]+1;
}
NSInteger TotalItems=tempTotal;//tempTotal>=6?tempTotal:6;
NSInteger PageSize=6;
NSInteger PageNo= (TotalItems/PageSize);
if(TotalItems%PageSize!=0){
PageNo++;
}
self.TotalPage=PageNo;
NSInteger i=0,temp=PageSize;
self.pageArray=[[NSMutableDictionary alloc] initWithCapacity:PageNo];
for (i=0; i<PageNo; i++) {
temp=(PageNo==i+1 && TotalItems%PageSize!=0)? TotalItems%PageSize:PageSize;
[self.pageArray setObject:[NSString stringWithFormat:#"%d",temp] forKey:[NSString stringWithFormat:#"%d",i+1]];
}
//NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
//_pageData = [[dateFormatter monthSymbols] copy];
self.pageData=[self.pageArray allKeys];
}
return self;
}
- (HomeViewController *)viewControllerAtIndex:(NSUInteger)index {
NSLog(#"viewControllerAtIndex is called..");
// Return the data view controller for the given index.
if (([self.pageData count] == 0) || (index >= [self.pageData count])) {
return nil;
}
// Create a new view controller and pass suitable data.
HomeViewController *homeViewController = nil;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
homeViewController = [[[HomeViewController alloc] initWithNibName:#"HomeViewController_iPhone" bundle:nil] autorelease];
} else {
homeViewController = [[[HomeViewController alloc] initWithNibName:#"HomeViewController_iPad" bundle:nil] autorelease];
}
homeViewController.dataObject = [self.pageData objectAtIndex:index];
homeViewController.PageItemsCount= [[self.pageArray valueForKey:[NSString stringWithFormat:#"%d",index+1]] integerValue];
homeViewController.TotalPage=self.TotalPage;
homeViewController.CurrentPage=index+1;
return homeViewController;
}
- (NSUInteger)indexOfViewController:(HomeViewController *)viewController
{
// Return the index of the given data view controller.
// For simplicity, this implementation uses a static array of model objects and the view controller stores the model object; you can therefore use the model object to identify the index.
return viewController.CurrentPage-1;
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(HomeViewController *)viewController];
if ((index == 0) || (index == NSNotFound)) {
return nil;
}
index--;
return [self viewControllerAtIndex:index];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(HomeViewController *)viewController];
if (index == NSNotFound) {
return nil;
}
index++;
if (index == [self.pageData count]) {
return nil;
}
return [self viewControllerAtIndex:index];
}
This is working fine when I swipe the screen, but I also want to change screen on any button tap of main view page (in this case it's homeViewController.m), as it's code is very lengthy I didn't post code here.
can anyone tell me how to achieve this?
Thanks
Ashish
[UIView beginAnimations:#"View Flip" context:nil];
[UIView setAnimationDuration:0.80];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight
forView:self.view cache:NO];
[UIView commitAnimations];
//Addd this line on click event of button action
You could simply try and animate the view transform like this (for a vertical flip):
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut
animations:^(void) {
view.transform = CGAffineTransformMakeScale(1, -1);
}
completion:nil];
Thanks
I am working on creating custom Container ViewController class and am able to create 5 sub viewcontrollers and able to move from one child view controller to other child using the following API:
transitionFromViewController:fromViewController
toViewController:toViewController
duration:1.0
options:0
animations:^{
}
completion:^(BOOL finished) {
}];
Child view controllers are occupying part of Parent View Controller. Each time when user swipe I am making previous controller goes off and present controller comes on displayable area.
Now I have buttons on child viewcontrollers and when i click on them I am not getting events or trigger to the Action method of Child View Controller. But Parent View Controller is getting callbacks for its buttons even currently childview is getting displayed. Adding the code snippet below (though it is little bigger, this will help you to analyze if I am doing any mistake).
Parent ViewController:
- (void) handleSwipeGestureLeft:(UISwipeGestureRecognizer *)gesture
{
if (showPopupValue) {
return;
}
NSInteger index = [arrayOfChildVCs indexOfObject:selectedViewController];
index = MIN(index+1, [arrayOfChildVCs count]-1);
UIViewController *newSubViewController = [arrayOfChildVCs objectAtIndex:index];
endFrame.origin.x = -1024;
startOfNewVCFrame.origin.x = 1024;
[self transitionFromViewController:selectedViewController toViewController:newSubViewController];
selectedViewController = newSubViewController;
}
- (void) handleSwipeGestureRight:(UISwipeGestureRecognizer *)gesture
{
if (showPopupValue) {
return;
}
NSInteger index = [arrayOfChildVCs indexOfObject:selectedViewController];
index = MAX(index-1, 0);
UIViewController *newSubViewController = [arrayOfChildVCs objectAtIndex:index];
endFrame.origin.x = 1024;
startOfNewVCFrame.origin.x = -1024;
[self transitionFromViewController:selectedViewController toViewController:newSubViewController];
selectedViewController = newSubViewController;
}
- (void) populateChildVCList
{
if (self.currentactivity == EPI_Normal)
{
arrayOfImageNames = [[NSArray alloc]initWithObjects:#"AD_Initiate_SPI_Normal.jpg", #"AD_Initiate_CPI_Normal.jpg", #"AD_Initiate_EAC_Normal.jpg", #"AD_Initiate_PV_Normal.jpg", #"AD_Initiate_EV_Normal.jpg", nil];
}
else
{
arrayOfImageNames = [[NSArray alloc]initWithObjects:#"AD_Initiate_SPI_Trigger.jpg", #"AD_Initiate_CPI_Trigger.jpg", #"AD_Initiate_EAC_Trigger.jpg", #"AD_Initiate_PV_Trigger.jpg", #"AD_Initiate_EV_Trigger.jpg", nil];
isTriggerOn = YES;
}
arrayOfChildVCs = [[NSMutableArray alloc] init];
[arrayOfChildVCs addObject:[[PopupDummyViewController alloc]initWithNibName:#"PopupDummyViewController" bundle:nil]]; // Note: Add one dummy VC to initiate swap
for (int vcIndex = 0; vcIndex < [arrayOfImageNames count]; vcIndex++)
{
PiSLAComplianceViewController *childPopupController = [[PiSLAComplianceViewController alloc]initWithNibName:#"PiSLAComplianceViewController" bundle:nil];
childPopupController.popUpImageName = [arrayOfImageNames objectAtIndex:vcIndex];
childPopupController.imageIndex = vcIndex;
childPopupController.isTriggerOn = isTriggerOn;
[arrayOfChildVCs addObject:childPopupController];
}
selectedViewController = [arrayOfChildVCs objectAtIndex:0];
selectedViewController.view.frame = CGRectMake(0, 100, 100, 511); // Took the imageview coordinates
// Add the dummy view controller to Container View Initially
[self addChildViewController:selectedViewController];
[self.view addSubview:selectedViewController.view];
// notify it that move is done
[selectedViewController didMoveToParentViewController:self];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[super viewDidLoad];
UISwipeGestureRecognizer *swipeGestureRecognizerRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipeGestureRight:)];
[swipeGestureRecognizerRight setDirection:UISwipeGestureRecognizerDirectionRight];
[ self.view addGestureRecognizer:swipeGestureRecognizerRight];
UISwipeGestureRecognizer *swipeGestureRecognizerLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipeGestureLeft:)];
[swipeGestureRecognizerLeft setDirection:UISwipeGestureRecognizerDirectionLeft];
[ self.view addGestureRecognizer:swipeGestureRecognizerLeft];
endFrame = deliverableimageview.frame;
startOfNewVCFrame = deliverableimageview.frame;
[self populateChildVCList]; // To Create list of child view controllers and loads one invisible dummy view controller to be ready for swiping
}
- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController
{
if (fromViewController == toViewController)
{
// cannot transition to same
return;
}
// animation setup
toViewController.view.frame = startOfNewVCFrame;
// notify
[fromViewController willMoveToParentViewController:nil];
[self addChildViewController:toViewController];
// transition
[self transitionFromViewController:fromViewController
toViewController:toViewController
duration:1.0
options:0
animations:^{
toViewController.view.frame = fromViewController.view.frame;
fromViewController.view.frame = endFrame;
}
completion:^(BOOL finished) {
[toViewController didMoveToParentViewController:self];
[fromViewController removeFromParentViewController];
}];
}
I've created two storyboards both identical except one contains portrait views and one contains landscape views.
I don't want to use autoresize masks because the layout of some of the views changes completely between portrait and landscape. I've manually moved controls on the view in code in the past but I was after an easier way this time :)
The closest solution I've found was from 'benyboariu' - Storyboards orientation support for xCode 4.2?
Here's the code I'm using which works fine on iOS 5.x but not iOS 6.x.
- (void)updateLandscapeView
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (deviceOrientation == UIDeviceOrientationUnknown)
{
if ([[UIScreen mainScreen] bounds].size.height > [[UIScreen mainScreen] bounds].size.width)
{
deviceOrientation = UIDeviceOrientationPortrait;
self.appDelegate.isShowingLandscapeView = NO;
}
else
{
deviceOrientation = UIDeviceOrientationLandscapeLeft;
self.appDelegate.isShowingLandscapeView = YES;
}
}
if (UIDeviceOrientationIsLandscape(deviceOrientation) && !self.appDelegate.isShowingLandscapeView)
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard-Landscape" bundle:[NSBundle mainBundle]];
UIViewController * landscape = [storyboard instantiateViewControllerWithIdentifier:#"RootViewController-Landscape"];
self.appDelegate.isShowingLandscapeView = YES;
[UIView transitionWithView:landscape.view duration:0 options:UIViewAnimationOptionTransitionCrossDissolve|UIViewAnimationCurveEaseIn animations:^{
self.view = landscape.view;
} completion:NULL];
}
else if (UIDeviceOrientationIsPortrait(deviceOrientation) && self.appDelegate.isShowingLandscapeView)
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard-Portrait" bundle:[NSBundle mainBundle]];
UIViewController * portrait = [storyboard instantiateViewControllerWithIdentifier:#"RootViewController"];
self.appDelegate.isShowingLandscapeView = NO;
[UIView transitionWithView:portrait.view duration:0 options:UIViewAnimationOptionTransitionCrossDissolve|UIViewAnimationCurveEaseIn animations:^{
self.view = portrait.view;
} completion:NULL];
}
}
The error I'm getting when debugging on iOS 6.x is:
Terminating app due to uncaught exception 'UIViewControllerHierarchyInconsistency', reason: 'A view can only be associated with at most one view controller at a time! View UIView: 0x108276b0; frame = (0 0; 568 268); autoresize = RM+BM; animations = { position=CABasicAnimation: 0x10815c60; bounds=CABasicAnimation: 0x1082a5e0; }; layer = CALayer: 0x10827710 is associated with RootViewController: 0x10821f10. Clear this association before associating this view with RootViewController: 0x961a150.'
I usually unlink the view controller from the NIBs to fix this kind of error but can't seam to do that with storyboards.
Anyone got any ideas?
Change your portait view controller to a normal view (take it out of the view controller). Now you should be able to instantiate it as a UIView and do the transition without worrying about it being associated with multiple view controllers.
Well, after trying all sorts of ways to try and fix this problem I've come up with this solution which works on iOS 5.x and 6.x.
Basically, the problem appears to be because of the navigation controller, so, instead of doing
self.view = landscape.view;
or
self.view = portrait.view;
you need to replace all the view controllers contained in the navigation controllers stack.
What I'm doing in the code below is to create an NSMutableArray of the all view controllers in the navigation controller stack. Then loop each view controller and get the view tag to determine which view needs replacing. Then I simply replace the view controller with the landscape or portrait version and then set the navigation controllers view controllers array to the modified array thus replacing all the view controllers.
if (UIDeviceOrientationIsLandscape(deviceOrientation) && !self.appDelegate.isShowingLandscapeView)
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard-Landscape" bundle:[NSBundle mainBundle]];
SearchViewController * landscape = [storyboard instantiateViewControllerWithIdentifier:#"SearchViewController-Landscape"];
self.appDelegate.isShowingLandscapeView = YES;
[UIView transitionWithView:landscape.view duration:0 options:UIViewAnimationOptionTransitionCrossDissolve|UIViewAnimationCurveEaseIn animations:^{
NSMutableArray * viewControllers = [NSMutableArray arrayWithArray:[self.navigationController viewControllers]];
if (viewControllers && [viewControllers count] > 0)
{
for (NSUInteger index = 0; index < [viewControllers count]; index++)
{
if ([[[viewControllers objectAtIndex:index] view] tag] == 1000)
{
RootViewController * root = [storyboard instantiateViewControllerWithIdentifier:#"RootViewController-Landscape"];
[viewControllers replaceObjectAtIndex:index withObject:root];
}
else if ([[[viewControllers objectAtIndex:index] view] tag] == 2000)
{
[viewControllers replaceObjectAtIndex:index withObject:landscape];
}
}
[self.navigationController setViewControllers:viewControllers];
}
} completion:NULL];
}
else if (UIDeviceOrientationIsPortrait(deviceOrientation) && self.appDelegate.isShowingLandscapeView)
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard-Portrait" bundle:[NSBundle mainBundle]];
SearchViewController * portrait = [storyboard instantiateViewControllerWithIdentifier:#"SearchViewController"];
self.appDelegate.isShowingLandscapeView = NO;
[UIView transitionWithView:portrait.view duration:0 options:UIViewAnimationOptionTransitionCrossDissolve|UIViewAnimationCurveEaseIn animations:^{
NSMutableArray * viewControllers = [NSMutableArray arrayWithArray:[self.navigationController viewControllers]];
if (viewControllers && [viewControllers count] > 0)
{
for (NSUInteger index = 0; index < [viewControllers count]; index++)
{
if ([[[viewControllers objectAtIndex:index] view] tag] == 1000)
{
RootViewController * root = [storyboard instantiateViewControllerWithIdentifier:#"RootViewController"];
[viewControllers replaceObjectAtIndex:index withObject:root];
}
else if ([[[viewControllers objectAtIndex:index] view] tag] == 2000)
{
[viewControllers replaceObjectAtIndex:index withObject:portrait];
}
}
[self.navigationController setViewControllers:viewControllers];
}
} completion:NULL];
}
I've expanded the code to show how to get this working on nested views in the navigation controller. If you're working on the root view controller you obviously don't need to loop through all the view controllers, just replace the view controller at index 0.
Hope this helps someone!
i need help here, i'm using a custom segue to push a view controller to another view controller.
when pushed using cutom segue, the uiactivityindicator will not animate automatically when view loaded.
however if i used the push method on the storyboard, the uiactivityindicator animate automatically.
what i had done wrong ?
-(void)perform {
UIViewController *sourceVC = (UIViewController *) self.sourceViewController;
UIViewController *destinationVC = (UIViewController *) self.destinationViewController;
[UIView transitionWithView:sourceVC.navigationController.view duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
[sourceVC.navigationController pushViewController:destinationVC animated:NO];
//NSLog(#"count of subview %i", [self.view.subviews count]);
}
completion:NULL];
}
Try this:
UIViewController *sourceVC = (UIViewController *) self.sourceViewController;
UIViewController *destinationVC = (UIViewController *) self.destinationViewController;
[UIView transitionFromView:sourceVC.view toView:destinationVC.view duration:.5 options:0 completion:^(BOOL finished) {
[sourceVC.navigationController pushViewController:destinationVC animated:NO];
}];
The -pushViewController:animated: should not be putted in the animation block but the completion block.
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
[[self model] setTransitioning:NO];
[[[[self view]subviews] objectAtIndex:0] removeFromSuperview];
}
How can I tell what kind of viewController controls the subview at index 0?
If it's a QuizViewController I need to call a function on it.
Thanks in advance.
Good answers. I don't believe I explained enough. This is a all in a view controller that is a view stack. The view controller adds and deletes views manually with an animated transition. I am not using a navigationController and cannot in this particular instance for other reasons.
Sometimes the views I add are simple UIImageViews. Sometimes they are QuizViews. The QuizViews have a QuizViewController because they need internal functionality. Here are the two functions I use to add the views.
- (void)loadQuiz:(NSInteger )quizNum
{
if([self quizViewController] != nil)
{
[self setQuizViewController:nil];
}
QuizViewController *quiz = [[QuizViewController alloc] initWithNibName:#"QuizViewController" bundle:nil];
[quiz setUp:quizNum];
[self setQuizViewController:quiz];
[quiz release];
[[self view] addSubview:[[self quizViewController]view]];
[self setSlide1:[[[self view] subviews] objectAtIndex:0]];
[self setSlide2:[[[self view] subviews] objectAtIndex:1]];
[[self slide1] setHidden:NO];
[[self slide2] setHidden:YES];
[self performTransition];
}
- (void)loadImage:(NSString *)slideImage
{
UIImage *tempImg = [UIImage imageWithContentsOfFile:[Utilities localPathForFileName:slideImage]];
UIImageView *temp = [[UIImageView alloc] initWithImage:tempImg];
[[self view] addSubview:temp];
[temp release];
//[topView release];
if ([[[self view]subviews] count] > 2) {
//add the 2nd subview
//[[[[self view]subviews] objectAtIndex:0] removeFromSuperview];
}
[self setSlide1:[[[self view] subviews] objectAtIndex:0]];
[self setSlide2:[[[self view] subviews] objectAtIndex:1]];
[[self slide1] setHidden:NO];
[[self slide2] setHidden:YES];
NSLog(#"%s %d",__FUNCTION__,[[[self view]subviews] count]);
[self performTransition];
}
So my question is still, in the animationDidStop function how do I detect if it's a quiz?
if ([[self view]subviews] objectAtIndex:0] isKindOfClass:CLASS(class))
...
or isMemberofClass
From memory so you'll have to test it out...
You can also set a tag on the view when you create it, then look for the view when you retrieve it.
someUIView.tag=99;
then
if ( [[self view]subviews] objectAtIndex:0].tag == 99 )
...
Cheers
You maintain one UIViewController (for e.g. UIViewController *currentViewController) object for keeping the track of current view controller.
Now before pushing to the viewcontroller which according to my understanding gets pushed by more than one view controller,you set the currentViewController with the viewController from which you are pushing.
So if you are in QuizViewController and pushing to viewController which controlls subviews,you first set currentController of it and then push it.
subViewController.currentController = self;
[self.navigationController pushViewController:subViewController animated:YES];
Views don't inherently have view controllers attached to them, so the way your question is worded doesn't quite make sense. If what you're doing is having a UIView subclass, say QuizView, as a subview and need to know when that is being removed and act on it, then the code would look like this;
-(void) animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)flag
{
[[self model] setTransitioning:NO];
UIView *subview = [self.view.subviews objectAtIndex:0];
if([subview isKindOfClass:[QuizView class]])
{
[(QuizView*)subview yourFunction];
}
[subview removeFromSuperview];
}
If you mean something different and you can provide some code I might be able to help more, but like I said your original question isn't quite clear to me if this isn't what you mean ;)