Retain Subview contents from one View Controller to another - objective-c

I'm using the Cordova cleaver to insert some subview into parts of my native app. I'm having difficulty retaining the contents of these subviews between the pages of my app. For example if I go from ViewController1 to ViewController2 and then back again the contents of the subview on the first view controller has reset as if it had just been loaded for the first time. I'd like a way to preserve these subviews across the app so they don't reset as a user moves around.
Here's what I'm doing right now:
Retaining the subview as a property in ViewController.h
#import <UIKit/UIKit.h>
#import <Cordova/CDVViewController.h>
#interface ViewController : UIViewController
#property (nonatomic,retain) CDVViewController* viewController;
#end
And then loading it here like so in ViewController.m
#import "ViewController.h"
#import <Cordova/CDVViewController.h>
#interface ViewController ()
#end
#implementation ViewController
#synthesize viewController;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
viewController = [CDVViewController new];
viewController.useSplashScreen = NO;
viewController.view.frame = CGRectMake(0, 44, 320, 450);
[self.view addSubview:viewController.view];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Any help or pointing in the right direction is greatly appreciated.

There are a few approaches you could take here. One of them is to use a singleton pattern for your view controller, so that only one instance is ever created. With that pattern, the view controller will retain its state because it will never be re-created. An example of that pattern for objective c is here.
But that may not be the best approach. Another option is to store the parts of your view controller that you want to keep the same as static variables, so that if a new instance of the view controller is created, the portions of your view controller that you want to preserve will still be the same. If you do that, you can restore the state of your view controller in a viewDidAppear method.
If you were to use the second approach, I would do it like this. First, remove the #property declaration from your header file for the CDVViewController. Then, in your implementation file do something like this:
#import "ViewController.h"
#import <Cordova/CDVViewController.h>
#interface ViewController ()
#end
#implementation ViewController
static CDVViewController *__MY_STATIC_CDVViewController;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
if( ! __MY_STATIC_CDVViewController ) {
__MY_STATIC_CDVViewController = [CDVViewController new];
__MY_STATIC_CDVViewController.useSplashScreen = NO;
__MY_STATIC_CDVViewController.view.frame = CGRectMake(0, 44, 320, 450);
}
[self.view addSubview: __MY_STATIC_CDVViewController.view];
}
#end
I would add that this recommendation isn't the best overall approach for your problem, but it should work for your needs.

Related

variables returning null value upon switching View Controllers in Objective C

