Calling SEL from parentViewController in iOS - objective-c

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];

Related

UIViewController Retaining in ARC

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.

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]);
}

UIGestureRecognizer - Get the reference to the touched UIViewController Instead of its View?

How do I get a reference to the UIViewController of a touched view?
I am using a UIPanGestureRecognizer on the view of a UIViewController. Here's how I initialize it:
TaskUIViewController *thisTaskController = [[TaskUIViewController alloc]init];
[[self view]addSubview:[thisTaskController view]];
UIPanGestureRecognizer *panRec = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(handlePan:)];
[[thisTaskController view] addGestureRecognizer:panRec];
In the tiggered action triggered using the gesture recognizer I am able to get the view from the parameter using recognizer.view
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
UIView *touchedView = [[UIView alloc]init];
touchedView = (UIView*)[recognizer view];
...
}
However what I really need is the underlying UIViewController of the view touched. How can I get a reference to the UIViewController that contains this view instead of only the UIView?
I would say that it is more a design issue than just getting a reference. So I would follow several simple advises:
Owner should catch events from its view. I.e. TaskUIViewController sould be a target to UIPanGestureRecognizer which you added to its view.
If a controller has a sub-controller and waits from its sub-controller some responses - implement this as delegate.
You have memory leak in your "handlePan:" method.
Here is a skeleton to solve your issue:
#protocol CallbackFromMySubcontroller <NSObject>
- (void)calbackFromTaskUIViewControllerOnPanGesture:(UIViewController*)fromController;
#end
#interface OwnerController : UIViewController <CallbackFromMySubcontroller>
#end
#implementation OwnerController
- (id)init
{
...
TaskUIViewController *thisTaskController = [[TaskUIViewController alloc] init];
...
}
- (void)viewDidLoad
{
...
[self.view addSubview:thisTaskController.view];
...
}
- (void)calbackFromTaskUIViewControllerOnPanGesture:(UIViewController*)fromController
{
NSLog(#"Yahoo. I got an event from my subController's view");
}
#end
#interface TaskUIViewController : UIViewController {
id <CallbackFromMySubcontroller> delegate;
}
#end
#implementation TaskUIViewController
- (id)initWithOwner:(id<CallbackFromMySubcontroller>)owner
{
...
delegate = owner;
...
}
- (void)viewDidLoad
{
UIPanGestureRecognizer *panRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[self.view addGestureRecognizer:panRec];
[panRec release];
}
- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
...
[delegate calbackFromTaskUIViewControllerOnPanGesture:self];
...
}
#end
[touchedView nextResponder] will return the UIViewController object that manages touchedView (if it has one) or touchedView's superview (if it doesn’t have a UIViewController object that manages it).
For more information, see the UIResponder Class Reference. (UIViewController and UIView are subclasses of UIResponder.)
In your case, since you happen to know that touchedView is your viewController's view (and not, for instance, a subview of your viewController's view), you can just use:
TaskUIViewController *touchedController = (TaskUIViewController *)[touchedView nextResponder];
In the more general case, you could work up the responder chain until you find an object of kind UIViewController:
id aNextResponder = [touchedView nextResponder];
while (aNextResponder != nil)
{
if ([aNextResponder isKindOfClass:[UIViewController class]])
{
// we have found the viewController that manages touchedView,
// so we break out of the while loop:
break;
}
else
{
// we have yet to find the managing viewController,
// so we examine the next responder in the responder chain
aNextResponder = [aNextResponder nextResponder];
}
}
// outside the while loop. at this point aNextResponder points to
// touchedView's managing viewController (or nil if it doesn't have one).
UIViewController *eureka = (UIViewController *)aNextResponder;

(Target: Object) not working for setting UIButton from outside viewController

I can create the UIButton.
But the callback: target: object won't work within the current object. ??
I can only do "self.viewController" object, and can't do "self" for current object.
thx
#import <Foundation/Foundation.h>
#import "ViewController.h"
#class ViewController;
#interface CGuiSetup : NSObject
{
#public
ViewController *viewController;
}
- (void) Setup;
- (void) ButtonRespond:(UIButton*) btn;
#end
#import "CGuiSetup.h"
#implementation CGuiSetup
- (void) ButtonRespond:(UIButton*) btn
{
NSLog(#"ButtonRespond");
}
- (void) Setup
{
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(10,10,100,100)];
// But I want to call the following in the CGuiSetup object (self) instead... but it crashes out if I leave it as just "self"
[btn addTarget:self.viewController action:#selector(ButtonRespond:)
forControlEvents:UIControlEventTouchUpInside]; //works: for "self.viewController" if I put ButtonRespond in the ViewController
[btn addTarget:self action:#selector(ButtonRespond:)
forControlEvents:UIControlEventTouchUpInside]; //fails: for "self"
[self.viewController.view addSubview:btn];
}
#end
#import <UIKit/UIKit.h>
#import "CGuiSetup.h"
#class CGuiSetup;
#interface ViewController : UIViewController
{
CGuiSetup *guiSetup; //<---- had to take this out of the "viewDidLoad" method
}
#end
- (void)viewDidLoad
{
[super viewDidLoad];
guiSetup = [CGuiSetup alloc];
guiSetup->viewController = self;
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(10,10,100,100)];
[btn addTarget:guiSetup action:#selector(ButtonRespond:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
If you're using ARC, does any object have a retaining reference to CGuiSetup? I sounds like CGuiSetup is instantiated, creates (or maybe receives from another object) the viewController, adds the button to it and then gives the view controller to another object (perhaps by pushing it on a navController or setting it to be the root controller of the app)? Whatever the case is, CGuiSetup it being dealloc'd and the button is trying to send a message to an object that's already been destroyed. How/where is CGuiSetup created? What object retains a reference to it? That's probably where your problem is.
If you don't understand what retain/release is and/or you don't know what ARC is, you need to read Apple's memory management guide: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html
That's probably because your object is destroyed, while _viewController still has retain count greater than 0 (so it's not destroyed). Balance you're retain/release count.

