UIViewController Retaining in ARC - objective-c

I have a subclass of UIViewController -> MyPopUpViewController
#protocol MyPopUpViewController Delegate;
#interface MyPopUpViewController : UIViewController
{
}
#property (nonatomic, strong) id <MyPopUpViewControllerDelegate> delegate;
-(IBAction) buttonPressed:(id)sender;
#end
#protocol MyPopUpViewControllerDelegate
-(void) popupButtonPressed: (MyPopUpViewController*)controller;
#end
I cannot have this MyPopUpViewController as an instance variable because this comes externally, and there could be many and multiple of these popups can be up. So far I tried this, and it crashes on the delegate call due to not being retained:
MyMainViewController:
-(void)externalNotificationReceived: (NSString*) sentMessage
{
MyPopUpViewController *popupView = [[MyPopUpViewController alloc] init];
popupView.delegate = self;
[self.view addSubview:popupView.view];
[popupView setInfo :sentMessage :#"View" :#"Okay"];
popupView.view.frame = CGRectMake(0, -568, 320, 568);
popupView.view.center = self.view.center;
}
-(void)popupButtonPressed:(MyPopUpViewController *)controller :(int)sentButtonNumber
{
NSLog(#"Popup Delegate Called");
[controller.view removeFromSuperview];
controller.delegate = nil;
controller = nil;
}
Once the popup comes up, and when the ok button is tapped, it crashes and never gets to that NSLog. How can I change
MyPopUpViewController *popupView = [[MyPopUpViewController alloc] init];
..so it would retain without making it an instance variable?
Thanks in advance.

You should be doing proper view controller containment by calling addChildViewController:.
- (void)externalNotificationReceived: (NSString*) sentMessage {
MyPopUpViewController *popupView = [[MyPopUpViewController alloc] init];
popupView.delegate = self;
[popupView setInfo :sentMessage :#"View" :#"Okay"];
popupView.view.frame = CGRectMake(0, -568, 320, 568);
popupView.view.center = self.view.center;
[self addChildViewController:popupView];
[self.view addSubview:popupView.view];
[popupView didMoveToParentViewController:self];
}
This will keep a proper reference to the view controller as well as properly pass various view controller events. Read about this in the docs for UIViewController and the "View Controller Programming Guide for iOS".
BTW - you should name your methods better. Example:
popupButtonPressed::
should be named:
popupButtonPressed:buttonNumber:

Usually delegates are weak-referenced instead of strong. I, myself, would name it something else as to not confuse other people.
Also, the following bit of code will have no effect:
-(void)popupButtonPressed:(MyPopUpViewController *)controller :(int)sentButtonNumber
{
...
controller = nil;
}
the controller would be released (set to nil) automatically at the end of the scope.

Related

Understanding delegation

I am trying to understand delegation. I have written a small project to try to tackle this. I have also had help from S.O. I am stuck on the very last part of it. My project is simple. We have a main view controller that has a button "start". This button triggers a container view that's hooked to a ContainerViewController. I have done a small animation to get the container to slide from the side. I have another button "back" that makes the container view disappear with the opposite animation. Note, I am copying a lot of code and making up the rest as I am learning, so there may be unnecessary lines, please feel free to comment.
ViewController.h
#import <UIKit/UIKit.h>
#import "ContainerViewController.h"
#interface ViewController : UIViewController <ContainerViewControllerDelegate>
- (IBAction)Start:(id)sender;
- (IBAction)back:(id)sender;
#end
Here is the m file:
#import "ViewController.h"
#interface ViewController ()
#property UIViewController *childView;
#property NSString *myReceivedValue;
#property ContainerViewController *controller;
#property IBOutlet UILabel *myLabel;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.childView = [self.storyboard instantiateViewControllerWithIdentifier:#"childVC"];
self.controller = [self.storyboard instantiateViewControllerWithIdentifier:#"childVC"];
self.controller.delegate = self;
self.childView = [self.childViewControllers lastObject];
[self.childView.view removeFromSuperview];
[self.childView removeFromParentViewController];
self.childView.view.userInteractionEnabled = NO;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)Start:(id)sender {
self.childView.view.frame = CGRectMake(0, 84, 320, 210);
[self.childView didMoveToParentViewController:self];
CATransition *transition = [CATransition animation];
transition.duration = 1;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
[transition setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[self.childView.view.layer addAnimation:transition forKey:nil];
[self.view addSubview:self.childView.view];
self.childView.view.userInteractionEnabled = YES;
}
- (IBAction)back:(id)sender {
[self.childView willMoveToParentViewController:nil];
[UIView animateWithDuration:1
delay:0.0
usingSpringWithDamping:1
initialSpringVelocity:1
options:UIViewAnimationOptionCurveEaseIn
animations:^{
self.childView.view.frame = CGRectMake(-320, 84, 320, 210);
} completion:^(BOOL complete){
[self.childView removeFromParentViewController];
}];
}
- (void) passValue:(NSString *) theValue
{
// here is where you receive the data
}
#end
Ok, so the Container View has a pickerView of which it is the delegate and this pickerView has just an array of ten colors to chose from:
h file for the container view:
#import <UIKit/UIKit.h>
#protocol ContainerViewControllerDelegate;
#interface ContainerViewController : UIViewController <UIPickerViewDelegate>
#property NSArray *colors;
#property (weak)id <ContainerViewControllerDelegate> delegate;
#property (weak, nonatomic) IBOutlet UIPickerView *myPickerView;
- (IBAction)chosenCol:(id)sender;
#end
#protocol ContainerViewControllerDelegate <NSObject>
- (void) passValue:(NSString *) theValue;
#end
m file for the container view:
#import "ContainerViewController.h"
#interface ContainerViewController ()
#property NSString *selValue;
#end
#implementation ContainerViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.colors = [[NSArray alloc] initWithObjects:#"blue", #"red", #"green", #"purple", #"black", #"white", #"orange", #"yellow", #"pink", #"violet", nil];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
return 10;
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
return self.colors[row];
}
- (IBAction)chosenCol:(id)sender {
[self.delegate passValue:[self.colors objectAtIndex:[self.myPickerView selectedRowInComponent:0]]];
}
#end
Here is a picture of what it looks like. Note that the "chosen" button is just a provisional one that I put there to make sure everything is hooked alright and that I can log out the chosen color using a button in the container. What I want to do is be able to pass that color to the parent view controller. So that after I dismiss the container with its picker view I have the data stored of what color was chosen. I have had help from someone in S.O. and he's done a very good job of helping me start this up. The only thing I didn't understand is what happens when I receive the data, at the end of the m file of the parent:
- (void) passValue:(NSString *) theValue
{
// here is where you receive the data
}
This is obvioulsy noob question but I really do need it spelt out. How do I actually access the data in the parent. I asked in the comments section, and the reply was (I am changing the class, it was originally uicolor):
"No, you'll receive the data inside the method - (void) passValue:(NSString *) theValue; Put a breakpoint in that method to be sure that it's working, you can access it like this: NSString *myReceivedColor = theValue;
I tried to write word for word "NSString *myReceivedColor = theValue;" but "theValue" is unrecognised.
Ultimately, what I want, is to pass the data back to the parent so that when I hit the button "back", in the parent, the label "you chose" is updated with the chosen color".
I have never touched delegation before so I am lost. Can a charitable soul take the time to explain this last bit in very obvious terms? many thanks
UPDATE-----------------------------------------------------------------------
So, what I am looking at, is to add, at the end of my method for the "back" button,
- (IBAction)back:(id)sender {
[self.childView willMoveToParentViewController:nil];
[UIView animateWithDuration:1
delay:0.0
usingSpringWithDamping:1
initialSpringVelocity:1
options:UIViewAnimationOptionCurveEaseIn
animations:^{
self.childView.view.frame = CGRectMake(-320, 84, 320, 210);
} completion:^(BOOL complete){
[self.childView removeFromParentViewController];
}];
the couple of lines:
self.myReceivedValue = theValue;
self.myLabel.text = self.myReceivedValue;
}
To be able to update the text of myLabel to the the color I've chosen in the view container. It comes back with the error: "use of undeclared identifier "theValue". This is all new to me so I am just copying what people have said on S.O. with the hope of understanding eventually. What am I doing wrong here? tx
It looks like your delegate is nil.
self.childView = [self.storyboard instantiateViewControllerWithIdentifier:#"childVC"];
self.controller = [self.storyboard instantiateViewControllerWithIdentifier:#"childVC"];
self.controller.delegate = self;
You create two instances of "childVC" (a copy/paste typo maybe?) then set the delegate on 'controller' but you use 'childView'. Just change the childView property to be a ContainerViewController and set self.childView.delegate=self.
(BTW its an easy mistake, so many times when you're thinking "why isn't this working??" check that the delegate property is set)
EDIT
The return value property you're logging is nil b/c you never set it. You have to implement the delegate method, i.e.
-(void) passValue:(nsstring*)theValue
{
self.receivedValue = theValue
}
Also what i was saying about the chosenCol action is that is where you are calling your delegate - your 'back' action does not call this method.

How to Access `UItextView` data from another view controller

How I access the body of UITextView from another view controller for email here is my code
EmergencyMessageController *ViewB1 = [[EmergencyMessageController
alloc]initWithNibName:#"EmergencyMessageController" bundle:nil];
NSString *Em1 = ViewB1.Emsg.text;
NSString *body = [[NSString alloc] initWithFormat:#" ",Em1];//my problem is here to access Em1 data
controller.mailComposeDelegate = self;
[controller setSubject:#"Emergency Message"];
[controller setMessageBody:body isHTML:YES];
[controller setToRecipients:recipients];
[self presentModalViewController:controller animated:NO];
As you are creating another instance of "EmergencyMessageController" viewcontroller here, it won't give you any values.
You have to use the same instance variable using which you have set the email body in EmergencyMessageController.
or else as Vertogo said, this EmergencyMessageController should be the parent of this second view controller, so that you can easily access parent view controllers properties.
Hey user2396021 this is a simple example of Parent-Child View Controller,hope this will help you.
ParentViewController.h
Declare your textView as a class variable in parent view controller as below.
#import <UIKit/UIKit.h>
#interface ParentViewController : UIViewController
#property (nonatomic,strong) UITextView *mytextView;
#end
ParentViewController.m
Add textview to parentView Controller as below
- (void)viewDidLoad
{
[super viewDidLoad];
_mytextView = [[UITextView alloc]initWithFrame:CGRectMake(100, 100, 200, 30)];
[_mytextView setBackgroundColor:[UIColor grayColor]];
[self.view addSubview:_mytextView];
}
ChildViewController.h
Make ParentViewController as a superclass(or you can say parentclass) of ChildViewController as below
#import "ParentViewController.h"
#interface ChildViewController : ParentViewController
#end
ChildViewController.m
Now you can easily access any class variable of parent class(ParentViewController) using "super" keyword.
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Text value of uitextview from Parent class %#",[[super mytextView]text]);
}

Customized init method on an object to initWithWindowNibName an NSWindowController property

What I'm trying to do is create a method to an object which opens a window.
In this window I want to output some properties of the object's instance.
To do this I created a "Profile" subclass of NSObject, which has an NSWindowController property called "view".
#interface Profile : NSObject {
\\...
}
#property (readwrite, assign) NSWindowController *view;
Since I cannot connect "view" to the window with Interface Builder (or at least I don't know how) I have to do so with the "initWithWindowNibName". So I tried overriding the "Profile"'s init method like this:
-(Profile *)init{
self = [super init];
if(self){
[[self view] initWithWindowNibName:#"Profile"];
}
return self;
}
I don't know whether my approach is correct, fact is when I try showing the window it doesn't appear. Here's how I tried:
Profile *profile = [[Profile alloc] init];
[[profile view] showWindow:self];
Hope you can help :)
Don't you want something like:
#interface Profile:NSObject
#property (nonatomic, strong) NSWindowController *windowController;
#end
and:
- (Profile *)init {
self = [super init];
if( !self ) { return nil; }
self.windowController = [[NSWindowController alloc] initWithWindowNibName:#"Profile"];
return self;
}
and:
// show window
Profile *profile = [[Profile alloc] init];
[[profile windowController] showWindow:self];
(I'm assuming ARC.)
EDIT:
For clarity to the OP, I followed the his property nomenclature, which was to name the NSWindowController property view. It is confusing, though because a NSWindowController is not a view. For clarity to others, I've changed it.

Add and control UIImageView Subview from another class

I have two classes: MainViewController and PlayerImageController (NSObject)
How would I be able to add the subview of my UIImageView from PlayerImageController to my MainViewController and dictate actions like
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:[PlayerImageController addPlayerImage]];
}
- (void)somethingHappened
{
[PlayerImageController changePlayerImage];
}
and have my methods in my PlayerImageController class like
+ (UIImageView *) addPlayerImage
{
heroPlayerImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"hero-still.png"]];
[heroPlayerImageView setFrame:CGRectMake(151, 200, 17, 23)];
return heroPlayerImageView;
}
+ (void) changePlayerImage
{
//change image
}
Thanks!
I think in your case you should use the Delegate pattern.
Declare:
#protocol PlayerImageUpdater
- createPlayerImage;
- changePlayerImage;
#end
Then add:
#interface PlayerImageController <PlayerImageUpdater>
then add to MainViewController ivar and property like:
#property (...) id<PlayerImageUpdater> playerDelegate;
set this delegate like: mainViewController.playerDelegate = playerImageControllerInstance;
and use in code:
[playerDelegate createPlayerImage];
[playerDelegate changePlayerImage];
On one hand, I would not recommend using class methods but instance methods. This way, you could implement as many instances of your class as you need and keep a reference to your instances to update them.
On the other hand, if the UIImageView is the important attribute of your class, I suggest you implement it as a UIView subclass (if it is not, you can do it as an NSObject subclass as well, and get its UIImageView attribute).
Have a look at the following code:
PlayerImageController.h:
#interface PlayerImageController : UIView{
UIImageView *_heroPlayerImageView;
}
-(void) changePlayerImage;
PlayerImageController.m:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_heroPlayerImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"hero-still.png"]];
// x = 0 and y = 0 because its relative to its parent view, the object itself.
[_heroPlayerImageView setFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
[self addSubview:_heroPlayerImageView];
}
return self;
}
MainViewController.h:
#import "PlayerImageController.h"
#interface MainViewController : UIViewController{
PlayerImageController *_player;
}
MainViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
_player = [[PlayerImageController alloc] initWithFrame:CGRect(151, 200, 17, 23)];
[self.view addSubview:_player];
}
- (void)somethingHappened
{
[_player changePlayerImage];
}
I hope it can help you (I haven't actually tried the code above, it could have some syntax errors).
If you are not using ARC, remember to retain and release your variables! Good luck!

Calling SEL from parentViewController in iOS

I have a ViewController in which I create a Modal ViewController. I have a SEL value in my modal that I set when it is being instantiated from the parent.
setDateViewController.selectorName = #selector(myMethod:);
In my modal I am trying to call this SEL like:
[[self parentViewController] performSelector:self.selectorName withObject:selectedDate afterDelay:.5];
{selectedDate} is obviously a value from my modal.
I don't get any errors or stack, however, this SEL (method) on my parent is never being called. For some reason I think this should work, but something tells me I'm way off track.
Thanks.
I guess [self parentviewcontroller] is not returning anything.
Try UiviewController* v = [self parentviewcontroller]; and check if is nil. Most probably it should be nil. Else if its was pointing to another object of different class then it would have crashed. Please do one thing. YOu should set bot the object and the methd you need to call. IT will solve any issues if it has any.
setDateViewController.selectorDelegate = self;
setDateViewController.selectorName = #selector(myMethod:);
call like this from parent class. So you can dynamically specify the method and the object you want to call gives more flexibility.
and use,
[selectorDelegate performSelector:self.selectorName withObject:selectedDate afterDelay:.5];
this should solve any issues.
Perhaps you would consider added a delegate protocol to your modal that will allow it to call the method on the parent.
Quick (untested) example:
// MyController.h
#protocol MyControllerDelegate;
#interface MyController : UIViewController
{
id<MyControllerDelegate> delegate;
}
#end
#protocol MyControllerDelegate <NSObject>
- (void)methodToCall:(id)sender;
#end
// MyControler.m
#implementation MyController
- (void) loadView
{
[super loadView];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.frame = CGRectMake(20.0f, 20.0f, 50.0f, 30.0f);
[btn setTitle:#"blah" forState:UIControlStateNormal];
[btn addTarget:self.delegate
action:#selector(methodToCall:)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
#end
// ParentController.h
#interface ParentController : UIViewController<MyControllerDelegate>
{
}
#end
// ParentController.m
#implementation ParentController
- (void)methodToCall:(id)sender
{
NSLog(#"HERE");
}
#end
Just make sure when you are creating your modal controller you set it's delegate to self on the parent:
MyController *controller = [[MyController alloc] init];
controller.delegate = self;
[self presentModalViewController:controller animated:YES];