Passing NSMutableArray between classes - ios

i'm new to ios.
I have a ViewController class that scan a barcode and save the result into an NSMutableArray. Then i want to show the content of the Array in TableView, so need to use that Array in another class.
CODE :
APPDELEGATE.H
#property (nonatomic, strong) ViewController *objViewCont;
APPDELEATE.M
#synthesize objViewCont;
VIEWCONTROLLER.H
#property (nonatomic, retain) NSMutableArray *dataSource;
VIEWCONTROLLER.M
#synthesize dataSource;
...
NSString data = //result of scanning
[dataSource addObject:testData];
TABLEVIEWCONTROLLER.M
#import "ViewController.h"
#import "AppDelegate.h"
....
- (void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
self.navigationItem.rightBarButtonItem = self.editButtonItem;
AppDelegate *objAppDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
// how to populate table with dataSource ????
}
Now, using objAppDelegate.objViewCont.dataSource i have access to the Array. The problem is that : i don't know how to populate the table using dataSource !

If your issue is with how to display the contents of that array in the uitableview, you will need to connect the table to your viewcontroller's data source delegate and uitableview delegate.
Then you can use the delegate methods to specify the number of rows in each section depending on the size of your array, then use another delegate to specify what to display in each cell of that table view.
You can find a lot of interesting tutorials online with a quick google search. That's how I started 😎
Here is an example: http://www.appcoda.com/ios-programming-tutorial-create-a-simple-table-view-app/
You will certainly use a lot of uitableviews in the future. It wouldn't hurt to take the time and learn how they work exactly.
Enjoy coding!

Related

CellForRowAtIndexPath not called - UITableViewCells disappear/are blank

