I am attempting to make a facebook/youtube-like slide-out menu for my app. My main view controller has two UIViews, one for the menu tableview and those, and one where I plan to contain whatever view the user has switched to.
I tried to make another UIViewController view made in interface builder as a subview contained in the UIView for content. I did that like this
[self.contentViewLayer addSubview:[[PersonViewController alloc]init].view];
with and without the .view at the end. It didn't work. Is it possible to have a UIViewController from storyboard contained in a UIView?
You should probably take a look at Child View Controllers from the Apple documentation.
Here's some code that exemplifies this technique:
- (void)addContentViewController:(UIViewController *)viewController {
/* This should be inside your slide out view controller manager */
[self addChildViewController:viewController];
viewController.view.frame = [self frameForContentController];
[self.view addSubview:viewController.view];
[viewController didMoveToParentViewController:self];
}
You can use a container view:
[self addChildViewController:vc2];
[self.container addSubview:vc2.view];
Or you can do [vc1.view addsubview:vc2.view];
Related
We have a custom view, which looks like a tabbar but is ultimately a subclass of UIView.
The view is shown as a tabbar would at the bottom of a UIViewController. When an image is touched in the view controller we transition to another view controller.
The second view controller has the same fake tabbar view being shown at the bottom. The user can close the second view controller and it will transition back to the first.
What is the best way to keep the same view and its state for both view controllers? For example part of the fake tabbar might be a usable button with a badge icon showing (2). If that is touched it would go down to (1). This would need to be reflected on both instances of the view.
Would the correct approach be to just use prepareForSegue as normal and keep updating the view state or passing the views instance around? Or is there a better approach?
I think the best approach is to implement something similar with the native tab bar. You can achieve this by implementing a container view . How you do that is a long story to post here but there are many resources on the internet. Basically you will have the same fake bar and your view controller will be shown in container view that should be put just above the tab bar. The view controller with both the container view and the tab bar should manage the transitions and update the bar.
Yeah, just as Jelly said I'd go the parent/child view controller route, with the 'tab bar' managing adding/removing the view controllers and associated views in response to touch events.
I am working on the same scenerio. In a UIViewController take your tabBar view at the bottom and above that take a blank UIView. Now on click of tabBar button, add and remove your new ViewController's view using AutoLayout like as -
#pragma mark - TAB BAR METHODS
-(void)setSelecedView:(VIEWSELECTION)selecedView
{
[self RemoveChildViewControllers ];
switch (selecedView)
{
case VIEWSELECTION_HOME:
{
HomeViewController *homeVC = [[HomeViewController alloc]initWithNibName:#"HomeViewController" bundle:nil];
self.titleString=#"Wellborn Company App";
[self displayContentController:homeVC OnView:self.DumpingView];
}
break;
case VIEWSELECTION_SEARCH:
{
SearchViewController *searchVC = [[SearchViewController alloc]initWithNibName:#"SearchViewController" bundle:nil];
self.titleString=#"Search";
[self displayContentController:searchVC OnView:self.DumpingView];
}
break;
}}
#pragma mark - VC Adding/Removing Methods
- (void)RemoveChildViewControllers
{
NSArray *childVCArray = [self childViewControllers];
for ( __strong UIViewController *childvc in childVCArray)
{
[childvc willMoveToParentViewController:nil];
[childvc.view removeFromSuperview];
[childvc removeFromParentViewController];
}
}
- (void)displayContentController:(UIViewController*) content OnView:(UIView*)parentView
{
[self addChildViewController:content];
[parentView addSubview:content.view];
NSDictionary *views = #{
#"childView" : content.view,
};
NSArray *arr;
[content.view setTranslatesAutoresizingMaskIntoConstraints:NO];
arr = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[childView]-0-|" options:0 metrics:nil views:views];
[parentView addConstraints:arr];
arr = [NSLayoutConstraint constraintsWithVisualFormat:#"|-0-[childView]-0-|" options:0 metrics:nil views:views];
[parentView addConstraints:arr];
[content didMoveToParentViewController:self];
}
If it is just a view and your simply pushing view controllers on a navigation stack, then add your view to your navigation view controllers view.
[self.navigationController.view addSubview:view];
I am designing an iOS app with 10 main UIViewControllers in it. Each representing a different section of the app. Its basically for a company and shows info about the company.
One of the things I am doing on bottom section of the app (in all the different view controllers) is displaying a UIView which contains a map. This map shows a certain location.
Now it works, but the problem I have is that I have 10 copies of the same code and 10 copies of the same UIView.
Is there anyway I could make a small view controller with one class attached to it that would handle the map and then just create an instance of the view controller in all my 10 view controllers in my app?
I hope my question makes sense. Basically I want to know how I can go about reusing ONE UIView in all 10 of my ViewControllers. So I can just call it or something and it appears.
Update - this is basically what I am trying to achieve
Thanks, Dan.
View controllers can contain other view controllers. You can either use a container view in a storyboard or setup the relationship programmatically (see: Creating Custom Container View Controllers).
The storyboard container view is easiest, but the programmatic solution isn't too bad.
- (void)displayContentController:(UIViewController *)content
{
[self addChildViewController:content];
content.view.frame = [self frameForContentController];
// NOTE: You could also add it to any subview of self.view.
[self.view addSubview:content.view];
[content didMoveToParentViewController:self];
}
- (CGRect)frameForContentController
{
return CGRectMake(…);
}
- (void)viewDidLoad
{
…
MyMapViewController *mapViewController = …;
[self displayContentController:mapViewController];
…
}
- (void)dismissContentController:(UIViewController *)content
{
[content willMoveToParentViewController:nil];
[content.view removeFromSuperview];
[content removeFromParentViewController];
}
Final Note: Have each parent view create its own instance of the map view controller. Resist the temptation to reuse an instance of the map view controller between parents.
Update to address questions
So lets say I had 2 of the same view controllers open at once and they both were displaying the same imported viewcontroller then it wouldn't work right?
You can't do this. An instance of a view controller can only have 1 parent view controller. Create separate instances for each use.
So if I create different instances, I can reuse the same view lets say 5 times in one view?
Yes, if you create different instances, you can put as many as you need on a view.
Let me be clear, an instance is a distinct memory location created using a constructor.
MyMapViewController *mapViewController1 = [[MyMapViewController alloc] initWithNibName:#"MyMapViewController" bundle:nil];
MyMapViewController *mapViewController2 = [[MyMapViewController alloc] initWithNibName:#"MyMapViewController" bundle:nil];
or
MyMapViewController *mapViewController1 = [self.storyboard instantiateViewControllerWithIdentifier:#"MapViewController"];
MyMapViewController *mapViewController2 = [self.storyboard instantiateViewControllerWithIdentifier:#"MapViewController"];
Updated to demonstrate dismissing a container view controller.
Here is a method for a child view controller, so it can use to dismiss itself.
- (void)dismissFromParentViewController
{
[self willMoveToParentViewController:nil];
[self.view removeFromSuperview];
[self removeFromParentViewController];
}
Please try below method:
Create "map controller" super class with inherit to UIViewController and define your need common method and variables.
Inherit your 10 child class into "map controller" super class. And connect common IBOutlets and IBActions to super class.
You can access common methods and variable to super class from child class(10 view controller child class).
Please refer below code
#interface mapController : UIViewController
{
NSString *mapControllerVariables;
}
-(IBAction)mapControllerActions:(id)sender;
#end
#interface yourChileView : mapController
{
}
#end
Hi i have been working on an iOS app.What i am doing is navigating among diffrent view controllers. But the problem is i want finish the current view controller from emoery and then move to the next view controller.
I am using `[self.view removeFromSuperview]; for finishing the cureent view & using
self.loginView = [self.storyboard instantiateViewControllerWithIdentifier:#"LOGIN"];
[self presentViewController:self.loginView animated:NO completion:nil];
for moving to next view controller but the thing is i am not able to remove it from memory.
Please tell me how can i do it?
Thanks in advance.
`
It's better to create a container view controller which manages your view controllers. For example, in viewDidLoad: of container controller you add current controller:
[self addChildViewController:self.currentViewController];
[self.currentViewController didMoveToParentViewController:self];
[self.view addSubView:self.currentViewController.view];
//here set up currentViewController view's frame or constraints if needed
When you need to open login controller, do this:
[self addChildViewController:loginViewController];
[self.loginViewController didMoveToParentViewController:self];
[self.view addSubView:loginViewController.view];
//here set up loginViewController view's frame or constraints if needed
//then remove current view controller
[self.currentViewController willMoveToParentViewController:nil];
[self.currentViewController removeFromParentViewController];
[self.currentViewController.view removeFromSuperview];
Remove from superview will remove it from the current view, but OS won't remove it until he needs to (this is topic for more explanation, let's say it won't remove it asap).
If you want something deleted just call it nil:
self.view = nil;
This will make the pointer to nil, so view won't be there any more. (the view really will be somewhere but you won't have access to it)
I am revising your code
self.loginView = [self.storyboard instantiateViewControllerWithIdentifier:#"LOGIN"];
[self presentViewController:self.loginView animated:NO completion:nil];
What you are doing here is presenting your login viewcontroller.
self: This is the instance of the viewcontroller you are currently working on. So how could you remove self from memory. (Not Possible)
You can approach alternate ways.
For example: 1. Changing root view controller
Pop to root view controller and then Push Login View controller.
If you try to remove not only UIView but the whole UIViewController from a navigation controller stack use this snippet
NSMutableArray *stack = [[NSMutableArray alloc] initWithArray: self.navigationController.viewControllers];
[stack removeObject:yourController];
self.navigationController.viewControllers = stack;
Be aware of using this only when you've already pass to the next controller view.
UPD:
Oh, now I see what you are trying to do. And I can't figure out, why you're trying to step your controllers this way (modally). I think you should use UINavigationController with navigation segues defined directly from your storyboard. Look at this article where apple purely explains what navigation is. That article is about modal views
I am trying to add custom UIViewController on top of everything but not covering full screen (basically popover), like this:
- (void) displayPopoverController: (UIViewController*) content;
{
[self addChildViewController:content];
content.view.frame = [self frameForContentController];
[self.view addSubview:content.view];
[content didMoveToParentViewController:self];
}
Everything works, but unfortunately it is underneath the navigation bar. So I decided to add UIViewController to the navigation controller like this:
- (void) displayPopoverController: (UIViewController*) content;
{
[self.navigationController addChildViewController:content];
content.view.frame = [self frameForContentController];
[self.navigationController.view addSubview:content.view];
[content didMoveToParentViewController:self.navigationController];
}
It worked, but there are 2 problems:
1) viewWillAppear is not called when I add popover (only viewDidLoad is called)
2) If I change orientation, my popover receives notification and adjusts to new orientation, but UIViewController behind it does not. It will only update its view after I remove popover.
Is there any way to fix 1 and 2? Maybe there is better approach(I don't want to use UIPopoverController with custom UIPopoverBackgroundView)?
IMO you should make a custom transition and present UIViewController modally.
You can get help on Custom UIViewController transition here : http://www.doubleencore.com/2013/09/ios-7-custom-transitions/
I am trying to add custom UIViewController on top of everything but not covering full screen
If you can confine yourself to iOS 7, your problems are over. You can use presentViewController: and a custom transition to do exactly what you are trying to do. This, in my view, is the most important new feature of iOS 7: you can present a view controller's view only partially covering the main interface.
See my book; for the particular example code from the book, see https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch06p304customPresentedAnimation2/ch19p620customPresentedAnimation2/ViewController2.m
Plus I've now posted a single project at https://github.com/mattneub/custom-alert-view-iOS7. It shows how to make a view controller presented view that only partially covers the interface, plus it demonstrates that device rotation works correctly for all visible views (i.e. what's in front and what's visible behind).
I'm trying to make a form that spans three tabs. You can see in the screenshot below where the tabs will be. When the user taps a tab, the Container View should update and show a particular view controller I have.
Tab 1 = View Controller 1
Tab 2 = View Controller 2
Tab 3 = View Controller 3
The view controller shown above has the class PPAddEntryViewController.m. I created an outlet for the Container view within this class and now have a Container View property:
#property (weak, nonatomic) IBOutlet UIView *container;
I also have my IBActions for my tabs ready:
- (IBAction)tab1:(id)sender {
//...
}
- (IBAction)tab2:(id)sender {
//...
}
- (IBAction)tab3:(id)sender {
//...
}
How do I set the container in those IBActions to change the view controller that the Container View holds?
Among a few other things, here's what I've tried:
UIViewController *viewController1 = [self.storyboard instantiateViewControllerWithIdentifier:#"vc1"];
_container.view = viewController1;
...but it doesn't work. Thanks in advance.
Switching using Storyboard, Auto-layout or not, a Button of some sort, and a series of Child View Controllers
You want to add the container view to your view and when the buttons that 'switch' child view controllers are pressed fire off the appropriate segue and perform the correct setup work.
In the Storyboard you can only connect one Embed Segue to the Container View. So you create an intermediate handling controller. Make the embed segue and give it an identifier, for example EmbededSegueIdentifier.
In your parent view controller wire up the button or whatever you want and keep are reference to your child view controller in the prepare segue. As soon as the parent view controller loads the segue will be fired.
The Parent View Controller
#property (weak, nonatomic) MyContainerViewController *myContainerViewController;
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"EmbeddedSegueIdentifier"]) {
self.myContainerViewController = segue.destinationViewController;
}
}
It should be fairly easy for you to delegate to your container controller the button presses.
The Container Controller
This next bit of code was partly borrowed from a couple of sources, but the key change is that auto layout is being used as opposed to explicit frames. There is nothing preventing you from simply changing out the lines [self addConstraintsForViewController:] for viewController.view.frame = self.view.bounds. In the Storyboard this Container View Controller doesn't do anything more that segue to the destination child view controllers.
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%s", __PRETTY_FUNCTION__);
[self performSegueWithIdentifier:#"FirstViewControllerSegue" sender:nil];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UIViewController *destinationViewController = segue.destinationViewController;
if ([self.childViewControllers count] > 0) {
UIViewController *fromViewController = [self.childViewControllers firstObject];
[self swapFromViewController:fromViewController toViewController:destinationViewController];
} else {
[self initializeChildViewController:destinationViewController];
}
}
- (void)initializeChildViewController:(UIViewController *)viewController
{
[self addChildViewController:viewController];
[self.view addSubview:viewController.view];
[self addConstraintsForViewController:viewController];
[viewController didMoveToParentViewController:self];
}
- (void)swapFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController
{
[fromViewController willMoveToParentViewController:nil];
[self addChildViewController:toViewController];
[self transitionFromViewController:fromViewController toViewController:toViewController duration:0.2f options:UIViewAnimationOptionTransitionCrossDissolve animations:nil completion:^(BOOL finished) {
[self addConstraintsForViewController:toViewController];
[fromViewController removeFromParentViewController];
[toViewController didMoveToParentViewController:self];
}];
}
- (void)addConstraintsForViewController:(UIViewController *)viewController
{
UIView *containerView = self.view;
UIView *childView = viewController.view;
[childView setTranslatesAutoresizingMaskIntoConstraints:NO];
[containerView addSubview:childView];
NSDictionary *views = NSDictionaryOfVariableBindings(childView);
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[childView]|"
options:0
metrics:nil
views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[childView]|"
options:0
metrics:nil
views:views]];
}
#pragma mark - Setters
- (void)setSelectedControl:(ViewControllerSelectionType)selectedControl
{
_selectedControl = selectedControl;
switch (self.selectedControl) {
case kFirstViewController:
[self performSegueWithIdentifier:#"FirstViewControllerSegue" sender:nil];
break;
case kSecondViewController:
[self performSegueWithIdentifier:#"SecondViewControllerSegue" sender:nil];
break;
default:
break;
}
}
The Custom Segues
The last thing you need is a custom segue that does nothing, going to each destination with the appropriate segue identifier that is called from the Container View Controller. If you don't put in an empty perform method the app will crash. Normally you could do some custom transition animation here.
#implementation SHCDummySegue
#interface SHCDummySegue : UIStoryboardSegue
#end
- (void)perform
{
// This space intentionally left blank
}
#end
I recently found the perfect sample code for what I was trying to do. It includes the Storyboard implementation and all the relevant segues and code. It was really helpful.
https://github.com/mhaddl/MHCustomTabBarController
Update: UITabBarController is the recommended way to go, as you found out earlier. In case you'd like to have a custom height, here is a good start: My way of customizing UITabBarController's tabbar - Stackoverflow answer
As of iOS 5+ you have access to customize the appearance via this API; UIAppearance Protocol Reference. Here is a nice tutorial for that: How To Customize Tab Bar Background and Appearance
The most obvious way to achieve what you're looking for is to simply manage 3 different containers (they are simple UIViews) and implement each of them to hold whatever content view you need for each tab (use the hidden property of the containers).
Here is an example of what's possible to achieve with different containers:
These containers "swapping" can be animated of course. About your self-answer, you probably chose the right way to do it.
have a member variable to hold the viewController:
UIViewController *selectedViewController;
now in the IBActions, switch that AND the view. e.g.
- (IBAction)tab1:(id)sender {
UIViewController *viewController1 = [self.storyboard instantiateViewControllerWithIdentifier:#"vc1"];
_container.view = viewController1.view;
selectedViewController = viewController1;
}
to fire view did appear and stuff call removeChildViewController, didMoveToParent, addChildViewController, didMoveToParent
I got this to work by using a UITabBarController. In order to use custom tabs, I had to subclass the TabBarController and add the buttons to the controller in code. I then listen for tap events on the buttons and set the selectedIndex for each tab.
It was pretty straight forward, but it's a lot of junk in my Storyboard for something as simple as 3 tabs.