i have a question. How can i pass and object to the previous view when i press the back button in UINavigationBar?
If you have a link to your view in your current VC like this :
previousVC.objectToPass = objectToPass;
Or with a notification like this :
1 - in your back method :
[[NSNotificationCenter defaultCenter] postNotificationName:#"PassObject" withObject:objectToPass];
2 - In your previousVC :
- (void) didReceiveNotificationPassObject:(NSNotification*)notification
{
YourObjectClass *theObject = (YourObjectClass*)notification.object;
}
3 - In the init of your previousVC :
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didReceiveNotificationPassObject:) name:#"PassObject" object:nil];
I am explaining with an example
there are two screens first and second.
Currently i m on second view.
so make a property of that object which you want to pass
MyObject *obj and make it property
#property(nonatomic,retain) MyObject *obj; in second.h
then set this in viewWillDisappear or wherever you want
make object of Second class
Second *objSecond=......;
then objSecond.obj is that object which you need on this page.now you can do whatever you want.
Related
Hi im developing an app that has a parent view that then used containers to embed other views as seen below.
For now im only working with the left and centre container which are both table views. The main view or the Project screen view is my parent controller and i want it to pass data to and from the two child controller and i know for this the best option is to use delegates. However each example i have looked at that uses delegates, created and initialises a new view controller so for example lets say the left container embeds a view using the leftviewcontroller. Each example has this line of code.
LeftViewController *sampleProtocol = [[LeftViewController alloc]init];
LeftViewController.delegate = self;
Im thinking i dont need to create a new LeftViewController since it is embeded it is already in my list of child controllers. So my queston is how would i get the controller from the list of child controllers and set the parent as the delegate. I know i it is an array and i can use objectAtIndex but how do i know the order of items in the array will not change can i not call it but a tag or identifier? Thank you for any help sorry if the question is not that clear its my first time setting up delegates.
i know for this the best option is to use delegates.
In this case, I wouldn't be so sure. I think the best option would be to have a robust model and use KVO and notifications to signal updates between view controllers.
The direct answer to your question is not too bad.
for (UIViewController *viewController in self.childViewControllers) {
if ([viewController isKindOfClass:[LeftViewController class]]) {
LeftViewController *leftViewController = (id)viewController;
leftViewController.delegate = self;
break;
}
}
I think a minor improvement on this would be to use the segue. Make sure each of the containers have a named segue. In this example, the left view controller has a segue with the identifier "Load Child LeftViewController".
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"Load Child LeftViewController"]) {
LeftViewController *leftViewController = segue.destinationViewController;
leftViewController.delefate = self;
}
}
Its always better to use NSNotificationCenter for such complex mechanism.
*** put following code in LeftController.m ***
// *** Register a Notification to recieve a Data when something happens in Center controller ***
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receivedNotification:)
name:#"hasSomeData"
object:nil];
// *** create a method to receive Notification data ***
- (void)receivedNotification:(NSNotification *) notification {
if ([[notification name] isEqualToString:#"hasSomeData"])
{
// do your stuff here with data
NSLog(#"data %#",[notification object]);
}
}
*** when something happen in center controller post a notification to inform Left Controller ***
[[NSNotificationCenter defaultCenter] postNotificationName:#"hasSomeData" object:self];
//Secondvc.h
#protocol Sendmessage<NSObject>
#required
-(void)Object:(NSArray *)tosend;
#end
#interface Secondvc:UIViewcontroller{
id <Sendmessage> delegate;
}
#property(strong,nonatomic) id <Sendmessage> delegate;
#end
//Secondvc.m
#implementation Secondvc
#synthesize delegate;
-(void)viewDidLoad{
//Do Something here!
}
//Pass Some Value When a button event occured in Second vc
-(IBAction)Send_Data{
[self dismissViewControllerAnimated:Yes completion:nil];
[self.delegate Object:[NSArray Arraywithobjects:#"Hello",nil]];
}
#end
//FirstVc.h
#import "Secondvc.h"
#interface FirstVc<Sendmessage>
#end
//FirstVc.m
#implementation FirstVc
-(void)viewDidLoad{
Secondvc* Object=[[Secondvc alloc]init];
Object.delegate=self;
}
#pragma mark Secondvc Deklegate method implementation
-(void)Object:(NSArray *)tosend{
NSLog(#"Recieved data Form Second VC Is:\n%#",tosend);
}
#end
HTH!Enjoy Coding.
I have MainController which is the (UIViewController) main view in the app, and MenuController which is a UITableView.
In the MainController.h
- (void) menu1:(NSInteger ) row;
In the MainController.m
- (void) menu1:(NSInteger ) row{
switch(row){
case 0:
//DO SOMETHING.......
break;
default:
break;
}
}
I want "menu1" to make action when I click a cell in the MenuController.
I made this: (in MenuController.m)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.section == 0){
[menu1:indexPath.row]; //<=== What should I do to make it work ?
}
}
You can access the method using your UINavigationController.
So in your MainController.h, add this:
- (void) menu1:(NSInteger)row;
Now in your MenuController.m:
#import "MainController.h";
and change didSelectRowAtIndexPathto:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.section == 0){
MainController *mC = [self.navigationController.viewControllers objectAtIndex:0]; // Change the objectAtIndex number to the number of your MainController in the navigationController view hierarchy
[mC menu1:indexPath.row];
}
}
Your menu1 method is an instance method on the MainController, so you'll want to send the message to an instance of that class. Like so:
MainController *mainVC = [[MainController alloc] init];
[mainVC menu1:indexPath.row];
Or, if you were to define your menu1 method as a class method, you could just send the message to the MainController without initializing. Use plusses instead of minuses when defining your menu1 method, then call like this:
[MainController menu1:indexPath.row];
However, this probably isn't the best way for you to pass data back and forth between View Controllers. You'll likely want to at the very least set a property on your MenuController, and then send the message to that (of course, setting the property wherever it makes sense).
[self.mainController menu1:indexPath.row];
But another really good method for calling a method on another View Controller is the delegate pattern, but I'll leave that up to you to research on your own.
https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html
in your MenuController.h
You get back to your previous view like this.
[[self navigationController] popToViewController:yourController animated:YES];
or
[self.navigationController popToRootViewControllerAnimated:animated];
and use UINotificationCenter or Delegate pattern.
If you have no idea about them UINoitifcation is easier but Delegate pattern is better in this situation. I ll explain how to use UINotificationCenter.
Throw notification from your menuController like this.
[[NSNotificationCenter defaultCenter]
postNotificationName:#"yourNotificationName"
object:nil ];
This code is for catch notification write it down to your viewappear method.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(yourMethodAboutWhatYouWantToDoAfterCatchNotification:)
name:#"yourNotificationName"
object:nil ];
Do what you want to do after catch it
- (void) yourMethodAboutWhatYouWantToDoAfterCatchNotification:(NSNotification *) notification
{
// do your job.
}
And in viewDidDisappear remove it. Or you can catch it multiple times after time.
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"yourNOtificationName" object:nil];
There are two ways to achieve this.
Implement a protocol, whose implementation should be there in MainController.m
In that implementation, you should call the method which you have in MainController.m.
Set the table views delegate to MainController instead of setting it as self (MenuController)
This will help to implement the delegate methods directly in the MainController.m, from which the menu1 method can be called.
Provided code below for the first way:
Create a protocol file, which will help to make a callback, and create a method.
//
// utilProtocol.h
// tempProject
//
#protocol utilProtocol <NSObject>
#optional
-(void)captureCellSelectForRow:(int)rowNumber;
#end
Now Import this file inside in MenuController.
#import "utilProtocol.h" // Inside MenuController
And create a variable for the protocol.
#property(readwrite,assign)id<utilProtocol>utilDelegate;
In MainController.h, Import the utilProtocol.h and have its delegate into it.
Don't forget to add utilProtocol
#import <UIKit/UIKit.h>
#import "utilProtocol.h"
#interface ViewController : UIViewController <utilProtocol>
#end
Now Implement the method in MainController.m
-(void)captureCellSelectForRow:(int)rowNumber
{
[menu1:rowNumber];
}
Also set the delegate for it when the tableVIew's object is created in MainController.m
[tableViewObject setUtilDelegate:self];
Now when ever didSelectRowAtIndexPath is called in MenuController, just call the method using protocol.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.section == 0){
[utilDelegate captureCellSelectedForRow:indexPath.row];
}
}
Using this, the method inside MainController can be called.
I have a view controller, that is called from several other view controllers. This view controller contains a UITextField, which collects different information, depending on which view controller has called it.
The information has to be stored after it was collected. To be as independent as possible, the method to store the information should be located in the calling view controller.
Thus, I use the following code in the method to collect the information:
- (void) collectContent
{
NSString *info = [textField text];
[textField resignFirstResponder];
[[NSNotificationCenter defaultCenter] postNotificationName:#"NewValueA" object:info];
[[self navigationController] popToRootViewControllerAnimated:YES];
}
In the calling view controller, I have the following line in its init method:
- (id) init
{
...
if (self)
{
...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(storeNewValueA) name:"NewValueA" object:nil];
}
return self;
}
But now, I want to use this view controller from another view controller, to collect ValueB or ValueC. How do I reference the calling view controller to call a method for storing the collected value just there? I want to decide, that if the view controller was called from vcB, the entered value must have been valueB, an so on...
You can create a protocol for this commonly used view controller and implement this protocol by the calling view controllers :
#protocol TextFieldViewControllerDelegate : NSObject {
- (void)contentCollected;
}
In your text field (the common) view controller define a delegate property:
#property (nonatomic, strong) id<TextFieldViewControllerDelegate> delegate;
The calling view controller will set itself as the delegate and implement the needed method:
- (void) contentCollected:(NSString *)value
{
// Store the value where needed
}
In the common (textfield) view controller notify the delegate when needed :
- (void) collectContent
{
NSString *info = [textField text];
[textField resignFirstResponder];
[_delegate contentCollected:info];
[[self navigationController] popToRootViewControllerAnimated:YES];
_delegate = nil;
}
This way your common view controller does not need to know which view controller called it. Using the delegate the calling view controller will be notified.
Hm, if you want to persist this simple string even when the application was close, I would use NSUserDefaults:
Store the string:
[[NSUserDefaults standardUserDefaults] setObject:#"MyValue" ForKey:#"USER_DEFAULTS_VIEWCONTROLLER_VALUES"];
[[NSUserDefaults standardUserDefaults] synchronize];
Get the string:
[[NSUserDefaults standardUserDefaults] objectForKey:#"USER_DEFAULTS_VIEWCONTROLLER_VALUES"];
Hi I have Navigation based application in which there is a timer in one view. (View like : A, B & C)
I have timer in C when I start timer it's working fine but when I push back to any view and again come to View C it's not showing updated values.
here is my code .
App Delegate
-(int)updateTimer
{
timer_value--;
return timer_value;
}
View "C" Code
- (IBAction)buttonClick:(id)sender {
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(update) userInfo:nil repeats:YES];
}
-(void)update
{
MFAppDelegate *appDelegate = (MFAppDelegate *) [[UIApplication sharedApplication]delegate];
int Time=[appDelegate updateTimer];
int minute=Time/60;
int second=Time-(minute*60);
NSString *strValue=[NSString stringWithFormat:#"%d:%d",minute,second];
NSLog(#"%#",self.lbl_timer.text);
[self.lbl_timer setText:strValue];
}
update function is calling every time and NSlog of label text is showing correct.
Anything I am doing wrong ? please help me out.
In your class
-(void)update
{
MFAppDelegate *appDelegate = (MFAppDelegate *) [[UIApplication sharedApplication]delegate];
int Time=[appDelegate updateTimer];
int minute=Time/60;
int second=Time-(minute*60);
NSString *strValue=[NSString stringWithFormat:#"%d:%d",minute,second];
NSLog(#"%#",self.lbl_timer.text);
[[NSNotificationCenter defaultCenter] postNotificationName:#"DoUpdateLabel" object:strValue userInfo:nil];
}
- (void) updateLabel:(NSString *)string
{
yourLabel.text = string;
}
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateLabel:) name:#"DoUpdateLabel" object:nil];
}
I think the views you are referring to are actually view controllers. When you go back from a view controller to previous view controller in the navigation controller stack, depending on your code, it will be released if you do not retain it, which I assume happened in your case.
Because of that, everytime you push a new view controller, it is actually a newly allocated instance of the view controller, and it is not the same instance as the previously viewed view controller.
Try to make the view controller, e.g. A, which has the timer label as a strong property of the view controller that push A.
#property (nonatomic, strong) UIViewController *viewControllerA;
And push the property instead of allocating a new instance everytime you push A.
[self.navigationController pushViewController:self.viewControllerA animated:YES];
you need to tell the UILabel to redraw itself after its text property has changed. I don't know about the method Ahmed Z. describes, but in my projects something like
[[self lbl_timer] setNeedsDisplay];
does the trick.
cheers
I have an NSTimer that runs every 10 seconds and is kicked off from LandingController.m. It continues to run as you go to other views in the application. I want to be able to (when a certain condition is met within that timer) update a label field from another view GuardMenu.m The label I want to update is called CurrentZone.text and I want to update it from value "N" to value "Y."
Here's my timer on LandingController.m
self.messageTimer = [NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector:#selector(checkForMessages)
userInfo:nil
repeats:YES];
Which calls this on LandingController.m
- (void)checkForMessages
{
if ( //some condition here ){
//update CurrentZone.text label in GuardMenu view and set equal to "Y"
} else {
//no need to update label in GuardMenu as it's currently equal to "N"
}
}
First create a NSNotification in your init method of GuardMenu class
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification:) name:#"TextChangeNotification" object:nil];
}
return self;
}
Then implement the notification's selector, this is where you will be changing your CurrentZone label text.
- (void)receiveNotification:(NSNotification *) notification {
if ([[notification name] isEqualToString:#"TextChangeNotification"]) {
NSLog (#"Change you label here");
self.lblCurrentZone.text = #"Y";
}
}
Now in your LandingViewController.m -viewDidLoad Method
Start the timer.
self->msgTimer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(checkForMessages) userInfo:nil repeats:YES];
Now implement the #selector for the NSTimer, this is where you will be sending the notification back to the GuardMenu class
- (void)checkForMessages {
NSString *strText = #"test";
if ([strText isEqualToString:#"test"]){
[[NSNotificationCenter defaultCenter] postNotificationName:#"TextChangeNotification" object:nil];
}
else {
}
}
NOTE: the NotificationName should be the same.
Sample Project Code Dropbox Link
You can use the prepareForSegue method to pass objects between view controllers in the storyboard. For example, to pass a string from the GreyViewController to the OrangeViewController, in GreyViewController.m you have:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
OrangeViewController *orange = [segue destinationViewController];
orange.self.orangeString = #"text for orangeView";
}
Then in the viewDidLoad of the other view controller, in the OrangeViewController.m, you can set the text of the label by doing the following:
self.orangeLabel.text = self.orangeString;
Maybe you should describe which error you're getting. Is your checkForMessages method (not) firing? Use an NSLog() message to check. Otherwise, check if the UILabel you want to change is actually loaded into memory (i.e. is not nil). Please also let us know if the currentZone.text is part of the view hierarchy of the LandingController or of another view controller.
You can make use of notifications.
In GuardMenu class init register for custom notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveNotification:)
name:#"MessageChangeNotification"
object:nil];
In LandingController->checkForMessages method post the notification when condition is satisfied.
[[NSNotificationCenter defaultCenter] postNotificationName:#"MessageChangeNotification"
object:nil];
In GuardMenu class implement the notification callback selector
- (void) receiveNotification:(NSNotification *) notification
{
if ([[notification name] isEqualToString:#"MessageChangeNotification"]) {
NSLog (#"Successfully received the notification");
//Change the label text here..
}
}
Hope it helps!
Amar.
Make sure that the label you are trying to edit is declared as a property in the appropriate view and properly synthesised. Also make sure it is connected in Interface Builder.
In GuardMenu.h
#property (strong, nonatomic) IBOutlet UILabel *CurrentZone;
Also, in LandingController.h, import GuardMenu.h:
#import "GuardMenu.h"
You will now be able to access the label and its text property from LandingController.h using
-(void)checkForMessages
{
GuardMenu *guardMenu = [[GuardMenu alloc]init];
if (/* some condition here */) {
//update CurrentZone.text label in GuardMenu view and set equal to "Y"
guardMenu.CurrentZone.text = #"Y";
} else {
//no need to update label in GuardMenu as it's currently equal to "N"
}
}
For this you should use KVO(Key Value Observing). There are lot of ways to pass notifications, but KVO is potentially much simpler. I suspect that Notification is used more often because you can do a ‘chain of responsibility’ for an event as opposed to just assigning an observer. However, just having an observer in a controller that can watch a particular property in another object and get notified of changes is a powerful and simple way to solve a whole class of problems.
Firstly set a public property in LandingController like "lablelText" .
Then add the observer once, when you create the LandingController view. Once you've added the observer, the observeValueForKeyPath:ofObject:change:context: method will be executed in GuardMenu, so you can do the update to the GuardMenu UI from there. You shouldn't need to do anything every time GuardMenu is about to appear.
In GuardMenu, you should probably create LandingController just before you are going to push LandingController onto the controller stack, presumably in the event handler for some action the user took. Immediately after you create LandingController, add the observer in GuardMenu with the correct NSKeyValueObservingOption value.
If you just want to be notified whenever the public property "lablelText" in LandingController is changed, then try this:
LandingController
#interface LandingController : UIViewController {
}
#property (strong, nonatomic) NSString* lablelText;
- (void)checkForMessages;
#end
#implementation LandingController
#synthesize lablelText;
- (void)checkForMessages
{
if ( //some condition here ){
//update CurrentZone.text label in GuardMenu view and set equal to "Y"
self.lablelText = #"Y";
} else {
//no need to update label in GuardMenu as it's currently equal to "N"
self.lablelText = #"N";
}
}
#end
GuardMenu
#interface GuardMenu : UIViewController {
}
#property (strong, nonatomic) IBOutlet UILabel* nameLabel;
- (IBAction) methodToHandleEvent:(id)sender;
#end
#implementation GuardMenu
- (IBAction) methodToHandleEvent:(id)sender{
LandingController* tempVC = [[LandingController alloc]init];
[tempVC addObserver:self forKeyPath:#"lablelText" options:NSKeyValueObservingOptionNew context:NULL];
[self.navigationController pushViewController:tempVC animated:YES];
}
- (void) observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context {
// Here you will be notified everytime lablelText changes
if ([keyPath isEqual:#"lablelText"]) {
NSString* changedName = [change objectForKey:NSKeyValueChangeNewKey];
// do something with the changedName - call a method or update the UI here
self.nameLabel.text = changedName;
}
}
#end
As an alternative for this you can use NSNotificationCeneter to pass notifications from one class to another for some event.
For this you can check my detailed answer How to pass Notifications from one class to another for some event.
Hope it helps you.
Create a notification in the init of GuardMenu class
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveNotification:)
name:#"UpdateCurrentZoneNotification"
object:nil];
In the LandingController,
(void)checkForMessages
{
if ( //some condition here ){
[[NSNotificationCenter defaultCenter] postNotificationName:#"UpdateCurrentZoneNotification"
object:nil];
//update CurrentZone.text label in GuardMenu view and set equal to "Y"
} else {
//no need to update label in GuardMenu as it's currently equal to "N"
}
}