Set UIImageView.image between view controllers - ios

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.

Related

Passing information from one UiView to Another

I am beginning to learn Objective-c and my problem is the following:
I have created a UIView to create a profile and in that UIView, i have an imageView for the Picture as a Datepicker for the Birthday and a Slider for the Weight of the person. When the user presses the button "Conclude" i wish for him to have his Profile created and show all the info that it was set before , Picture , Birthday,name and Weight there.
I tried to use prepareForSegue but i have no idea of what i am doing so could you guys give me a little hint on what i can do to make it work? thank you :)
In the view controller you will be segueing to, you can setup variables to hold the information from the original view controller, then assign them in prepare for segue.
So in your destination view controller .h file, declare
#property (nonatomic) UIImageView *imageView; then in your prepare for segue in your original viewController
NextViewcontroller *nvc = [segue destinationViewController];
nvc.imageView = currentImageView;
You can access the imageView in the destination view controller by calling self.imageView

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!

Can an action be called from a separate view controller?

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.

Changing label in one ViewController from a button in another ViewController

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.

initWithNibName help: returning blank screen when trying to pass data

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.

Resources