My PenViewController has three labels and a Container View, which means I am using an embedded segue. The thing about embedded segue, at least per my understanding, is that they are not caused by user actions the way push segues are. But now I need my Container View to show a different child respectively when a different label is clicked. How do I pass that data to the Container View? Here is my embed segue.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"embedded_segue_to_container_vc"]) {
if ([segue.destinationViewController isKindOfClass:[BCDPenDetailContainerViewController class]]) {
BCDPenDetailContainerViewController *container = (BCDPenDetailContainerViewController *)segue.destinationViewController;
container.details=self.details;
}
}
}
The container view is just a UIView (with some IB magic thrown in), so you can create an IBOutlet to it if you need to reference it to change (or add) a child view controller. To add another child view controller, use the standard custom container controller api, and add the new controller's view to the container view,
-(IBAction)addNew:(id)sender {
UIViewController *newVC = [self.storyboard instantiateViewControllerWithIdentifier:#"NewVC"];
[self addChildViewController:newVC];
[newVC didMoveToParentViewController:self];
newVC.view.frame = self.containerView.bounds; // containerView is the IBOutlet to the container view in the storyboard
[self.containerView addSubview:newVC.view];
}
Related
I'm trying to use a custom segue (at bottom) to present a view controller modally with a blur view. I need to instantiate the view controller with properties before I present the view controller.
Instating the VC works great when I am using pushVC, but when I use perform segue with identifier, I don't see an option to choose an already instantiated VC.
CustomViewController* VC = [self.navigationController.storyboard instantiateViewControllerWithIdentifier:#"customVC"];
[self.navigationController performSegueWithIdentifier:#"blurSegue" sender:self];
How can I perform the custom segue and force it to use the view controller I allocated (called VC above)?
https://github.com/AlvaroFranco/AFBlurSegue
I need to instantiate the view controller with properties before I
present the view controller.
Why do you have to instantiate CustomViewController before triggering the segue. That shouldn't be necessary, if you need access to a property of CustomViewController before it's shown, you can set it in prepareForSegue.
Try this:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"blurSegue"]) {
CustomViewController *customViewController = (CustomViewController *)segue.destinationViewController;
customViewController.propertyToSet = XXX; // set the property here
}
}
Ah, by the way, instead of
[self.navigationController performSegueWithIdentifier:#"blurSegue" sender:self];
just use:
[self performSegueWithIdentifier:#"blurSegue" sender:self];
I have 3 different buttons which upon touch present the same UINavigationViewController with a container.
However, each button represents which view controller will be embed at the container.
How can I embed the necessary viewController by code?
what you can do is use an identifier which would be assigned to your various viewController as storyboardID
such as fisrtVC, SecondVC, thirdVC
the depending upon whichButton is pressed just set the identifier and use this identifier when
you want to push the controller such as
for example
while you push the navigation viewController just pass the storyboard Identifier such as
Declare a NSString *identifier;
-(IBAction)firstButtonClicked{
identifier=#"firstVC";
//pass this identifier to your navigationController
}
similarly for other Controllers
When you push the navigation controller make sure to pass this identifier along now depending upon the value you can initiate the controller on you VC as
on ViewDIdApppear:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
NSString* viewType = passedIdentifier
UIViewCOntroller* viewController = [storyboard instantiateViewControllerWithIdentifier:viewType];
Load this "viewCOntroller in your ContainerView"
You should implement prepareForSegue method:
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
id is your button that will fire segue. You do segues by drugging and drop in storyboard. Put an if statement in this method and tell your UIViewController which UIView to load in container. You can pass data like this:
// Get reference to the destination view controller
YourViewController *vc = [segue destinationViewController];
// Pass any objects to the view controller here, like...
[vc setMyObjectHere:object];
Second snippet taken from this answer.
Update.
To load different UIView in container put if statement into viewWillApper method.
This method firing earlier than viewDidLoad. If statement must check some property that tell what UIView to init. You setting up this property in prepareForSegue.
It will look like this:
if (self.viewToLoad == 1)
{
self.dynamicView = MyCustonUIViewNumberOne *view = [MyCustonUIViewNumberOne alloc] init];
}
Update 2.
Or you can do it dynamically like in this answer:
if (self.viewToLoad == 1)
{
// Replacing with your dimensions
CGRect frame = CGRectMake(x, y, width, height);
MyCustonUIViewNumberOne *dynamicView = [[MyCustonUIViewNumberOne alloc] initWithFrame:frame];
[self.container addSubview: dynamicView];
} else {
// Init other view
}
The container property:
I want to create a single person lap timer xib/class and load it twice onto a single View Controller on my Story Board. Each of the two instances will be used to time and compare two persons lap times on a single ViewController.
I have laid out two Container Views side by side within the Story Board View Controller (LapCounterViewController)
I have also created an xib and class files as a single person lap timer ( LapCounterNibViewContainer)
How do I create two instances of LapCounterNibViewContainer and put it inside each of the Container Views
_vc1 = [[LapCounterNibViewController alloc] initWithNibName:#"LapCounterNibViewController" bundle:nil];
_vc1.view.frame = self.LapCounterFrame1.frame;
//_vc1.delegate = self;
[_LapCounterFrame1 addChildViewController:_vc1];
[_vc1 didMoveToParentViewController:self];
[self.view addSubview: _vc1.view];
In the storyboard, you can add two container views to the same view controller and connect them both with the same child view controller by right-click dragging and choosing embed. This created the segue like so:
Click on the segue and give it an identifier. Then, add the prepareForSegue method to your parent view controller, and set some properties for the lap timers separately if you want.
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segueName isEqualToString: #"embedSegueToLapTimerOne"]) {
LapCounterNibViewController * childViewController = (LapCounterNibViewController *) [segue destinationViewController];
[childViewController setFoo:bar1];
}
if ([segueName isEqualToString: #"embedSegueToLapTimerTwo"]) {
LapCounterNibViewController * childViewController = (LapCounterNibViewController *) [segue destinationViewController];
[childViewController setFoo:bar2];
}
}
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.
Before storyboards I was able to set delegates and datasources just by dragging an outlet to a class. With storyboards, I cannot drag the outlet to another view controller; there is no destination that will respond to it.
If I click on a view controller object, I am able to see the class owner at the bottom, but as soon as I select the other view controller containing the outlet, the old selection is gone, so I cannot connect the two.
Is this Apple's way of saying we should only connect them programmatically?
Correct. Set the delegate or other data in your prepareForSegue:sender: method. Here is an example:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Check the segue identifier
if ([segue.identifier isEqualToString:#"showDetail"])
{
// Get a reference to your custom view controller
CustomViewController *customViewController = segue.destinationViewController;
// Set your custom view controller's delegate
customViewController.delegate = self;
}
}
If your storyboard segue destination View Controller is an UIViewController then #Marco answer is right. But if your destination View Controller is a UINavigationViewController then you have to get the UIViewController from UINavigationViewController :
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Check the segue identifier
if ([segue.identifier isEqualToString:#"chooseCategoryType"])
{
// Get a reference of your custom view controller if your segue connection is an UIViewController
// CustomViewController *customViewController = segue.destinationViewController;
// Get a reference of your custom view controller from navigation view controller if your segue connection is an UINavigationViewController
CustomViewController *customViewController = [[[segue destinationViewController] viewControllers] objectAtIndex:0];
// Set your custom view controller's delegate
customViewController.delegate = self;
}
}