I have a container view controller with 3 child UIViewController subclasses (added with addChildViewController). I want one of my child view controllers to do something when something is dropped from my container view controller onto it. I'm having trouble grasping how this communication should happen. If I try making a delegate, I get an error in my child view controller because I would both subclasses to import each other.
It sounds like you're having a problem compiling your app because of mutual .h files importing each other, right?
Edit: upon reading your question again, I'm not 100% clear on which view controller needs to call which other one. If I mixed up the
roles of parent and child view controller in my solution, just switch
them. The techniques below let you communicate between any two view
controllers (parent and child, sibling and sibling, etc.)
There's a number of ways to handle this. If you want to stay with a delegate pattern, you could simply rewrite the header to avoid the #import in one of the .h files:
ParentViewController.h:
#import "ChildViewController.h"
#interface ParentViewController: UIViewController {
#private
ChildViewController* childVc;
}
- (void) doSomething;
ChildViewController.h
#class ParentViewController; // NOT #import!
#interface ChildViewController: UIViewController {
#private
ParentViewController* parentVc;
}
ChildViewController.m
#import "ParentViewController.h"
This should avoid the circular dependency that keeps your app from compiling.
Now, although the above works, I might choose another solution, for the sake of cleanliness. Use a protocol. The parent can implement the protocol and then the child only needs to have a delegate that implements the protocol:
#import "MyProtocol.h"
#interface ParentViewController: UIViewController<MyProtocol> {
}
- (void) doSomething;
In MyProtocol.h:
#protocol MyProtocol
- (void) doSomething;
#end
Then in ChildViewController.h
#import "MyProtocol.h"
#interface ChildViewController: UIViewController {
#private
id<MyProtocol> delegate;
}
#property (nonatomic, assign) id<MyProtocol> delegate;
And in ChildViewController.m:
[delegate doSomething];
Or, you could avoid using delegates altogether, and communicate between the controllers using NSNotificationCenter, which decouples them a bit, and avoids your compiler circularity (bidirectional dependency).
Here are the Apple docs on NSNotificationCenter
Couldn't you just go:
MyChildViewController *myChildViewController = (MyChildViewController *)[self.childViewControllers objectAtIndex:0];
[myChildViewController doWhatever];
?
That should let you message the child view controller at the first index of the array childViewControllers (which is a property on UIViewController).
Related
I am trying to figure out delegation in iOS. Basically, I have classA which contains methodA. I also have classB which I would like to call methodA from.
To be specific, I have a class called ViewControllerRootHome and class called ViewControllerRootHomeLeftPanel. The ViewControllerRootHome has a method in it called, movePanelToOriginalPosition I would like to call this method from the ViewControllerRootHomeLeftPanel class.
Any help would be greatly appreciated. Ohh forgot to mention I'm still using Objective-C for the project.
I'll give this an attempt.
Let's say you've got a ViewController called ViewControllerA, and another ViewController called ViewControllerB. We want to call a method inside A from B. How are we going to achieve this?
Simple. We will define a protocol inside B that A will comply to. Let me do that right here.
#import ...
#protocol myProtocol; // Declare Protocol
#interface ViewControllerB : UIViewController
#property (nonatomic, weak)id <myProtocol> myDelegate; // Create Delegate property
#end // Notice this is AFTER the #end of the #interface declaration
#protocol myProtocol <NSObject> // Define Protocol
-(void)doSomething;
#end
Okay, now you have defined a protocol called myProtocol that you wish to use inside ViewControllerA
Let us use it there. We will have to do several things: One, comply to the protocol. And two, set our current VC as it's delegate!
#import ...
#import "ViewControllerB" // IMPORT the VC with the Protocol
#interface ViewControllerA : UIViewController <myProtocol> // Conform to Protocl
#property (nonatomic)ViewControllerB *viewControllerB;
#end
Notice I've defined a property of type ViewControllerB. You will need to have a reference to ViewControllerB in some shape or form. This is usually easy to achieve because you normally create an instance of ViewControllerB from ViewControllerA. Otherwise it will need to be set externally or passed to ViewControllerA upon initialization and you set it as a property there.
Inside ViewControllerA.m, set ViewControllerA as it's delegate:
self.ViewControllerB.myDelegate = self;
Now, all you have to do is define the method from the protocol inside ViewController A so it can be called:
-(void)doSomething
{
...
}
This is all you need to do. However, please note that if you have TWO ViewControllers complying to each other's protocols, you will likely have to declare the protocols inside their own header files.
Edit: How to call the method.
If you want to call the method defined inside the protocol. You will do so inside ViewControllerB, like so:
if ([self.myDelegate respondsToSelector:#selector(doSomething)])
{
[self.myDelegate doSomething];
}
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'm completely new to Objective-C, XCode, and iOS development and I'm trying to figure out how to run certain code at startup, after all UI views and controls have been instantiated. I have a generic NSObject that I've added through interface builder by dragging it into my view controller scene. It's defined as follows:
#import <Foundation/Foundation.h>
#interface Controller : NSObject {
IBOutlet UISlider *slider;
IBOutlet UILabel *label;
}
-(IBAction)sliderChanged:(id)sender;
#end
I need to run sliderChanged on initialization. I've tried the following way:
#import "Controller.h"
#implementation Controller
-(id)init {
self = [super init];
if (self){
[self sliderChanged:nil];
}
return self;
}
// More code here
But both my slider and label are nil when this is called. I understand there's a viewDidLoad method within the ViewController class which may be what I need, but I'm not sure how to access the instance of my Controller class (which seems to be instantiated somewhere behind the scenes by the interface builder) from within that method. Should all of this code simply be moved to the ViewController itself? That would seem to make sense, but the design above is what we've been instructed in class, so I'm not really sure how to go about doing this.
After the XIB/Storyboard loader finishes loading all the objects and wiring them up, it sends awakeFromNib to every object that was instantiated from the XIB. So try adding this to your Controller class:
- (void)awakeFromNib {
[super awakeFromNib];
[self sliderChanged:nil];
}
You can find more information in the NSObject UIKit Additions Reference and “The Nib Object Life Cycle” in the Resource Programming Guide.
HOWEVER, if you created Controller as a top-level object, and you didn't connect any outlets to it, then nothing references it after the XIB loader finishes with it, so the system will deallocate it again. That's probably not what you want, so you should connect an outlet in your view controller to the Controller. If you do that (and let's say the outlet is named controller), then you can access it in viewDidLoad in your view controller class:
#import <UIKit/UIKit.h>
#import "Controller.h"
#interface ViewController : UIViewController {
IBOutlet Controller *controller;
}
#end
Implementation:
- (void)viewDidLoad {
[super viewDidLoad];
[self.controller sliderChanged:self];
}
When the views are simple, their IBActions and IBoutlets are in viewcontroller, viewcontrollers assigns respective models to be loaded and viewcontroller get notified when models are prepared.
As My project contains lot of custom views for each viewcontroller, I want to implement actions in custom view itself and set data from controller (ViewController).
I should be able to use the same controllers and models for both iPhone and iPad where only UI changes.
I am concerned about how to pass data from view to viewcontroller and displaying data back on view when model changes?
Can anyone please suggest me to pass data between views <---> viewcontroller (controller) <---> model?
To do this I use Delegate design-pattern. It looks like this :
MyView.h
#protocol MyViewDelegate <NSObject>
- (void)customViewDidSomething;
#end
#interface MyView : UIView
#property (nonatomic, assign) id<MyViewDelegate> delegate
#end
MyView.m
- (void)userDidSomething {
[_delegate customViewDidSomething];
}
MyViewController.h
#import "MyView.h"
// ViewController has to implement the protocol
#interface MyViewController <MyViewDelegate>
#property (nonatomic, retain) IBOutlet MyView myView;
MyViewController.m
- (void)viewDidLoad { // Set the delegate somewhere
_myView.delegate = self
}
- (void)customViewDidSomething {
// Ok VC is aware that something happened
// Do something (tell subview to do something ?)
}
Instead of using different custom views, try using a UIViewController and then use the viewcontroller's view to display your UI. Also, this will also ensure that you will be able to communicate between the views and controller efficiently without confusion.
I have an app where I have a long routine to draw out a pdf document. I need to access this from a number of view controllers but I am not sure how. As the moment the code is copied into each of the VC's .m file which I know is ridiculous. One of the problems is that each VC has a large number of variables that need to be sent to the MakePdf routine and sending data between VCs appears to be problematic (or at least that is what I am beginning to understand).
Any pointers?
This is what I would like:
You should make a class, with a singleton methods (like "+sharedObject") with all the code.
Then you access it with this code :
[[MyClass sharedObject] mySharedMethodForPdf];
http://www.johnwordsworth.com/2010/04/iphone-code-snippet-the-singleton-pattern/
You could make all your view controllers that need access to this method (and any others) a subclass of a class which implements this function. They would then all inherit the make pdf code.
I'd definietly create an abstract UIViewController class that holds the common characteristics, or a protocol at least, something like <PDFMakerDataSource>.
The PDFMaker singleton could be fine, define an activeViewController property on PDFMaker.
So when the VC appears, I'd set that property, then you can call make on PDFMaker, that will use the currently bound VC as data source.
Anyway, why singletons? Why don't just create a PDFMaker object? You can create it with every VC, so every VC should have an instance of it.
Something like:
#interface PDFMaker : NSObject
+(id)pdfMakerWithDataSource:(id<PDFMakerDataSource>) dataSource;
-(void)makePDFwithCompletion:(void(^)(id PDF)) completionBlock;
#end
And the data source, like:
#protocol PDFMakerDataSource <NSObject>
#optional
-(NSString*)fileName;
-(UIImage*)coverImage;
-(NSString*)whateverData;
#end
So in every VC of the world can be now PDFMaker compilant, like:
#interface SomeViewController : UIViewController <PDFMakerDataSource>
#property (nonatomic, strong) PDFMaker *pdfMaker;
#end
#implementation SomeViewController
-(void)viewDidLoad
{
[super viewDidLoad];
self.pdfMake = [PDFMaker pdfMakerWithDataSource:self];
}
// PDFMaker data source implementation (bind to UI for example)
-(NSString*)fileName
{ return self.fileNameTextField.text; }
-(NSString*)coverImage
{ return self.coverImageView.image; }
...
// Make That PDF
-(IBAction)makePDF
{
[self.pdfMaker makePDFwithCompletion:^(id PDF)
{ NSLog(#"Shiny PDF just made: %#", PDF); }
}
#end