Let's say that I have view controller Origin and Destination. I would like to declare something like:
//origin.m file
-(void)pushNextView {
self.conditional = YES;
[self performSegueWithIdentifier:#"toDestination" sender:self];
}
Where I have set my conditional as:
//origin .h file
#propery BOOL conditional;
Now in my Destination view controller I'd like to set a conditional based on the property that I've set in my origin:
// destination .m file
#import "OriginViewController.h"
OriginViewController *origin = [OriginViewController alloc] init];
if (origin.conditional == YES){
self.navigationItem.hidesbackbutton = YES;
}else{
// Do Nothing
}
for some reason this conditional statement does not work. Does this have to do anything with storyboards?
With the setup you seem to have it would be most easy to do this. You could directly access destination.hidesbackbutton when executing the segue:
//in origin.m
-(void) prepareForSegue.... {
if ([segue.identifier isEqualToString: #"identifierString"]) {
DestinationVC *destination = (DestinationVC*)[segue destinationViewController];
destination.hidesbackbutton = self.conditional; //you can set the .hidesbackbutton property here directly, no need for another property, if your setup is just as simple as in the given example
}
}
Like this, the destination doesn't check the origin's state and then set it's state, instead the origin just sets the destination's state.
You can do it both ways, but this way is more common.
Of course hidesbackbutton has to be a public property declared in DestinationVC .h file.
And as already mentioned, it should really be hidesBackbutton Or hidesBackButton.
(This all assumes that the class of your DestinationViewController is called DestinationVC)
Two things: hidesbackbutton looks like there's some camelCase missing. This should at least give a warning, doesn't it?
Also from an architectural point of view, I would not ask for the condition. Pass the state to the destination view controller and implement a BOOL variable there.
Draft:
// origin.m
- (void)prepareForSegue...
{
if([segue.identifier isEqualToString:"yourIdentifier"]) {
[(XYZViewController *)segue.destinationViewController setConditional:YES];
}
}
you should pass the state from your main controller to destination controller where you should handle this View state like and you have to define
//destination.h file
#propery BOOL conditional;
so when you push the controller from main you can set the destination controller view state in
- (void)viewDidLoad
{
[super viewDidLoad];
if (origin.conditional == YES) {
self.navigationItem.hidesBackButton = YES;
} else {
}
}
Related
As I segue from SearchViewController to MatchCenterViewController, I want to set the bool value of _didAddNewItem in MatchCenterViewController to YES. I'm attempting to accomplish this like so:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ShowMatchCenterSegue"]) {
MatchCenterViewController *controller = (MatchCenterViewController *) segue.destinationViewController;
_didAddNewItem = YES;
NSLog(#"we're about to set controller values before segueing to MC");
// Send over the matching item criteria
controller.itemPriority = self.itemPriority;
if (_didAddNewItem == YES) {
NSLog(#"Did add new item is YES");
}
[self.tabBarController setSelectedIndex:1];
}
}
The destination view controller is tab 1, so I want to set the bool before it switches to that tab. It is logging out "Did add new item is YES", which tells me that the value of the bool is indeed YES just before segueing.
However, upon switching to the destination, when the viewDidAppear of the destination controller runs, it logs out "don't refresh".
- (void)viewDidAppear:(BOOL)animated
{
if (_didAddNewItem == YES) {
NSLog(#"well then lets refresh the MC");
}
else if (_didAddNewItem == NO) {
NSLog(#"dont refresh");
}
}
I set the bool property in the headers of both originating and destination VC like so:
#property (assign) BOOL didAddNewItem;
Very confused as to what I'm missing here.
You are confusing access to a property with access to the automatically synthesised iVar.
When you say
#property BOOL something;
and don't use an #synthesize statement the compiler will automatically create an iVar BOOL _something to store the property. You can then access this property using self.something or _something. You should get into the habit of always using self.something (or object.something in the case of another object reference) except in initialiser methods.
In your code you are setting the local iVar _didAddNewItem when what you want is to set the property on the destination controller -
controller.didAddNewItem = YES;
I'm new to Objective-C and have a question. Did the search multiple times but I couldn't find what I was looking for.
I'm using storyboard for this app. On the homescreen you've got some buttons with labels above them. Those labels should tell a number. When pushing the button you go to a new viewController where you have input that (after 'save') goes back to the homescreen and updates the label with the correct number. All that works great for one button and I'm very happy about it.
The problems are:
1. Since I have multiple buttons with labels, I want to use the same viewController to give input over and over again. I tried connecting every button to slide to the viewController under the identifier "AddData", but Xcode doesn't allow the same identifiers twice or more in storyboard. So I would need something else for this. Any idea?
2. Currently I use the following code to bring back the data to the homescreen:
homeScreenViewController
- (IBAction)unwindToHomeScreen:(UIStoryboardSegue *)segue;
{
inputDataViewController *source = [segue sourceViewController];
self.logoOneLabel.text = source.endTotalNumber;
}
inputDataViewController:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if (sender != self.saveButton) {
return;
} else {
if (endTotalLabelNumber > 0) {
self.endTotalNumber = [NSString stringWithFormat:#"%.0f", totalLabelNumber + endTotalLabelNumber];
} else if (endTotalLabelNumber == 0 && totalLabelNumber == 0){
self.endTotalNumber = 0;
} else {
self.endTotalNumber = [NSString stringWithFormat:#"%.0f", totalLabelNumber + endTotalLabelNumber];
}
}
}
This works great for the one button, but how to use this with multiple? I heard about Delegates to use the same viewController multiple time and get data back to different places, but I just don't get it. Any help?
You shouldn't need delegates.
What you will need is a property on the view controller that handles input to it knows which button it is handling input for.
When you segue to the input controller, set this property, based on which button was pushed. When you unwind back, fetch this property to know which label to modify.
For example, in your input view controller's .h file, add a property like this:
#property (nonatomic,assign) NSInteger handlingTag;
Or something, whatever name makes sense to you.
Now you need to implement your home screen view controller's prepareForSegue:sender:.
Use the sender argument to determine which button was pushed, and based on that, set the input view controller's new handlingTag property based on the button in a way that you will know what to do with it when we unwind.
Now in the unwind method:
switch (source.handlingTag)
Create a switch structure based on the source's handlingTag property, and set the appropriate label based on this value.
As Jeff points out in the comments, it'd be a really good idea to define an NS_ENUM to use here for the property rather than an NSInteger. The NS_ENUM would allow you to name the values you're using.
There is a few different way to implement what you need. But i think most common its a delegate.
This is how your inputDataViewController looks like:
#import <UIKit/UIKit.h>
#protocol inputDataDelegate;
#interface inputDataViewController : UIViewController
#property (weak) id<inputDataDelegate> delegate;
#property (strong, nonatomic) NSNumber *buttonTag;
#end
#protocol inputDataDelegate <NSObject>
-(void) inputDataViewControllerDismissed:(id)data;
#end
Then in #implementation, you should in "save" button action, message to you delegate method :
[self inputDataViewControllerDismissed:#{#"buttonTag":buttonTag,#"endTotalNumber":endTotalNumber}
Next in homeScreenViewController connect delegate :
#interface homeScreenViewController : UIViewController<inputDataDelegate>
After that in #implementation:
-(void)inputDataViewControllerDismissed:(id)data
{
// if you use modal
[self dismissViewControllerAnimated:YES completion:nil];
// or if you use push
//[self.navigationController popViewControllerAnimated:YES];
switch (data[#"buttonTag"]) {
case 1:
self.lableWtiTagOne = data[#"endTotalNumber"];
break;
case 2:
self.lableWtiTagTwo = data[#"endTotalNumber"];
break;
// number of cases depend how many buttons you have
}
Also, most important, thing didn't forget send self to our delegate:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"inputDataController"])
{
inputDataViewController *inputCtrl = [segue destinationViewController];
inputCtrl.delegate = self;
inputCtrl.buttonTag = sender.tag
}
}
Hi I'm currently developing an app where you have a viewController with a UISwitch that i have got working with NSLog. My question is if you can change stuff in other viewControllers by creating a BOOL and then referring to that in the if statement? Heres some code:
UISwitch *mySwitch = [[UISwitch alloc] initWithFrame:CGRectMake(251, 111, 0, 0)];
[mySwitch addTarget:self action:#selector(changeSwitch:) forControlEvents:UIControlEventValueChanged];
[mySwitch setOnTintColor:UIColorFromRGB(0xac1f2d)];
[self.view addSubview:mySwitch];
- (void)changeSwitch:(id)sender{
if([sender isOn]){
NSLog(#"Switch is ON");
} else{
NSLog(#"Switch is OFF");
}
If you want to change something in another ViewController, you can do this by creating a reference to the other ViewController.
If you add public properties to the 'other' ViewController, you can change them in your if statement.
Say you have another VC, OtherViewController.
Create a BOOL property in it like:
#property(nonatomic) BOOL flag;
Implement the setter for the property in .m file of OtherViewController:
-(void)setFlag:(BOOL)flag {
_flag = flag;
if(_flag) { //if flag is true do something
//...........
}
else { //do anything else
//........
}
}
Now create a reference of OtherViewController instance in your first view controller, containing the switch. Say its referred by a property named other from the current view controller. Then you can trigger the required action on OtherViewController based on the switch's value from your present view controller like this:
- (void)changeSwitch:(UISwitch*)sender{
self.other.flag = sender.on;
}
This will call the setter of flag property in OtherViewController and would according to what you have written there.
Of course, the instance of OtherViewController should not be deallocated in between.
To expand on Stefan answer:
you would need to add property to the ViewController you want to change
so in ViewControllerA you would import the ViewControllerB header:
#import "ViewControllerB.h"
So that you can change that property in the method
- (void)changeSwitch:(id)sender{
ViewControllerB *viewcontroller = [[ViewControllerB alloc]init]];
if([sender isOn]){
viewcontroller.log = #"Switch is ON";
} else{
viewcontroller.log = #"Switch is OFF";
}
}
I'm interpreting your question to be asking whether you can change the state of another view controller using a switch on your existing view controller. The answer is yes.
You have a conditional to decide the state of the switch already. To make the connection you want to add a property to the view controller you are about to push. When you instantiate the new view controller, set the state of its property after checking if([mySwitch isOn]) (create an IBOutlet so you can check it at any time rather than responding to an IBAction), then push it. If you're going to get there via segue, you would check the state of the switch in prepareForSegue: instead.
Here's how you set the property in prepareForSegue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"uniqueSegueIdentifierFromIB"])
{
// Get a reference to the DestinationViewController
DestinationViewController *destinationVC = [segue destinationViewController];
// DestinationViewController has public #property (nonatomic) BOOL configurationOption;
if([mySwitch isOn])
{
[destinationVC setConfigurationOption:YES];
}
else
{
[destinationVC setConfigurationOption:NO];
}
}
}
Check the state of the new view controller's property in viewDidLoad: to decide how you want to configure it.
I have a container view, which uses a storyboard embed segue to load an embedded static table view. The segue ID is 'CONTAINER'.
When I run the following code, the prepareForSegue never actually gets called so no data is passed from the parent to the child.
- (void)viewDidLoad {
[super viewDidLoad];
if ([sGender isEqualToString:#"MALE"]) {
containerGender = #"MALE";
if ([containerGender isEqualToString:#"MALE"]){
NSLog(#"MALE");
}else{
NSLog(#"BROKEN");
}
}
else if ([sGender isEqualToString:#"FEMALE"]) {
containerGender = #"FEMALE";
}
}
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"CONTAINER"]) {
if([containerGender isEqualToString:#"MALE"]) {
if ([containerGender isEqualToString:#"MALE"]){
NSLog(#"MALE");
}else{
NSLog(#"BROKEN");
}
SportTableViewController *tableView = segue.destinationViewController;
tableView.sportGender = #"MALE";
}
else if ([containerGender isEqualToString:#"FEMALE"]){
SportTableViewController *tableView = segue.destinationViewController;
tableView.sportGender = #"FEMALE";
}
}
}
My question is:
a)Why is prepareForSegue not called? Does the Storyboard Embed Segue behave differently to a standard seque?
b)Is there a better way of passing data from the container view to the embedded table?
Also please ignore the messy implementation/various log tests, just my attempts to work out whats going wrong.
My question actually stems from a misunderstanding of how container views load their embedded views. Apparently, it all happens before viewDidLoad. That means my conversion of sGender into containerGender took place too late.
I fixed it by passing sGender to the embedded view directly. I had thought I'd already tried this, but yesterday was obviously a slow day ;).
you can do it..
try
in childViewController
set-
#property(nonatomic,retain)parentViewController *parent;
and
in ParentViewController set-
ChildViewController *child = [[childviewController alloc]init];
child.parent=self;
It will set your parent class in childview and you can get data through parent object in child view controller.
I'm trying to use storyboard and get things working properly. I've added a a Container View to one of my existing views. When I try to add a reference to this in my view controller .h file (ctrl-drag), I get a IBOutlet UIView *containerView. How do I get a reference to the container view's view controller instead? I need the container view controller so I can set it's delegate to my view's controller so they can "talk" to each other.
I have my story board setup as:
And its referenced in my .h file as:
Notice in the .h that is is a UIView, not my InstallViewController for the view. How do I add a reference to the view controller? I need to be able to set its delegate.
There is another solution by specifying an identifier for the embed segue(s) and retrieve the corresponding view controllers in method prepareForSegue:
The advantage of this way is that you needn't rely on a specific order in which your child view controllers are added due to the fact that each child view controller is embedded via an unique segue identifier.
Update 2013-01-17 - Example
- (void) prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
// -- Master View Controller
if ([segue.identifier isEqualToString:c_SegueIdEmbedMasterVC])
{
self.masterViewController = segue.destinationViewController;
// ...
}
// -- Detail View Controller
else if ([segue.identifier isEqualToString:c_SegueIdEmbedDetailVC])
{
self.detailViewController = segue.destinationViewController;
// ...
}
}
c_SegueIdEmbedMasterVC & c_SegueIdEmbedDetailVC are constants with the corresponding ID of the segue IDs defined in the storyboard.
When you add a container view the xcode calls the UIViewController method addChildViewController:
In your case, you can get the container ViewController looking for it on the SplashViewController's list of childViewControllers, something like this:
for (UIViewController *childViewController in [self childViewControllers])
{
if ([childViewController isKindOfClass:[InstallViewController class]])
{
//found container view controller
InstallViewController *installViewController = (InstallViewController *)childViewController;
//do something with your container view viewcontroller
break;
}
}
I had the same doubt yesterday :)
The answer of Vitor Franchi is correct but could be more performant and convenient. Especially when accessing the child view controller several times.
Create a readonly property
#interface MyViewController ()
#property (nonatomic, weak, readonly) InstallViewController *cachedInstallViewController;
#end
Then create a convenient getter method
- (InstallViewController *)installViewController
{
if (_cachedInstallViewController) return _cachedInstallViewController;
__block InstallViewController *blockInstallViewController = nil;
NSArray *childViewControllers = self.childViewControllers;
[childViewControllers enumerateObjectsUsingBlock:^(id childViewController, NSUInteger idx, BOOL *stop) {
if ([childViewController isMemberOfClass:InstallViewController.class])
{
blockInstallViewController = childViewController;
*stop = YES;
}
}];
_cachedInstallViewController = blockInstallViewController;
return _cachedInstallViewController;
}
From now on access the child view controller that way
[self.installViewController doSomething];
UIView* viewInsideOfContainer = installerView.subviews[0];
Will give you the UIView inside of the UIViewController that your controller UIView references. You can cast the subview to any type that inherits from UIView.
If the nib is loaded it will call addChildViewController as part of the initialisation process
so a performant solution could be also to overwrite
- (void)addChildViewController:(UIViewController *)childController
there you can catch your childController e.g. by comparing its Class and assign it to a property / ivar
-(void)addChildViewController:(UIViewController *)childController
{
[super addChildViewController:childController];
if([childController isKindOfClass:[InstallViewController class]])
{
self.installViewController = (InstallViewController *)childController;
}
}
This will save your from iterating trough the childViewControllers.