Im new to programming with objective C and am working on moving data between View controllers. I am wondering if Bi-directional flow of data (variables) between ViewControllers is possible.
I can move data backwards (to the presentingViewController / sourceViewController) however i cannot move data forward (to the presentedViewController / destinationViewController).
I have made a simple case scenario (involving strings to get a principle of the idea) of this and it involves updating a UItextField on the destinationViewController using a UILabel in the sourceViewController and vice-versa.
I CANNOT update the UITextField using the UILabel, but can update the UILabel using the UITextField.
I have made Logs of different statements to track the variable values however when I switch ViewControllers the variables Data returns to null even if they are marked as strong.
Can you please offer any guidance, its been tearing away at my mind, or am I missing something obvious? I don't get why I keep getting a (null) value (in my NSLog) when I switch ViewControllers.
My sourceViewController / presentingViewController is named "ViewController."
My destinationViewController / presentedViewController is named "Gears2ViewController".
I have attached my code files below:
ViewController.h:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (strong, nonatomic) IBOutlet UILabel *outputLabel;
- (IBAction)ExitToHere:(UIStoryboardSegue *)sender;
#end
ViewController.m:
#import "ViewController.h"
#import "Gear2ViewController.h"
#interface ViewController ()
- (IBAction)changeItem:(id)sender;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)changeItem:(id)sender {
Gear2ViewController *G2VC=[[Gear2ViewController alloc] init];
G2VC.peterSido=[NSString stringWithFormat:#"%#",self.outputLabel.text];
[self performSegueWithIdentifier:#"toGear2" sender:self];
NSLog(#"ViewController UILabel reads %#",G2VC.peterSido);
}
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
}
- (IBAction)ExitToHere:(UIStoryboardSegue *)sender {
}
#end
Gears2ViewController.h:
#import <UIKit/UIKit.h>
#import "ViewController.h"
#interface Gear2ViewController : UIViewController
#property (strong, nonatomic) NSString *peterSido;
#end
Gears2ViewController.m:
#interface Gear2ViewController ()
#property (strong, nonatomic) IBOutlet UITextField *updatedOutput;
- (IBAction)updateOutput:(id)sender;
#end
#implementation Gear2ViewController
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(#"Gears2ViewController ViewDidAppear reads %#",self.peterSido);
}
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"Gears2ViewController ViewDidLoad responds %#",self.peterSido);
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)updateOutput:(id)sender {
self.peterSido = self.updatedOutput.text;
((ViewController *)self.presentingViewController).outputLabel.text = self.peterSido;
NSLog(#"Gears2View Controller updating ViewController UILabel reads %#",self.peterSido);
}
#end
NSLog:
2015-06-29 18:52:58.798 testerBeta[21735:645772] Gears2ViewController ViewDidLoad responds (null)
2015-06-29 18:52:58.799 testerBeta[21735:645772] ViewController UILabel reads I like Pie
2015-06-29 18:52:59.317 testerBeta[21735:645772] Gears2ViewController ViewDidAppear reads (null)
2015-06-29 18:53:12.651 testerBeta[21735:645772] Gears2View Controller updating ViewController UILabel reads No I dont
Quite Lengthy but Thanks in Advance!!!
You want to pass the data in prepareForSegue:, like so:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)__unused sender
{
if ([[segue identifier] isEqualToString:#"toGear2"])
{
Gear2ViewController *controller = (Gear2ViewController *)segue.destinationViewController;
controller.peterSido = self.outputLabel.text;
}
}
The reason why is that the segue instantiates the presented view controller for you, and you then set the property of the instantiated view controller which the segue will present.
To pass the data back, you can use an unwind segue, which can get the value from the presented view controller's property.
- (IBAction)unwindFromGear2:(UIStoryboardSegue *)segue
{
Gear2ViewController *controller = (Gear2ViewController *)segue.sourceViewController;
self.outputLabel.text = controller.peterSido;
}
This is the proper way to pass data back and forth via segues. Gear2ViewController shouldn't be setting properties on its presentingViewController.
Update:
The preferred way to test that a property isn't nil is like this:
if (self.peterSido)
{
self.updatedOutput.text = self.peterSido;
}
else // No need for if test here
{
self.updatedOutput.text = #"";
}
That's the long form, but the assignment and if test can be more concisely written as:
self.updatedOutput.text = self.peterSido ?: #"";
When you declare any variable as #property then you need to synthesize it in .m file .
You have declared your outputLabel as #property but you missed to synthesize it in .m file.
When you synthesize any variable then it allows you to get and set the values to it .
Do it it will help you.
Thank you.

Button that takes me to a new UIViewController based on the content of a TextField not working

I'm trying to make a button take me to a new UIViewController based on the content of a textField, but when I run it and hit the button (with the right condition in the text field to take me to the new UIViewController), the screen blacks out. This is what I wrote in my .h and .m files. Can anyone help me (Im using storyboards)
#interface ViewController : UIViewController
- (IBAction)boton:(id)sender;
#property (strong, nonatomic) IBOutlet UITextField *texto;
#end
#import "ViewController.h"
#import "ViewController2.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize texto;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)boton:(id)sender {
if ([texto.text isEqualToString:#"1"]) {
ViewController2 *vc1=[[ViewController2 alloc]init];
[self presentViewController:vc1 animated:YES completion:nil];
}
}
#end
As you say the screen is blacking out, I expect your viewController is getting initialised without a view.
To initialise with a view hierarchy from a xib(nib) file:
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle
where nibName can be nil if it shares it's name with the View Controller, and nibBundle can be nil it the nib is in the main bundle.
i.e....
ViewController2 *vc2;
vc2 = [[ViewController2 alloc] initWithNibName:nil
bundle:nil];
where the xib file is named ViewController2.xib
To initialise from a storyboard:
UIStoryboard *storyboard = self.storyboard;
vc2 = [storyboard instantiateViewControllerWithIdentifier:#"ViewController2"];
(you need to set up a viewController in storyboard and give it a matching identifier)
To initialise with neither storyboard or xib, you should override your view controller's - (void)loadView, create a view and assign it to self.view.
Update
In answer to your comment - the UIStoryboard... and ViewController2 *vc2= ... code would go into your button code (in your case it you would replace / adapt the line containing vc1=.... It would look like this:
- (IBAction)boton:(id)sender {
if ([texto.text isEqualToString:#"1"]) {
ViewController2 *vc2;
UIStoryboard *storyboard = self.storyboard;
vc2 = [storyboard instantiateViewControllerWithIdentifier:#"ViewController2"];
[self presentViewController:vc2 animated:YES completion:nil];
}
You will need to have created a storyboard scene in your storyboard with a viewController whose custom class is ViewController2 and identifier is "ViewController2". The identifier name is arbitrary, but must match the identifier string you use in your code.
As you are using storyboards, an alternative way to do this is to create a modal segue from the 'ViewController' scene to a 'ViewController2' scene, give it an identifier, and use performSegueWithIdentifier in your button method.

How to call viewDidLoad after [self dismissModalViewControllerAnimated:YES];

Okay. If you have two viewControllers and you do a modal Segue from the first to the second, then you dismiss it with [self dismissModalViewControllerAnimated:YES]; it doesn't seem to recall viewDidLoad. I have a main page (viewController), then a options page of sorts and I want the main page to update when you change an option. This worked when I just did a two modal segues (one going forward, one going back), but that seemed unstructured and may lead to messy code in larger projects.
I have heard of push segues. Are they any better?
Thanks. I appreciate any help :).
That's because the UIViewController is already loaded in memory. You can however use viewDidAppear:.
Alternatively, you can make the pushing view controller a delegate of the pushed view controller, and notify it of the updates when the pushed controller is exiting the screen.
The latter method has the benefit of not needing to re-run the entire body of viewDidAppear:. If you're only updating a table row, for example, why re-render the whole thing?
EDIT: Just for you, here is a quick example of using delegates:
#import <Foundation/Foundation.h>
// this would be in your ModalView Controller's .h
#class ModalView;
#protocol ModalViewDelegate
- (void)modalViewSaveButtonWasTapped:(ModalView *)modalView;
#end
#interface ModalView : NSObject
#property (nonatomic, retain) id delegate;
#end
// this is in your ModalView Controller's .m
#implementation ModalView
#synthesize delegate;
- (void)didTapSaveButton
{
NSLog(#"Saving data, alerting delegate, maybe");
if( self.delegate && [self.delegate respondsToSelector:#selector(modalViewSaveButtonWasTapped:)])
{
NSLog(#"Indeed alerting delegate");
[self.delegate modalViewSaveButtonWasTapped:self];
}
}
#end
// this would be your pushing View Controller's .h
#interface ViewController : NSObject <ModalViewDelegate>
- (void)prepareForSegue;
#end;
// this would be your pushing View Controller's .m
#implementation ViewController
- (void)prepareForSegue
{
ModalView *v = [[ModalView alloc] init];
// note we tell the pushed view that the pushing view is the delegate
v.delegate = self;
// push it
// this would be called by the UI
[v didTapSaveButton];
}
- (void)modalViewSaveButtonWasTapped:(ModalView *)modalView
{
NSLog(#"In the delegate method");
}
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
ViewController *v = [[ViewController alloc] init];
[v prepareForSegue];
}
}
Outputs:
2012-08-30 10:55:42.061 Untitled[2239:707] Saving data, alerting delegate, maybe
2012-08-30 10:55:42.064 Untitled[2239:707] Indeed alerting delegate
2012-08-30 10:55:42.064 Untitled[2239:707] In the delegate method
Example was ran in CodeRunner for OS X, whom I have zero affiliation with.

How can I access the property of a parent Class correctly in Objective-C?

I am working at a an Interface for an iPhone App. I recognised that I have repeating objects in my different views so i want to make a parent ViewController. But now when I want to acces a property of this parent ViewController from a child ViewController I get some problems. To be specific I want to have a general ProgressView in each View but I want it to be hidden when the View appears. When I access the ProgressView.hidden from the child ViewController to get it the ProgressView shown i get no errormessage, but at the running programm nothing happens.
My Code looks like this:
ParentViewController.h:
#import <UIKit/UIKit.h>
#interface ParentViewController : UIViewController{
UIProgressView *progressView;
}
#property (readwrite) UIProgressView *progressView;
#end
ParentViewController.m:
- (void)viewDidLoad
{
CGRect progressViewFrame = CGRectMake(0, 407, 320, 9);
progressView = [[UIProgressView alloc] initWithFrame:progressViewFrame];
progressView.hidden = TRUE;
[self.view addSubview:progressView];
[super viewDidLoad];
}
ChildViewController.h:
#import <UIKit/UIKit.h>
#import "ToolbarViewController.h"
#interface ChildViewController : ParentViewController
#end
ChildViewController.m:
- (void)viewDidLoad
{
progressView.hidden = FALSE;
[super viewDidLoad];
}
if i do it like this the ProgressView Bar stays hidden and i get no errors. I also checked whether the viewDidLoad method was called and yes it gets called.
greetings
C4rmel
When you call progressView.hidden = FALSE; before calling [super viewDidLoad];, the progressView has not been initialized yet; it is still nil, so the assignment has no effect.
In the ChildViewController.m, the function calling sequence is incorrect.
Change it by below way:
- (void)viewDidLoad
{
[super viewDidLoad];
progressView.hidden = FALSE;
}
Here call to super will create progressView. Then you can hide it. Otherwise you are hiding a view which is not even created.

Custom UIView delegate not detecting touchesBegan

I did my research but haven't found an answer to the following problem: I have a custom delegate –subclass of UIView– and for some reason touchesBegan isn't working in the delegate implementation.
TestView.h
#import <UIKit/UIKit.h>
#class TestView;
#protocol TestViewDelegate <NSObject>
#end
#interface TestView : UIView
#property (assign) id <TestViewDelegate> delegate;
#end
TestView.m
#import "TestView.h"
#implementation TestView
#synthesize delegate = _delegate;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"Touch detected on TestViewDelegate");
}
#end
ViewController.h
#import <UIKit/UIKit.h>
#import "TestView.h"
#interface ViewController : UIViewController<TestViewDelegate>
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UILabel* title = [[UILabel alloc] initWithFrame:CGRectMake(20, 30, 280, 40)];
[title setFont:[UIFont fontWithName:#"Helvetica-Bold" size:30]];
[title setTextColor:[UIColor blackColor]];
[title setTextAlignment:UITextAlignmentCenter];
[title setBackgroundColor:[UIColor clearColor]];
[tile setText:#"Test"];
[self.view addSubview:title];
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
#end
What am I missing to make sure touchesBegan from TestView.m gets called when touches occur in ViewController.m?
Your last line indicates a fundamental misunderstanding of views and view controllers. Touches don't occur in view controllers; touches occur in views. After a view is touched, it tells its controller that it was touched, and the controller does something with this information. The way that it does this is through a pattern called delegation.
So let's go through this piece by piece. In order to get what you want, you would have to do the following:
First: create an instance of TestView and add it as a subview of the view controller's view.
Now the view exists, and when you tap it you will see your "Touch detected on TestViewDelegate" logged to the console. But it won't actually do anything with the delegate (there isn't even a delegate yet!).
Second: set the newly created TestView's delegate property to the view controller. Do this after you create the TestView instance but before you add it to the view hierarchy.
Now they're hooked up a little, but the view is never talking to its delegate (this doesn't happen automatically; when you create a delegate protocol you have to specify what messages the view will be able to send it).
Third: add a method to the TestViewDelegate protocol and implement that method in the view controller. This could be something like touchesBeganOnTestView:(TestView *)sender, or whatever else you want the view to tell the delegate when it's touched. That looks like this:
#class TestView;
#protocol TestViewDelegate <NSObject>
- (void)touchesBeganOnTestView:(TestView *)sender;
#end
You have to add the #class line because the protocol declaration comes before the declaration of TestView -- at that point in the file, the compiler doesn't know what "TestView" means, so to avoid a warning you say "don't worry, I'm going to declare this later."
Fourth: invoke that method from TestView's touchesBegan. This is as simple as adding the line [self.delegate touchesBeganOnTestView:self];.
That'll get you what you want. From your question I'm gathering that you're pretty new to iOS/Objective-C, and it's going to be difficult if you don't have a solid understanding of the fundamentals. A good place to start might be Apple's description of delegation.