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.
Related
Please have a look at the screenshot of my project for better understanding
I have browsed a lot in stack overflow and other sites regarding writing custom delegates. But in my case the delegate is not getting called. As can be seen in the screenshot, the initial view controller has a button on click of which the tab bar gets called. From the first Tab bar screen, suppose I want to pass the data back to the initial view controller, how do I do? I can even use NSUserDefaults but I want to code the right way. So I came to know that delegates are best way to pass data back from one controller to the previous controller. But since I am using tab bars in between, the delegate is not getting called.. Kindly help,
Following is the code,
I want data to be passed from first tab to the initial controller.
TabScreenController.h:
#protocol TabScreenControllerDelegate <NSObject>
-(void)someFunction:(NSString*)someValue;
#end
#interface TabScreenController : UIViewController
#property (nonatomic, weak) id <TabScreenControllerDelegate> delegate;
#end
InitialViewController.h:
#interface InitialViewController : UIViewController<TabScreenControllerDelegate>
InitialViewController.m:
TabScreenController* controller = [[TabScreenController alloc]init];
[controller setDelegate:self];
//The delegate is not getting called
-(void)someFunction:(NSString *)someValue{
NSLog(#"%#", someValue);
}
The delegate is not getting called perhaps the TabScreenController in InitialViewController.m is a different instance than the one that gets created on clicking the tab.
Please help me regarding this.. How do I handle such scenarios. Let me know if I have not made myself clear..
Thank you
The error is that you are not setting the delegate the existing TabScreenController but instantiating a new TabScreenController and setting the delegate. When using TabBarController all the childViewControllers are already instantiated.
In your case you need to look for TabScreenController and then set its delegate.
NSArray* tabChildViewControllers = tabBarController.viewControllers;
for (UIViewController * childVC in tabChildViewControllers) {
if ([childVC isKindOfClass:[TabScreenController class]]) {
// set your the delegate
}
}
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.
I have declared my method 'callWEBservice()' in ViewController1.m and i want to call in ViewController2.m . I have created object of ViewController1.m in ViewController2.m as:
ViewController1* mainVC = [[ViewController1 alloc] init];
Now i am trying to call that method but i am unable to do. Please help on this as I am new to iOS and I have searched some are saying to use delegates.
You need to define the method signature in your .h file -
- (void) callWebService;
and then in your .m file you define the method body:
- (void)callWebService
{
// Whatever you need to do to call the web service
}
Then in ViewController2.m you can #import "ViewController1.h"
Now you can call [mainVC callWebService];
BUT The code you have shown creates a new instance of ViewController1 - If you already have an instance of ViewController1, such as the main view in your app, then this probably isn't what you wanted - you may need to set a property in ViewController2 and store a reference to your ViewController1
e.g. in ViewController2.h
#import "ViewController1.h" // or use #class ViewController1 directive
#property (strong,nonatomic) ViewController1 *mainVC;
Then before in ViewController 1, before you present ViewController2 instance
vc2.mainVC=self;
Your invocation in ViewController2 then becomes
[self.mainVC callWebService];
At the risk of confusing you further, as a design note, it probably isn't best to have the callWebService method in a view controller. It might be more appropriate to create a singleton class for this purpose.
First of all, don't use view controllers for this purpose, create a new class to handle methods of the same kind, then use that one across your view controllers. IF you want the SAME class to be shared across your program, then create a singleton.
How to call method from one class in another (iOS)
However, if you still want to do the view controller to view controller thing, the reason its not working is because you are instantiating a new view controller, not the one you were already using.
You have to pass the reference of the first VC to the second VC. It depends on how you are presenting the second VC. If you are using the Interface Builder, then you need to use:
How to pass prepareForSegue: an object
If you are manually creating and presenting the VC, before presenting it let it know which is the first VC.
You can use delegates like this:
How do I set up a simple delegate to communicate between two view controllers?
STILL consider redesigning your usage of the view controllers.
EDIT:
2 options,
1) Singleton:
Follow this guide http://www.galloway.me.uk/tutorials/singleton-classes/
2) AppDelegate:
Instantiate an object of the class in the .m of the app delegate and assign it to a property in the .h of the App Delegate.
Then, retrieve this object.
This is an example of doing it with the motion manager from ios:
AppDelegate.h:
#property (strong,nonatomic) CMMotionManager *motionManager;
AppDelegate.m
_motionManager = [[CMMotionManager alloc] init];
ViewController1-2-etc
CMMotionManager *motionManager;
motionManager = ((AppDelegate*)[UIApplication sharedApplication].delegate).motionManager;
If you want to use methods from outside your class, you should declare them in your ViewController1.h file, not in the m, otherwise they are not visible (you could still call them using performSelector:withObject:afterDelay: but you should use the first solution)
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.
Ok so I am trying to pass a string from one view controller to another via the AppDelegate. I want to stay on the current view while this happens.
This is the main body of the code I am currently using to do this:
AppDelegate *dataCenter = (AppDelegate *)[[UIApplication sharedApplication] delegate];
MyMealViewController *vc = [[MyMealViewController alloc] initWithNibName:nil bundle:nil];
dataCenter.selectedMenuItem = recipeLabel.text;
[self presentViewController:vc animated:YES completion:NULL];
When I run the program I am able to confirm that the string is correctly passed. However, then the view on the simulator just turns black. I assume that this is because initWithNibName is set to nil.
So my question is: how should I change my code so that the string will still be passed, but the current view will continue to be displayed on the iphone. Is there a line of code that I could write that would just reload the current view?
Thanks for your help with this issue. I am new to xcode so I may be making a very basic error. Please let me know if any additional information would be helpful in answering this question.
Edit: It looks like you want to show a list of food items in the first view. Tapping an items opens a detail view. From that detail view, the user can press a button to add it to the meal. Eventually, they can tap a button on the first view to open the meal view, which should contain all of the items that they selected.
If this is the case, keep an array on the first view controller, and make sure the detail (second) view controller has a reference to the first view controller when it is presented. This will let us use that array. Note that there are better ways to architect this, but this will work for now:
#interface FoodListViewController : UIViewController
#property (strong, nonatomic) NSMutableArray *foodItems
#end
#implementation FoodListViewController
- (void)showFoodItem
{
FoodItemDetailViewController *detailViewController = [[FoodItemDetailViewController alloc] initWithNibName:nil bundle:nil];
detailViewController.foodListController = self;
[self presentModalViewController:detailViewController animated:YES];
}
#end
Once the detail view is presented, tapping the 'add to meal' button should add the current 'mealItem' to the array. In your example, you were using strings - if you would rather keep an array of strings for some reason, I'll leave that to you.
#interface FoodItemDetailViewController : UIViewController
#property (nonatomic, weak) FoodItemsViewController *foodListController;
#end
#implementation FoodItemDetailViewController
- (IBAction)buttonTapped:(id)sender
{
[self.foodListController.foodItems addObject:self.mealItem];
// Update the UI to let the user know that the item was added to the meal
}
#end
Finally, when it comes time to present the MealDetailsViewController, just pass it the array that you have been building:
#interface MealDetailsViewController : UIViewController
#property (nonatomic, strong) NSArray *foodItems;
#end
#implementation MealDetailsViewController
// Set foodItems before this view controller is presented, then use it to drive the
// UITableView data source, or find some other way of displaying it.
#end
As you can see, both the second and third view controllers are presented by the first. View controllers (nearly) always form a hierarchy - so keeping your data at the top of that hierarchy (by storing it in FoodListViewController) lets you neatly pass it down the hierarchy as you present other view controllers.