I have three buttons and a UIView (I call it containerView), tap each of the buttons, the containerView will show a View in a UIViewController through a custom segue, the buttons use one IBAction method(switchView), I put the three buttons in a IBOutletCollection called navButtons;
and in viewDidLoad I call switchView to make the containerView show first view controller.
And the code run well, no error,the only question is when I tap first button,the first UIViewController will shown in containerView, the UIImageView (buttons,labels,etc) should be in the centre of the screen, but it never behave like this when it's first loaded, when I tap another button then tap first button again,it behaved as expected,
I have no idea what happened and what's the difference between first load and tapping it again.
IS THERE ANYTHING I MISSED IN THE CODE ?
I am not good at English,Sorry for anything unclear .
- (void)viewDidLoad {
[super viewDidLoad];
self.availableIdentifier = [[NSMutableArray alloc] initWithObjects:#"Seg1",#"Seg2",#"Seg3", nil];
[self switchView:self.navButtons[0]];
}
- (IBAction)switchView:(UIButton *)sender
{
[self setSelectedIndexs:(int)sender.tag];
}
- (void)setSelectedIndexs:(int)index
{
[self performSegueWithIdentifier:self.availableIdentifier[index] sender:self.navButtons[index]];
}
//Code of Custom Segue:
-(void)perform
{
ViewController *controller = (ViewController *)self.sourceViewController;
UIViewController *destController = (UIViewController *)self.destinationViewController;
for (UIView *view in controller.containerView.subviews)
{
[view removeFromSuperview];
}
controller.currentController = destController;
[controller.containerView addSubview:destController.view];
[controller.containerView setTranslatesAutoresizingMaskIntoConstraints:NO];
[destController didMoveToParentViewController:controller];
}
I think you should add:
destController.view.frame = controller.containerView.frame;
before
controller.currentController = destController;
is your clipSubViews set in UIViewController ?
Solved. Thanks for the answer of Bojan Bozovic ;
CGRect frame = controller.containerView.frame;
frame.origin.y = 0;
destController.view.frame = frame;
Add above before controller.currentController = destController;
Related
So I am trying to add a view as a subview onto an exisiting View Controller by loading it from an XIB in such manner -
- (void)showInView:(UIView *)aView animated:(BOOL)animated
{
self.view.frame = aView.frame;
dispatch_async(dispatch_get_main_queue(), ^{
[aView addSubview:self.view];
if (animated) {
[self showAnimation];
}
});
}
I see the button on the parent View Controller and it gets pressed too with the little animation that is there on UIButtons by default. The problem is that the IBAction connected to the UIButton never gets called.
Thanks in advance.
Adding the view as a class variable instead of a local variable did it for me.
I have created UISwipeGestureRecognizer sample app. When I slide left a new view is appear as the picture shows.
I want to edit this red view from my nib file. How can I add another UIView from the nib & connect to this menuDrawer??
This is my Code
MainViewController.h file
#interface MainViewController : UIViewController{
UIView *menuDrawer;
}
#property (readonly,nonatomic)UISwipeGestureRecognizer *recognizer_open , *recognizer_close;
#property (readonly,nonatomic) int menuDrawerX,menuDrawerWidth;
-(void)handleSwipe :(UISwipeGestureRecognizer *)sender;
-(void)drawerAnimation;
#end
MainViewController.m file
#implementation MainViewController
#synthesize menuDrawerWidth,menuDrawerX,recognizer_open,recognizer_close;
-(void)handleSwipe :(UISwipeGestureRecognizer *)sender{
[self drawerAnimation];
}
-(void)drawerAnimation{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:-5];
CGFloat new_x = 0;
if (menuDrawer.frame.origin.x < self.view.frame.origin.x) {
new_x = menuDrawer.frame.origin.x + menuDrawerWidth;
}else{
new_x = menuDrawer.frame.origin.x - menuDrawerWidth;
}
menuDrawer.frame = CGRectMake(new_x, menuDrawer.frame.origin.y, menuDrawer.frame.size.width, menuDrawer.frame.size.height);
[UIView commitAnimations];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
int statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
menuDrawerWidth = self.view.frame.size.width *0.75;
menuDrawerX = self.view.frame.origin.x - menuDrawerWidth;
menuDrawer = [[UIView alloc]initWithFrame:CGRectMake(menuDrawerX, self.view.frame.origin.y+statusBarHeight, menuDrawerWidth, self.view.frame.size.height-statusBarHeight)];
menuDrawer.backgroundColor = [UIColor redColor];
recognizer_close = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(handleSwipe:)];
recognizer_open = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(handleSwipe:)];
recognizer_open.direction = UISwipeGestureRecognizerDirectionLeft;
recognizer_close.direction = UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:recognizer_open];
[self.view addGestureRecognizer:recognizer_close];
[self.view addSubview:menuDrawer];
}
Please check it out: https://github.com/mutualmobile/MMDrawerController
This is example of making menu drawer in the application.
Hope, it will may help you,
:)
You can directly add your menu drawer view in your MainViewController's nib file. Set correct frame(Which is origin.x is -(width of menu drawer)). Create a outlet for that view in your controller.
What you can do:
Rather than loading the view in code, you should have a nib (storyboard of xib) file for the entire controller. In that xib file you should create all the views that you want to and link them to the controller using IBOutlets.
If you do this you then would be able to put all the views that you want as children of the red one and all of them will be loaded from the nib.
What you should do:
Even tho the option I said before works, I am guessing you are trying to somehow show a menu within the drawer, or any kind of contect not fully related with the rest of the window. Usually it is better to separate logic in different controllers, so I would create a separated controller to handle the drawer. Just create a new VC in the same storyboard where you create the other one. When the method drawerAnimation is triggered:
1) Load the VC from the storyboard programmatically
2) Add the VC as a child view controller using (content would be the drawer):
[self addChildViewController: drawerViewController]; // self is your main controller, and content is the one that handles the drawer view.
drawerViewController.view.frame = CGRectMake(blabla..); //
[self.view addSubview:drawerViewController.view];
[drawerViewController didMoveToParentViewController:self]; // 3
3) Animate it
If you want to completely remove the view at some point you can just remove the entire VC using:
[drawerViewController willMoveToParentViewController:nil]; // 1
[drawerViewController.view removeFromSuperview]; // 2
[drawerViewController removeFromParentViewController]; // 3
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.
I'm trying to pass data to a childviewcontroller.
I have a view controller with two buttons and one view. Pressing the buttons defines the view that is shown.
The specific case is I want to show a list of items. The first way (button) is in a list, the second on a mapview. To show the items i need to pass a category to the child.
In my viewDidLoad I add both viewcontrollers with addchildviewcontroller en set my view to the view of the listviewcontroller.
-(void)viewDidLoad
{
[super viewDidLoad];
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
ItemListViewController * itemListViewController = (ItemListViewController *)[sb instantiateViewControllerWithIdentifier:#"ItemList"];
itemListViewController.view.frame = detailView.bounds;
[self addChildViewController:itemListViewController];
[itemListViewController didMoveToParentViewController:self];
itemListViewController.category = category;
ItemListMapViewController * itemListMapViewController = (ItemListMapViewController *)[sb instantiateViewControllerWithIdentifier:#"ItemListMap"];
itemListMapViewController.view.frame = detailView.bounds;
[self addChildViewController:itemListMapViewController];
itemListMapViewController.category = category;
childControllers = [NSArray arrayWithObjects:itemListViewController, itemListMapViewController, nil];
[self.detailView addSubview:itemListViewController.view];
currentPage = 0;
}
Acoording to the button pressed I change the my view
- (IBAction)buttonClicked:(id)sender
{
UIButton * button = sender;
UIViewController *source = (UIViewController *)[childControllers objectAtIndex:currentPage];
[source.view removeFromSuperview];
UIViewController *destination = (UIViewController *)[childControllers objectAtIndex:button.tag - 100];
[self.detailView addSubview:destination.view];
currentPage = button.tag - 100;
button = nil;
}
But passing the category to my childviewcontrollers does nothing. The category in my childcontrollers is always null.
I also tried to get the category by accessing the parentviewcontroller on the childviewcontroller,
NSLog(#"::%#", ((ItemListHeaderViewController *)self.parentViewController).category);
but this also results in null.
I don't know what I'm doing wrong or maybe I'm understanding the whole containment story wrong... I'm new to ios development, so don't shoot me if the question is stupid. :) This is also my first question on stack overflow, so again don't shoot if I did something wrong.
Help would be appreciated. Thanks in advance.
Kind regards...
In the child view controller .h:
#property(nonatomic, strong) NSString *category;
Child VC .m:
#synthesize category;
Then get a reference to that class in the parent class and set the property. I wonder why you are doing this in code. Laying this out in IB is soooo much easier and working with segues more straightforward to me.
Wow, I gave a big thought on the question!
So I have a view Controller called "ContentView" within another view Controller (Main VC). The Main VC has a Navigation Controller which was created using Storyboards. And the contentView loads 3 different view controllers (vc1, vc2 and vc3) depending on the options that a Segmented Control has. So the question now is:
How can I load a new View Controller from the button within one of the subviews (vc2) that will appear once the user selects the option from the segmented control?
Although I have the visible view controller (vc2) in my storyboard, obviously I cannot connect an action to the button to the vc2' file's owner since the Nav Controller is on the Main VC.
I tried to access it with the following code:
AppDelegate *del = (AppDelegate *)[UIApplication sharedApplication].delegate;
UINavigationController *navigationController = (UINavigationController*)del.window.rootViewController;
DetalleMisMarcas *detalleMarcas = [[DetalleMisMarcas alloc] initWithNibName:#"DetalleMarcas" bundle:nil];
[navigationController pushViewController:detalleMarcas animated:YES];
But it does not work.
I have tried to find a solution from this forum, but I had no luck. Most consider the existence of a Main Window Xcode 4.2 does not have.
Finally, the way I load the 3 subviews, is here:
-(IBAction)segmentCtrlChanged:(id)sender {
UISegmentedControl *seg = sender;
if (seg.selectedSegmentIndex == 0)
{
MMViewController *mm= [self.storyboard instantiateViewControllerWithIdentifier:#"MMView"];
mm.view.frame = CGRectMake(0, 0, 320, 240);
[contentView addSubview:mm.view];
}
else if (seg.selectedSegmentIndex == 1) {
MPViewController *mp = [self.storyboard instantiateViewControllerWithIdentifier:#"MPView"];
mp.view.frame = CGRectMake(0, 0, 320, 240);
[contentView addSubview:mp.view];
}
}
-(IBAction)mainSubView:(id)sender
{
MMViewController *mm = [[MMViewController alloc] initWithNibName:#"MTView" bundle:nil];
[contentView addSubview:theMTView];
mm.view.frame = CGRectMake(0, 0, 320, 240);
}
Any ideas?
EDIT: Based on your comment, I think delegates will work better. This is how you may implement it.
Let's say in contentView controller class, add a delegate
// in the header file
#property (weak, nonatomic) id <ContentViewControllerDelegate> delegate;
#protocol ContentViewControllerDelegate <NSObject>
- (void)contentViewDidSelectAView:(UIView *)view;
#end
// in the implementation
- (IBAction)segmentCtrlChanged:(UISegmentedControl *)sender {
case 0:
[self.delegate contentViewDidSelectAView:[[self.storyboard instantiateViewControllerWithIdentifier:#"MMView"] view];
break;
// and the rest of the code
}
// in MainView header file
#interface MainViewController : UIViewController <ContentViewControllerDelegate>
// in MainView implementation
- (void)contentViewDidSelectAView:(UIView *)view {
[self.view addSubView:view];
}
I haven't actually implemented the above code. But I think this should be the way for a controller to talk to its parent. Hope this one helps.
I am not sure what exactly you are doing but let's say you have a UINavigationController in the root, and mainViewController is linked via segue as a relationship. As you have implemented, the segmentedControl in mainVC should be linked to -(IBAction)segmentCtrlChanged:(id)sender with valueChange event.
In this method you can switch the index, rather than using if, although they work the same, and you can also use UISegmentedControl * instead of id :
-(IBAction)segmentCtrlChanged:(UISegmentedControl *)sender {
switch(sender.selectedSegmentIndex) {
case 0: {
MMViewController *mm= [self.storyboard instantiateViewControllerWithIdentifier:#"MMView"];
[self.navigationController pushViewController:mm animated:YES];
break;
}
case 1: {
MPViewController *mp = [self.storyboard instantiateViewControllerWithIdentifier:#"MPView"];
[self.navigationController pushViewController:mp animated:YES];
break;
}
case 2: {
// similar to above
break;
}
default:
break;
}
vc1, vc2 and vc3, -- I assume -- are instances of UIViewController. So make sure that their class is linked, and that their identifiers are set properly. and then, just leave them as they are, because you are not going to make any segue for them individually. They are going to be pushed to the stack via code.
Hope it helps.