Passing Delegate through multiple hierarchy - iphone

I wrote a custom class, TagsScrollView, which displays tags inside a scroll view.
When the tags are pressed, TagsScrollView relies on its delegate to implement what to do. Almost all the time, this involves:
Changing the tab index to another index
Pushing TagsDetailVC to the current navigation controller.
Right now, this is how my app is structured:
Dotted line indicates a "has" relationship. MainVC has a FeedView, which has a few FeedCellViews, which in turn has a TagsScrollView each.
Solid line indicates a "push" relationship. ImageDetailVc is pushed onto the navController of MainVC.
How can I organize my code such that TagsScrollView's delegate can be pointed at MainVC elegantly?
right now I have defined the following:
TagsScrollView.h
#protocol TagPressedDelegate<NSObject>
#required
- (void)tagPressed:(id)sender forQuery:(NSString *)query;
#end
FeedCellView.m
self.tagsScrollView.tagPressedDelegate = self.tagPressedDelegate
FeedView.m
self.cells[0].tagPressedDelegate = self.tagPressedDelegate
MainViewVC.m
self.feed.tagPressedDelegate = self
....
- (void)tagPressed...
How can I avoid this pattern? What can I do better? Should I have TagsScrollViewDelegate extend ScrollViewDelegate instead?

You can definitely do better, remove the delegation pattern, use blocks.
Add a block based property to your TagsScrollView .h file
#property (copy, nonatomic) void (^tagPressedBlock)(id sender, NSString *query);
in the .m file add the related callbacks
- (void)tagPressed:(id)sender {
if (_tagPressedBlock) {
_tagPressedBlock(sender, self.query); // I'm assuming that the query is your iVar
}
}
assign the property like this
tagsScrollView.tagPressedBlock = ^(id sender, NSString *query) {
// do stuff with those parameters
}
That's for "doing it better"
As for how to pass a tag pressed event to your MainVC class, you should use NSNotificationCenter.
Define the notification name somewhere globally visible, for instance I'd suggest creating a Defines.h file and #including it in your Prefix.pch file.
Anyway, define the notification name:
static NSString *const TagPressedNotification = #"TagPressedNotification";
Next publish that notification when the -tagPressed: is executed and encapsulate the valuable information into the userInfo dictionary:
- (void)tagPressed:(id)sender {
[[NSNotificationCenter defaultCenter] postNotificationName:TagPressedNotification object:nil userInfo:#{#"sender" : sender, #"query" : self.query, #"scrollView" : self.tagScrollView}];
//.. code
}
Next add your MainVC as an observer to that notification:
MainVC.m
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(tagPressed:)
name:TagPressedNotification
object:nil];
}
And implement -tagPressed: method in your MainVC
- (void)tagPressed:(NSNotification *)notification {
id sender = notification.userInfo[#"sender"];
NSString *query = notification.userInfo[#"query"];
TagScrollView *scrollView = notification.userInfo[#"scrollView"];
if (scrollView == myScrollView) { // the one on your mainVC
// do stuff
}
}
Add don't forget to clean yourself out of NotificationCenter's register:
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
easy
edit
I suppose you should also pass the scroll view, which is the sender, since your mainVC contains that scroll view as well. Edited the code
Another edit
Create an enumeration define in your Defines.h file
enum {
TagSenderTypeFeed = 1,
TagSenderTypeImageDetail
};
typedef NSInteger TagSenderType;
When creating a notification add appropriate enum value to your notification's userInfo dictionary #"senderType" : #(TagSenderTypeFeed)

Related

Move a value between UITabBarController /UIViewControllers

I have a project i'm working on which involves 3 tabs in a UITabBarController (all done in a storyboard).
Each tab is running off a different view controller.
I have a button on tab 1 that performs a calculation and returns a result in a text box. I want it so that when I hit calculate, the result is also returned in a text box in tab 2.
I'm not really sure how to pass data between UIViewControllers so any help is appreciated.
as per vshall says you can do this stuff like bellow:-
yourAppdelegate.h
#interface yourAppdelegate : UIResponder <UIApplicationDelegate,UITabBarControllerDelegate>
{
NSString *myCalResult;
}
#property (nonatomic,retain) NSString *myCalResult;
yourAppdelegate.m
#implementation yourAppdelegate
#synthesize myCalResult,
yourCalclass.h
#import "yourAppdelegate.h"
#interface yourCalclass : UIViewController
{
yourAppdelegate *objAppdelegate;
}
yourCalclass.m
- (void)viewDidLoad
{
objAppdelegate = (yourAppdelegate *) [[UIApplication sharedApplication]delegate];
[super viewDidLoad];
}
-(IBAction)ActionTotal
{
objAppdelegate.myCalResult=result;
}
Now you result stored in objAppdelegate.myCalResult you can use this variable in your another tab with creating object of yourAppdelegat. Hope its helps you
You can define a variable in app delegate and store the result in that variable for class one. And once you switch the class you can fetch that value in your class two by creating an instance of your appDelegate and assign it to your textfield.
As Sanjit has suggested, NSUserDefaults is also a very convenient and clean way to achieve this.
Thanks.
If you don't really need to store the computed value but just notify the other controller in tab2 that the value changed, you can use NSNotificationCenter to post an NSNotification.
When you initialize the controller in tab2 you'll need to add it as an observer of the notification.
Something like that:
in tab1:
NSNumber *value = nil; // the computed value
[[NSNotificationCenter defaultCenter]
postNotificationName:#"com.company.app:ValueChangedNotification"
object:self
userInfo:#{#"value" : value}];
in tab2: register as an observer (in init or viewDidLoad methods)
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(valueChanged:)
name:#"com.company.app:ValueChangedNotification"
object:nil];
the method that will be called when the notification is posted:
- (void)valueChanged:(NSNotification *)note
{
NSDictionary *userInfo = note.userInfo;
NSNumber *value = userInfo[#"value"];
// do something with value
}
Don't forget to remove the controller from the observers in viewDidUnload or sooner:
[[NSNotificationCenter defaultCenter] removeObserver:self];

it possible to Pass Data with popViewControllerAnimated?

I came across an interesting problem, i have main ViewController let's call him MainVC with navigationController and i am doing performSegueWithIdentifier from him to Mine second ViewController let's call him SecVC. so when i am trying to do the popViewControllerAnimated i want to pass some data from the SecVC to the MainVc.. i know i can do it with appDelegate Param or with singletons class but my question is : can i do it with more Elegant solution? like i use prepareForSegue and use local parmeters..
Thank you...
The best way to do it is by using a delegate.
//SecVCDelegate.h
#import <Foundation/Foundation.h>
#protocol SecVSDelegate <NSObject>
#optional
- (void)secVCDidDismisWithData:(NSObject*)data;
#end
//SecVC.h
#import <UIKit/UIKit.h>
#import "SecVSDelegate.h"
#interface SecVC : UIViewController
/** Returns the delegate */
#property (nonatomic, assign) id<SecVSDelegate> delegate;
#end
//SecVC.M
...
- (void) dealloc
{
...
delegate = nil
...
}
When ever you popViewControllerAnimated, right after it (or before it) you do this
if(_delegate && [_delegate respondsToSelector:#selector(secVCDidDismisWithData:)])
{
[_delegate secVCDidDismisWithData:myDataObject];
}
And in the MainVC you must be certain that you implement the delegate function
//MainVC.m
- (void)secVCDidDismisWithData
{
//do whatever you want with the data
}
To avoid any warnings you must tell that the MainVC class implements the delegate like this:
//MainVC.h
#import "SecVCDelegate.h"
...
#interface MainVC : UIViewController <SecVCDelegate>
...
secVCInstance.delegate = self;
[self.navigationController pushViewController:secVCInstance];
...
While I agree that the best option is to use Delegate,
but still if any one is looking for something different, he can use NSNotificationCenter as an alternative.
In viewDidLoad of MainVC:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(recvData:)
name:#"SecVCPopped"
object:nil];
}
And add method recvData in MainVC.m
- (void) recvData:(NSNotification *) notification
{
NSDictionary* userInfo = notification.userInfo;
int messageTotal = [[userInfo objectForKey:#"total"] intValue];
NSLog (#"Successfully received data from notification! %i", messageTotal);
}
Now in SecVC, before popping, add this line
NSMutableDictionary* userInfo = [NSMutableDictionary dictionary];
[userInfo setObject:[NSNumber numberWithInt:messageTotal] forKey:#"total"];
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:#"SecVCPopped" object:self userInfo:userInfo];
I would do it in one of the following ways, but I'm not sure if it's elegant enough...
In SecVC, add an #property MainVC *mainVC; Use [self.mainVC setSomeValue:...]; before calling [self.navigationController popViewControllerAnimated:...];
Use [self.navigationController viewControllers]; to find out the MainVC *mainVC, and call [mainVC setSomeValue:...]; before the line of code that pop the ViewController.
Is this what you want?
I simply set up a protocol in the view being dismissed (example in Swift):
protocol ExampleTableViewControllerDismissDelegate {
func didDismiss(withData: Any)
}
var delegate: SearchableTableViewControllerDismissDelegate?
You can then call this when you dismiss/pop your view like this
self.navigationController?.popViewController(animated: true)
delegate?.didDismiss(withData: Any)
Then in the view being dismissed to (the parent in the hierarchy), we can conform to the delegate and essentially get a callback with the data after the view has been dismissed.
//MARK: ExampleTableViewControllerDismissDelegate
func didDismiss(withData: Any) {
//do some funky stuff
}
And don't forget to subscribe to the delegate in the parent view by using
viewController.delegate = self
There is another way to pass data between views including popViewControllerAnimated and it's with a global var instance, so If you modify that Var in your detail view and after do the popViewControllerAnimated, you can call the new data in the viewWillAppear method.
The first step is declare the Global var in main.h
NSMutableArray * layerList;
And now you have to call it in detail view.
SecondView.m
extern NSString *layerList;
SecondView.h
-(void)back{
layerList = #"Value to send";
[self.navigationController popViewControllerAnimated:YES];
}
Now you can use the information in the Master View after detect the pop action.
FirstView.m
extern NSString *layerList;
FirstView.h
-(void)viewWillAppear:(BOOL)animated{
NSLog(#"This is what I received: %#",layerList);
}

iOS Accessing views data from delegate without allocating a new view

I need to change a data (a label) from the app's delegate method ApplicationDidEnterForeground without allocating a new view. The view is called "Reminder", so I imported it into the delegate and I can access its data only if I allocate it (Reminder *anything = [Reminder alloc...etc), but since I want to change the current view loaded I need to have direct access to the view that's already loaded.
How would I do to change the main view's label from the delegate as soon as my application enters foreground?
obs: I know I can do it on -(void)ViewDidLoad or -(void)ViewWillAppear but it won't solve my problem, since it won't change the label if, for example, the user opens the app through a notification box (slide icon when phone is locked). In that case, none of the above methods are called if the app was open in background.
I don't know if I was clear, hope I was. Thank you in advance.
IF you are using storyboards, you can do this to access the current view being seen
- (void)applicationDidEnterBackground:(UIApplication *)application
{
UINavigationController *a=_window.rootViewController;
Reminder *rem = a.topViewController;
rem.label.text=#"test";
}
IF not using story boards
When I create views that I need to access later, I define them as a property, like this
on AppDelegate.h
//#interface SIMCAppDelegate : UIResponder <..........>
//{
//Some variables here
//}
//Properties here
#property (strong, nonatomic) Reminder *reminder;
//Some method declaration here
//eg: -(void) showSomething;
on AppDelegate.m
//#implementation AppDelegate
#synthesize reminder;
so when I alloc/init the view like this
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//codes before here
self.reminder = [[Reminder alloc] init];
self.reminder.label.text = #"OLD LABEL";
//codes after here
}
I will be able to access it again after allocation on other methods, like this
- (void)applicationWillEnterForeground:(UIApplication *)application
{
self.reminder.label.text = #"NEW LABEL";
}
just send a notification from your ApplicationDidEnterForeground: method and receive it on that class where you want to update the label... Like this..
//Your ApplicationDidEnterForeground:
[[NSNotificationCenter defaultCenter] postNotificationWithName:#"UpdateLabel" withObject:nill];
and add observer in it viewDidLoad: of that controller where you want to update label
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateLabel:)
name:#"UpdateLabel"
object:nil];
made your method in same class ...
- (void)updateLabel:(NSNotification *)notification{
update label
}
Might be you can try following code -
NSMutableArray *activeControllerArray = [self.navigationController.viewControllers mutableCopy];
for (int i = 0; i< [activeControllerArray count]; i++) {
if ([[activeControllerArray objectAtIndex:i] isKindOfClass:[Reminder Class]) {
Reminder *object = [activeControllerArray objectAtIndex:i];
//Perform all the task here which you want.
break; //Once found break the loop to do further processing.
}
}

NSnotifications for multiple downloads

I have a parser class and some view controller classes. In the parser class i am sending a request and receiving an asynchronous response. I want multiple downloads, say one per viewcontroller. So i register an observer in each of these classes :
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dataDownloadComplete:) name:OP_DataComplete object:nil];
and then post a notification in :
-(void)connectionDidFinishLoading:(NSURLConnection *)connection method of the parser class.
[[NSNotificationCenter defaultCenter] postNotificationName:OP_DataComplete object:nil];
but then on running the code the first viewcontroller works fine but for the second one onwards after download and parser class posting notification infinitely the code enters the first class's dataDownloadComplete: method although i have specified a different method name in the selector each time. I don't understand what the error might be. Please help. Thanks in advance.
Both view controllers are listening for the notification so both methods should be being called, one after another.
There's a few ways to solve this. The easiest would be for the notification to contain some sort of identifier that the view controller can look at to see if it should ignore it or not. NSNotifications have a userInfo property for this.
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:#"viewController1", #"id", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:OP_DataComplete object:self userInfo:info];
and when you recieve the notification, check to see who it's for :
- (void)dataDownloadComplete:(NSNotification *)notification {
NSString *id = [[notification userInfo] objectForKey:#"id"];
if (NO == [id isEqualToString:#"viewController1"]) return;
// Deal with the notification here
...
}
There's a few other ways to deal with it but without knowing more about your code, I can't explain them well - basically you can specify the objects that you want to listen to notifications from (see how I have object:self but you sent object:nil) but sometimes your architecture won't allow that to happen.
it's better to create a protocol:
#protocol MONStuffParserRecipientProtocol
#required
- (void)parsedStuffIsReady:(NSDictionary *)parsedStuff;
#end
and to declare the view controller:
#class MONStuffDownloadAndParserOperation;
#interface MONViewController : UIViewController < MONStuffParserRecipientProtocol >
{
MONStuffDownloadAndParserOperation * operation; // add property
}
...
- (void)parsedStuffIsReady:(NSDictionary *)parsedStuff; // implement protocol
#end
and add some backend: to the view controller
- (void)displayDataAtURL:(NSURL *)url
{
MONStuffDownloadAndParserOperation * op = self.operation;
if (op) {
[op cancel];
}
[self putUpLoadingIndicator];
MONStuffDownloadAndParserOperation * next = [[MONStuffDownloadAndParserOperation alloc] initWithURL:url recipient:viewController];
self.operation = next;
[next release], next = 0;
}
and have the operation hold on to the view controller:
#interface MONStuffDownloadAndParserOperation : NSOperation
{
NSObject<MONStuffParserRecipientProtocol>* recipient; // << retained
}
- (id)initWithURL:(NSURL *)url Recipient:(NSObject<MONStuffParserRecipientProtocol>*)recipient;
#end
and have the operation message the recipient when the data is downloaded and parsed:
// you may want to message from the main thread, if there are ui updates
[recipient parsedStuffIsReady:parsedStuff];
there are a few more things to implement -- it's just a form. it's safer and involves direct messaging, ref counting, cancellation, and such.

Call method in nested parent controllers

I have a UIViewController that has an UIPopoverController that has an UINavigationController then a UIViewController. How can I from the child UIViewController call a method (eg. -(void)update) in the parent UIViewController? tried many combinations but still didn't work.
In your child:
#interface MyChildController:UIViewController {
MyParentController *parent;
}
#property(nonatomic, assign) MyParentController *parent;
#end
#implementation MyChildController
#synthesize parent;
...
In your parent controller, when instantiating your child:
MyChildController *newChild = [[[MyChildController alloc] initWithNibName:nil bundle:nil] autorelease];
newChild.parent = self;
...
now in your child you have a ref to your parent that you can use. For instance, some method in your child:
- (IBAction)someAction {
[self.parent doSomethingParentsDo];
}
One possible approach would be to use NSNotificationCenter. In the viewDidLoad: method of the parent ViewController, register it as an observer of a certain notification (I'll use #"DummyNotification" as the notification name in my example). Then post that notification from the appropriate method in the child ViewController. The result will look something like this:
ParentViewController.m
- (void) viewDidLoad
{
/* existing code */
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(update) name:#"DummyNotification" object:nil];
}
- (void) update
{
//method body
}
ChildViewController.m
//put this line wherever you want the ParentViewController to call -update
[[NSNotificationCenter defaultCenter] postNotificationName:#"DummyNotification" object:self];
Reference:NSNotificationCenter Class Reference
Also, this question is tagged as iPhone, but Apple's UIPopoverController documentation states that the class is used specifically for iPad, and will not work on other devices. Is this question tagged wrong?
Reference: UIPopoverController Class Reference