How do I connect an array of buttons w/i singleton?

My appDelegate, in the method didFinishLaunchingWithOptions I load my view controller then create a singleton:
UIViewController *MainWinVC = [[p3VC alloc] init];
[[self window] setRootViewController:MainWinVC];
ansButSingleton *ansButs = [[ansButSingleton alloc] init];
ansButSingleton.h looks like this:
#import <Foundation/Foundation.h>
#interface ansButSingleton : NSObject {
// View objects:
UIButton *Ans1;
UIButton *Ans2;
UIButton *Ans3;
UIButton *Ans4;
UIButton *Ans5;
UIButton *Ans6;
UIButton *Ans7;
UIButton *Ans8;
// Other objects:
NSArray *ansButs;
}
#property (strong) UIButton *Ans1, *Ans2, *Ans3, *Ans4, *Ans5, *Ans6, *Ans7, *Ans8;
#property (strong) NSArray *ansButs;
+ (ansButSingleton *) ansButsName; // Declare class method
#end
and ansButSingleton.m like this:
#import "ansButSingleton.h"
#implementation ansButSingleton
static ansButSingleton *ansButsName;
#synthesize Ans1, Ans2, Ans3, Ans4, Ans5, Ans6, Ans7, Ans8;
#synthesize ansButs;
//////////////////// instantiate ////////////////////
+ (ansButSingleton *) ansButsName { // class method
#synchronized(self)
{
if (!ansButsName)
ansButsName = [[ansButSingleton alloc] init];
return ansButsName;
}
}
//////////////////// initialize ////////////////////
- (id)init { // class instance method
NSLog(#"Initializing answer buttons");
if (ansButsName) {
return ansButsName;
}
self = [super init];
if(self) {
// First initialze the individual buttons
self.Ans1 = [[UIButton alloc] init];
[Ans1 setTitle:#"" forState:UIControlStateNormal];
self.Ans2 = [[UIButton alloc] init];
[Ans2 setTitle:#"" forState:UIControlStateNormal];
self.Ans3 = [[UIButton alloc] init];
[Ans3 setTitle:#"" forState:UIControlStateNormal];
self.Ans4 = [[UIButton alloc] init];
[Ans4 setTitle:#"" forState:UIControlStateNormal];
self.Ans5 = [[UIButton alloc] init];
[Ans5 setTitle:#"" forState:UIControlStateNormal];
self.Ans6 = [[UIButton alloc] init];
[Ans6 setTitle:#"" forState:UIControlStateNormal];
self.Ans7 = [[UIButton alloc] init];
[Ans7 setTitle:#"" forState:UIControlStateNormal];
self.Ans8 = [[UIButton alloc] init];
[Ans8 setTitle:#"" forState:UIControlStateNormal];
// Make an array containing the objects: this is the objective-C way!
self.ansButs = [[NSArray alloc] initWithObjects: Ans1, Ans2, Ans3, Ans4, Ans5, Ans6, Ans7, Ans8, nil];
}
NSLog(#"Done initializing answer buttons");
return self;
}
#end
This builds and runs fine (it doesn't do much yet). The buttons are visible due to the successful loading of the nib. However, they are not active since I haven't connected them to the code (no IBActions).
Question: How do I connect buttons created this way to the buttons in the nib? If these were simple buttons (not an array of buttons) I would create a method and use IBAction as part of that method declaration. But this case seems a bit different. Or maybe not. If these were labels (which I also need to do later), my reading leads me to believe IBOutletCollection might work, but there is no IBActionCollection that I can see. Expert guidance needed! Thanks.
EDIT ... working with Rob to implement his ideas. My viewLoad method was copied and pasted from yours, but maybe there was something in it I needed to change?
#pragma mark - View lifecycle
- (void)loadView {
NSDictionary *externals = [NSDictionary dictionaryWithObject:[AnswerButtons answerButtons]
forKey:#"answerButtons"];
NSDictionary *nibOptions = [NSDictionary dictionaryWithObject:externals
forKey:UINibExternalObjects];
[self.nibBundle loadNibNamed:self.nibName owner:self options:nibOptions];
[[AnswerButtons answerButtons] buttonsDidLoad];
}
There are a number of ways you could do this. I'd do it by hooking up the singleton's connections in the nib. Here's how.
First, let's fix up the singleton class to match iOS programming conventions and provide the support for wiring up the button connections in the nib:
AnswerButtons.h
#import <Foundation/Foundation.h>
#interface AnswerButtons : NSObject
#property (strong, nonatomic) IBOutlet UIButton *button1, *button2, *button3, *button4, *button5, *button6, *button7, *button8;
#property (strong, nonatomic, readonly) NSArray *buttons;
+ (AnswerButtons *)answerButtons;
- (void)buttonsDidLoad;
#end
AnswerButtons.m
#import "AnswerButtons.h"
#implementation AnswerButtons
#synthesize button1 = _button1;
#synthesize button2 = _button2;
#synthesize button3 = _button3;
#synthesize button4 = _button4;
#synthesize button5 = _button5;
#synthesize button6 = _button6;
#synthesize button7 = _button7;
#synthesize button8 = _button8;
#synthesize buttons = _buttons;
+ (AnswerButtons *)answerButtons {
static AnswerButtons *singleton;
static dispatch_once_t once;
dispatch_once(&once, ^{
singleton = [[AnswerButtons alloc] init];
});
return singleton;
}
- (void)buttonsDidLoad {
_buttons = [[NSArray alloc] initWithObjects:_button1, _button2, _button3, _button4, _button5, _button6, _button7, _button8, nil];
}
#end
Note that we create the singleton in the class method the first time the singleton is requested. Don't do it in application:didFinishLaunchingWithOptions:.
Next, let's hook up the buttons in the nib:
Open the nib for the p3VC class. (You should consider changing this name to start with a capital letter and spell out ViewController or just Controller.)
Drag an “External Object” from the Object Library into the nib.
In the Identity Inspector, set the External Object's custom class to AnswerButtons.
In the Attributes Inspector, set its External Object Identifier to answerButtons.
Control-drag from the Answer Buttons object to each of the eight buttons, connecting the button to the appropriate outlet.
Finally, we need to provide the AnswerButtons singleton to the nib loader when we load the nib. Edit p3VC.m and give it a loadView method:
p3VC.m
- (void)loadView {
NSDictionary *externals = [NSDictionary dictionaryWithObject:[AnswerButtons answerButtons]
forKey:#"answerButtons"];
NSDictionary *nibOptions = [NSDictionary dictionaryWithObject:externals
forKey:UINibExternalObjects];
[self.nibBundle loadNibNamed:self.nibName owner:self options:nibOptions];
[[AnswerButtons answerButtons] buttonsDidLoad];
}
With this approach, you can also create IBAction methods in the AnswerButtons class and use the nib to connect the buttons to those actions.
Since UIButton inherits from UIControl you can use the method:
[yourButton1 addTarget:yourObject action:#selector(yourMethod) forControlEvents:UIControlEventTouchUpInside];
Hook that in the loop where the buttons are created and you are good to go. If you are adding the buttons from interface builder then you need to hook an IBOutlet for each. Example: In your view controller init:
self.Ans1 = [[UIButton alloc] init];
[Ans1 setTitle:#"" forState:UIControlStateNormal];
//set the action
[Ans1 addTarget:self action:#selector(yourMethod) forControlEvents:UIControlEventTouchUpInside];
Another way would be to create the IBAction method for each button and hook that method to the method in the singleton, which in my opinion is much more clear.
Example: in your view controller:
-(IBAction) button1Press:(id)sender{
[yourSingleton button1Press];
}
To connect the buttons to interface builder, you'd have to place "IBOutlet" before the UIButton in each one of your variable declarations.
IBOutlet UIButton * Ans1;
Then in interface builder, you can right click and drag from view controller onto the button and select the right button. Then if you wanted to have each button perform a method, you should declare an IBAction method in your .h file:
-(IBAction)doButtonStuff:(id)sender;
Then to hook up the act to each button in interface builder, go to interface builder and right click and drag from the button to view controller and select what method you want to associate it with. Typically using interface builder is quick and easy but if you want to do some extra behind the scenes stuff, you can use the code whitelion posted.
Also, just a side note, to use less code when setting up your buttons, you can do a for each loop.
for (UIButton * button in ansButs) {
self.button = [[UIButton alloc] init];
[button setTitle:#"" forState:UIControlStateNormal];
}
This is a lot less code!!! You'd have to declare the array before this code and include all of the buttons in it.