How do I hook up a UIButton that is inside controller A, so that it opens a controller B that is contained inside controller A (inside a "Container View") using storyboards?
Ie controller B only takes up part of controller A area. Controller A would still be partly visible.
Background:
When adding a controller B to a Container View inside another controller A, it defaults to opening the controller B as soon as controller A loads. I want controller B to be hidden first, then have it open by the tap of a button.
Taking apart the view lifecycle for nib/storyboard launched resources will help here.
You need to hide the view of Controller B sometime after it has been created and loaded, but before it has been displayed. Then in response to an action, you need to unhide the view (or do some fancier presentation).
Typically you will declare a property within Controller A of:
#property (weak, nonatomic) IBOutlet ControllerB *controllerB;
Which you wire up in the storyboard.
Now you have a reference to to your controllerB instance which you can make use of from within controllerA's code.
Since you've nested controllerB's view inside of the view hierarchy of controllerA in the storyboard, your instance of controllerB will exist and be ready to manipulate as soon as -viewDidLoad is called on controllerA.
- (void)viewDidLoad
{
[_controllerB.view setHidden:YES];
//other setup and configuration of controllerA
}
You could do this at viewWillAppear, or a few other places, but as long as you hide controllerB.view before -viewDidAppear is called, you'll be fine.
Then you have controllerA respond to the button push something like this:
- (IBAction)userPressedTheButton:(id)sender
{
[_controllerB.view setHidden:NO];
}
This is a pretty easy stuff. You could create an outlet for the container view
#property (weak, nonatomic) IBOutlet UIView *containerView;
In viewDidLoad just hide it
- (void)viewDidLoad
{
[super viewDidLoad];
self.containerView.hidden = YES;
// Do any additional setup after loading the view, typically from a nib.
}
Unhide it on button click
Related
I want to segue back from ViewControllerTwo to ViewControllerOne. I created a button that is responsible for doing that, but my problem is that the button is part of custom UIView class that is added to ViewControllerTwo, the button is not a part of the main view of ViewControllerTwo.
So in the custom UIView class I have the method that reacts if the button is clicked...
-(void)buttonClicked{
[SecondViewController performSegueWithIdentifier: "ShowFirstViewController" sender:nil];
}
When I do this I get an error: "performSegueWithIdentifier not a method of class" which makes sense.
So how can I segue between two viewcontrollers where the button responsible for the segue is not actually part of either view controller and is in a different class.
I think you can have a delegate call back to your SecondViewController and implement the performSegueWithIdentifier in the delegate callback method in SecondViewController.
It goes like this:
Above your custom UIView class interface create a protocol like this
#protocol CustomViewDelegate <NSObject>
- (void)buttonDidTap;
#end
Then create a property in your interface
#property (nonatomic, weak) id <CustomViewDelegate> delegate;
In your custom UIView *.m add this
-(void)buttonClicked{
[self.delegate buttonDidTap];
}
Conform the protocol to your SecondViewController like this
#interface SecondViewController: UIViewController <CustomViewDelegate>
set the delegate in your viewDidLoadMethod like this
-(void)viewDidLoad{
[super viewDidLoad];
self.yourCustomView.delegate = self;
}
implement this method inside the view controller .m file
- (void)buttonDidTap{
[self.performSegueWithIdentifier: "ShowFirstViewController" sender:self];
}
I'm more of a swift guy i think this should work fine.
iOS 9.3, Xcode 7.3, ARC enabled
This is what I'd do to troubleshoot:
Step 1: Make sure that you have a proper storyboard identifier for the view controllers you wish to segue between. The views simply attach to the view controllers, custom or not.
To do this, go to "*.storyboard" show the Utilities (right pane) and navigate to the Identity Inspector. Make sure you have "ShowFirstViewController" entered in the Storyboard ID field.
I am trying to use the .text of UITextField in my first view controller in another .text of UITextField in my second view controller, but my firstPage.firstTField.text turns out to be (null) in my second view controller even though I printed _firstTField.text in my first view controller and it printed out the input that was entered.
What may be the problem? Why is null?
FirstViewController.h
#property (weak, nonatomic) IBOutlet UITextField *firstTField;
SecondViewController.h
#property (weak, nonatomic) IBOutlet UITextField *secondTField;
SecondViewController.m
#import "FirstViewController.h"
- (void)viewDidLoad {
[super viewDidLoad];
FirstViewController *firstPage = [[FirstViewController alloc] init];
_secondTField.text = firstPage.firstTField.text;
}
You should treat a view controller's views as private. That's part of the appearance of the view controller, not it's function, and if objects outside of the view controller expect the views to look a certain way then when you change the appearance of the view controller's views it breaks other code. Bad.
In this situation it's worse. It just doesn't work, for the reason #nhgrif explains. You just created a new FirstViewController object, and it's views don't even exist yet. (A view controllers views are not created until the system is asked to display the view controller to the screen.)
You should create a property in your view controller that exposes the string(s) you need to read/write and use that instead.
However it's strange that you would create a new instance of a view controller and then immediately try to read text from one of it's fields. How can it possibly have useful data if the view controller was created on the line before? What are you expecting to happen?
I am relatively new to Xcode and have tried to find the answer by searching, without luck.
My app has 5 View Controllers, V1 through V5, which are embedded in one Tab Bar Controller. Each View Controller has a segue to one and the same Setup Menu View Controller. The Menu changes some labels on the View Controllers. I use a delegate to make sure that the View Controller that calls the Menu gets updated with the new settings when you leave the Menu. However, this allows me to modify only the labels on the View Controller that called the Menu Controller, not on the 4 other ones.
I work form a Story Board. Is there a simple way to set the UILabels on V2, V3, V4 and V5 from V1 (and vice versa), or even better, set the labels on V1 through V5 from the Menu View Controller (which is not embedded in the Tab Bar Controller)?
I have seen something that could help here, but this seems rather complicated for what I want. The label changes I need are quite simple and are all predefined. Is there a method that is called every time you switch tabs in a tabbed application? Similar to ViewDidLoad?
This sounds like a good time for NSNotificationCenter. You are going to have your MenuViewController generate a notification with the new data that should be updated in your other view controllers:
// User has updated Menu values
[[NSNotificationCenter defaultCenter] postNotificationName:#"MenuDataDidChangeStuffForLabels" object:self userInfo:#{#"newLabelValue" : labelText}];
In your V1, V2, etc. you can add subscribe to these notifications using this code in your viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
// Subscribe to NSNotifications named "MenuDataDidChangeStuffForLabels"
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateLabelText) name:#"MenuDataDidChangeStuffForLabels" object:nil];
}
Any object that subscribes using that code will call the updateLabelText method anytime a notification with that name is posted by the MenuViewController. From that method you can get the new label value and assign it to your label.
- (void)updateLabelText:(NSNotification *)notification {
NSString *newText = notification.userInfo[#"newLabelValue"];
myLabel.text = newText;
}
What I would do is subclass the tab bar controller and set that as the delegate for the menu view controller. From there, you can get updated when the labels are supposed to change and then communicate with the 5 tabs and update the labels.
Alternatively, you could use NSNotifications to let all the 5 view controllers know when settings change.
Lastly, you could add the menu settings to a singleton and have all of the view controllers observe the various properties that can change.
The label changes I need are quite simple and are all predefined. Is there a method that is called every time you switch tabs in a tabbed application? Similar to ViewDidLoad?
Regarding this question, the methods you're looking for are viewWillAppear: and viewDidAppear.
Here is a very simple solution if your workflow is also simple. This method changes all the labels from the different ViewControllers directly from what you call the Menu ViewController.
Let's say you have the following situation :
The blue ViewController is of the FirstViewController class. The green ViewController is of the SecondViewController class. The labels on each of those are referenced by the properties firstVCLabel and secondVCLabel (on the appropriate class' header file). Both these ViewControllers have a "Modal" button which simply segues modally on touch up inside.
So when you clic on any of these two buttons, the orange ViewController (of ModalViewController class) is presented. This ViewController has two buttons, "Change Label" and "Back", which are linked to touch up inside IBActions called changeLabel: and back:.
Here is the code for the ModalViewController :
#import "ModalViewController.h"
#import "FirstViewController.h"
#import "SecondViewController.h"
#interface ModalViewController ()
#end
#implementation ModalViewController
// Action linked to the "Change Label" button
- (IBAction)changeLabel:(id)sender {
// Access the presenting ViewController, which is directly the TabBarController in this particular case
// The cast is simply to get rid of the warning
UITabBarController *tabBarController = (UITabBarController*)self.presentingViewController;
// Go through all the ViewControllers presented by the TabBarController
for (UIViewController *viewController in tabBarController.viewControllers) {
// You can handle each ViewController separately by looking at its class
if ([viewController isKindOfClass:[FirstViewController class]]) {
// Cast the ViewController to access its properties
FirstViewController *firstVC = (FirstViewController*)viewController;
// Update the label
firstVC.firstVCLabel.text = #"Updated first VC label from Modal";
} else if ([viewController isKindOfClass:[SecondViewController class]]) {
SecondViewController *secondVC = (SecondViewController*)viewController;
secondVC.secondVCLabel.text = #"Updated second VC label from Modal";
}
}
}
// Action linked to the "Back" button
- (IBAction)back:(id)sender {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
For the sake of completeness, here are FirstViewController.h :
#import <UIKit/UIKit.h>
#interface FirstViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *firstVCLabel;
#end
And SecondViewController.h :
#import <UIKit/UIKit.h>
#interface SecondViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *secondVCLabel;
#end
There is no relevant code in the implementation of these classes.
Thanks a lot guys, I am impressed by your quick responses. In this particular case, viewWillAppear does the trick:
- (void)viewWillAppear:(BOOL)animated
{ [self AdaptLabels];
NSLog(#"View will appear.");
}
Every time a new tab is chosen, it updates the labels in the new View, according to a global variable set by the Menu, just before they appear. Very quick and clean. Thanks to all of you!
I have a ViewController with 2 sub containers. The first sub container points to a ViewController with a TabBar inside it. The second sub container is a ViewController that contains a collection view. Now my issue is trying to access the first sub containers TabBar so that when an Item is clicked, I can know which item is clicked and process my data.
The main ViewController has a class. All the other sub containers for that ViewController also have a class. Here is the .h for my sub container with the Tab Bar:
#import <UIKit/UIKit.h>
#interface home_tab : UIViewController <UITabBarControllerDelegate>{
}
#end
.m:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
-(void) tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
NSLog(#"working");
}
Now when clicking on the Tab Bar that is populated, didSelectViewController is never called.
I am using storyboard.
Suggestions and thoughts?
Try this on your viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
[[self tabBarController]setDelegate:self];
// Do any additional setup after loading the view, typically from a nib.
}
It's just an suggestion :)
[[self tabBarController]selectedIndex] This will return the index of the selected tab.
I think you have a couple problems atleast from what I can see here,
You are using a TabBar inside of a ViewController, not a UITabBarController, thus you need to use UITabBarDelegate not UITabBarControllerDelegate. You will have to manage the view controllers or whatever view you will want to be loaded on your own most likely with the delegate callback:
-(void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item;
Also you dont have a UITabBar defined in your controller, therefore your ViewController has no idea you have a UITabBar in your Storyboard. You need something like this:
#interface ViewController : UIViewController <UITabBarDelegate>
#property (weak, nonatomic) IBOutlet UITabBar *tabBar;
#end
Then you will need to Control drag from the ViewController to your UITabBar and back to connect the Delegate in your Storyboard.
Id recommend using a UITabBarController so you dont have to manage the views yourself depending on what you are trying to accomplish.
Hope this helps!
the didSelectViewController method is part of the UITabBarControllerDelegate Protocol and is called on the UITabBarController's delegate. Did you set the delegate of the tab bar controller to the current instance of your subcontainer? Inside ViewDidLoad: do something like this:
[self.tabBarController setDelegate:self];
You can also set a delegate on the UITabBar rather than the controller, and the UITabBarDelegate Protocol contains a method tabBar:didSelectItem: that would be called.
I have a main view which has a UISlider on it.
From the main view I add a subview using:
gameView *myViewController = [[gameView alloc] initWithNibName:#"gameView" bundle:nil];
[self.view addSubview:myViewController.view];
The subview is created on top of the main view.
When I remove the sub view using:
[self.view removeFromSuperview];
the main view underneath becomes visible.
I want to be able to update the value of the UISlider on the main view, from the sub view, before I call [self.view removeFromSuperview]
Is it possible?
Basically the question can be generalized to how to update an IBOutlet on the main view from the sub view.
Help is greatly appreciated.
Many thanks!
Yes, it's possible.
And there's a few ways to do this. Here's how I would do it:
First, make your parent view controller's UISlider a property that can be accessed by other objects.
Secondly, give your gameView object an instance variable that you'll link to the parent view (let's call it id savedParent;)
Then, before you do removeFromSuperview, you can simply do something like:
ParentViewController * parentVC = (ParentViewController *) savedParent;
if(parentVC)
{
// some float value of whatever you want to set the slider value to
parentVC.slider.value = 0.5f;
}
Also, why are you instantiating a whole View Controller object (gameView) if you simply want to add a subview? When you do your removeFromSubview call, the view gets removed but your gameView view controller isn't released (and might even be getting lost & leaked in memory, leading to a crash). If you want to do a subview, subclass UIView. If you want to push a new view controller, push the whole controller (and not just the view it contains).
Here is another way:
I'm not sure what the slider is representing, but you need to create an object that represents this
#interface MyGameThing : NSObject
#property (assign) CGFloat myValue;
#end
#implementation MyGameThing {
CGFloat *_value;
}
#synthesize myValue = _myValue;
#end
You then need to pass that object to both of your view controllers (or make it a singleton).
Then, on ParentViewController, in the viewWillAppear, just set the slider to the new value.
Daniel.
(p.s. don't just add view controllers views to the superview, use presentModalViewController / dismissModalViewController or a navigation controller).