Message Passing with UIView as delegate - iphone

I have written a third party framework that displays a UIView on top of the existing view of the calling app, i.e., the app calls the framework/SDK and the SDK shows a UIView on top of the view that is currently on screen.
This is done by the use of delegates. The app sets the current view on screen as the delegate to the framework. The framework then uses this delegates and adds its own UIView as a subview using the code:
[[self delegate] addSubview:myVc.view];
where myVc is the ViewController in the framework.
Now I need to pass a method back to the calling app saying the view was shown on screen. Since, the delegate is a UIView, how do I pass a message to the calling class?
The reason why I have asked for a UIView delegate is because my UIView takes only a part of screen and I need the other part of the screen to show the remaining part of the app and be active. When I used ViewController as a delegate, it resulted in the other part of the screen being black instead of transparent.
So my question is how do I pass a message to the calling app, which calls the SDK and sets its view as a delegate. Thanks

You can post a notification that the caller listens for or you can accept a block as a parameter from the caller and execute that when the view is shown.

If I understood your question correctly, then you simply need to define protocol :
.h
#protocol YourFrameWorkViewDelegate <NSObject>
- (void)yourFrameWorkViewDidAppear;
#end
#interface YouFrameWorkView : UIView
#property (weak, nonatomic) id <YourFrameWorkViewDelegate> delegate;
#end
.m
#implementation YouFrameWorkView
- (void)didMoveToWindow
{
if ([self.delegate respondsToSelector:#selector(yourFrameWorkViewDidAppear)]) {
[self.delegate yourFrameWorkViewDidAppear];
}
}
#end
or you could call delegate from your VC like
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if ([self.view.delegate respondsToSelector:#selector(yourFrameWorkViewDidAppear)]) {
[self.view.delegate yourFrameWorkViewDidAppear];
}
Of course any app that wants to use you frame work needs to implement that protocol. This would probably be rootVCs view would have to be subclass and implement your protocol. This is standard practice. Hope it helped.
Follow up after your comment:
Masure that view implements your protocol
.h
#interface ViewThatUsesYourFrameWork : UIView <YourFrameWorkViewDelegate>
#end
.m
#implementation ViewThatUsesYourFrameWork
- (void)yourFrameWorkViewDidAppear
{
}
#end
and also confirm that delegate is indeed set.
before you call respondsToSelector
if(!self.delegate)NSLog(#"Delegate is not set")

Related

Common views in viewControllers - Code re-usability

I have few common views in most of my viewControllers. What I noticed is that I can reuse single code for all viewControllers which is absolutely wise. For this I decided to create a class Utils which has static methods like
+(void)createCommonViews:(float)yAxis:(NSString*)text;
In my case common views are three labels and two images.
Problem : I am not able to add these views from Utils. I am wondering how can I send self as a parameter so that I may add the views from Utils. It may be wrong to add views outside the viewController. In that case what can be the solution? Taking all these views in a UIView, setting return type of Utils method as UIView and then adding UIView to viewController (after calling method from viewController) might solve my problem. But what I am looking for is some other solution.
+(void) createCommonViews:(float)yAxis withText:(NSString*) text toTarget:(UIViewController*) target
{
//create views
[target addSuview:view];
}
But I think returning a Uiview and then adding it in the UIViewController afterwards, is a far better solution.
The method you're attempting is to have your view object as a singleton. This is uncommon at best, at worst a crash waiting to happen. Better design is for each of your view controller classes to have its own instance of your custom view, like so:
#interface MyCommonView : UIView
// ...
#end
#interface MyViewController_A : UIViewController {
MyCommonView *commonView;
}
#property (strong, nonatomic) IBOutlet MyCommonView *commonView;
#end
// Meanwhile somewhere else...
#interface MyViewController_B : UIViewController {
MyCommonView *commonView;
}
#property (strong, nonatomic) IBOutlet MyCommonView *commonView;
#end
Create a viewController that acts as a parent view for all your common stuff, call it CommonViewController then implement this in all the viewcontrollers you want it to appear
-(void) viewDidLoad
{
[self.view addSubView:[[CommonViewController alloc] initWithRect:..];
}
Or alternatively using xib files

Call a ViewController method from NSObject Class

I am trying to call a method thats in my ViewController from a NSObject Class thats doing some parsing.
I initally call a connection class I have made wich downloads some data from my server, I then pass this data over to a parser class I have made, now what I am trying to do is pass this data back to the viewcontroller and reload the tableview thats in this view (thats on a navigation stack)
anyway this is causing some errors and I think it might be the way I am trying to call this method thats doing it. here is how I call it.
MyViewController *myViewController = [[MyViewController alloc] init];
[myViewController initFilterArray:filteredArray];
Now I think this is causing an issue because I am allocating a new viewcontroller object? is that right.. not to sure of the terminoligy.. but yea..
the result of which is that reloaddata is only calling
numberOfSectionsInTableView
tableView:numberOfRowsInSection
then thats it.
any help would be appreciated.
UPDATE:
so I am trying to set up a protocol/delegate to see if that fixes my problem.
so in my class.h this is what I am doing
#protocol PassParsedData <NSObject>
#required
- (void) sendMyArray:(NSArray *)modelArray;
#end
//..
id <PassParsedData> delegate;
//..
#property (strong) id delegate;
then in class.m
//..method
[[self delegate]sendMyArray:filteredArray];
//..
so thats my class, then over in my view controller where I want to call this sendMyArray I do this
viewcontroller.h
#import "class.h" //delegates & protocols
//..
interface VehicleSearchViewController : UITableViewController <PassParsedData> {
//..
then i call it like this
viewcontroller.m
//..
- (void)sendArray:(NSArray *)array
{
ICMfgFilterArray = array;
[self.tableView reloadData];
}
One way of doing this would be the recommended approach of delegates and protocols.
Your NSObject declares a protocol. The ViewController actually implements the protocol and sets itself as the delegate. Then the NSObject calls the method (not knowing who implements it). It is a loosely-coupled way to communicate between objects.
I actually recently wrote a blog post on a basic introduction to protocols and delegates if you're interested...
UPDATE
Based on your update above in question.
Don't forget to set your ViewController to be the delegate.
- (void)viewDidLoad {
// State that you will take care of messages from graphView (provided you have the protocol implementation!)
self.yourClass.delegate = self;
}
And the method in your ViewController should match the protocol signature. So in ViewController.m
- (void) sendMyArray:(NSArray *)modelArray {
ICMfgFilterArray = array;
[self.tableView reloadData];
}

Multiple delegates per one object?

I have a UIScrollView that I need to subclass and within the subclass I need to attach the UIScrollViewDelegate so I can implement the viewForZoomingInScrollView method.
Then I have a UIViewController where I need to instantiate an object of this UIScrollView subclass that I created, and I would also like to make the UIViewController a UIScrollViewDelegate for this object so I can implement scrollViewDidZoom in this UIViewController class.
How is it possible to make one object have two delegates? (I know I could easily just have one delegate and just implement both methods there, but for design purposes I'd like to do it the way that I'm mentioning).
Sometimes it makes sense to attach several delegates to a scroll view. In that case you can build a simple delegation splitter:
// Public interface
#interface CCDelegateSplitter : NSObject
- (void) addDelegate: (id) delegate;
- (void) addDelegates: (NSArray*) delegates;
#end
// Private interface
#interface CCDelegateSplitter ()
#property(strong) NSMutableSet *delegates;
#end
#implementation CCDelegateSplitter
- (id) init
{
self = [super init];
_delegates = [NSMutableSet set];
return self;
}
- (void) addDelegate: (id) delegate
{
[_delegates addObject:delegate];
}
- (void) addDelegates: (NSArray*) delegates
{
[_delegates addObjectsFromArray:delegates];
}
- (void) forwardInvocation: (NSInvocation*) invocation
{
for (id delegate in _delegates) {
[invocation invokeWithTarget:delegate];
}
}
- (NSMethodSignature*) methodSignatureForSelector: (SEL) selector
{
NSMethodSignature *our = [super methodSignatureForSelector:selector];
NSMethodSignature *delegated = [(NSObject *)[_delegates anyObject] methodSignatureForSelector:selector];
return our ? our : delegated;
}
- (BOOL) respondsToSelector: (SEL) selector
{
return [[_delegates anyObject] respondsToSelector:selector];
}
#end
Then simply set an instance of this splitter as a delegate of the scroll view and attach any number of delegates to the splitter. All of them will receive the delegation events. Some caveats apply, for example all the delegates are assumed to be of the same type, otherwise you’ll have trouble with the naive respondsToSelector implementation. This is not a big problem, it’s easy to change the implementation to only send delegation events to those who support them.
You don't want an object with 2 delegates. You want to keep your customScrollView keep the responsibility of its own UIScrollViewDelegate functions.
To make your parentVC respond to the delegate methods of UIScrollView as well you will have to make a custom delegate inside your customScrollView.
At the moment a UIScrollViewDelegate function gets called you will also call one of your delegate functions from your custom delegate. This way your parentVC will respond at the moment you want it to.
It will look somewhat like this.
CustomScrollView.h
#protocol CustomDelegate <NSObject>
//custom delegate methods
-(void)myCustomDelegateMethod;
#end
#interface CustomScrollView : UIScrollView <UIScrollViewDelegate>
{
id<CustomDelegate> delegate
//the rest of the stuff
CustomScrollView.m
-(void) viewForZoomingInScrollView
{
[self.delegate myCustomDelegateMethod];
//rest of viewForZoomingInScrollView code
ParentVC.h
#interface CustomScrollView : UIViewController <CustomDelegate>
{
//stuff
ParentVC.m
-(void)makeCustomScrollView
{
CustomScrollView *csv = [[CustomScrollView alloc] init];
csv.delegate = self;
//other stuff
}
-(void)myCustomDelegateMethod
{
//respond to viewForZoomingInScrollView
}
I hope this fully covers your problem.
Good luck.
Short answer: you don't. Delegates are typically a weak one-to-one relationship:
#property (nonatomic, weak /*or assign*/) id<MyViewDelegate> delegate;
Sometimes you will see a "listener" design pattern, which is the one-to-many form of delegates:
- (void) addListener:(id<MyViewListener>)listener;
- (void) removeListener:(id<MyViewListener>)listener;
In your case, there doesn't appear to be a nice public override point in UIScrollView that allows subclasses to specify the viewForZoomingInScrollView. I would avoid making the UIScrollView its own delegate, if possible. You could make the UIViewController the UIScrollViewDelegate and have it provide the viewForZooming. Or you could make an intermediate view subclass which uses UIScrollView, provides the viewForZooming, and forwards the other delegate methods up.
I don't think you can have two UIScrollViewDelegate delegates directly connected to the same object.
What you can do is having the two delegates chain-connected. I.e., you connect one delegate to the other, then have the former forward messages to the latter when it cannot handle them itself directly.
In any case, I think I am missing a bit to fully suggest a solution, namely the reason why you do need a second delegate and cannot do always through one single delegate. In other words, what I think is that there might be alternative designs that would avoid needing two delegates.
Here's another potential problem with what you're trying to do...
Let's say you have two instances of a UIScrollView and one delegate object. In the delegate object, you override scrollViewDidScroll(UIScrollView *): method of the UIScrollViewDelegate protocol.
Inside the method, you want to access the value of the contentOffset property of both scroll views because, perhaps, you have two adjacent collections views, and you're trying to get the index path of the item at the center of the collection view to get the values of properties associated with those two items (think UIDatePicker).
In that case, how do you different between scroll views? The scrollView property only refers to one scroll view; but, even if it referred to both, how do you get the value of their respective contentOffset properties?
Now, you might say, "I can create an IBOutlet for both, and use their assigned references instead of the scrollView property in the delegate method, such as self.collectionViewFirst.contentOffset and self.collectionViewSecond.contentOffset, and ignore the scrollView property of the delegate method.
The problem is this: that property isn't stored. It's only available when the delegate method is called. Why? Because there's only one delegate object, and only one contentOffset property. By scrolling another scroll view, the value of the contentOffset property would change, and not reflect the content offset of any other scroll view except the last one scrolled.
It's bad practice to do what you're trying to do, even if the case (or a case like it) as I described doesn't apply to your situation. Remember: writing code is about sharing code. Incorrect code sends a message to others that diminishes your reputation.

iPhone - webViewDidFinishLoad not called

I have a simple View into IB that contains just a UIWebView and a UIButton.
The webView is retained, connected, not nil, the delegate property os also connected, the Controller is UIWebViewDelegate compliant and set in the .h.
in ViewDidLoad, I use [self.webView loadHTMLString:htmlContent baseURL:nil];
The content loads, but webViewDidFinishLoad is never triggered, neither didFailLoadWithError, neither webViewDidStartLoad.
How can I know when my content has finished loading, as it take a short time, but still a time to load (and I need to do things during that time on display, for an example not showing the button) ?
#interface MyController : UIViewController <UIWebViewDelegate> {
UIWebView* webView;
}
#property (nonatomic, retain) IBOutlet UIWebView* webView;
#end
#implementation MyController
#synthesize webView;
- (void)viewDidLoad
{
[super viewDidLoad];
constructing the htmlString
id a = self.webView.delegate; // for debug
[self.webView loadHTMLString:htmlContent baseURL:nil];
}
#end
EDIT :
I've deleted the whole XIB and built it from scrathc : no more problem. IB sucks sometimes.
You need to implement the UIWebViewDelegate protocol in your class and set the delegate property of self.webView = self. You will need to implement non-optional delegate methods to the class, plus the webViewDidFinishLoad, didFailLoadWithError and webViewDidStartLoad methods.
I've deleted the whole XIB and built it again from scratch : no more problem. IB sucks sometimes.
Just a sort of stab in the dark. As far as your code above is concerned, you still haven't set the responding object to the webView delegate, you have set a pointer to the webView delegate object but when the webView goes to respond to the delegate it will still be nil.
You should probably have: self.webView.delegate = a; but even then I don't know if that will work because I don't know what a is, is it the object that will respond to the delegate call backs?
You have to implement the UIWebViewDelegate protocol in your class and set self.webView.delegate=self;

How to pass variables between 2 view conrollers

I have 2 view controllers now, And it both got tableviews.
When I choose a row in the second tableview (Using didSelectRowAtIndexPath),
and I want to pass the Information I got in the second View to the first View,
I tried to use delegate&protocol, but don't know why, It didn't work.
And I tried to use class method inside the first class, when I got variable in sencond View,
Call the class method inside the first class. The variable successfully pass to first View,
but when I want to set the Lable's text, it still failed..
Can somebody teach me how to do? thanks!
My protocol&delegate.
This is the second view.
#protocol CategoriesViewControllerDelegate;
#interface CategoriesViewController : UIViewController {
TableViewNewAppDelegate *appDelegate;
id <CategoriesViewControllerDelegate> delegate;
}
#property (nonatomic, assign) id <CategoriesViewControllerDelegate> delegate;
#end
#protocol CategoriesViewControllerDelegate <NSObject>
-(void)backstring:(NSString *)String;
#end
In the .m file , synthesize it
#implementation CategoriesViewController
#synthesize delegate;
didSelectRowAtindexPath
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
CategoryData *CateObj = [appDelegate.CateArray objectAtIndex:indexPath.row];
NSString *Strings = [NSString stringWithString:CateObj.CateTitle];
[delegate backstring:Strings];
[self.parentViewController dismissModalViewControllerAnimated:YES];
}
In the first view controller .h file.
#import "CategoriesViewController.h"
#interface DataController : UIViewController <CategoriesViewControllerDelegate>{
.m file
-(void)backstring:(NSString *)String {
NSLog(#"%#",String);
jCateField.text = String;
}
This is how I do my protocol+delegate. Are there something wrong?
btw, I created a Class method in the first view controller, and use the Class method in the sencond view controller, it succesfully pass variable to first view controller.
But the problem is, I can't set my Label's text inside my Class method, even calling Instance method to set text. Is there any way to solve this problem?
The code you provided seems to be correct. In your case you must set :
#property (nonatomic, assign) id <CategoriesViewControllerDelegate> delegate;
correctly to point to the first view controller which conforms to the protocol you defined :
#import "CategoriesViewController.h"
#interface DataController : UIViewController <CategoriesViewControllerDelegate>{
So it seems that you pushed a CategoriesViewController onto a first DataController, you probably missed to do so just before.
// self is the first view controller
// [myCategoriesViewController setDelegate:self]; old fashion
myCategoriesViewController.delegate = self;
[self presentModalViewController:myCategoriesViewController animated:YES];
This can probably solve your issue. Hope this helps.
Also consider let the first controller dismiss the second.
Here is a link to Apple's documentation.
You could just pass the information straight on to your second view controller;
SecondViewController.h
#interface SecondViewController
{
Information *info;
}
#property (nonatomic, retain) Information *info;
#end
SecondViewController.m
#implementation SecondViewController
#synthesize info;
...
#end
And in your didSelectRowAtIndexPath method;
SecondViewController *controller = [[SecondViewController alloc] initWithNibNamed:#"SecondViewController" bundle:nil];
[controller setInfo:YOUR_INFO_OBJECT];
[self.navigationController pushViewController:controller animated:YES];
[controller release];
Import second view controller header file in the first view controller implementation file. Import first view controller header file in second view controller header file.
Create the property (text/label/whatever) in the first view controller.
Create the property of first view controller in the second view controller.
Create the second view controller instance, set the first view controller property to what you need, push controller to the navigation controller. In the second view controller change whatever you want in the first view controller. Instance methods allowed. Do not forget to release first view controller.
Delegate pattern works in that way too.
View controllers are objects. Objects can have methods that can be called from other objects, and they can have instance variables. ("Delegate" is just a fancy term for this.)
There's no inherent reason why passing data between your view controllers should be hard or complicated, so long as the caller has the address of the callee. (And whether or not a given VC has an XIB is irrelevant.)
It sounds like your real problem is not knowing what to do with the data once it's been passed to the callee.
Stupid question: Is "jCateField" actually connected to the label you want to change, or is it nil? If you created the label from code (since you don't have an XIB), you will need to have stored the created label's address into "jCateField" during the view creation.
Can you post the code for as to ho you are displaying the contents when you come back to 1 st view controller.As here if the log gives you proper value then the issue is with the connection (if taken through iboutlet) or with addsubview .
Do you get nil in label or no value (label is hidden).