I have a class that subclasses UITableViewController. Based on user actions that are recognized in this class, I need to call a method on a table in the UIViewController were the table is instantiated. I can't figure out how to do this.
I tried to make the function static, but that won't work since there is an instance variable that I need to reach. I could probably use NSNotificationCenter but my intuition is that there is a better way. Can someone help? Thanks!
MonthsTableViewController.h
#interface MonthsTableViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate>
{
NSArray *monthsArray;
}
#end
MonthsTableViewController.m
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSLog(#"calling the UIViewController");
//this is where I am stuck!!!
}
SubscribeViewController.h
#interface SubscribeViewController : UIViewController <SIMChargeCardViewControllerDelegate>
{
MonthsTableViewController *monthsController;
IBOutlet UITableView *monthsTable;
}
- (void) snapMonthsToCenter;
#end
SubscribeViewController.m
- (void) snapMonthsToCenter {
// snap the table selections to the center of the row
NSLog(#"method called!");
NSIndexPath *pathForMonthCenterCell = [monthsTable indexPathForRowAtPoint:CGPointMake(CGRectGetMidX(monthsTable.bounds), CGRectGetMidY(monthsTable.bounds))];
[monthsTable scrollToRowAtIndexPath:pathForMonthCenterCell atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}
Basically in order to do this, you need a reference to your UIViewController from your UITableViewController. This will allow you to call the methods of this object. Typically you would call this property a delegate, because you're assigning the "parent" UIViewController as the delegate of the "child" UITableViewController.
Modify your UITableViewController (MonthsTableViewController.h) to add a delegate property like so:
#interface MonthsTableViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate>
{
NSArray *monthsArray;
id delegate;
}
#property (nonatomic, retain) id delegate;
#end
You will need to #synthesize the property in your .m file. You'll also want to import SubscribeViewController.h in your header here, if you haven't already.
Then, when you instantiate your MonthsTableViewController, set the delegate to your current object MonthsTableViewController like so:
MonthsTableViewController *example = [[MonthsTableViewController alloc] init.... // This is the line you should already have
[example setDelegate:self]; // Set this object's delegate property to the current object
Now you have access to the parent SubscribeViewController from your MonthsTableViewController. So how do you call functions? Easy! You can either hardcode the method call, or, to be super safe, use respondsToSelector::
[(MonthsTableViewController*)[self delegate] snapMonthsToCenter];
In your case, the above code is absolutely fine, because you know that this method will always exist on this object. Typically, however, delegates are declared as protocols that may have optional methods. This means that although methods are declared in the #interface, they may not actually exist (be implemented) in the object. In this case, the following code would be used to make sure that the method can actually be called on the object:
if([[self delegate] respondsToSelector:#selector(snapMonthsToCenter)]) {
[[self delegate] snapMonthsToCenter];
}
Related
EDIT: edited for clarity
Disclaimer: I'm new and pretty bad. But I have tried very hard and read lots of stuff to figure this out, but I have not...
I think my whole delegate pattern would work, except I can't figure out how to set the delegate property of ViewController to self in the MatchLetter class. The reason is because I can't figure out how to call code there. It's not a view controller, so viewDidLoad or prepareForSegue won't work.
This is what I've got:
ViewController.h
#import <UIKit/UIKit.h>
#class ViewController;
#protocol letterMatchProtocol <NSObject>
- (BOOL) isLetterMatch:(char) firstLetter;
#end
#interface ViewController : UIViewController
#property (nonatomic, weak) id <letterMatchProtocol> delegate;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
char c = 'a';
// This is the method I want to delegate to MatchLetter, to have a BOOL returned
BOOL returnValue = [self.delegate isLetterMatch:c];
}
#end
MatchLetter.h
#import <Foundation/Foundation.h>
#import "ViewController.h"
#interface Delegate : NSObject <letterMatchProtocol>
#end
MatchLetter.m
#import "MatchLetter.h"
#implementation Delegate
// this is the code I think I need to run here, to set the delegate property...
// ViewController *viewController = [ViewController new];
// viewController.delegate = self;
// ... so that isLetterMatch can be run here from ViewController.m
// But I don't know where to put this code, or how to get it to run before the ViewController
// especially since there are no segues or views to load.
- (BOOL) isLetterMatch:(char)firstLetter {
if (firstLetter == 'a') {
return YES;
}
else {
return NO;
}
}
#end
Can somebody please tell me the best way to proceed? Thanks for reading
You asked "Where to set delegate = self? Or should I just use a different design pattern?".
Answer: Don't. An object should never be it's own delegate.
Your code is quite a mess.
Don't name a class "Delegate". A delegate is a design pattern. The whole point of a delegate is that any object that conforms to a particular protocol ("speaks the language") can serve as the delegate. You don't need to know what class of object is serving as the delegate, but only that it speaks the language you need.
An analogy: When you call the operator, you don't care who is working the operator desk. You don't care about his/her gender, religion, ethnic background, how tall they are, etc. You just care that they speak your language.
Likewise, when you set up a delegate, it doesn't matter what type of object gets set as the delegate. All that matters is that the object that is the delegate conforms to the protocol for that delegate.
A table view can have ANY object serve as it's delegate, as long as that object conforms to the UITableViewDelegate protocol. You usually make you view controller be the table view's delegate, but you don't have to. You could create a custom class that manages your table views, and have it be the delegate. There is no "TableViewDelegate" object class. There is instead a UITableViewDelegate protocol, and any object that conforms to the protocol can act as a table view's delegate.
Edit: Your question is confusing. I think what you're proposing is that your Delegate class would create a view controller and make itself the delegate for the view controller.
If that's what you are talking about, your thinking is backwards. The view controller is using the Delegate class as a helper class. Any given instance of a view controller class can create an instance of the Delegate class and set it as it's delegate if it desires. You might have 3 instances of ViewController at one time, each with it's own instance of your Delegate class.
Thus, the ViewController object is the one that should create and set up an instance of Delegate if it needs one:
- (void) viewDidLoad;
{
self.delegate = [[Delegate alloc] init];
//other setup here
}
I feel frustrated with this problem, I my delegate doesn't work, Here's the code snippet below. I'm just using xib in this application.
//classA.h file
ClassA.h
#import "ClassB.h"
#interface ClassA : UIViewController< ClassBDelegate >
//classA.m file
**ClassA.m**
-(void)didSuccessPreview:(ClassB *)controller andLog:(NSString *)log{
NSLog(#"%#", log);
}
//classB.h file
**ClassB.h**
#import <UIKit/UIKit.h>
#class ClassB;
#protocol ClassBDelegate <NSObject>
- (void)didSuccessPreview:(ClassB *)controller andLog:(NSString *)log;
#end
#interface ClassB : UIViewController
#property (weak, nonatomic) id< ClassBDelegate >delegate;
//classB.m file
**ClassB.m**
I add this code below inside view did load
[self.delegate didSuccessPreview:self andLog:#"zz"];
I have other delegate inside my application same as the code above, but it works but this one is not, I don't know why. Inside my classA I have a button then when i click it it goes to classB, then when it goes to viewdidload in ClassB, the delegate doesn't fired.
Depending on where you assign the delegate, your code might need to look like this:
ClassB viewController = [ClassB new];
ClassA delegateStrongRef = [ClassA new]
viewController.delegate = delegateStrongRef; // <-- you are missing this now;
// Fixed: the ClassA object must be referenced strongly somewhere else
// besides the (weak)delegate property, otherwise it will be deallocated.
(The code above is the part where you create the instance of ClassB, the view controller. It might be inside the AppDelegate, another viewController's implementation, anything - depending on how your app is structured).
Then, in the view controller's -viewDidload method, you can have the delegate execute a method like this:
- (void)viewDidLoad
{
[self.delegate didSuccessPreview:self andLog:#"zz"];
}
so, at some point after instantiating your view controller, you need to assign an object of type ClassA (the class that adopts the delegate protocol) to its delegate property, otherwise it will be nil and any messages sent to it will be ignored (no method executed).
Properties and instance variables of object type are not allocated automatically, but initialized to nil (ints, floats etc, are initialized to 0, 0.0f, etc.)
EDIT: If you are assigning the delegate from ClassA's code, then it might look like this:
ClassA.m:
- (void) someMethodOfClassA
{
ClassB viewController = [ClassB new];
viewController.delegate = self;
// e.g., Do something with the view controller...
[navigationController pushViewController:viewController animated:YES];
}
Since the delegate property is defined as a weak reference, before calling any method of the delegate make sure it has not been deallocated and its reference set back to nil (use NSLog or set a breakpoint).
The title is what I think I need but i will go back one step. I want to create a class which handles certain things in an iOS app. This class might be called by multiple UIViewcontrollers in an iOS app. The class may need to show a UIView at some stage for user input. So my question is how can I show a UIView when I don't know which subclass of UIViewController is calling it? To what can I add the UIView from this class?
I suppose there are two possible answers either the class finds the current UIViewController or the calling subclass of UIViewController passes itself to the class so the class knows.
How is this supposed to be done.
Thanks guys for your help.
I'm going to expand on #ericleaf's comment regarding using a protocol and subclasses. It sounds like you are asking the following:
How can I create a resusable, generic class that presents a view
within a UIViewController subclass?
A great way to do this is to define a protocol in your generic class and have your view controller subclasses support this protocol. The protocol defines an interface for your custom class to comunicate with it's delegate, in this case a UIViewController subclass. Other than the protocol, the objects don't need to know anything else about the implementation of each other.
Any information your custom object needs to be able to present views within it's delegate would be passed via protocol methods. The specifics of the protocol are up to you based on your needs. You could have the custom object "ask" the delegate for information (e.g. what view should I put a subview in?) or you could have the protocol provide information to the delegate and let the delegate deal with it (e.g. here is a subview you can put wherever you want).
There is a lot of great documentation on protocols available on SO and elsewhere. This is long enough already so I kept the example fairly simple.
custom class .h file with protocol definition
// my custom class that adds adds a view to a view controller that supports it's protocol
// forward class definition for the protocol
#class MyAwesomeObject;
#protocol MyAweseomeObjectDelegate <NSObject>
- (UIView *)viewForMyAwesomeObject:(MyAwesomeObject *)awesomeObject;
#end
// this could be defined such that the delegate *must* be a UIViewController. I've left it generic.
#interface MyAwesomeClassObject : NSObject
#property (nonatomic, weak) id <MyAwesomeObjectDelegate> delegate;
#end
custom class .m file
// MyAwesomeObject.m
#import "MyAwesomeObject.h"
#implementation MyAwesomeObject
// this is a dumb example, but shows how to get the view from the delegate
// and add a subview to it
- (void)presentViewInDelegate
{
UIView *containingView = [self.delegate viewForMyAwesomeObject:self];
if (containingView) {
UIView *subview = [[UIView alloc] initWithFrame:containingView.bounds];
subview.backgroundColor = [UIColor redColor];
subview.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
[containingView addSubview:subview];
}
}
MyViewController .h using the custom object
// MyViewController.h
#import "MyAwesomeObject.h"
#interface MyViewController : UIViewController <MyAwesomeObjectDelegate>
#property (nonatomic, strong) MyAwesomeObject *awesomeObject;
#end
MyViewController .m using the custom object
// MyViewController.m
#import "MyViewController.h"
#implementation MyViewController
- (void)init
{
self = [super init];
if (self) {
_awesomeObject = [[MyAwesomeObject alloc] init];
_awesomeObject.delegate = self;
}
return self;
}
// MyAwesomeObjectDelegate
- (UIView *)viewForMyAwesomeObject:(MyAwesomeObject *)awesomeObject
{
return self.view;
}
You can get the class into a string and do a compare.
For example, lets assume your custom UIViewController subclass is CustomViewCon and the UIViewController object reference is myUnknownClassObject, then:
NSString *classString = NSStringFromClass([myUnknownClassObject class]);
Then you can:
if([classString isEqualToString:#"CustomViewCon"]){
//do something like maybe present a particular view
myUnknownClassObject.view = myCustomView; //or anything..
}
Similarly you can check for any class.
Edit: According to the suggestions from comments, you could also do the following(better way):
if([[myUnknownClassObject class] isKindOfClass:[CustomViewCon class]]){
//same as before
}
Why wont you use a block for this?
BaseViewController.h:
#property (copy) void (^addViewBlock)();
- (IBAction)showViewWhenNeeded;
BaseViewController.m:
- (IBAction)showViewWhenNeeded
{
if (self.addViewBlock)
self.addViewBlock();
}
And in your child class, set that block's actions, and call the method when you feel like you should put up a view.
ChildViewController.m
// within some method, propably init or smth
[self setAddViewBlock:^{
[self.vied addSubView:...];
}];
// when need to actually add the view
[self showViewWhenNeeded];
I'm trying to set the delegate for my custom protocol that has one required method allowing me to pass an array of objects back in the hierarchy of two UITableViewControllers. My delegate continues to return nil. Due to this, my required method is never called.
I'm wondering if the datasource and delegate implementations with my UITableViewControllers is causing a conflict. Also, perhaps ARC is getting in the way when declaring the delegate?
It should be noted that both UITableViewControllers were built using Storyboard and are navigated using segues within a UINavigationController (not sure if this may be causing issues or not).
The nav is --> AlarmViewController --> AlarmDetailsViewController. I create an Alarm object in my AlarmDetailsViewController that contains all the details for an alarm, place it into an array and I want to pass that array back to my AlarmViewController to be displayed in a custom cell in the table.
NOTE: I want to use the Delegate pattern here. I'm not interested in solutions that invoke NSNotifications or use my AppDelegate class.
AlarmDetailsViewController.h
#import "Alarm.h"
#protocol PassAlarmArray <NSObject>
#required
-(void) passAlarmsArray:(NSMutableArray *)theAlarmsArray;
#end
#interface AlarmDetailsViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate>
{
//.....
id <PassAlarmArray> passAlarmsArrayDelegate;
}
#property (nonatomic, retain) id <PassAlarmArray> passAlarmsArrayDelegate;
#end
AlarmDetailsViewController.m
#import "AlarmDetailsViewController.h"
#interface AlarmDetailsViewController ()
#end
#implementation AlarmDetailsViewController
#synthesize passAlarmsArrayDelegate;
-(void) viewWillDisappear:(BOOL)animated
{
NSLog(#"delegate = %#", self.passAlarmsArrayDelegate); // This prints nil
[[self passAlarmsArrayDelegate] passAlarmsArray:alarmsArray];
}
//....
#end
AlarmViewController.h
#interface AlarmViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate, PassAlarmArray>
{
//...
AlarmDetailsViewController *alarmDetailsViewController;
}
#property (nonatomic, retain) AlarmDetailsViewController *alarmDetailsViewController;
#end
AlarmViewController.m
#import "AlarmViewController.h"
#import "AlarmDetailsViewController.h"
#import "AlarmTableViewCell.h"
#import "Alarm.h"
#interface AlarmViewController ()
#end
#implementation AlarmViewController
#synthesize alarmDetailsViewController;
- (void)viewDidLoad
{
[super viewDidLoad];
// This is where I'm attempting to set the delegate
alarmDetailsViewController = [[AlarmDetailsViewController alloc]init];
[alarmDetailsViewController setPassAlarmsArrayDelegate:self];
}
//....
//My #required protocol method which never gets called since my delegate is nil
-(void) passAlarmsArray:(NSMutableArray *)theAlarmsArray
{
alarmsTableArray = theAlarmsArray;
NSLog(#"alarmsTableArray contains: %#", alarmsTableArray); // Never gets called due to delegate being nil
NSLog(#"theAlarmsArray contains: %#", theAlarmsArray); // Never gets called due to delegate being nil
}
#end
I've attempted to set the delegate in a method that fires when a button is pressed in AlarmViewController (as opposed to the viewDidLoad method) but that does not work either.
I'm assuming I've got a logic flow error somewhere here . . . but nearly 2 days of hunting and rebuilds haven't uncovered it. Ugh.
You're setting your delegate in the wrong place, and on a different instance of the controller than the one you will get when you do the segue. You should set the delegate in the prepareForSegue method if you're pushing AlarmDetailsViewController from AlarmViewController
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
AlarmDetailsViewController *alarm = segue.destinationViewController;
alarm.passAlarmsArrayDelegate = self;
}
You really need to understand the life cycle of view controllers, how and when they're instantiated, and when they go away. This is the very heart of iOS programming, and Apple has extensive documentation on it. Reading up on segues would also be very useful. A segue (other then an unwind segue) always instantiates a new instance of the destination controller. So, when your segue is performed, whether directly from a button, or in code, a new (different from the one you alloc init'd directly) details controller is instantiated. Before that segue is performed, prepareForSegue: is called, and that's when you have access to the one about to be created. That's the place to set a delegate or pass any information on to the destination view controller.
Did you try replace (nonatomic, retain) with (nonatomic, strong) since you are using ARC?
Auto-synthesized properties like your alarmDetailsViewController property have backing ivars prefixed with underscores, e.g. _alarmDetailsViewController. Your alarmDetailsViewController ivar (the alarmDetailsViewController declared inside the #interface ... {} block in AlarmViewController.h) is different from the backing ivar of your alarmDetailsViewController property.
Just delete your alarmDetailsViewController ivar and use the #property, preferably through self.alarmDetailsViewController.
I´m having problems declarating my own delegate. Well...thats not exactly true: i have it declarated and, when i build the project, the compiler reports no issues. I declarated it in this way:
I made a file (enviarDatos.h) for declare the protocol:
#protocol enviarDatos <NSObject>
- (void)addItemViewController:(NSMutableArray *)item;
#end
In the Vista2.h (ViewController) file I imported the file enviarDatos.h and declared a property:
#property (nonatomic, weak) id <enviarDatos> delegare;
In the Vista2.m (ViewController) file I use the protocol method:
#interface ViewController : UIViewController <enviarDatos> {
And, finally, in the ViewController.m file I implement the delegates method:
- (void)addItemViewController:(NSMutableArray *)ar {
origen = ar;
}
Does anyone see something wrong? the code of the last function its never executing.
Thanks for your help.
EDIT:
What i need is to change an array in ViewController from Vista2 (another viewcontroller)
Then create delegate property in next view(child view) & set it to self in parent view while pushing or showing child view.
ParentView.m
1.Implement protocol methods
- (void)addItemViewController:(NSMutableArray *)ar
{
origen = ar;
}
2.While showing child view
ChildViewController *child = [[ChildViewController alloc] init];
child.delegate = self;
//present child view
ChildView.h
#property (nonatomic, weak) id <enviarDatos> delegare;
ChildView.m
-(void) anyMethod
{
if([self.delegate respondsToSelector:#selector(addItemViewController:)])
{
[self.delegate addItemViewController:mutableArray];
}
}
Ah, it looks like you are declaring the delegate property in the wrong place.
You should declare the property delegate in enviarDatos.h.
#property (nonatomic, weak) id <enviarDatos> delegate;
Then in Vista2.m you will do something like this...
EnviarDatos *myObject = [[EnviarDatos alloc] init];
myObject.delegate = self;
This then sets up the EnviarDatos object and assigns the Vista2 object as the delegate.
Now, in EnviarDatos.m you can run...
[self.delegate addItemViewController:someObjectArray];
And this will then run that code in the Vista2 object.
Delegates are used for calling back to objects that create them (or some other objects). If you create an object and then want to run a method in it then you won't need a delegate.
Can you say at what condition addItemViewController is invoked?
You seem to be on the right track, but are you sure you are setting the delegate as
[yourObject setDelegate: self];
Have you tried debugging it? Does the debugger pause at addItemViewController if you set a breakpoint there? Can you confirm the delegate is not null inside the method? I may post some code but your seems to be right except for the assigning of delegate, I think you should check it.