Understanding properties in a UINavigationController with viewWillAppear and viewWillDisappear - ios

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.

Related

Change all the app color scheme

So, I have a HomeViewController (picture 1) with two buttons, one white and one blue. On the right bottom corner you can see a button which modally presents a SettingsViewController (picture 2), on this view controller there are 4 buttons so the user can choose which color scheme do they prefer. Imagine the user press the first one (red) then, when dismissing the view controller the color scheme of HomeViewController should look like picture 3.
Any ideas on how to do this on a efficiently/simple way?.
There are two good ways you could do this: 1) Delegation, and 2) viewWillAppear:.
For delegation, you'll need to define a protocol. Your HomeViewController will be a delegate for this protocol, and your SettingsViewController will call it.
//SettingsViewController.h
#protocol SettingsDelegate <NSObject>
#required
-(void)colorChanged:(UIColor *)color;
#end
#interface SettingsViewController : UIViewController
#property (nonatomic, weak) id<SettingsDelegate> delegate;
#end
Somewhere when the settings view controller is set up, make sure to set self.delegate equal to a reference to HomeViewController. This is a must.
Then, when your user changes the color, call:
[self.delegate colorChanged:whateverColor];
Your delegate must obviously observe this method, and change the color appropriately:
-(void)colorChanged:(UIColor *)color {
[myButton setBackgroundColor:color];
}
For viewWillAppear:, just save the color somewhere and set the color of your button in your view controller's method for this. viewWillAppear: will get called when your settings view is about to disappear and show the home view controller:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[myButton setBackgroundColor:mySavedColor];
}
In HomeViewController
On SettigButton Click
{
Pass the HomeViewController delegate object SettingsViewController
Present your color Picker SettingsViewController
}
In SettingsViewController
Define protocol name SettingsViewControllerDelegate
{
-(void)selectedColor:(UIColor*)color;
}
return the selected color On dismissViewController
if(delegate)
{
[delegate selectedColor:color];
}
Again In HomeViewController
-(void)selectedColor:(UIColor*)color
{
view.backgroundColor=color;
}

How to change a UILabel one one View Controller from another View Controller?

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!

accessing navigation controller 'target''s properties in segue

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.

Reloading data for collectionview

I have 2 ViewControllers
ViewControllerWithCollectionView (FIRST) and ModalViewControllerToEditCellContent (SECOND)
I segue from FIRST to SECOND modally. Edit cell. Return.
After dismissing SECOND controller, edited cell doesn't get updated until i call
[collection reloadData]; somewhere manually.
Tried to put it in viewWillAppear:animated:, when i check log, it's not called (after dismissing SECOND)
I've tried various solutions, but i can't brake thru (maybe I'm just too exhausted). I sense that I'm missing something basic.
EDIT dismiss button
- (IBAction)modalViewControllerDismiss
{
self.sticker.text = self.text.text; //using textFields text
self.sticker.title = self.titleText.text;// title
//tried this also
CBSStickerViewController *pvc = (CBSStickerViewController *)self.stickerViewController;
//tried passing reference of **FIRST** controller
[pvc.cv reloadData];//called reloadData
//nothing
[self dismissViewControllerAnimated:YES completion:^{}];
}
It's tough to tell from the posted code what's wrong with the pointer to the first view controller that you passed to the second. You should also be able to refer in the second view controller to self.presentingViewController. Either way, the prettier design is to find a way for the first view controller to learn that a change has been made and update it's own views.
There are a couple approaches, but I'll suggest the delegate pattern here. The second view controller can be setup to have the first view controller do work for it, namely reload a table view. Here's how it looks in almost-code:
// SecondVc.h
#protocol SecondVcDelegate;
#interface SecondVC : UIViewController
#property(weak, nonatomic) id<SecondVcDelegate>delegate; // this will be an instance of the first vc
// other properties
#end
#protocol SecondVcDelegate <NSObject>
- (void)secondVcDidChangeTheSticker:(SecondVc *)vc;
#end
Now the second vc uses this to ask the first vc to do work for it, but the second vc remains pretty dumb about the details of the first vc's implementation. We don't refer to the first vc's UITableView here, or any of it's views, and we don't tell any tables to reload.
// SecondVc.m
- (IBAction)modalViewControllerDismiss {
self.sticker.text = self.text.text; //using textFields text
self.sticker.title = self.titleText.text;// title
[self.delegate secondVcDidChangeTheSticker:self];
[self dismissViewControllerAnimated:YES completion:^{}];
}
All that must be done now is for the first vc to do what it must to be a delegate:
// FirstVc.h
#import "SecondVc.h"
#interface FirstVc :UIViewController <SecondVcDelegate> // declare itself a delegate
// etc.
// FirstVc.m
// wherever you decide to present the second vc
- (void)presentSecondVc {
SecondVc *secondVc = // however you do this now, maybe get it from storyboard?
vc.delegate = self; // that's the back pointer you were trying to achieve
[self presentViewController:secondVc animated:YES completion:nil];
}
Finally, the punch line. Implement the delegate method. Here you do the work that second vc wants by reloading the table view
- (void) secondVcDidChangeTheSticker:(SecondVc *)vc {
[self.tableView reloadData]; // i think you might call this "cv", which isn't a terrific name if it's a table view
}

Can i know in viewWillAppear that it was called after navigationController pop (back button)?

Say I have UIViewController A and B.
User navigates from A to B with a push segue.
Than user presses back button and comes to A.
Now viewWillAppear of A is called. Can I know in the code here that I came from back button (navigationController popTo...) and not by another way? And without writing special code in the B view controller.
hm, maybe you can use self.isMovingToParentViewController in viewWillAppear, see docs, if it is NO then it means the current view controller is already on the navigation stack.
I like to do the following in view controller A:
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (_popping) {
_popping = false;
NSLog(#"BECAUSE OF POPPING");
} else {
NSLog(#"APPEARING ANOTHER WAY");
}
//keep stack size updated
_stackSize = self.navigationController.viewControllers.count;
....
}
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
_popping = self.navigationController.viewControllers.count > _stackSize;
....
}
What you are doing is keeping track of whether your view controller (A) is disappearing because a view controller (B) is being pushed or for another reason. Then (if you did not modify the child view controller order) it should accurately tell you if (A) is appearing because of a pop on the navigation controller.
Add a BOOL property to UIViewController A:
#property (nonatomic) BOOL alreadyAppeared;
Then in your viewWillAppear: method, add:
if (!self.alreadyAppeared) {
self.alreadyAppeared = YES;
// Do here the stuff you wanted to do on first appear
}

Resources