I'm new to Obj-c, and I've been trying to figure this out and I've found a few posts, but I couldn't get the solutions to work. I don't know what I am doing wrong.
So here's the setup. I have a viewcontroller with a button in it. That button, when touched, is supposed to update the label in another viewcontroller.
This is what I've done so far.
VC1:
I've set the property in header of VC1:
MainScene *msc;
I have this method in implementation file being called upon clicking of the button:
-(void) button {
[msc updateLabel];
}
VC2:
Here is the method updateLabel.
-(void)updateLabel {
label.string = [NSString stringWithFormat:#"%.2LF", points];
}
I also have the method in the header:
-(void)updateLabel;
Not sure what I'm doing wrong here.
That's not how it works.
From VC1 you first create an instance of the ViewController VC2.
And before you present it set the string value on VC2 property.
you have just define a property in the VC1 named msc, but you have not assign it to any instance of VC2 so when you try call the method updateLabel nothing happened.
in your case I assume VC2 is the parent viewcontroller and VC1 is present by VC2. so you need setup a protocol and a delegate in VC1 to udate the label of vc2.
add these in VC1 head file:
#protocol VC1Delegate <NSObject>
-(void)updateLabel;
#end
#property (weak, nonatomic) id<VC1Delegate> delegate;
and set the VC1's delegate = VC2 while presenting the VC1
if VC1 is parent viewcontroller things will be easier, just use [msc updateLabel]; after you asign the instance value to msc.
Related
I have 3 viewControllers.(Let VC1,VC2,VC3)I have overloaded VC2 on VC1 from a button click event of VC1.Now I want to overload the VC3 on VC1 from the button click event of VC2 and at the same time dismiss the VC2.
If I do these task separately it works fine..but when I do the same task concurrently It is not working..
Please Help
So what I would recommend in this case is setting up a delegate protocol inside of VC2 that VC1 implements.
Something like:
#protocol SomeDelegate <NSObject>
- (void)requestsDismissalOfViewController;
#end
#interface ViewController : UIViewController
#property (weak, nonatomic) id<SomeDelegate> delegate;
#end
So in VC2 you would call [self.delegate requestsDismissalOfViewController]; and then in VC1 you would implement the requestsDismissalOfViewController method where you could dismiss VC2 and present VC3. Your best bet would be to present in the completion block of the dismissal.
I am coding an app in Xcode and would like to know if it is possible to have an action called from a button in separate view controller.
For example, if there is a button on ViewController1 and the user presses the button, I would like an image to appear on ViewController2 and stay there even if the user navigates back to ViewController1 and then back to ViewController2 again. Can anyone help me please? Thank you.
UPDATED: I found a the answer to the question at the link below...
if my "button" is selected on view controller 1, then image should then display and stay on view controller 2
Yes, it is possible. If you declare the instance of ViewController2 within ViewController 1, then you can have full control over what's going on in ViewController2 at all times from ViewController1. For instance, in the .m, have
#implementation ViewController1
#synthesize viewController2
- (void) viewDidLoad
{
viewController2 = [[UIViewControllerClass alloc] init];
}
and in the .h, have
#interface ViewController1
#property (nonatomic, strong) UIViewControllerClass *viewController2;
This will make sure that ARC does not scrap your second ViewController before you're done with it, which in this case is when you terminate the program.
Then, you can simply call methods that belong to ViewController2 from ViewController1 like you would with any other class.
[viewController2 doSomethingWithThisData: stringData];
A few considerations not yet mentioned...
Depending on how you transition between viewControllers & where the shared data (images etc) are set, one option is for VC2 to have a public #property to set the image.
VC2.h
#IBOutlet (nonatomic, weak) UIImageView *imageFromVC1;
VC1 should import the VC2 header so it can create & hold an instance of VC2 and set the image.
You'd need to keep track of VC2's state in VC1, as a #property of VC1.
VC1.m
#import VC2.h
#interface VC1()
#property (nonatomic, strong) VC2 *viewController2;
#end
#implementation
...
Note that this does not need to go in the .h for VC1 - unless other classes need to know about it, this should be a private property in a class extension of .m & not exposed in the header. You don't need to use #synthesize anymore either - that's automatic if you're using XCode 4.4 or later.
The issue at this point is that VC2 shouldn't exist until it's needed (it's a waste of limited memory). You could create it right away in -viewDidLoad in VC1, but then it's just sitting there sucking up resources.
Better option is 'lazy instantiation' where you only create a VC2 instance when you transition to showing VC2 for the first time. Also set the #property in VC1 at this point.
-(void)someMethodToShowVC2OrStoryboardPrepareForSegue {
if (self.viewController2 == nil)
{
self.viewController2 = [[VC2 alloc] init];
}
//handle transition & set up VC2.
}
You could also ask VC2 to be sure it has the public method that allows you can set the image. If it doesn't, it will crash. By writing & importing the VC2 header obviously you should know that the method exists - this is just a defensive practice and alternative to sending the message to VC2 directly:
if ([self.viewController2 respondsToSelector:#selector(setImageFromVC1)])
{
UIImage *myImage = [UIImage imageNamed:#"myImageName"];
[self.viewController2 setImageFromVC1:myImage];
}
Here "setImageFromVC1" is the name of the setter method that is automatically generated when you use an #property. Change this if you use another name.
Alternative is to use dot notation: self.viewController2.imageFromVC1 = myImage;
Note that this "Key-Value Observing" approach to setting properties on other objects isn't the only, or necessarily the best, approach to doing this - but it works.
I'm currently trying to have a better understanding on how the mechanisms of passing data between controllers work and I'm a little confused especially when passing data back from a second view controller to the main view controller.
This is what I have that works but don't fully understand. I have two view controllers, in the first one I have a button that when clicked it basically goes to the second view controller and a label which shows a message sent from the second view controller. In the second view controller I have a button and a textField, the button basically sends whatever is in the textfield to the label in main view controller.
Here is the code...
// FirstVC.h
#import <UIKit/UIKit.h>
#import "SecondVC.h"
#interface FirstVC : UIViewController <passNames>
#property (nonatomic, strong) NSString* firstNameString;
#property (weak, nonatomic) IBOutlet UILabel *firstNameLabel;
#end
//FirstVC.m
#import "FirstVC.h"
#implementation FirstVC
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier]isEqualToString:#"secondController"])
{
UINavigationController *navController = segue.destinationViewController;
SecondVC *vc2 = (SecondVC*)navController.topViewController;
[vc2 setDelegate:self];
}
}
-(void)viewWillAppear:(BOOL)animated
{
self.firstNameLabel.text = _firstNameString;
}
-(void)setFirstName:(NSString *)firstName
{
_firstNameString = firstName;
}
#end
//SecondVC.h
#import <UIKit/UIKit.h>
#protocol passNames <NSObject>
-(void)setFirstName:(NSString*)firstName;
#end
#interface SecondVC : UIViewController
#property (retain)id <passNames> delegate;
- (IBAction)send:(UIBarButtonItem *)sender;
#property (nonatomic, strong) NSString *firstNameString;
#property (weak, nonatomic) IBOutlet UITextField *firstNameText;
#end
//SecondVC.m
#import "SecondVC.h"
#import "FirstVC.h"
#interface SecondVC ()
#end
#implementation SecondVC
- (IBAction)send:(UIBarButtonItem *)sender
{
_firstNameString = _firstNameText.text;
[[self delegate]setFirstName:_firstNameString];
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
Can someone explain how the prepareForSegue method works in the above code? The reason for this question is because I added an NSLog and it looks like this method is only called in the transition from main view controller to the second controller. Why is this method needed if it is not called when transitioning from second view controller to main view controller which in my case is what I'm doing? It makes sense to use it when passing data from main view controller to a second controller not on the case shown above.
Can some explain the whole mechanism when passing data back to the main view controller?
FYI, I do understand about protocols and delegation.
Thanks a lot.
In your case, you are setting your delegate method of the second view controller to self in mainViewController in you prepareForSegue. This means that apart from navigating to the SecondViewController, you are implementing the callback mechanism in your main view controller, so that your delegate method gets called when the value is passed from the second view controller and this delegate method collects the value as a parameter to handle it in the main View Controller. You might have set the delegate of VC2 as self inn your prepareForSegue because you are creating the instance of VC2 in this method to navigate to the second controller.
Your goal is to hand back the data, like this:
[[self delegate] setFirstName:_firstNameString];
But you can't do that unless you know who to send setFirstName: to, and the compiler won't let you do it unless you guarantee that whoever you are sending setFirstName: to can accept that message.
That is what prepareForSegue prepares. FirstVC has declared that it adopts the passNames protocol, which means that it implements setFirstName:. And now you are saying:
[vc2 setDelegate:self];
...where self is the FirstVC instance. This solves both problems at once. The SecondVC instance (vc2) now has a delegate (the FirstVC instance), it is the right object to send the info back to, and because its delegate is declared as adopting passNames, we know that SecondVC can actually send setFirstName: to that delegate.
Now to the heart of your actual question: The reason for doing this in prepareForSegue is merely that this is the only moment when the FirstVC instance and the SecondVC instance "meet" one another! There is no other moment when the FirstVC instance has a reference to the SecondVC instance so as to be able to call setDelegate on it in the first place. If you weren't using segues and storyboards, the FirstVC would simply create the SecondVC instance directly - and would set itself as its delegate, just as you do:
SecondVC *vc2 = [SecondVC new];
UINavigationController *nav = [
[UINavigationController alloc] initWithRootViewController: vc2];
[vc2 setDelegate:self];
[self presentViewController: nav animated: YES completion: nil];
This is one reason I don't like storyboards: they muddy the story. It's all so simple and obvious when you don't use them and just do everything directly like this.
I'm trying to figure out... if I am in viewcontroller1 (VC1) and I have an NSTimer running so that after 100 seconds an image will get set in viewcontroller1 (VC2) and then I display VC2 (modal) after 10 seconds and wait another 90 seconds how can I, from the .m file of VC1 set the image on the VC (VC2) that is currently presented (and has been for about 90 seconds)?
I've found a few methods online (after an hour of searching) on how to set the image by passing UIImage with #property nonatmoic,retain ... but even then, all of this sample code just shows me how to set the data, I still would have to run a separate NSTimer or some other function in VC2 that is constantly checking to see if this variable has been updated, and then in VC2's .m I set the UIImageView's UIImage to the image (#property strong) that was passed.
In any event, in every case I could find, this is ALWAYS done during the "Prepare for segue" section...
Is there not a way from VC1's .m to change the image of a UIImageView in VC2? Or is the only way to change the UIImageView to actually do it from VC2's .m file following a prepare for segue from VC1?
In short, I want to change VC2's UIImageView's image from VC1... not change VC2's UIImage from VC1. And I want to do this WAY after I've segued to the new VC but I still have code running in the background int the old VC
There are many ways for the VCs to communicate. Most straightforwardly, VC1 can keep a pointer to VC2, and VC2 can provide public access to it's imageView.
This isn't particularly good design as it builds in dependencies between the two VCs, but here's how it would look:
// vc1.m
#import "VC2.h"
#interface VC1 ()
#property (nonatomic, strong) VC2 *myVc2;
#end
- (void)presentVC2 {
// allocate init VC2 however you do that
VC2 *vc2 = [[VC2 alloc] init // ...
// keep a pointer to it
self.myVc2 = vc2;
// do whatever you do to present it, navigation vc push, or presentViewController, etc.
}
- (void)timerFired:(NSTimer *)timer {
// not sure from your question if you have the image at this point or if you
// start a fetch for the image here. let's say you have it
UIImage *image = [UIImage imageNamed:#"someimageinmyappbundle.png"];
self.myVc2.publicImageView.image = image;
}
This works if VC2 provides public access to it's UIImageView like this:
// VC2.h
#interface VC2 : UIViewController
// assuming you painted it in a storyboard or xib
#property(weak, nonatomic) IBOutlet UIImageView *publicImageView;
#end
Please note, I'm not saying I like any of this, but it is the straightest line from point A to B. A nicer pattern might be for VC1 to announce the readiness of the image with an NSNotification, and for VC2 to listen for that and set it's own image. But the question appears to insist that VC1 does the work on the VC2 view hierarchy.
You can export (make a property) of your imageView, if you do not already do this.
Just note about your original assumption - if your image is a property, you can create your own setter, and do whatever you want when VC2 sets your image.
I have the problem that many already have reported, didSelectViewController doesn't get called, but in my case it sometimes gets called. I have three tabs and three view controllers. Every time user presses second or third tab I need to execute some code. In my SecondViewController and ThirdViewController I have:
UITabBarController *tabBarController = (UITabBarController *)[UIApplication sharedApplication].keyWindow.rootViewController;
[tabBarController setDelegate:self];
Now everything works fine with the SecondViewController, the didSelectViewController gets called every time the second tab is pressed. Also in ThirdViewController didSelectViewControllergets called every time the third tab is pressed but only when second bar is meanwhile not pressed. So when I switch back and forth between FirstViewController and ThirdViewController everything is OK. But when I go in a pattern like first->second->third, then didSelectViewController doesn't get called in ThirdViewController. Also when I go like first->third->second->third didSelectViewController gets called in ThirdViewController the first time but not the second time. Any ideas?
It's hard to follow what exactly you are doing, but from what I understand you are responding to tab switches by changing the UITabBarController's delegate back and forth between SecondViewController and ThirdViewController.
If that is true, I would advise against doing this. Instead I would suggest you try the following:
Assign a delegate that never changes. For a start you could use your app delegate, but it would probably be better if you had a dedicated small class for this. I am sure that now you have a non-changing delegate, it will get 100% of all the calls to tabBarController: didSelectViewController:.
The object that is the delegate must have a reference to both the SecondViewController and ThirdViewController instances. If you are designing your UI with Interface Builder, you might do this by adding two IBOutlets to the delegate class and connecting the appropriate instances to the outlets.
Now when the delegate receives tabBarController: didSelectViewController: it can simply forward the notification to either SecondViewController or ThirdViewController, depending on which of the tabs was selected.
A basic code example:
// TabBarControllerDelegate.h file
#interface TabBarControllerDelegate : NSObject <UITabBarControllerDelegate>
{
}
#property(nonatomic, retain) IBOutlet SecondViewController* secondViewController;
#property(nonatomic, retain) IBOutlet ThirdViewController* thirdViewController;
// TabBarControllerDelegate.m file
- (void) tabBarController:(UITabBarController*)tabBarController didSelectViewController:(UIViewController*)viewController
{
if (viewController == self.secondViewController)
[self.secondViewController doSomething];
else if (viewController == self.thirdViewController)
[self.thirdViewController doSomethingElse];
}
EDIT
Some hints on how to integrate the example code from above into your project:
Add an instance of TabBarControllerDelegate to the .xib file that also contains the TabBarController
Connect the delegate outlet of TabBarController' to the TabBarControllerDelegate instance
Connect the secondViewController outlet of TabBarControllerDelegate to the SecondViewController instance
Connect the thirdViewController outlet of TabBarControllerDelegate to the ThirdViewController instance
Add a method - (void) doSomething to SecondViewController
Add a method - (void) doSomethingElse to ThirdViewController
Make sure that you don't have any code left in SecondViewController and ThirdViewController changes the TabBarController delegate!
Once you are all set and everything is working fine, you will probably want to cleanup a bit:
Change the names of the notification methods doSomething and doSomethingElse to something more sensible
If you followed the discussion in the comments, maybe you also want to get rid of the secondViewController and thirdViewController outlets
I too had this problem and got fed up with it. I decided to subclass UITabBarController and override the following methods. The reason I did both was for some reason on application launch setSelectedViewController: wasn't being called.
- (void)setSelectedIndex:(NSUInteger)selectedIndex
{
[super setSelectedIndex:selectedIndex];
// my code
}
- (void)setSelectedViewController:(UIViewController *)selectedViewController
{
[super setSelectedViewController:selectedViewController];
// my code
}
I just dug through this tutorial on storyboards, and I thought of an alternative to using UITabBarControllerDelegate. If you want to stick to UITabBarControllerDelegate then feel free to ignore this answer.
First, create a subclass of UITabBarController, let's call it MyTabBarController. In the storyboard editor you need to change the "Class" property of the tab bar controller so that the storyboard picks up your new class.
Add this code to MyTabBarController.m
- (void) prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"SecondVC"])
{
SecondViewController* secondViewController = (SecondViewController*)segue.destinationViewController;
[secondViewController doSomething];
}
else if ([segue.identifier isEqualToString:#"ThirdVC"])
{
ThirdViewController* thirdViewController = (ThirdViewController*)segue.destinationViewController;
[thirdViewController doSomethingElse];
}
}
In the storyboard editor, you can now select the two segues that connect to SecondViewController and ThirdViewController and change the segue identifier to "SecondVC" and "ThirdVC", respectively.
If I am not mistaken, that's all you need to do.