I'm doing some "interesting" view transitions, and I'm finding myself working around the functionality of "presentModalViewController" in a way that doesn't feel right.
I'd prefer to take total control over the presentation of the modal view controller's view and skip "presentModalViewController" altogether.
However, I'm not sure about the ramifications of doing this.
Currently, I've got code that looks works something like like this (this is just a pseudo-code example, and I can't use the built in transitions, they won't do what I need):
// Create the child view controller:
ModalViewController * child = [[ModalViewController alloc] init];
// Present it:
[parentViewController presentModalViewController:child animated:NO];
// This rect is what the child view's ultimate "destination" should be,
// and, what the parent view's old frame was:
CGRect frame = child.view.frame;
// Put the parent view controller's view back in the window:
[child.view.window insertSubview:parentViewController.view belowSubview:child.view];
// Show it if it's hidden:
[parentViewController.view setHidden:NO];
// Put the parent back where it was:
[parentViewController.view setFrame:frame];
// Put the child at the "top" of the screen (the bottom edge
// of the child's view is at the top of the screen):
[child.view setFrame:CGRectMake(frame.origin.x,
frame.origin.y - frame.size.height,
frame.size.width,
frame.size.height)];
// Animate a transition which slide the parent and child views
// down together:
[UIView animateWithDuration:0.7 animations:^(void) {
child.view.frame = frame;
parentViewController.view.frame = CGRectMake(frame.origin.x,
frame.origin.y + frame.size.height,
frame.size.width,
frame.size.height);
} completion:^(BOOL finished) {
// We're done, remove the parent view from the window
// like it's supposed to be:
[parentViewController.view removeFromSuperview];
}];
[child release];
If you don't want to have UIKit set modalViewController and control the presentation and dismissal of the child view controller, then don't. You can skip the presentModalViewController:animated: call and manually add or remove subviews, or if you want to switch to an entirely new view controller then disconnect the old one's view from the heirarchy and connect the new one, etc. Other ways of presenting include UINavigationController or a UITabBarController, and they don't use the modalViewController methods.
To be more specific, you should set the rootViewController property of your application's UIWindow to the new view controller.
Docs say:
The root view controller provides the content view of the window. Assigning a view controller to this property (either programmatically or using Interface Builder) installs the view controller’s view as the content view of the window. If the window has an existing view hierarchy, the old views are removed before the new ones are installed.
Note that the docs mention an automatic process of installing the view as the content view of the heirarchy. What I'm saying is you can use the provided automatic methods - UIWindow for root views, modalViewController and other systems for non-root views - or you can do it manually, but it's accomplishing the same thing. Particularly since the rootViewController property has only existed since iOS 4, and applications prior to this used auto-generated default code of [window addSubview:rootView] at launch.
If UIKit has some extra magic occurring in [UIWindow setRootViewController:] I'm totally prepared to be corrected on this though.
Related
I have a UIPageViewController subclass that shows images. This view controller is inside a larger view controller that has other content. I want to be able to tap on an image in the page view controller and have that page view controller removed from where it is and presented full screen, where additional controls such as zooming and panning around the image would be available. Then, I also need a way to be able to dismiss it from being presented full screen and to re-insert it in the original parent view controller.
- (void)handleTapGesture {
UIViewController *parentViewController = self.parentViewController;
[self didMoveToParentViewController:nil];
[self.view removeFromSuperview];
[self removeFromParentViewController];
self.modalPresentationStyle = UIModalPresentationFullScreen;
[parentViewController presentViewController:self animated:YES completion:nil];
}
But when I do this, I can see the dimming view and everything that is set up automatically when presenting the view controller, but the view controller itself is not visible.
I viewed it in the view debugger, but it looks like the frame of the page view controller is zero sized. Here is some output from the debugger:
Unbalanced calls to begin/end appearance transitions for <MyPageViewController: 0x10ca8f000>.
Printing description of $21:
<_UIPageViewControllerContentView: 0x117b04c40; frame = (0 0; 0 0); clipsToBounds = YES; opaque = NO; autoresize = W+H; layer = <CALayer: 0x283491be0>>
I am not sure why that is though and I do not know how to debug this since I am not specifying any layout explicitly. From what I understand, when I present this view controller, I should not have to specify any constraints or sizes as that is to be handled by the view controller transition. All I am doing is trying to make a view controller, that was a child view controller, be presented modally full screen.
The view containment calls are incorrect. (See below.)
But the “Unbalanced calls” error message suggests that there might be some other, deeper problem elsewhere in your code base. The incorrect view controller containment calls are insufficient to manifest this error.
One generally gets this error when attempting to initiate a transition while another is underway (e.g., trying to present/dismiss view controllers inside the viewDidLoad, viewWillAppear or viewWillDisappear methods).
But the supplied code snippet is insufficient to manifest the problem you describe. We need MCVE. I would suggest you create a blank project and figure out what you need to add to it in order to manifest your error.
That having been said, the correct view controller containment calls to remove a child are willMoveToParentViewController, followed by removeFromSuperview, followed by removeFromParentViewController, e.g.:
[self willMoveToParentViewController:nil];
[self.view removeFromSuperview];
[self removeFromParentViewController];
Note, I did not call didMoveToParentViewController, because, as the documentation says:
The removeFromParentViewController method automatically calls the didMoveToParentViewController: method of the child view controller after it removes the child.
Obviously, when adding a child, the converse is true, that you do not call willMoveToParentViewController, but you do call didMoveToParentViewController:
[self addChildViewController:child];
[self.view addSubview:child.view];
child.view.frame = ...;
[self didMoveToParentViewController:self];
Again, the documentation advises us:
When your custom container calls the addChildViewController: method, it automatically calls the willMoveToParentViewController: method of the view controller to be added as a child before adding it.
UINavigationBars within a UINavigationController. . . they don't participate in frame animations. Useful, but exactly not what I want right now. Is there a way to turn this off?
I know I could set the showsNagitationBar property to hidden, and add my own to the view, put am looking at possible alterntives.
What I'm Trying to Achieve:
I've put my UINavigationController into a (screen-sized) container view, and want to slide it across to reveal a side menu. . last time that I did this I had custom push/pop methods on the RootVC, and my own navigation bar - worked fine, though a fair amount of boiler-plate code to set up.
This time I've got the same kind of requirement - main content is push/pop based, and some auxiliary VCs that can be revealed from the side. And so for another approach, and considering that this app's look and feel is very standard, I just included a UINavigationController within the RootVC and expected it to work the same.
However the UINavigationBar stays anchored in place, while the rest of the content within the container view moves.
I am not entirely sure what you are trying to achieve, but when using a navigation controller, the entirety of what you see on screen (your top view controller's view PLUS your navigation bar) is rooted in the Navigation Controller's view.
This means that if you do something like:
[UIView animateWithDuration:1.0 animations:^{
CGRect frame = self.navigationController.view.frame;
frame.size.width -= 30;
self.navigationController.view.frame = frame;
}];
You will get your view AND the navigation bar to shrink.
EDIT: You can add/remove the sliding-in views to the navigation controller's view where it is appropriate (for example the nav controller is your root view controller, you could do it in your appdelegate's didFinishLaunch: method). The following code would show an entirely red view sliding in from the left. In your case, this view would be the one from your side view controller.
UIView *left = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
left.backgroundColor = [UIColor redColor];
CGRect ff = left.frame;
ff.origin.x = -ff.size.width;
left.frame = ff;
[self.navigationController.view addSubview:left];
[UIView animateWithDuration:1.0 animations:^{
CGRect frame = self.navigationController.view.frame;
frame.origin.x += 50;
self.navigationController.view.frame = frame;
}];
Is something like this what you were looking for?
I am working to get user survey before user starts using my app, it is a typically collection of data for my research.
My problem is to add subview on the top of mainviewcontroller.
I have two subviewcontroller as you can see in the first screenshoots. I would like to add first subview on the mainviewcontroller, and whenever user clicks on next customized button, then firstsubview disappear and secondsubview appear on the top of mainviewcontroller.
firstsubview implemented as follows:
CGRect rect = [firstSurveyViewController.view frame];
rect.origin.x = 5;
rect.origin.y = 5;
[firstSurveyViewController.view setFrame:rect];
[self.view addSubview:firstSurveyViewController.view];
But I want to drop the firstsubview and add the second when user clicks on next button.
How could I implement?
For composing modal view controllers, you have two real options (ignoring cool stuff under NDA):
Use the Container View Controller pattern to insert the inner view controller into its parent.
Add a new window with that view controller, similar to how you would do it in your AppDelegate. Create the window, add your child as a root view controller, make it key and visible. This is actually what UIAlertView does to perform a similar behavior to your app.
I can't say what would be better for your case, but I have more experience with container view controllers so I'll give you the highlights. To add a child view you will need these steps (from the link above) in your parent view controller:
- (void) displayContentController: (UIViewController*) content;
{
[self addChildViewController:content]; // 1
content.view.frame = [self frameForContentController]; // 2
[self.view addSubview:self.currentClientView]; // 3
[content didMoveToParentViewController:self]; // 4
}
This will:
Add your child view controller.
Set the frame to whatever you want. This could be self.view.bounds if you want it to take up the full space.
Add the view to its parent.
Notify the child view controller that it was added.
To remove a child view controller you would do the opposite, again from the link:
- (void) hideContentController: (UIViewController*) content
{
[content willMoveToParentViewController:nil]; // 1
[content.view removeFromSuperview]; // 2
[content removeFromParentViewController]; // 3
}
This will:
Notify your child view controller that it will disappear.
Remove its view from the visual stack.
Remove it from its parent.
You have got two options.
Use both controllers as modal controllers. After tapping Next on first controller, dismiss it and call second controller modally.
Another much better option (in my opinion) is to present these two series of controllers one after another using modal UINavigationController. You can push your controllers in navigation controller, and when done, you can dismiss it and show your main controller.
EDIT
See this post.
Transitioning between views is easy-
1) Straight forward
-(IBAction):onNextButtonClick:(id)sender
{
[self.firstViewController.view removeFromSuperView];
self.secondViewController.view.frame = newFrame;
[self.view addSubView:self.secondViewController.view];
}
2) If you want some fancy animations try this-
[UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionTransitionCurlDown animations:^{
[self.firstViewController.view removeFromSuperView];
self.secondViewController.view.frame = newFrame;
[self.view addSubView:self.secondViewController.view];
} completion:^(BOOL finished) {
//Completion
}];
I've been struggling with this for quite a while now.
I have to implement an user profile similar to what Instagram has in their ios app.
When clicking on the first to buttons on that tab bar like thing all the contents downwards from it changes. The tableview that is displayed on the bottom part has dynamic size so they keep account of that also.
I have something implemented where the top part is a UIView with 5 buttons and based on them the bottom part (witch is like a container view) changes content. And these two (top uiview and bottom container view) are part of UIScrollView. But this way I can't get information back in time on the size about the tableview's size that I want to display in the bottom part in order to modify the UIScrollView's size. And I have a feeling this is a flawed way to do it.
I would really appreciate any ideas oh how to implement this king of interaction. Thank you.
I believe it's a headerView on a UITableView or a UICollectionView, depending on which view mode you have selected. When you tap one of the buttons it changes out the UITableView to a UICollectionView or vice versa.
You want to keep track of the current contentOffset for whichever is being displayed (UICollectionView and UITableView are both subclasses of UIScrollView so you will be able to get this from both) and then set the contentOffset on the view you're switching to.
Setup an ivar for the UIView header subclass so you can easily re-use it.
This is what I have. My problem is that I'm mot getting back in useful time the tableview's frame height from the tableview controller to the UserProfileViewController in order to change the latter's scrollview size. I also feel that I'm somehow doing this backwards so any suggestions are more than welcome.
This view has two parts: an upper part and a lower part. The parent view is a scroll view. What I wanted to achieve with this is having a sort of tab bar in the upper part that will controll waht will appear in the lower part.
The upper part has a flip animation when the upper left button is pressed to reveal another view.
The way this is achieved is by having 2 views: a dummy view and the back view. The dummy view has the front view as a child. The front view is the one that containes all the buttons.
The code for this animation is achieved in this way:
- (IBAction)infoButtonPressed:(id)sender
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1.0];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.hoverView cache:YES];
if ([self.headerView superview]) {
[self.headerView removeFromSuperview];
[self.hoverView addSubview:self.backOfHeaderView];
[self.infoButton removeFromSuperview];
[self.backOfHeaderView addSubview:self.infoButton];
} else {
[self.backOfHeaderView removeFromSuperview];
[self.hoverView addSubview:self.headerView];
[self.infoButton removeFromSuperview];
[self.headerView addSubview:self.infoButton];
}
[UIView commitAnimations];
}
The lower part is made out of a container view that acts as a place holder.
When a button is pressed a different view controller is displayed in the container view.
Each view controller has a container view of it's own. The specific view of that view controller (tableview) is added to it's container view when the controller is loaded. It also makes sure that if the tableview is already added to the container view it will be removed. All this is done in each specific view controller.
In the view controller of the User Profile view there is an instance of the container view and one of a UIViewController that also acts as a placeholder(named currentViewController from now on). When a specific button is pressed it checks if the an instance of the view controller that we want to display already exists. If not it will make one and will set it's tableview's frame to the bounds of the container view. After that it will remove the currentViewController's view from the superview and the currentViewController itself from the parent viewcontroller to make sure that if there is something assigned to these they will not be there. Then it goes and assigns the desired viewcontroller to the currentViewController. It also assigns the desired viewcontroller's containerView instance to the containerview in the parent viewcontroller (the User Profile viewcontroller). At the end it will add the desired viewcontroller as a child to the main viewcontroller (the User Profile viewcontroller) and desired viewcontroller's view to the containerView of the main viewcontroller.
This is the code for one of the buttons:
//Check if there is an instance of the viewcontroller we want to display. If not make one and set it's tableview frame to the container's view bounds
if(!_userWallViewController) {
self.userWallViewController = [[WallViewController alloc] init];
// self.userWallViewController.activityFeedTableView.frame = self.containerView.bounds;
}
[self.userWallViewController.containerView addSubview:self.userWallViewController.activityFeedTableView];
//If the currentviewcontroller adn it's view are already added to the hierarchy remove them
[self.currentViewController.view removeFromSuperview];
[self.currentViewController removeFromParentViewController];
//Add the desired viewcontroller to the currentviewcontroller
self.currentViewController = self.userWallViewController;
//Pass the data needed for the desired viewcontroller to it's instances
self.userWallViewController.searchURLString = [NSString stringWithFormat:#"event/user/%#/", self.userID];
self.userWallViewController.sendCommentURLString = [NSString stringWithFormat:#"event/message/%#", self.userID];
self.userWallViewController.totalCellHeight = ^(float totalCellHeight){
self.userWallViewController.numberOfCells = ^(float numberOfCells){
NSLog(#"The total number of cells: %f", numberOfCells);
NSLog(#"The total cell height: %f", totalCellHeight);
self.scrollView.contentSize = CGSizeMake(320.0, totalCellHeight + 172.0 + 33.0);
CGRect newFrame = self.userWallViewController.containerView.frame;
newFrame.size.height = totalCellHeight + 33.0;
self.userWallViewController.containerView.frame = newFrame;
NSLog(#"Container view: %f", self.containerView.frame.size.height);
NSLog(#"Scroll view: %f",self.scrollView.contentSize.height );
};
};
//Add this containerview to the desired viewcontroller's containerView
self.userWallViewController.containerView = self.containerView;
//Add the needed viewcontroller and view to the parent viewcontroller and the containerview
[self addChildViewController:self.userWallViewController];
[self.containerView addSubview:self.userWallViewController.view];
[self performSelector:#selector(changeScrollView) withObject:self afterDelay:0.5];
//CLEAN UP THE CONTAINER VIEW BY REMOVING THE PREVIOUS ADDED TABLE VIEWS
[self.userFansViewController.userSimpleTableView removeFromSuperview];
[self.fanOfViewController.userSimpleTableView removeFromSuperview];
[self.userPublishedMovellaListViewController.gridView removeFromSuperview];
[self.userPublishedMovellaListViewController removeFromParentViewController];
self.userPublishedMovellaListViewController = nil;
}
I know this answer is over a year late, but I wanted to state my hypothesis on it...just incase it might help someone else later. Im implementing a similar view and came to this conclusion. Anyone is welcomed to correct me if I'm wrong.
I think that perhaps the top view is a header view and the two options that seem like a collection view and a table view are both collection views.
Because the layout of collection views can be fine tuned to the most minute details, I think the view that looks like a table view is just a really specifically designed collection view. And when switching between the views, the collection view's data and properties are being swapped and reloaded.
I would like to have a UITableView in a navigation controller occupying the entire screen. I have a smaller custom UIView which needs to slide up from the bottom, squeezing the table view by 100 pixels. The custom view needs to be static, not moving while the user navigates the tableview. Ive been told not to have 2 UIViewControllers (VC) managing views on the same screen.
Currently, my AppDelegate adds a subview to its window from a VC, which then loads the tableview and custom view with
[self addSubview:tablviewcontroller.view];
[self addSubview:customViewController.view];
How should this be implemented?
the way I would structure this is as follows:
have a UIViewController subclass whose view takes up the entire screen. It will have two subviews.
First subview: The view of the UINavigationController that contains your table view controller.
Second subview: the custom UIView.
Have the UINavigationController's frame initially be set to the entire bounds of the main view controller's view and the custom view's frame just below the visible area of the screen.
When you need to slide up the view, use UIView animation to animate changing the frame of the UINavigationController's view by decreasing the height and change the frame of the custom UIView by changing its y coordinate to now be in-frame.
Okay. You want a navigation controller, of which the root view is a table view. Then, possible by an user input, you want this table view to slide up by 100 pixels, and another view takes place at the bottom. While the other view stays there, the user may keep using the table view.
Here is how I would do it:
Create a generic view controller (let's call it NavigationWithAuxiliaryViewController). The root view of this class covers all your application window.
This view has an instance of UINavigationControlleras its property, say navController. It also has an UIView (for the other view) as its property (say, auxView). Position the other view at the bottom. However, this view is hidden by default. Also, the frame of the root view of UINavigationController covers the entire view.
When you decide to squeeze up the table view, modify frame property of UINavigationController. Do something like this (not this ugly though):
if (slideViewOn) {
[UIView beginAnimations:#"slideUp" context:nil];
navController.view.frame = CGRectMake(0, 0, 320, 260);
auxView.hidden = NO;
[UIView commitAnimations];
} else {
[UIView beginAnimations:#"slideDown" context:nil];
navController.view.frame = CGRectMake(0, 0, 320, 480);
auxView.hidden = YES;
[UIView commitAnimations];
}
The easiest way to squeeze up the whole navigation/table stuff is to modify the whole frame for the navigation controller, which is why you need a separate view (out of the navigation controller) for the other view.