I have a segue between a scene and a navigation controller. The navigation controller embeds a view which has a textfield which I want to initialize in the prepareForSegue method.
So it's:
Scene A -> Nav controller -> scene B
But, in Scene A.prepareForSegue, only Nav controller's properties are allocated, and all Scene B's properties are still nil. So if I do:
UINavigationController *cont=[segue destinationViewController];
AddToDoItemViewController *atodo=(AddToDoItemViewController*)cont.topViewController;
[atodo preparetextfield2];
It won't work, because the textfield in the todo is still set to nil. So how do I fix this?
And here is the preparetextfield2 method:
-(void)preparetextfield2
{
self.textField.text=#"test";
}
In your AddToDoItemViewController.h file create property to keep the value, for example:
#property(nonatomic, strong) NSString *myString;
And update your text field in viewDidLoad:
-(void)viewDidLoad{
//...Your code
self.textField.text = self.myString;
}
And in preparetextfield2 just update the string:
-(void)preparetextfield2 {
self.myString = #"test";
}
This happened because your controls hasn't been loaded in that stage when you call prepareForSegue so your text field is nil.
Related
I am having trouble linking up a switch. I understand how to connect the switch as an outlet and have it have actions change the boolean state of a value, but I need the switch to perform an action in a different view controller.
Here's the situation: I have a main table view controller, called View Controller A. I have a second view controller, lets call it view controller B, that controls a menu sidebar (on a regular view controller, not a table view) triggered by a bar item. I want to be able to open up the menu, hit a switch in the sidebar, and have something change in the main table view that is controlled by view controller A.
Is there any way that I can accomplish this? I seem to have no way of accessing or changing the IBOutlets in View Controller A from B. Is there a way that I can have the action in B linked with the switch change the boolean state of a value, and have an action waiting in controller A that will respond to a change in boolean? I am not sure how to solve this problem. Help is appreciated!
You should use delegation pattern. You'll have an action waiting in controller A, but instead of responding to value changed in B the action will be triggered by B when appropriate
ViewControllerB.h
// Create delegate protocol and property
#protocol ViewControllerBDelegate <NSObject>
- (void)switchPressed:(BOOL)switchStatus;
#end
#interface ViewControllerB : NSObject
#property (nonatomic,weak) id<ViewControllerBDelegate> delegate;
#end
ViewControllerB.m
// When switch is tapped, call delegate method if it is implemented by delegate object
- (IBAction)flip: (id) sender {
UISwitch *onoff = (UISwitch *) sender;
if ([self.delegate respondsToSelector:#selector(switchPressed:)]) {
[self.delegate switchPressed:onoff.on];
}
}
ViewControllerA.h
// Conform to ViewControllerB protocol
#import ViewControllerB.h
#interface ViewControllerA : NSObject,ViewControllerBDelegate
ViewControllerA.m
// Set self (VC A) as VC B's delegate
- (void)ViewDidLoadOrSomeOtherFunction {
ViewControllerB *vcB = [[ViewControllerB alloc] init];
vcB setDelegate = self;
}
// Implement delegate method
- (void)switchPressed:(BOOL)switchStatus {
if (switchStatus) {
// Make changes on VC A
}
}
I have a UINavigationController that I reuse to push photos and comments looping over each other through the navigation controller.
In my MyViewController.h:
#property (nonatomic, strong) NSMutableAttributedString *pLabel;
In my MyViewController.m:
- (void) viewWillDisappear:(BOOL)animated
{
if ([self.navigationController.viewControllers indexOfObject:self] == NSNotFound)
{
// A photo function gets the right label
_pLabel = // function gets correct label I want
}
[super viewWillDisappear:animated];
}
- (void)viewWillAppear:(BOOL)animated
{
_pLabel = // is already set
}
I click on MyViewController once and set mvc.pLabel = #"1st one". Then I click a button to create a new MyViewController and set mvc.pLabel = #"2nd";
Then when I click the Back button viewWillDisappear gets called and my dictionary sets pLabel = #"1st". Then viewWillAppear gets run to show the first navigation controller view and pLabel = #"2nd";
Why isn't viewWillDisappear not saving the pLabel?
Thanks.
Your property is bound to 1 instance of MyViewController so when you pop your second view controller you'll set the property of the MyViewController that will disappear and not to the one that'll become visible.
If you don't keep a reference on the MyViewController that disappears there are no reason to update one of its label, since you'll probably show a fresh new instance of MyViewController next time.
I'm trying to make a form that one of the filed takes value from a two level selections' result.
The main progress will something like:
EditViewController ===> CategoryViewController (which embedded inside a NavigationController by storyboard and popped up as a modal view) ===> SubCategoryViewController (Which will be pushed to NavigationController).
Now I have a problem. After user tap to select a value in SubCategoryViewController, I'm supposed to dismiss SubCategoryViewController and return the value to EditViewController. But I don't know exactly how.
Please suggest any solution.
Thank you.
EDIT:
Every one of those view controllers should have a public property for a weak reference to a model object that represents whatever is being edited.
So every ____ViewController.h file would have:
#property (weak, nonatomic) CustomItem *item.
in its interface (assuming a strong reference is somewhere in some data store or array of all the items).
When EditViewController is preparing for the segue to show CategoryViewController modally, it should assign that same reference to CategoryViewController's item property after assigning any data entered in EditViewController's form to item:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
//TODO: assign data from controls to item, for example:
//self.item.title = self.titleField.text;
CategoryViewController *vc = (CategoryViewController *)segue.destinationViewController
vc.item = self.item; //pass the data model to the next view controller
}
Likewise for the segue from CategoryViewController to SubCategoryViewController. This ensures that every ViewController is editing the same object in memory. When you dismiss SubCategoryViewController (assuming somewhere in all of this CategoryViewController was already dismissed), viewWillAppear: will be called on EditViewController- there you can refresh any changes made in the modal views to the item property, just like you would when first displaying the view (it's actually the same method that's called):
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.titleField.text = self.item.title;
self.categoryLabel.text = self.item.category;
self.subcategoryLabel.text = self.item.subcategory;
//etc....
}
I have been trying to have a better understanding on how to pass data between view controllers and everything is make more sense but there is one thing I would like to understand better.
I came across this thread/tutorial here at StackOverFlow (Passing Data between View Controllers), I tried it and it worked as expected, but the one thing I don't know understand is why we can change a BOOL property located in the second view controller but NOT a Label. In other words if I add a second property and try to change the label in the prepareForSegue: method it doesn't work, why?
In section Passing Data Forward using Segue's I tried adding a second property for a label in the second view controller, right where isSomethingEnabled is, like this...
#property(nonatomic) BOOL *isSomethingEnabled;
#property (weak, nonatomic) IBOutlet UILabel *myLabel;
Than in prepareForSegue: method located in the first view controller I did this..
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"showDetailSegue"])
{
ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
controller.isSomethingEnabled = YES;// this works fine
controller.myLabel.text = #"Hello"; // here, no error but it doesn't update the label
}
}
Why you can modify a BOOL property located in a second view controller but not a label? Can someone explain this a little bit?
Thanks a lot
As Grzegorz Krukowski said the UIViewController isn't loaded at that moment. You can set #property values and access them in the viewDidLoad method and setup your Labels accordingly.
First of all, you should not do this. You should treat another view controller's views as private. The reason for this is the concept of encapsulation. A view controller's views are part of the view's appearance, not it's API. If at a future date you decide to refactor the VC so that it uses the text string in question as the title in a navigation controller, you should be free to do this. If another view controller reaches inside your view controller and manipulates it's view objects directly, you must keep those view objects unchanged forever, or you run the risk of breaking the way the other view controller interacts with it. That's bad, and 6 months from now you won't remember that 3 outside view controllers manipulate your view controller's views, and when you forget and change your views and decide to move a piece of text to a different UI object, your app will stop working correctly.
What you SHOULD do is to add a string property to your ViewControllerB, and set THAT in your prepareForSegue. Then in ViewControllerB's viewWillAppear:animated method, fetch the contents of that string property and install it in your label's text property.
Then the string property becomes part of your view controller's API contract (its public interface.) By adding a public string property to your interface, you guarantee that you will accept string values to that property and that you will do the right thing with them.
Now, as to why it doesn't work. A view controller's view hierarchy is not loaded until it's displayed. At the time of prepareForSegue, the view controller has been initialized, but it's views haven't been loaded yet. All its IBOutlets are nil. (this is another reason why you should not try to manipulate another view controller's views BTW)
Your prepareForSegue method looks like this: (wrong)
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"showDetailSegue"])
{
ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
controller.isSomethingEnabled = YES;// this works fine
controller.myLabel.text = #"Hello"; // here, no error but it doesn't update the label
}
}
Instead, your code should look like this:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"showDetailSegue"])
{
ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
controller.isSomethingEnabled = YES;// this works fine
controller.textToShow = #"Hello"; //This is the change
}
}
Then, in your ViewControllerB's viewWillAppear:animated method:
- (void) viewWillAppear: animated;
{
[super viewWillAppear: animated];
//install the text from our textToShow property into the label
self.myLabel.text = self.textToShow;
}
I am attempting to perform a Storyboard segue from a UIbutton that is a day on a calendar to a DayViewController with info on that day. The currentTitle of the sender UIbutton should be passed to a UILabel in the dayView of the DayViewController. It successfully segues to the new VC and assigns the button title to a property therein, however the dayView (and the base view) of my DayViewController is not getting initialized (their addresses in the debugger are both 0x0) and I get a blank page. How can I get these views to initialize in this segue? (I thought views linked to a VC were automatically initialized when segued to?)
Here's the prepareForSegue getting called:
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(UIButton *)sender
{
if ([segue.identifier isEqualToString:#"ShowDay"]) {
DayViewController *vc = segue.destinationViewController;
[vc setCurrentDayNumber:sender.currentTitle];
}
Here is setCurrentDayNumber in the new VC
-(void)setCurrentDayNumber:(NSString *)currentDayNumber
{
_currentDayNumber = currentDayNumber;
[self.dayView setNeedsDisplay];
}
Here is my view that is linked to DayViewController in the storyboard.
#property (nonatomic, weak) IBOutlet DayView *dayView;
Thanks in advance!
When you declare a property as weak, it will not "hold on" to the value unless someone else has a reference to it as well. When the new view controller is pushed, the old button goes away, leaving currentDayNumber as the only reference, so it automatically sets itself to nil. (This is what weak is supposed to do.)
If you want to keep a reference to it no matter what, use strong instead of weak and make sure to set _currentDayNumber to nil somewhere (like viewDidUnload) so that you don't retain it forever.