In an IPhone app I am making, I have an initialViewController
(1) with a button. Once the button is clicked, it segues to another View Controller
(2), which at that point loads data from a CoreData File and displays it to the user.
My issue is that there is a small delay between the loading of (2) and the actual Display of the data. That is of course because the data takes a little moment to be loaded. I am doing it asynchronously, and my goal is to never show a spinning wheel or a loading screen (more user friendly).
What I want to do is to "preload" the data at (1), not at (2), that way data should have already been loaded by the time (2) loads and should be displayed immediately. I know how to load the data at (1), but I have no idea how to easily pass it along to (2). I can't do it with a segue because my app is actually a bit more complicated than the description above and it's a hassle to do it via segue.
I have heard it is possible to use the "AppDelegate" but as I am new to programming I have no idea how to use it effectively. The online classes I've been following do not give very clear insight on how to use it, so I'm a bit lost.
After seeing your comment on Paras's post, I have a better solution for you:
Create a subclass of NSObject called fileLoader
//in fileLoader.h
#interface fileLoader : NSObject {
}
+(fileLoader *)sharedInstance;
+(void)createSharedInstance;
//add functions and variables to load, store data for use in another class
#end
Then,
//in fileLoader.m
#implementation fileLoader
static id _instance;
+(void)createSharedInstance{
_instance = [[fileManager alloc] init];
}
+(fileManager *)sharedInstance{
return _instance;
}
//other functions for storing, retrieving, loading, standard init function, etc.
#end
Now you can call [fileManager createSharedInstance] to instantiate a file manager that you can use from anywhere by calling functions on [fileManager sharedInstance].
You could use some kind of DataManager object in which you store your data after loading it for easy retrieval anywhere in your app. If you only have to load the data once you could do it when the app starts.
The trick is to make this a singleton object so no matter where in your app you refer to it it will that same instance that already pre-loaded the data. I use these a lot in my project to manage any and all data that I need inside the app.
There are countless examples out there
The AppDelegate would be better suited for passing data from (2) to (1). For loading data into (2) from (1), you can use the same function that you would use to load in (2) because (1) sees (2) as an instance of a viewController. It is simply like this:
//In OneViewController.m
-(void)viewDidLoad:(BOOL)animated{
[super viewDidLoad:animated];
TwoViewController *VCTwo = [[TwoViewController alloc] initWithNibName:#"TwoViewController" bundle:nil];
//note that I am only instantiating VCTwo, I am not displaying it.
}
-(void)loadVCTwoFromVCOne{
[TwoViewController preloadInformation];
//simply call the function to load the data on the instance of the viewController before displaying it
}
-(IBAction)presentVCTwo{
[self presentViewController:VCTwo animated:YES completion:nil];
//when you are ready, you can display at any time
}
Property synthesize the NSMutableArray with you data like this.. in yourNextViewController.h file
#property(nonatomic, retain) NSMutableArray *nextViewArray;
and in yourNextViewController.m file just Synthesize like bellow..
#synthesize nextViewArray;
and then just pass out like bellow..
yourNextViewController *objNextView = [[yourNextViewController alloc]initWithNibName:#"yourNextViewController" bundle:nil];
objNextView.nextViewArray = [[NSMutableArray alloc]init];
objNextView.nextViewArray = yourDataArray;
[objNextView.nextViewArray retain];
[self.navigationController pushViewController:objNextView animated:YES];
[objNextView release];
Also you can pass string or Dictionary instead of Array..
Related
I want to connect to some arbitrary device via wifi from a "connection" view. When pressing "back" and returning to the main menu, I want the connection I created to still exist(so that other views of the app can send/recive messages through it like ssh or telnet). Is it a good idea to create a connection (with say CFNetwork or such) in a separate thread or NSOperation and pass a reference to this thread to the main menu view controller?
You need to design your app in such a way that each separate group of functions are in a separate class. For example, as mentioned above, use a separate class for connections. You can use the Singleton pattern in order to create 1 instance only for your app to use from anywhere.
Also instead of worrying about NSOperations...which would be calls within your class, you can use a well-tested framework and off you go. You'll find it here with examples...
https://github.com/AFNetworking/AFNetworking
#interface NetworkConnections: NSObject
#end
#implementation NetworkConnections
(id)sharedInstance
{
// structure used to test whether the block has completed or not
static dispatch_once_t p = 0;
// initialize sharedObject as nil (first call only)
__strong static id _sharedObject = nil;
// executes a block object once and only once for the lifetime of an application
dispatch_once(&p, ^{
_sharedObject = [[self alloc] init];
});
// returns the same object each time
return _sharedObject;
}
(void) doSomething {
}
#end
Anytime you want to use that class:
[[NetworkConnections sharedInstance] doSomething];
in my app i send a GET to my server and receive some response. I have TavleView and TableViewController classes expect from my main view controller class. I do the parsing in my main ViewController and i want to populate the TableViewCells with the results i receive from the first ViewController. And also i want to display the details of any cell in a new ViewController called DetailViewController. Now any navigation amongst the Views work as how i want..
Well #ilhan çetin based on your request in the comment above here is a deeper explanation of the solution I propose.
First, in case you don't know what is a Shard Data Model, a Shared Data Model is a class that we define in our project and we create a static instance of it. Since that instance is static this means it exists for all our classes all the time. We use this static instance to share information between different classes in our project like what you need here in your question. For example if you feel that you need to send a string value from a class to a class you can put in the Shared Instance an NSString member. Here is how we do the Shared Instance:
The .h file (assuming that my class is named MyDataModel):
#import <Foundation/Foundation.h>
#interface MyDataModel : NSObject
{
NSString *stringToBeShared;
}
#property (nonatomic, retain) NSString *stringToBeShared;
+ (MyDataModel *) sharedInstance;
#end
In the .m file:
#import "MyDataModel.h"
#implementation MyDataModel
#synthesize stringToBeShared;
static MyDataModel *_sharedInstance;
+ (MyDataModel *) sharedInstance
{
if(!_sharedInstance)
{
_sharedInstance = [[MyDataModel alloc] init];
}
return _sharedInstance;
}
#end
From now on in our program, in any class that we need to access this shared string we will #import "MyDataModel.h" and access the string by: [MyDataModel sharedInstance].stringToBeShared. Now that we have a sharing mechanism ready we will move on to see how to populate the table in the other view.
In your code there is a comment that read:
//i want to populate TableViewCells with the messID and the details of each cell(push in new view controller) will contain 'content' above
You are asking for 2 things in this line but only one can be done in this part of the code. We will now populate the table and in the view of the table when you click on a row we will push the details you want.
To be able to populate the table we will need a function that will do so in the same class where the table exists. Let us name it populateTable. Well how will we be able to call this function when we are in a different class, and who will we send it the messID value? To send the messID we will use the Shared Instance, so you will add what is needed to hold the messID value. Now to call populateTable we will use Notification Center (described in the previous answer I posted).
A Notification center is a mechanism used to call functions in different classes. So in the class that contains populateTable you will add a listener to it in the viewDidLoad as follows:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(populateTable) name:#"DoPopulateTable" object:nil];
We will use the string #"DoPopulateTable" to call this listener. Now inside your loop you will do 2 things:
Store the messID in your Shared Instance
Call populateTable using: [[NSNotificationCenter defaultCenter] postNotificationName:#"DoPopulateTable" object:nil userInfo:nil];
By this you have populated your table as you asked. Are we missing something? Yes the complete data that you will push later on. If the data is important and will be needed after you turn the device off then you have to store it in a sqlite3 database. If not (I will demonstrate the not!) you can store the information inside a NSMutableDictionary where the keys of this dictionary are the messID (to be able to retrieve them when you click on the table), and the values of the table are NSArrays where each array conatins all the info you need.
Now when you want to push the information in the view that has the table, you can call each element inside the array using [yourArrayName objectAtIndex:theIndexYouWant].
I hope this helps you, and remember, no headaches at all ;)
go check out WWDC 2011's podcasts, WWDC 2011 - Session 309 - Introducing Interface Builder Storyboarding.
i believe this is the correct video and it will demostrate you exactly what you are looking for. they revamped UITableView's and added a lot of functionality into the IB storyboard in ios5. it will help demonstrate how you can take data in one View Controller and and display it in a second UIViewtable Controller.
I will read the code that provided in your comment above, but in the meanwhile I think this link can help you a lot. It is a way to send information between views, so you can parse your data in one view and send them to another one easily using this technique.
Are there alternatives to "delegates" to pass back data from one controller to another?
Just seems like a lot of work implementing a delegate just to pass back the result from a child controller, back to the parent controller. Is there not another method? Are "blocks" one answer, and if so some example code would be great.
Delegates aren't a lot of work, aren't a lot of code, and are commonly the most appropriate solution. In my opinion they're neither difficult nor messy.
Five lines of code in the child's interface. Before #interface:
#protocol MyUsefulDelegate <NSObject>
- (void)infoReturned:(id)objectReturned;
#end
Inside #interface:
id <MyUsefulDelegate> muDelegate;
After #inteface's #end:
#property (assign) id <MyUsefulDelegate> muDelegate;
One line of code in the child's implementation:
[[self muDelegate] infoReturned:yourReturnObject];
One addition to an existing line of code in the parent's interface:
#interface YourParentViewController : UIViewController <MyUsefulDelegate>
Three lines of code in the parent's implementation. Somewhere before you call the child:
[childVC setMuDelegate:self];
Anywhere in the implementation:
- (void)infoReturned:(id)objectReturned {
// Do something with the returned value here
}
A total of nine lines of code, one of which is merely an addition to an existing line of code and one of which is a closing curly brace.
It's not as simple as a returning a value from a local method, say, but once you're used to the pattern it's super straightforward, and it has the power of allowing you do do all kinds of more complex stuff.
You could use many ways:
Calling a method of the super controller, needs casting maybe
Notifications
Simple Key-Value-Observing
Core Data
Example for for 1.
interface of your MainViewController: add a public method for the data to be passed
- (void)newDataArrivedWithString:(NSString *)aString;
MainViewController showing ChildController
- (void)showChildController
{
ChildController *childController = [[ChildController alloc] init];
childController.mainViewController = self;
[self presentModalViewController:childController animated:YES];
[childController release];
}
Child Controller header / interface: add a property for the mainViewController
#class MainViewController;
#interface ChildController : UIViewController {
MainViewController *mainViewController;
}
#property (nonatomic, retain) MainViewController *mainViewController;
Child Controller passing data to the MainViewController
- (void)passDataToMainViewController
{
NSString * someDataToPass = #"foo!";
[self.mainViewController newDataArrivedWithString:someDataToPass];
}
KVO or notifications are the way to go in many cases, but delegation gives a very good foundation to build upon. If you plan on extending the relationship between the view controllers in the future, consider using delegation.
Blocks are not really relevant to the above, but in short - it is a technique introduced with iOS 4, where you pass around blocks of code as variables/ parameters. It is very powerful and has many uses. For example, here is how you enumerate objects in an array using a block:
[someArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
NSLog(#"obj descriptions is - %#", [obj description]);
}];
The part from the ^ until the } is a block. Note that I've passed it in as parameter. Now, this block of code will be executed for every object in the array (i.e. output will be the description of each object).
Blocks are also very efficient performance-wise, and are used heavily in many new frameworks.
Apple's blocks beginners guide is quite good.
Check out NSNotificationCenter — NSNotificationCenter Class Reference
Folks pay a lot of attention the the V and the C in MVC, but often forget the M. If you've got a data model, you can pass it from one controller to the next. When one controller makes changes to the data stored in the model, all the other controllers that share the same model will automatically get the changes.
You might find using a singleton is practical. Just use it as a central storage for all your shared data.
Then throw in saving the state of your application too;)
I'm trying to create a class which will let me get requested data from a web service. I'm stuck on how to return the values.
// FooClass.m
// DataGrabber is the class which is supposed to get values
dataGrabber = [[DataGrabber alloc] init];
xmlString = [dataGrabber getData:[NSDictionary dictionaryWithObjectsAndKeys:#"news", #"instruction", #"sport", #"section", nil]];
In this example, it's supposed to get the sports news. The problem is that the DataGrabber gets the data asynchronously and ends up hopping from several NSURLConnection delegate methods. How do know in FooClass when data has been received?
The delegate pattern used with a strict protocol is very useful for this (that's how DataGrabber would find out when NSURLConnection is done, right?). I have written a number of Web APIs that consume XML and JSON information this way.
// In my view controller
- (void) viewDidLoad
{
[super viewDidLoad];
DataGrabber *dataGrabber = [[DataGrabber alloc] init];
dataGrabber.delegate = self;
[dataGrabber getData:[NSDictionary dictionaryWithObjectsAndKeys:#"news", #"instruction", #"sport", #"section", nil]];
}
Then in your DataGrabber.h file:
#protocol DataGrabberDelegate
#required
- (void) dataGrabberFinished:(DataGrabber*)dataGrabber;
- (void) dataGrabber:(DataGrabber*)dataGrabber failedWithError:(NSError*)error;
#end
And in DataGrabber.m:
- (void) getData:(NSDictionary*)dict
{
// ... Some code to process "dict" here and create an NSURLRequest ...
NSURLConnection *connection = [NSURLConnection connectionWithRequest:req delegate:self];
}
- (void) connectionDidFinishLoading:(NSURLConnection*)connection
{
// ... Do any processing with the returned data ...
// Tell our view controller we are done
[self.delegate dataGrabberFinished:self];
}
Then make sure that Foo is implements the DataGrabberDelegate protocol methods to handle each case.
Finally, your DataGrabber has a delegate property (make sure you use assign, not retain to avoid retain cycles):
#property (nonatomic, assign) id<DataGrabberDelegate> delegate;
And when the NSURLConnection asynchronous loads are finished inside of DataGrabber, they call back to your UIViewController in the protocol laid out above so that you can update the UI. If it's ONE request, you could theoretically get rid of DataGrabber and put it inside your view controller, but I like to "separate my concerns" - API and View Controller stay separate. It generates an extra layer, but it keeps "text processing code" out of the view controllers (specifically for JSON and XML parsing code).
I've done this many times with success - one other key is that it's good to provide the user with some feedback that a page is loading - turn on the activity indicator in the status bar, show them a UIActivityIndicator, etc., and then when your delegate callback comes back with either success or failure, you get rid of it.
Finally, I've written a more detailed blog post about this: Consuming Web APIs on the iPhone
you could implement notifications for your DataGrabber class that go off any time you receive a certain amount of data (or when the download is finished if you want) and then the notified method (read about Notifications in the documentation) can do any handling you might want.
Note: it'd be helpful if FooClass was the delegate of DataGrabber
I also use notifications for this. Here is a good detailed explanation of how to set this up.
I have an XML reader class which I initialize with a URL
- (id)initWithURL:(NSURL *)url
This class adds objects to an array in the calling class using an instance variable
// in the interface
ViewController *viewController;
// in the implementation
[viewController addObject:theObject];
Now, I initialize my XML reader class, then set the View Controller separately:
XMLController *xmlController = [[XMLController alloc]
initWithURL:url];
xmlController.viewController = self;
My question is whether I should create a new init function which sets the viewController at the same time.
Thanks.
Edit: I forgot to add that my XML reader starts downloading and parsing the class in the init function.
It's entirely up to you. You can see examples of both styles all over Apple's code.
As long as you don't make any assumption about the viewController property's value being constant over time, it should be fine to leave it as-is.
BTW, you might think about refactoring the addObject: logic into a protocol instead of requiring a specific subclass. Something like:
-xmlController:didDecodeObject:
Or whatever makes sense for your XMLController object's logic.
If your init routine is going to cause delegate/controller calls, or set off asyncronous activities (potentially including your downloading) that could message the delegate, then you should include it in the init function.
Otherwise your controller might miss potential delegate messages such as xmlController:didStartConnection that might be called before your initWithURL routine returns.
Also, if the controller/delegate is a required part of the XMLController activities, then you should include it in your init routine.
So yes, in this case I would suggest:
XMLController *xmlController = [[XMLController alloc] initWithURL:url andController:self];