I have two view controllers, FirstViewController and FourthViewController. FirstViewController is my initial view controller. I present FourthViewController with
UIViewController *fourthController = [self.storyboard instantiateViewControllerWithID:#"Fourth"];
[self presentViewController:fourthController animated:YES completion:nil];
Then, in FourthViewController's .m I'd like to change the text of a UILabel in FirstViewController. So I use
UIViewController *firstController = [self.storyboard instantiateViewControllerWithID:#"First"];
firstController.mainLab.text = [NSMutableString stringWithFormat:#"New Text"];
However, after I use
[self dismissViewControllerAnimated:YES completion:nil];
I find that my mainLab's text has not updated. Does anyone know why?
When you are calling this line from FourthViewController.m you are actually creating a new instance of FirstViewController, rather than using the already created one.
UIViewController *firstController = [self.storyboard
instantiateViewControllerWithID:#"First"];
You can tackle this in two ways.
1) Using notification
post a notification from FourthViewController when label text need to be changed.
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateLabel"
object:self];
In your FirstViewController viewDidLoad methodcreate an observer that waits for this notification to get fired.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateLabelCalled:)
name:#"updateLabel"
object:nil];
Implement updateLabelCalled: and update label.
- (void) updateLabelCalled:(NSNotification *) notification
{
if ([[notification name] isEqualToString:#"updateLabel"]){
//write code to update label
}
}
2) Implementing delegate
It is already explained here in stackoverflow. The basic idea is you create a FourthViewController delegate, and create a delegate method to updateLabel. FirstViewController should implement this method.
If you want to update the label on first screen and nothing else then go for notifications. It's better rather you write the delegate. Because you want to update only label text thats it.
Related
I have a UINavigationGroup with a root view controller called MainViewController. Inside this MainViewController I'm calling another UINavigationController as a modal as following:
- (IBAction)didTapButton:(id)sender {
UINavigationController * someViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"someNavigationController"];
[self.navigationController presentViewController:someViewController animated:YES completion:nil];
}
Inside this someNavigationController, the user is going through some process so the nav controller is being pushed with some UIViewControllers. After the user completes the process, in the last UIViewController called finalStepViewController, I'm closing the modal as follow:
[self dismissViewControllerAnimated:YES completion:nil];
The modal is indeed dismissed and the user is back to the initial MainViewController.
However, I'd like to push another UIViewController to MainViewController's NavigationController (for example: a view saying that the user completed the process successfully). Preferably before the modal is dismissed.
I have tried the following things:
1. Using presentingViewController
UIViewController * successViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"successViewController"];
[self.presentingViewController.navigationController successViewController animated:YES];
Result: no error, but nothing happening either.
2. Delegate/protocol
Imported finalStepViewController.h inside MainViewController.h and appended <finalStepViewControllerDelegate>
Inside MainViewController.m added a method called parentMethodThatChildCanCall to be called from finalStepViewController.m
Added the following to finalStepViewController.h:
#protocol finalStepViewControllerDelegate <NSObject>
-(void)parentMethodThatChildCanCall;
#end
#property (assign) id <finalStepViewControllerDelegate> delegate;
and #synthesize delegate; in the model
Set the delegate property to someViewController in the above mentioned didTapButton IBAction to self. This showed a notice error saying: Assigning to id<UINavigationControllerDelegate>' from incompatible type UIViewController *const __strong'
Finally called [self.delegate parentMethodThatChildCanCall] just before closing the modal.
Result: except for the notice error, no fail but nothing happens as parentMethodThatChildCanCall is not called.
Any idea what I'm doing wrong/what I should be doing? It's my second week doing Objective-C, and most of the time I don't know what I'm doing so any help/code would be appreciated!
Thanks.
You can achieve this a lot easier using NSNotificationCenter.
In your MainViewController's -viewDidLoad add the following code
typeof(self) __weak wself = self;
[[NSNotificationCenter defaultCenter] addObserverForName:#"successfullActionName"
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
SuccessViewController *viewController; // instantiate it properly
[wself.navigationController pushViewController:viewController animated:NO];
}];
Remove your controller from NSNotificationCenter upon dealloc
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
In FinalStepViewController on action that dismisses the view controller before dismiss post the notification
- (IBAction)buttonTapped:(id)sender {
[[NSNotificationCenter defaultCenter] postNotificationName:#"successfullActionName" object:nil];
[self dismissViewControllerAnimated:YES completion:nil];
}
This example is very crude and is not ideal, you should use constants for your notification names and in some cases store the observers returned by NSNotificationCenter to remove specific ones.
-- EDIT
I'd like to also mention that the method addObserverForName:object:queue:usingBlock: actually returns the observer as an id type object. You need to store a reference to it as an iVar in your class and remove it from the NSNotificationCenter when dealloc method is called otherwise that observer will never get deallocated.
I have two classes I would like them to speak with each other. Class A contains a tableView and when users hitting a table row, I fire my didSelectRowAtIndexPath method. In this method I need to inform class B about this through a delegate. I know how delegates work but having a hard time to figure how to set the delegate of A without using the prepareForSegue method.
Normally I would do this when I set up my delegate
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"goToManipulator"]) {
ManipulatorViewController *secondVC = (ManipulatorViewController *) segue.destinationViewController;
[secondVC setDelegate:self];
}
}
But how can I set the delegate without the use of prepareForSegue?
Thanks in advance
EDIT :
This is how the structure of my storyboard looks like. The "receiver" viewcontroller is the one that will get the data and display in the "current name" label depending on what's been selected in the tableview from the "sender" viewcontroller, closest to the right.
http://oi62.tinypic.com/2li99w1.jpg
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ManipulatorViewController *secondVC = [[ManipulatorViewController alloc] init...];
[secondVC setDelegate:self];
//if you use push transition in UINavigationController
[self.navigationController pushViewController:secondVC animated:YES];
//if you use modal transition
[self presentViewController:secondVC animated:YES completion:nil]
}
init... means that initialization depends on your program architecture.
EDIT
If you want to get secondVC from storyboard, use
UIStoryboard* storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
ManipulatorViewController* secondVC = [storyboard instantiateViewControllerWithIdentifier:#"secondVC"];
And don't forget to add identifier for your viewController in storyboard.
I understand your use case like this:
In the Receiver, you open the Sender. There you select a value, and after selecting the value you want to tell the Receiver about the new value.
You can create a protocol on Sender, that Receiver implements. Then, in the function that catches the chosen value in Sender, you call the protocol method (e.g. didSelectNewName() or something).
Of course, you need a handle to the Receiver, which you typically get via the delegate. But wether you use a segue or other method to transition from Receiver to Sender, you will all the same have the opportunity to set the delegate of the Sender.
If this is not what you are looking for, please explain exactly how you initialize the Sender, and why segue is not desirable.
Is View Controller B already instantiated when A's cells are tapped? If it is and you're not using prepareForSegue to get the other View Controller's identity, it might be better to use NSNotification Center. In View Controller A's didSelectRowAtIndex method, you can put
[[NSNotificationCenter defaultCenter] postNotificationName:#"yourNotificationName" object:nil userInfo:dictionaryWithYourData];
and it will put up a notification to your whole app that the row was selected. If you initialize a dictionary with any info you want before hand, it can be passed through userInfo. Then, in View Controller B's viewDidLoad, add
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(yourMethod:) name:#"yourNotificationName" object:nil];
to make it listen for the notification. The selector you set will accept the NSNotification as a parameter, so you can get the dictionary as follows:
- (void)yourMethod:(NSNotification *)notification
{
NSDictionary *yourData = [notification userInfo];
}
Here's what I do.
In the .m file:
#implementation ViewController{
SecondViewController *svc;
}
And then below you need to an action like this:
- (IBAction)goToView2:(id)sender {
if (!svc) {
svc = [[self storyboard] instantiateViewControllerWithIdentifier:#"View2"];
[svc setDelegate:self];
}
[[self navigationController] pushViewController:svc animated:YES];
}
Just make sure to set the correct identifier in the StoryBoard to the ViewController where protocol is declared.
Scenario:
I have a calendar control as a UIview and I want that when I click on date button of the view I want to navigate to some other UIview controller but when I use:
HomeView *scrhome=[[HomeView alloc]init];
[self presentViewController:scrhome animated:YES completion:nil];
It gives an error:
HomeView is not in memory stack
Thanks in advance.
As schrome is UIView not a view controller,it can not be passed an argument as required type is uiviewcontroller. What you can do is use this uiview inside a controller and that viewcontroller's object can be passed as an argument.
you can use observer Pattern
see you can create an observer using NSNotificationCenter:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notify) name:#"Event" object:nil]; //1
put this in your view controller.
and a selector in view controller like:
-(void)notify
{
//2
HomeView *scrhome=[[HomeView alloc]init];
[self presentViewController:scrhome animated:YES completion:nil];
}
and in your view write under a condition where you want to throw an event:
[[NSNotificationCenter defaultCenter] postNotificationName:#"Event" object:nil]; //3
and you are ready to go.
you may use object:yourobject in place of nil # //1
and
id=sender;
if you want to get some attributes to work on.
it works fine for me.
presentViewController: expects to have view controller as a parameter, you are passing a view here. Have a controller for your view and pass it in presentViewController: animated:
I currently have written a slider (similar to the Facebook app) for my app. At the top of the slider is a Search Box, and the methods controlling the search functionality are also within the app delegate.
Similarly, I have the methods that control the slider's table view in a separate class (SliderMenuViewController).
I am looking for a way for the slider (either the search box or the tableview cells) to be able to tell the RootViewController (or whichever viewController is currently visible) to push a new ViewController (inside a UINavigationController).
This is what I tried to do (this code is in the AppDelegate):
-(void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
NSLog(#"Searching for: \"%#\"",searchBar.text);
[searchBar resignFirstResponder];
IndexAndSearch *vc = [[IndexAndSearch alloc]initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:vc animated:YES];
}
But it doesn't work (it writes to the log, but doesn't push the new ViewController). I also tried sending a message to the RootViewController like this:
-(void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
NSLog(#"Searching for: \"%#\"",searchBar.text);
[searchBar resignFirstResponder];
RootViewController *vc = [[RootViewController alloc]initWithNibName:nil bundle:nil];
[vc performSearchFromDelegateSlider];
}
With the following code in the RootViewController's implementation file:
-(void)performSearchFromDelegateSlider{
NSLog(#"Searching");
IndexAndSearch *vc = [[IndexAndSearch alloc]initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:vc animated:YES];
}
But once again it only wrote to the log, not pushing a viewController.
I've looked far and wide on Google and SO, but haven't been able to find anything useful. This question is similar to mine, but there haven't been any suitable answers. I know the answer probably involves delegation, but I can't wrap my head around a solution for this.
Important note: This slider is available from nearly every ViewController in the app, meaning that whatever solution I implement has to be able to push a new ViewController for every class. That is why I can't use a solution like this one (I would have to enter the NavigationDelegate code into each ViewController, which won't work in an app as large as mine).
Thanks in advance for your help guys.
I'm not convinced it is the best solution, but I was able to get this working using notifications. For anyone that is interested, here is what I did:
Step 1
The first step is to register for the notification in the RootViewController's viewDidLoad method:
-(void)viewDidLoad{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didReceiveNavSliderSearchNotification:) name:#"navSliderSearchNotification" object:nil];
}
Step 2
I then need to fire the notification when the search is performed from the slider. The searchBar code is located in my AppDelegate and looks like so:
-(void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
//Write to the log
NSLog(#"Searching for: \"%#\"",searchBar.text);
//Dismiss the keyboard
[searchBar resignFirstResponder];
//Post the notification (to be used by the RootViewController
[[NSNotificationCenter defaultCenter] postNotificationName:#"navSliderSearchNotification" object:self];
}
Step 3
I then need to write the didReceiveNavSliderSearchNotification class (which will be called in the RootViewController when the navSliderSearchNotification notification is posted and received):
-(void)didReceiveNavSliderSearchNotification:(NSNotification *) notification {
if ([[notification name] isEqualToString:#"navSliderSearchNotification"])
NSLog (#"Successfully received the search notification!");
//Push the next ViewController when the *navSliderSearchNotification* is received
IndexAndSearch *vc = [[IndexAndSearch alloc]initWithNibName:#"IndexAndSearch" bundle:nil];
[self.navigationController pushViewController:vc animated:YES];
}
And that is how I managed to push new viewControllers from a separate class (in this case the App Delegate, but I also have it working from other classes as well).
Final step (optional)
My slider is accessible from everywhere in the app, so I did not unregister from my notifications in the RootViewController (meaning these methods will continue to fire even if the user has been pushed to another viewController). If you do not want this functionality, make sure to unregister from these notifications using the following code (it would go in the RootViewController):
-(void)viewWillDisappear:(BOOL)animated{
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"navSliderSearchNotification" object:nil];
}
Like I said, I am not entirely convinced this is the best method. If you have another solution that you prefer, please feel free to post it.
Have you tried doing this:
Create a UIViewController variable in your AppDelegate that always references the current UIViewController on the screen (You might need to set the current controller every time you create a view controller)
Once that's all done.
In your AppDelegate use
[self.currentViewController.navigationController pushViewController:anotherNewViewController animated:YES];
I need to make a button to do something on another page. I know how to code a button to make wanted things on the same page(same viewcontroller) also make that button to open another page(another viewcontroller) but how can I make it both at the same time ?
Here is an example for a simple calculator.
Open the program
Enter two numbers
Click the button
SecondPage comes up and shows the result from the first page
Is it something about delegates? Please explain.
I receive some answers and thanks for that lets make it simple and make that button to write something on a label which is in the second page can you write that code too its simple a button at the first page will write something to a label on second page. First view controllers name is ru1 second viewcontrollers name is ru2
Also can you explain me where to write what I am noob and I have hard time understanding what you say ?
You can create a selector that does it...
[myButton addTarget:self action:#selector(mySelector) forControlEvents:UIControlEventTouchUpInside];
your selector
- (void)mySelector {
myNewViewController *secondController = [[myNewViewController alloc] init];
[secondController setMyProperty:#"SOME_VAR"];
[self.navigationController pushViewController:secondController animated:YES];
}
in the secondController.h
#property (nonatomic, strong) NSString *myProperty;
in the secondController.m
#synthesize myProperty;
in your second (ru2) controller in the -(void)viewDidLoad:
UILabel *lblSecond = [[UILabel alloc] initWithFrame:CGRect(10, 10, 20, 100)];
[lblSecond setText:myProperty];
[self.view addSubview:lblSecond];
In header file of second view controller add variables, that you want to pass to it:
#interface ATSecondViewController : UIViewController <UINavigationControllerDelegate>{
IBOutlet UITableView *_tableView;
NSFileManager *fileManager;
NSString *documentsDir;
IBOutlet UILabel *top_bar;
}
In method where you are pushing that new controller they will be accessible with something like this:
ATSecondViewController *detailViewController = [[ATSecondViewController alloc] initWithNibName:#"ATBuyTripViewController" bundle:nil];
detailViewController.documentsDir = #"SOME DIR";
If you synthesize that variables in SecondViewController they can be accessed through your code.
If the 'other' view controller exists at the time of button tap (e.g., it is somewhere in the navigation stack) you can post a notification. The 'other' view controller must do this on init:
[[NSNotificationCenter defaultCenter] addObserver:self name:YourCustomNotificationName object:nil];
And, on dealloc:
[[NSNotificationCenter defaultCenter] removeObserver:self];
On button tap (current view controller):
[[NSNotificationCenter defaultCenter] postNotificationName: YourCustomNotificationName object:self userInfo:someCustomDictionary];
'YourCustomNotificationName' is an NSString you must define somewhere visible to both view controllers.
Optionally, if you are creating the button programatically, you can use the other view controller (instead of self) when calling 'addTarget:action:forControlEvents'.
If you are using IB, I don't know... There's the whole 'file's owner' thing...