Sharing a routine between view controllers - ios

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

Related

Where to set delegate = self? Or should I just use a different design pattern?

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
}

iOS MVC implementation with custom views

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.

Clarification on Objective-C Protocol's and Delegation

I am looking for some additional explanation/insight as to how protocol's and delegation work in Objective-C. I have an app that I am working on, which is using a UINavigationController. There is a main page and a settings page, which will allow a user to input some text that will be used as the main pages' Title. I have everything implemented and working, but I just need some clarifications on how it is working.
Here is an example of how things are set up:
#interface MainPageViewController : UIViewController
#end
#interface MainPageViewController() <SettingsControllerDelegate>
// properties
#end
#implementation MainPageViewController
- (void)methodThatSetsTitle(NSString *)title
{
self.title = title;
}
#end
.....
#protocol SettingsControllerDelegate <NSObject>
{
- (void)methodThatSetsTitle(NSString *)title
}
#interface SettingsViewController
#property (weak, nonatomic) id <SettingsControllerDelegate> delegate;
#end
#interface SettingsViewController ()
// properties that will be used for a text field and holding an NSString
#end
#implementation SettingsViewController
- (void)methodThatPassesStringToDelegateProtocolMethod
{
// Code that will set the property for the NSString title
[self.delegate methodThatSetsTitle:self.titleNameProperty];
}
#end
My question is: How is the NSString title from the SettingsViewController actually getting passed to the MainViewController? My thinking is that the 'delegate' property is declared as a SettingsControllerDelegate so it inherently can hold information from the method's that the protocol has. Then obviously in the MainViewController I call that same protocol method, which will just take the parameter and set the current Navigation title to it. It is just a little confusing as to where that parameter and method information is stored for that other method call to take it. Does every time I call the SettingsViewController method, '- (void)methodThatPassesStringToDelegateProtocolMethod', just call the method in the MainViewController?
(Also in my code I have a prepareForSegue method that set's the SettingViewController.delegate to self.)
Any clarification as to how this information is passed and the details as to how it works would be great! I can understand the complexities, but if you can explain it in a way that is holistic and easy to grasp that'd be great. I can understand memory models and such as well so an explanation as to how this would work in memory would be very useful.
Thank you very much!
I think the main thing that you may be looking for is - what exactly is the delegate property? The declaration
id<SettingsViewControllerDelegate> delegate;
says that you are declaring an object (id) that conforms to the SettingsViewControllerDelegate protocol - meaning that it implements the methodThatSetsTitle: method. This can be any object, as long as it conforms to that protocol. So when you do this:
[self.delegate methodThatSetsTitle:self.titleNameProperty];
you are sending a message to that object, whatever it is, to do something with the given NSString.
In your particular case, you are using the Main Page View Controller as the delegate, so the above line of code sends a message from the Settings View Controller to the Main Page View Controller to set its title to the string you are sending as an argument.
In terms of memory, think of this as you would with any other "normal" instance method. The delegate in this case is the Main Page View Controller, so that is presumably on the navigation stack.
Hope this helps!

How to create a class with a UIViewController when the subclass of UIViewController is unknown

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

Container view controller communication with childViewControllers

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).

Resources