Posting a specific question/answer to a specific problem (unlike the general problems with this method I've seen elsewhere):
I have a UITableView, which is a part of a custom UITableViewController, which I setup and then add to a different view controller.
My UITableView is being loaded, calls all the appropriate setup methods (e.g. numberOfRowsInSection, numberOfSectionsInTableView, etc), but cellForRowAtIndexPath is never called.
I've confirmed that the dataset is being loaded - numberOfRowsInSection is not always zero.
What gives??
The goal appears to be the reuse of a table view's datasource. This can be accomplished by separating the datasource from the view controller. In outline, as follows:
// MyTableViewDatasource.h
#interface MyTableViewDatasource : NSObject <UITableViewDatasource>
#property(strong,nonatomic) NSMutableArray *array;
#end
// MyTableViewDatasource.m
#import "MyTableViewDatasource.h"
#implementation MyTableViewDatasource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)s {
return self.array.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// your cell config logic from the original view controller
// replace any mention of that vc's model array with self.array
}
#end
Now, say ViewControllerA has a tableView, and we want its datasource to be our newly defined datasource...
// ViewControllerA.m
#import "ViewControllerA.h"
#import "MyTableViewDatasource.h"
#interface ViewControllerA ()
#property(strong,nonatomic) MyTableViewDatasource *datasource;
#end
-(void)viewDidLoad {
// create our data and our datasource
// don't have to do this in viewDidLoad, but it needs to be done
// before the table can be seen, anytime after the model is ready
// this "model" in your case is whatever array that holds the data for the table
NSMutableArray *model = [#[#"Moe", #"Larry", #"Curly"] mutableCopy];
MyTableViewDatasource *datasource = [[MyTableViewDatasource alloc] init];
datasource.array = model;
self.tableView.datasource = datasource;
}
Now ViewControllerA, wherever it once modified its model array, should do the same this way...
[self.datasource.array addObject:#"Shemp"];
[self.tableView reloadData];
Hopefully it's clear that ViewControllerB and C and so on can do the same thing, replacing the code that you posted in your answer.
If you are using ARC, then it's very likely that your custom view controller, which is the ultimate owner of your UITableView, it being trashed as soon as you add the tableView to another view.
Try adding the UITableView's view controller to the master/other view controller's, either via a property or through the view hierarchy.
In my case, I simply created a new property for it in the view controller that wanted its table:
#property (strong, nonatomic) MyTableViewController *tvc;
and later assigned it to self when creating it:
self.tvc = [[MyTableViewController alloc] init];
[self.someOtherView addSubview:tvc.tableView];

Connecting data from different ViewController with same parents

So I have 2 different table views that use the same array (the array is originally created in the Role table view, the below one). How can I connect those two?
(Usually I use prepareForSegue to pass the data but since there is no segue, I'm not sure how can I do this)
EDIT 1: Add the location of the array.
What is a Model and why you need it
In most of the cases it's useless to pass data around if you don't have a Data Model. You can store your data using a technique called Data Persistence.
An example of a pattern you could use is MVC.
MVC or model-view controlelr is an software pattern widely using when making iOS Apps. In this architectural pattern your Controllers are a bridge between your View and your Model.
In this specific scenario both UITableViewControllers would use the same Model but they would display this data differently.
Persisting your Model
There are several ways to do that, the way I like the most is a little framework called CoreData, you can see this question for some reference on that.
You can also refer to this question to see the use of Singletons. But keep in mind that singletons alone do not persist the data. You'll have to add some sort of mechanism if you want the data to remain there between app sessions.
Persisting user preferences
The simplest way to store small chunks of data is using NSUserDefaults (but it's only meant to store defaults):
Let's assume you have an array
NSArray* testArray = #[#"first", #"second", #"third"];
You can set it to a key by using
[[NSUserDefaults standardUserDefaults] setObject:testArray forKey:#"myArray"];
You can sync NSUserDefaults using
[[NSUserDefaults standardUserDefaults] synchronize];
Then, anywhere in your app you can read it doing
[[NSUserDefaults standardUserDefaults] objectForKey:#"myArray"]
Passing data through the app
On the other hand you have to pass your data around somehow. To do so you can use formal protocols, specifically delegates.
As per the Apple documentation:
In a delegate-based model, the view controller defines a protocol for
its delegate to implement. The protocol defines methods that are
called by the view controller in response to specific actions, such as
taps in a Done button. The delegate is then responsible for
implementing these methods. For example, when a presented view
controller finishes its task, it sends a message to the presenting
view controller and that controller dismisses it.
Using delegation to manage interactions with other app objects has key
advantages over other techniques:
The delegate object has the opportunity to validate or incorporate
changes from the view controller.
The use of a delegate promotes
better encapsulation because the view controller does not have to know
anything about the class of the delegate. This enables you to reuse
that view controller in other parts of your app.
For more information on passing data through view controllers (the main point of this question) take a look at this SO answer.
You should never use data persistence just to pass data through the app. Neither user defaults nor core data.
Also using singletons is not good choice. All will mess up your memory.
Instead use call backs — either as delegates or blocks.
Or use unwind segues.
I explain delegates and unwind segues here: Passing row selection between view controllers
this example passes index paths, as it is appropriate in that situation, but the passed object might be of any type or size, as only pointers are passes.
if you use the NSUserDefaults on the other side, data is copied and written to the disk — there for data is copied and slowly processed — without any use.
I created a sample app how to pass data from one view controller to another view controller in another tab bar branch.
click to enlarge
TabBarController
We need to intercept the section of view controllers to set up some callback mechanism. In this case I am using blocks, but delegate would work as-well.
UITabController has a purely optional delegate. I create a subclass of UITabBarController to serv as it's own delegate, but actually a separate delegate should work in the same way.
#import "GameTabBarController.h"
#import "RoleViewController.h"
#interface GameTabBarController () <UITabBarControllerDelegate>
#end
#implementation GameTabBarController
-(void)viewDidLoad
{
[super viewDidLoad];
self.delegate = self;
}
-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
if ([viewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *navController = (UINavigationController *)viewController;
if ([navController.topViewController isKindOfClass:[RoleViewController class]]) {
RoleViewController *rvc = (RoleViewController *)[navController topViewController];
[rvc setSelectedRole:^(Role *role) {
UIViewController *viewController = self.viewControllers[0];
[viewController setValue:role forKey:#"role"];
[self setSelectedIndex:0];
}];
}
}
return YES;
}
#end
I set the initial tab bar controller to this sub class
Role, RoleDatasource and RoleViewController
The RoleViewController displays a list of Roles, but the datasource and delegate for it's table view are a separate class that I add to the role view controller scene in the storyboard, where i also were it up.
Role
#interface Role : NSObject
#property (nonatomic,copy, readonly) NSString *name;
-(instancetype)initWithName:(NSString *)name;
#end
#import "Role.h"
#interface Role ()
#property (nonatomic,copy) NSString *name;
#end
#implementation Role
- (instancetype)initWithName:(NSString *)name
{
self = [super init];
if (self) {
_name = name;
}
return self;
}
#end
RoleDatasource
#import <UIKit/UIKit.h>
#class Role;
#interface RoleDatasource : NSObject <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, copy) void(^roleSelector)(Role *role);
#end
#import "RoleDatasource.h"
#import "Role.h"
#interface RoleDatasource ()
#property (nonatomic,strong) NSArray *roles;
#end
#implementation RoleDatasource
- (instancetype)init
{
self = [super init];
if (self) {
_roles = #[[[Role alloc] initWithName:#"Magician"], [[Role alloc] initWithName:#"Soldier"], [[Role alloc] initWithName:#"Maid"]];
}
return self;
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.roles.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"RoleCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
cell.textLabel.text = [self.roles[indexPath.row] name];
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.roleSelector(self.roles[indexPath.row]);
}
#end
RoleViewController
#import <UIKit/UIKit.h>
#class Role;
#interface RoleViewController : UIViewController
#property (nonatomic, copy) void(^selectedRole)(Role *role);
#end
#import "RoleViewController.h"
#import "RoleDatasource.h"
#interface RoleViewController ()
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#end
#implementation RoleViewController
- (void)viewDidLoad {
[super viewDidLoad];
RoleDatasource *roleDataSource = (RoleDatasource *)[self.tableView dataSource];
[roleDataSource setRoleSelector:^(Role *role) {
self.selectedRole(role);
}];
}
#end
PlayViewController
As soon as a role is selected on the role view controller we want to tell our tab bar controller to switch to the game view controller and show the selected role there, see the code for the tab bar controller.
The GameViewController is just a simple view controller subclass that has a property to hold a role and if a role is set, it will displays it name.
#import <UIKit/UIKit.h>
#class Role;
#interface PlayViewController : UIViewController
#property (nonatomic, strong) Role *role;
#end
#import "PlayViewController.h"
#import "Role.h"
#interface PlayViewController ()
#property (weak, nonatomic) IBOutlet UILabel *roleNameLabel;
#end
#implementation PlayViewController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.roleNameLabel.text = (self.role) ? self.role.name : self.roleNameLabel.text;
}
#end
You'll find an example on github.
I think that I should put the array in the Tab bar Controller and connect it to the Role Table view (in order to maintain the behaviour like it is before) and connect it to my new Table view to do what I want to do.
The only problem I can think of is that since my program is small, adding this will not be a big problem. But if I have more vc, it's going to be so much pain.

addObject to NSMutableArray is nil even after initialization?

I have an NSMutableArray declared as property in .h and initialized in viewDidLoad in my SPOCVC .m (UIViewController)...
#property (strong, nonatomic) NSMutableArray* SPOCTrackList;
in viewDidLoad
if ([self SPOCTrackList] == nil) {
self.SPOCTrackList = [[NSMutableArray alloc]init];
NSLog(#"SPOTTrackList INITIALIZED");
}
In a separate VC, I'm trying to pass/addObject to SPOCTracklist...
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SCTrack* selectedTrack = self.trackList[indexPath.row];
[[[SPOCVC sharedInstance]SPOCTrackList]addObject:selectedTrack];
NSLog(#"%lu", (unsigned long)[[[SPOCVC sharedInstance]SPOCTrackList]count]);
So my NSMutableArray is initialized and I can add dummy objects, but why can't I pass it from another VC using singleton or anything, such as...
SPOCVC* spocVC = self.tabBarController.viewControllers[2];
[spocVC.SPOCTrackList addObject:selectedTrack];
Thanks for pointing me in the right direction.
View controllers are only intended to be around while they are on screen. They are not a place to store data. Generally when one view controller talks directly to another view controller that it didn't create, you're doing something wrong.
Move SPOCTrackList to your model and have both view controllers talk to it rather than to each other.
There should never be a "sharedInstance" on a view controller. That's a sure sign that you're abusing the view controller as the model.
What's probably happening in your particular case is that viewDidLoad is running on a completely different SPOCVC than your sharedInstance.
why not use appdelegate to handle this
appdelegate.h
//add property to hold the reference
#property (nonatomic, copy) NSMutableArray *referenceArray;
//share the app delegate
+(AppDelegate *)sharedAppDelegate;
#end
in appdelegate.m
//synthesize the property
#synthesize referenceArray;
//return the actual delegate
+(AppDelegate *)sharedAppDelegate {return (AppDelegate *)[UIApplication sharedApplication].delegate;}
in viewdidload method
//add the delegate
import "appdelegate.h"
//init the array
self.SPOCTrackList = [[NSMutableArray alloc]init];
//Add reference
[AppDelegate sharedAppDelegate].referenceArray = self.SPOCTrackList;
and add anywhere like this
import "appdelegate.h"
[[AppDelegate sharedAppDelegate].referenceArray addobject:object];

Copy a selected row to an array that populates another UITableView in Xcode

Here is my question: I have two UITableViewController that we're going to call. OriginalTableViewController and SecondTableViewController.
The SecondTableViewController is populated by an NSMutableArray and a UISegmentedControl where the users can navigate through a bunch of data and select multiple rows.
What I want to do is to enable the users to select multiple row, click on save button in the navigation bar and then on OK to dismiss the view and go back to OriginalTableViewController which has to be populated by the selected rows of the SecondTableViewController.
I don't know exactly how to proceed since I started to learn how to code like 4 months ago. Should I use delegation? Or anything else? I would appreciate any help.
For your problem, delegation would be the best choice. You define a protocol in SecondTableViewController and implement that protocol in OriginalTableViewController. When the Save button is pressed, the second table notifies the original table with selected data, and the original can pop/dismiss the second and reloads its table.
In SecondTableViewController.h, define the protocol:
#protocol SecondDelegate <NSObject>
#required
- (void) didSelectRows:(NSArray *)rows;
#end
#interface SecondTableViewController : UITableViewController
#property (retain) id<SecondDelegate> delegate;
#end
In OriginalTableViewController, implement the protocol:
.h:
#interface OriginalTableViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, SecondDelegate>
.m:
- (void) didSelectRows:(NSArray *)rows {
// Update the model with selected data and reload. Also pops/dismisses second table.
}
And set the delegate property just before pushing/presenting SecondTableViewController:
SecondTableViewController *second = nil; // instantiate the vc some how
second.delegate = self;
In SecondTableViewController.m, implement the save method:
- (void) save {
NSMutableArray *array = [NSMutableArray array];
for (NSIndexPath *indexPath in [self.tableView indexPathsForSelectedRows]) {
// Populate array with selected objects.
}
[self.delegate didSelectRows:array];
}
Hope this helps.

iPhone iOS 4 Core Data - Program received signal: “EXC_BAD_ACCESS”

I'm trying to create an app based on Apple's example project TheElements, but using Core Data for the model. In Core Data I have 4 related DB tables. In the UI I have several tableViews, each showing rows from a different Db table. Clicking a row in a tableView drills down to items in a related table, shown in another TableView.
Everything is working but the app crashes unexpectedly at random times with the error: Program received signal: “EXC_BAD_ACCESS”. BTW this error only shows in the console when debugging on the device. No error shows when debugging on the simulator. This screen grab shows the contents of the debugger after a crash.
I have no idea how to decipher the debugger. All I can see is the crash seems to stem from the main() function and _PFManagedObjectReferenceQueue is also listed, which leads to guess that I'm doing something wrong with Core Data.
To implement Core Data I'm adding the following to my App Delegate header:
#private
NSManagedObjectContext *managedObjectContext_;
NSManagedObjectModel *managedObjectModel_;
NSPersistentStoreCoordinator *persistentStoreCoordinator_;
#property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator
and adding Apple's default methods for these to App Delegate implementation file.
Then to my Data Source Protocol header I've added:
#property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;`
and my Data Source header files are as follows:
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#import "TableViewDataSourceProtocol.h"
#interface MatchesAllDataSource : NSObject <UITableViewDataSource,TableViewDataSource, NSFetchedResultsControllerDelegate>
{
NSFetchedResultsController *fetchedResultsController;
NSManagedObjectContext *managedObjectContext;
}
#end
When a table cell is clicked I pass the selectedObject as follows:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)newIndexPath
{
// deselect the new row using animation
[tableView deselectRowAtIndexPath:newIndexPath animated:YES];
// get the element that is represented by the selected row.
Match *selectedMatch = [dataSource objectForIndexPath:newIndexPath];
// create an AtomicElementViewController. This controller will display the full size tile for the element
MatchViewController *matchController = [[MatchViewController alloc] init];
// set the element for the controller
matchController.selectedMatch = selectedMatch;
// push the element view controller onto the navigation stack to display it
[[self navigationController] pushViewController:matchController animated:YES];
[matchController release];
}`
Does anyone have any idea what might be causing my crash?
Will someone please point me in the right direction to look for an answer?
Is there a better way to implement Core Data with multiple tableViews?
Will someone point me to a good example of Core Data with multiple tableViews?
I had this problem too with Marcus' code because I changed the UITableViewController to a regular UIViewController and forgot to put in the title. Here is his original code:
- (id)initWithStyle:(UITableViewStyle)style {
if (self = [super initWithStyle:style]) {
self.title = #"Australia";
}
return self;
}
So if you happened to have changed things make sure that you are setting the self.title somewhere so the custom NSArray+PerformSelector can see it when the app is initializing... like in init.
I had this a few days ago. In my case, I was accessing an object that had already been deallocated.
Also I passing the managedObjectContext object from view to view as the user navigates down the navigationController views like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)newIndexPath
{
matchSegmentedController.selectedMatch = selectedMatch;
matchSegmentedController.managedObjectContext = dataSource.managedObjectContext;

Resources