Passing Objects Between Classes - iphone

In my AppDelegate, I download some data from the internet and store it into an array. I want one of my ViewControllers to access that array. How would I go about in doing so? Is this a good situation to implement a delegate or a protocol? If so, can someone recommend a good tutorial for that?
Thank you
EDIT:
Please note that the data refreshes upon each launch so there is no need for Core Data or plists. Furthermore, the data are custom objects which I created so they can't be stored in a plist for example.

You have 2 options:
Implement a delegate protocol
Use NSNotifications
The advantages/disadvantages of each is set out well in this question and answer:
Delegates v Notifications
As notifications are easier to implement and may well be sufficient for your needs, you can implement it with the following steps:
In the class where you download the data:
When the data has been downloaded and the array populated, include the following lines:
NSDictionary *dict = [NSDictionary dictionaryWithObject:array forKey:#"Data"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"DataDownloaded" object:self userInfo:dict];
In the class where you want to receive the data:
2.1 Add the following line to your viewDidLoad method:
`[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dataDownloaded:) name:#"DataDownloaded" object:nil];
2.2 Create the dataDownloaded selector:
(void)dataDownloaded:(NSNotification *)note {
NSDictionary *dict = note.userInfo;
NSArray *dataArray = [note.userInfo objectForKey:#"DataDownloaded"];
2.3 Add the following line to dealloc and viewDidUnload:
[[[NSNotificationCenter defaultCenter] removeObserver:self];

You can store data in plist file and use it in all view controllers. This way, you need not worry about the size of data (as you will load it on demand and free it immediately).

if you want to store your array in delegate then in any view you have to create reference of your delegate and you can access your array in that view like :
in your other view you have to write : in .h file
#import "YourDelegateFile.h" and declare a varialble
YourDelegateFile *appDelegate ;
in your .m file :
- (void) viewDidLoad
{
appDelegate = (YourDelegateFile *)[UIApplication sharedApplication] delegate];
NSArray *aArray = [appDelegate yourArrayName]; //yourArrayName is an array that you have declare in delegate
}
hope it helps you.

you just need to access the data stored within the appdelegate. I dont think this is the best solution to your problem but in order to do things the way you want.
so declare you property in your Appdelegate .h file
NSMutableArray* myArray_;
then add a property to the same file
#property (nonatomic, retain) NSMutableArray* myArray;
in the .m file
make sure you synthesize your property
#synthesize myArray = myArray_;
somewhere in your appdelegate .m file you will set the value
then, elsewhere in your code you can access the property in the appdelegate like so
MyAppDelegate *appDelegate = (MyAppDelegate *)[UIApplication sharedApplication].delegate
NSMutableArray* localArray = appDelegate.myArray;
Note, for good encapsulation you should really use an NSArray but i used mutable to keep the code short.
Also, using the appdelegate as a global store for program data is not a good idea, it breaks a lot of rules you shouldnt break, single responsibility principle being a good one to start with. You should ideally be storing application data in a dedicated class, perhaps a singleton or for much better testability a single instance class served by a factory class. This way you data is accessible from a known well defined entity, it is testable and it honours good design principles

You can send notification if app delegate got new data and all interested controllers will know that they need to update views. For this you can use NSNotificationCenter. For example
- (void)newDataLoaded {
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:arrayOfData forKey:#"data"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"data updated notification name" object:nil userInfo:userInfo];
}
If some controller interested in data updates it should subscribe for this notification as soon as possible:
- (void)viewDidLoad {
...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dataUpdatedNotificationHandler:) name:#"data updated notification name" object:nil];
...
}
Do not forget to unsubscribe from notifications if you don't need it. For this use [[NSNotificationCenter defautCenter] removeObserver:self] in viewDidUnload and dealloc methods.
- (void)dataUpdatedNotificationHandler:(NSNotification*)notification {
NSArray *data = [[notification userInfo] objectForKey:#"data"];
// update your view here
}

Related

Get download progress from different ViewController

I have a VideoListController. It has list of videos with download button. When I press the download button, the control is transferred to DetailViewController. In DetailViewController, I am using AFNetworking to download file.
If I go back to VideoListController from DetailViewController. How can I know progress of download or when download gets completed from VideoListController. I want to know this because based upon that I will reload the list to show play button instead of download.
I think that the best solution for your problem would be to create a custom delegate protocol that the DetailViewController and the VideoListController can use to communicate with each other. Check this post for additional information How to use custom delegates in Objective-C
In a nutshell the strategy is the following:
1. The DetailViewController defines a delegate protocol that it uses to pass events to its delegate
2. The VideoListController becomes the delegate to that it knows whenever an upload has progressed or been completed
3. The VideoListController keeps track of which DetailViewControllers have completed the download
Here is come code:
DetailViewController.h:
#class DetailViewController;
#protocol Delegate <NSObject>
- (void) detailViewController: (DetailViewController *) theDetailViewController didFinishDownloadingVideoWithResults:(BOOL)successful;
#end
#property (nonatomic, weak) id<DetailViewController> delegate;
DetailViewController.m:
Whenever a download is complete do the following:
if ([[self delegate] respondsToSelector:#selector(detailViewController:didFinishDownloadingVideoWithResults:)]){
[[self delegate] detailViewController:self didFinishDownloadingVideoWithResults:YES];
}
Now, in the VideoListController.m make sure you establish yourself as the delegate of the DetailViewController.
[theDetailViewController setDelegate:self];
And implement the delegate method. You can for instance have a dictionary that defines which DetailViewControllers have completed the download:
- (void) detailViewController: (DetailViewController *) theDetailViewController didFinishDownloadingVideoWithResults:(BOOL)successful{
detailViewControllersDownloadInformation[theDetailViewController] = #(successful);
}
Now, whenever you need to check if a DetailViewController did indeed complete a download, all you have to do is check that dictionary
if (detailViewControllersDownloadInformation[theDetailViewController] && detailViewControllersDownloadInformation[theDetailViewController] == #(YES)){
// Did download a video
}
Keep in mind that the solution I provide will only let you know if the download has been completed. If you also want to keep track of the progress you need to pass that as an additional parameter in the delegate. We are also assuming that you keep all of the DetailViewControllers in memory. If you release and reuse them you will need to keep track of which element was downloaded in a different data structure.
I got it working using NSNotificationCenter.
In viewDidLoad of DetailViewController, I added this
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(movieDownloadDidFinish)
name:#"MovieDownloadDidFinishNotification"
object:nil];
When download gets complete. I call this:
[[NSNotificationCenter defaultCenter] postNotificationName:#"MovieDownloadDidFinishNotification" object:self];
I remove the observer from DetailViewController when when backbutton in navigation controller is clicked
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"MovieDownloadDidFinishNotification" object:nil];
And added method in DetailViewController that is called when download gets completed.
-(void) movieDownloadDidFinish {
NSLog(#"MovieDownloadDidFinish on DetailViewController");
}
Now in viewDidAppear of VideoListController, I added the observer
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(movieDownloadDidFinish)
name:#"MovieDownloadDidFinishNotification"
object:nil];
And in viewDidDisappear Of VideoListController, I remove the observer
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"MovieDownloadDidFinishNotification" object:nil];
And added method in VideoListController that is called when download gets completed.
-(void) movieDownloadDidFinish {
NSLog(#"MovieDownloadDidFinish On VideoListController");
}
In this way, when DetailViewController is visible, the method movieDownloadDidFinish of DetailViewController is called and similarly movieDownloadDidFinish of VideoListController is called when VideoListController is visible.

Passing Delegate through multiple hierarchy

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)

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

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.

Detecting current iPhone input language

Does anybody knows, can I get the current input language and/or keyboard layout in iPhone application? Can I also get a notification when input language was changed?
In iOS 4.2 and later, you can use the UITextInputMode class to determine the primary language currently being used for text input.
[UITextInputMode currentInputMode].primaryLanguage will give you an NSString representing the BCP 47 language code such as “es”, “en-US”, or “fr-CA”.
You can register for the UITextInputCurrentInputModeDidChangeNotification to be alerted when the current input mode changes.
(You might also be interested in the "Getting Your Apps Ready for China and other Hot New Markets" WWDC session, and Internationalization Programming Topics.)
You can ask current first responder (UITextField, UISearchBar, etc.) via UIResponder method textInputMode:
// assume you have instance variable pointing to search bar currently entering
UITextInputMode *inputMode = [self.searchBar textInputMode];
NSString *lang = inputMode.primaryLanguage;
You can add an observer to the default notification center:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(inputModeDidChange:)
name:#"UIKeyboardCurrentInputModeDidChangeNotification"
object:nil];
This method prints the currently selected input language (like "en_US" or "de_DE"):
- (void)inputModeDidChange:(NSNotification*)notification
{
id obj = [notification object];
if ([obj respondsToSelector:#selector(inputModeLastUsedPreference)]) {
id mode = [obj performSelector:#selector(inputModeLastUsedPreference)];
NSLog(#"mode: %#", mode);
}
}
BUT: All the above is not documented and you should not use it in shipping code!
From the Apple Reference Library - "Getting the Current Language and Locale":
NSUserDefaults* defs = [NSUserDefaults standardUserDefaults];
NSArray* languages = [defs objectForKey:#"AppleLanguages"];
NSString* preferredLang = [languages objectAtIndex:0];
In line with the top answers, the following is a generic solution to getting the keyboard language whenever it is changed. Register for the notification UITextInputCurrentInputModeDidChangeNotification:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(inputModeDidChange:) name:UITextInputCurrentInputModeDidChangeNotification object:nil];
Then in inputModeDidChange
-(void)inputModeDidChange:(NSNotification *)notification {
UIView *firstResponder = [UIView currentFirstResponder];
UITextInputMode *currentInputMode = firstResponder.textInputMode;
NSString *keyboardLanguage = [currentInputMode primaryLanguage];
NSLog(#"%#", keyboardLanguage); // e.g. en-US
}
Where currentFirstResponder is from a category on UIView to get the first responder view, as suggested in this SO post:
// UIView+Additions.h
#import <UIKit/UIKit.h>
#interface UIView (Additions)
+ (id)currentFirstResponder;
#end
Implementation
// UIView+Additions.m
#import "UIView+Additions.h"
static __weak id currentFirstResponder;
#implementation UIView (Additions)
+ (id)currentFirstResponder {
currentFirstResponder = nil;
// This will invoke on first responder when target is nil
[[UIApplication sharedApplication] sendAction:#selector(findFirstResponder:)
to:nil
from:nil
forEvent:nil];
return currentFirstResponder;
}
- (void)findFirstResponder:(id)sender {
// First responder will set the static variable to itself
currentFirstResponder = self;
}
#end
The way I would do it is as follows:
Register your ViewController as a listener to UIApplicationDidBecomeActiveNotification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
In applicationDidBecomeActive handler, check the current language using [NSLocale preferredLanguages] and act upon it accordingly.
This approach gives you what you want and is totally shippable without having to use private API.