Passing an array from model to table view within UITabBarController - ios

I have a simple app that:
Returns data from a server on load
Contains a UITabBarController with 2 items ViewController and TableViewController. The view is the first tab a user sees.
I have a model I am creating with data by calling this:
self.tide = [[TideModel alloc] initWithJSON:userLocationAsString];
I pass a longitude and latitude and it returns json. I have my view then accessing that data and displaying on the screen with no issues. What I am trying to solve now is how to get that data passed on to my TableViewController ?
TableViewController.m: (returns null
- (void)viewDidLoad
{
[super viewDidLoad];
self.tide = [[TideModel alloc] init];
NSLog(#"%#", self.tide.tideSummary);
}
TableViewController.h
#property (strong, nonatomic) TideModel *tide;
Few things
I am initializing my model with initWithJSON
When a user views the tableview the model is already populated with data, so I dont need to resend the JSON (that would be over kill).
Would a segue be needed if I am using data from a single model
initWithJSON: below:
-(id)initWithJSON:(NSString *)location {
self = [super init];
if(self) {
NSString *locationQueryURL = [NSString stringWithFormat:#"http://x/location/%#", location];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:locationQueryURL parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
// Extra code cut out to save space
..................
..................
..................
self.maxheight = [NSString stringWithFormat:#"%#", [dctOfTideSummaryStats valueForKey: #"maxheight"]];
self.tideSummary = [responseObject valueForKeyPath:#"tide.tideSummary"];
return self;
}
What is the best way to use that same data created and initialized from initWithJSON to be used within my TableViewController?
Thoughts?

I wouldn't pass data between two different view controllers in a tab bar controller. They're siblings so they shouldn't need to be tightly coupled to each other.
When you get the data from the server I would persist it somewhere like Core Data.
Then you could pass the NSManagedObjectContext to each view controller and they can access the data from Core Data individually. In a standard view controller you could use a NSFetchRequest to retrieve the data. In the table view controller you could use an NSFetchedResultsController to access the data.
If you use Core Data I would recommend looking at the Master-Detail Xcode project template. Make sure to select 'Use Core Data'. To get started look at the Core Data Programming Guide and the Core Data related sample code on Apple's developer site. Marcus Zarra's Core Data book is also excellent and there's good info on Core Data Libraries and Utilities here.
There are alternatives to Core Data as well. You could read/write to a plist file, you could use NSKeyedArchiver. There are also frameworks to make working with models easier. There are a number of discussions available to help decide which persistence approach to choose.
But with any of these choices I would recommend accessing the model data individually from each VC and not passing it between them because they're siblings. If you have a parent/child VC setup like if you wanted to edit a value in a table view then it would be correct pass the model object from the parent to the child.

Related

NSMutableArray changing old stored data issue

I am parsing JSON and storing that data into NSMutableArray on my homepage. I want to access that data on my other page not after the another, so I am using Singleton Pattern for sharing common data in my homepage. I am storing that NSMutableArray data to Singleton file for common access. In second page i am showing that data in tableview. There is one button which modifies the data but will not change original data i.e homepage arraydata. So I am storing that data into clonearray and updating that clonearray. But when I return back to homepage and in ViewDidAppear after placing breakpoint I am observing my original data is changed.
Please have a look on this issue and correct me where I am doing wrong with my code.
For more reference I added the project link please download and check this: [Project Link][1]
In this project I have created the 2 View Controllers , 1 model class, and one Singleton for common data across all file.
View Controller:
In this file I am parsing JSON and storing in NSMutableArray and also setting the same array data to Singleton file array for common access across all file.
SecondViewController:
In this file I am getting array data from Singleton file and storing in local array.
There is one button to modify data. In modify data function I am modifying the data and storing in the other array for common access (clonearray).
But when I return back to First View Controller and in ViewDidAppear after placing breakpoint, I am observing my original data is changed.
Please check, I don't want my original data to be changed. Please check and suggest me where I am doing wrong with my code.
Your problem because all your property( ViewController->shippingArray, SecondPageController->myshippingarray, PersonDtklSingleton->shippingAddress) point to same array.
You can fix problem by clone array with Deep Copies, to copy original to new array. Make sure all items in original array adopted NSCopying.
SecondPageController
#implementation SecondPageController
...
- (void)viewDidLoad {
[super viewDidLoad];
myshippingarray=[[NSMutableArray alloc]init];
// deep copy
self.myshippingarray = [[NSMutableArray alloc] initWithArray:[[PersonDtklSingleton sharedInstance]shippingAddress] copyItems:YES];
// Do any additional setup after loading the view.
}
...
#end
PersonModel
#implementation PersonModel
#synthesize housernumber,resident,street,city,pincode,defaultAddress;
- (id)copyWithZone:(NSZone *)zone {
PersonModel *copyObj = [PersonModel new];
copyObj.housernumber = [self.housernumber copyWithZone:zone];
copyObj.resident = [self.resident copyWithZone:zone];
copyObj.street = [self.street copyWithZone:zone];
copyObj.city = [self.city copyWithZone:zone];
copyObj.pincode = [self.pincode copyWithZone:zone];
copyObj.defaultAddress = [self.defaultAddress copyWithZone:zone];
return copyObj;
}
#end
If I did't wrong when you update the data it change both of your data since it's referencing the same memory
you need to alloc a new memory and copy the data into a new array
like this
NSMutableArray *newArray=[[NSMutableArray alloc] initWithArray:originalArray copyItems:YES];
You just update the newArray and the original one won't be affected

iOS: How to share data between different views of Tab Bar Controller

My app has two views managed by a Tab Bar Controller. One of the views is Google Map (GMSMapView using their SDK) and the other is a TableView showing a list of the same data. The markers on the map are the same data in the TableView (just alternate presentations of the same data).
I fetch the data from an NSURLSessionDataTask. I'm wondering what is the best way to share that data between the two views. Obviously, I don't want to fetch the data twice for each view. But I'm not sure what is the best practice for making that shared data available/synched between the two views.
A similar question was asked but not answered here.
You can create a model class which holds the map related data in an array/dictionary/custom class objects. You can make this model class as a singleton(can be initialized only once). Both view controllers (i.e the map and table view) can refer to this model to populate date in different views now.
Model Class
-----------
#property (strong, nonatomic) MyCustomDataRepresentationObj *data;
+ (id)sharedModel {
static MyModelClass *sharedModel = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedModel = [[self alloc] init];
});
return sharedModel;
}
-(void)fetchMapDataWithCompletionBlock:(void(^)(id response, NSError *error)onComplete
{
// Check if data is available.
// Note: You can add a refresh data method which will fetch data from remote servers again.
if (!data) {
__weak MyModelClass *weakSelf = self;
// Make HTTP calls here, assume obj is returned value.
// Convert network response to your data structure
MyCustomDataRepresentationObj *objData = [MyCustomDataRepresentationObj alloc] initWith:obj];
// Now hold on to that obj in a property
weakSelf.data = objData;
// Return back the data
onComplete(objData, error);
} else {
onComplete(objData, nil); // Return pre fetched data;
}
}
Now in view controllers you would have to call the model class method which will inturn make the network call(if needed) and returns data in completion block.
View Controller 1
-----------------
-(void)viewDidLoad
{
// This is where the trick is, it returns the same object everytime.
// Hence your data is temporarily saved while your app is running.
// Another trick is that this can be accessed from other places too !
// Like in next view controller.
MyModel *myModelObj = [MyModel sharedModel];
// You can call where ever data is needed.
[myModelObj fetchMapDataWithCompletionBlock:^(id response, NSError *error){
if (!error) {
// No Error ! do whats needed to populate view
}
}];
}
Do the same in other view controller.
View Controller 2
-----------------
-(void)viewDidLoad
{
// Gets the same instance which was used by previous view controller.
// Hence gets the same data.
MyModel *myModelObj = [MyModel sharedModel];
// Call where ever data is needed.
[myModelObj fetchMapDataWithCompletionBlock:^(id response, NSError *error){
if (!error) {
// No Error ! do whats needed to populate view
}
}];
}
Note: I have just jotted down these lines of code here, there might be syntax errors. Its just to get the basic idea.
A UITabBarController act as a Container.
So from your 2 child ViewControllers, you can access the TabBarViewController with the property parentViewController.
So if you want to share the same data with your 2 child ViewControllers, you can fetch and store your data in your UITabBarController. And, from your UIViewControllers, you can access it like this
MyCustomTabBarController *tabBar = (MyCustomTabBarController*)self.parentViewController;
id data = tabBar.myCustomData;
Use Singleton Patterns create a singleton class and initialize singleton instance in your AppDelegate.m this way you can access your singleton class instance from your AppDelegate by using
How about a data fetching object? Make a new class that makes requests for your data bits and stores the results internally.
You then could get the data into your ViewController with a number of different methods:
Direct Reference Associate this object with each ViewController as a property on the ViewControllers before setting the viewControllers property on the Tab Bar Controller.
Your interface to this new class could include the set of fetched results, as well as a method (with a callback when the request finished perhaps) to tell the object to fetch more results.
Notification Center Your object could post notifications when it has more data, and just include a method to start requesting more data.
Delegate + Registration You could create a protocol for objects that want to get told about changes to the data set, make sure all of your necessary ViewControllers conform, and have a delegates NSArray property on your data fetching object. This is far more manual than Notification Center, but it's slightly easier if you need a very robust interface.
Needless to say, there are a lot of ways to handle this, but they all start with designating a class to do the specific task of fetching/storing your data.

Core Data - many ways to add an object

i'm doing some testing of Core Data, let's say i have a mainViewController with a navigationBar and addButton.
Clicking on the addButton will open a detailViewController. When i press save to insert a new Object the detailVieController will close and show the table with the new data inserted.
I can think two different way to do that.
FIRST METHOD - Passing the ManagedObjectContext
In the action of the add button i create an instance of the new detailViewController and i pass the managedObjectContext to it. So will be the save button of the detailViewController that will take care of saving the context and then pop the controller.
This is the method called by the addButton in the MainViewController
-(void)addNewObject{
DetailViewController *detVC = [DetailViewController alloc]initWhit:self.managedObjectCOntext];
[self.navigationcontroller pushViewController:detVC animated:YES];
}
This method is called by the save button in the IngredientViewController
-(void)saveObject{
NSError *error;
if (![self.managedObjectContext save:&error]){
NSLog(#"Error");
}
}
SECOND METHOD - Using a delegate
In the action of addButton i create an instance of DetailViewController, i set it as delegate, so when i press the save button in the DetailViewCOntroller will call the delegate that will pass data to the main controller.
This is the method called by the addButton in the MainViewController
(void)addNewObject{
DetailViewController *detVC = [DetailViewController alloc]init];
detVC.delegate = self;
[self.navigationcontroller pushViewController:detVC animated:YES];
}
This method is called by the save button in the IngredientViewController
-(void)saveObject{
[self.delegate detailVCdidSaveObject];
}
This is the delegate implemented in the mainViewController
detailVCdidSaveObject{
NSError *error;
if (![self.managedObjectContext save:&error]){
NSLog(#"Error");
}
}
------------------------------ Passing the object
Is it best to pass raw data to the DetailViewController and create there the object or it's best to pass the instance of the object to DetailViewController that will take care of settin its data?
For Example
This way i link the object instance of the mainVC to the one DetailVC so i can easilly set its value
-(void)addObject{
DetailViewController *detailVC =[[DetailViewController alloc]init];
detailVC.delegate = self;
self.object = [NSEntityDescription insertNewObjectForEntityForName:#"Object" inManagedObjectContext:self.managedObjectContext];
detailVC.object = self.object;
[self.navigationController pushViewController:detailVC animated:YES];
}
this way i pass raw data and let the detailVC create the instance
-(void)addObject{
DetailViewController *detailVC =[[DetailViewController alloc]initWithName:#"objname"];
[self.navigationController pushViewController:detailVC animated:YES];
}
those code are just pseudocode for educational purpose. all ways works, i just want to know which do you think it's the most correct and why. thanks
I have used the first two methods and in my opinion they are both equally valid (though I personally prefer delegation). However, the third method caused problems if you give the user the option to cancel or go back in a navigation controller. If that happens, you will have an object that you never needed to create.
This sounds like a perfect use case for a NSFetchedResultsController. A NSFetchedResultsController is an object makes displaying data from core data in a UITableView a lot easier. It even tells you when the objects in core data matching a predicate change (insert, delete, update, move).
So the way I would do it is that MainViewController would have a NSFetchedResultsController that provides the data to the UITableView. When you press the add button, it would do what you have in the first method. The DetailViewController will create the new instance, set the values on it then save the managedObjectContext.
Since the MainViewController has the NSFetchedResultsController, it will automatically know that a new object have been created and it can update the UITableView to show it.
The NSFetchedResutsController documentation and the NSFetchedResutsControllerDelegate documentation show you exactly how to use it with a UITableView including code you can copy into your view controller that do the majority of the work.
The actual answer depends on your preference. In my project, I have implemented the first two methods. A definite No for the third method from my side because of same reasons as Kevin mentioned. If the user cancels the operation or some error occurs, then you will have to take care of removing the change (Perhaps write the following code in your didMoveToParentViewController method and cancel method):-
[self.managedObjectContext rollback]
Assuming of course that you do not have any other process modifying that managedObjectContext at the same time.
Now, I prefer the first two methods because :-
The first method allows me to write additional code in saveObject method. Lets say that you want to validate some properties before saving the object. These properties are only present in detailViewController. So, you cannot use a delegate in that situation without explicitly passing each and every property back to delegate function (which can get messy).
Now, assume that you are creating a object in your mainViewController and the detailViewController is only used to populate a field of the object that was created in mainViewController. In such a situation, I would use the delegate method and pass the field back to the mainViewController so that when the user saves the object in mainViewController, then the field values are saved along with it. If the user cancels mainViewController, then the field values are also not saved.

Array + UIView Controller + UITableViewController

I have a UIViewController class and a UITableViewController class. Within the UIViewController class I have an NSMutableArray.
I now have the issue of how to load data into my table view, a separate class, I must access the NSMutableArray I used to populate the previous UIViewController class.
I tried using a delegate to access the array in the UIViewControllerClass however the array had "0 objects" and was NULL
I would appreciate some guidance in the right direction here.
You could have one view controller hold a reference to the other view controller and query the public NSMutableArray on it for data. Aaron suggested this and it might be your best solution.
Or.. you have multiple view controllers trying to access the same set of data. Potentially you have other classes which will want to access this data also. You might want to consider pulling the data out of the view controller and storying it in a neutral location. You could store it in the AppDelegate and then reference the app delegates from any place you need it.
id<UIApplicationDelegate> appDelegate = [UIApplication sharedApplication].delegate;
NSMutableArray *myData = appDelegate.data;
You could also consider pulling all the logic of your data and the data itself into a separate class and use a Singleton It would allow you to access/manipulate the data fairly easy from anywhere.
The last 2 methods would insulate data from user interface controller objects and prevent the need from potentially unrelated objects needing to hold references to one another. Used properly it will reduce code complexity and mage future changes easier to manage.
Create an NSMutableArray property on your UITableViewController class like so:
#interface CustomTableViewController : UITableViewController
#property (strong, nonatomic) NSMutableArray *dataFromOtherClass;
#end
And then when you transition, perhaps like this, you can set the dataFromOtherClass property:
CustomTableViewController *controller = [[CustomTableViewController alloc] initWithNibName:#"CustomTableViewController" bundle:nil];
controller.dataFromOtherClass = myNSMutableArrayData; // <-- Set data like this
[self.navigationController controller animated:YES];
// Or ...
[self presentViewController:controller animated:YES];
// Etc...

Obj - C: Having trouble creating a UITableView that updates cells from an HTTP API in real-time (one at a time)

I am polling an HTTP API - it returns one item at a time, in real-time (about every 4 seconds). As each item is received, I would like a new UITableView cell to be populated. The full list of received items must remain in a class property, I'm guessing an NSMutableArray. What is the best way to initialize an NSMutableArray as a class property, update it as new information comes in, and then use the count to update a new UITableViewCell?
Here's how I'm adding content to an NSMutableDictionary:
NSMutableDictionary *messageContents = [[NSMutableDictionary alloc] init];
[messageContents retain];
[messageContents setValue:messageText forKey:#"text"];
[messageContents setValue:image forKey:#"image"];
[self addMessageToDataArray:messageContents];
Here's the method stuffing objects into the array:
- (void)addMessageToDataArray:(NSArray *)messageDictionary {
[self.messageDataArray addObject:messageDictionary];
NSLog(#"count = %#", [self.messageDataArray count]);
[self reloadTableData];
}
At this point, calling count on the messageDataArray class property crashes the application. I'm very used to working with arrays in Actionscript, Obj-C is obviously totally different. Please explain the method for instantiating an NSMutableArray as a class property, filling it with NSMutableDictionary's and then finding the NSMutableArray count (which will be dynamically updating in real-time) so I can use that info to update a UITableView (on the fly).
Or... tell me I'm being silly and suggest a much easier solution.
From your description I would guess you're not allocating the messageDataArray before using it.
The init function for your table view (controller?) class should have a line like this
messageDataArray = [[NSMutableArray alloc] initWithCapacity:20];
It's also worth checking that you have [messageDataArray release]; in your dealloc method.

Resources