I am populating a tableview with the contents of an array. Right now, I create the array in viewdidload and I calculate the number of rows in the delegate method
//in viewdidload
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL: kItemsURL];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:data waitUntilDone:YES];
});
[self.tableView reloadData];
//method called in viewdidload to create array...
- (void)fetchedData:(NSData *)responseData {
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseData //1
options:kNilOptions
error:&error];
NSLog(#" %#",json);
NSArray* latestItems = [json objectForKey:#"items"];
NSLog(#" array:%#",latestItems);
//getItems is a property in .h file
self.getItems = latestItems;
NSLog(#"getItems %#",_getItems); //logs out array ok
int size = [_getItems count];
NSLog(#"there are %d objects in the array", size);//provides correct number
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"getItems %#",_getItems); //logs (null)
int size = [_getItems count];
NSLog(#" %d objects in the array", size);//logs 0
When I count rows in viewdidload after creating the array, I get the correct number, however, when I call count on the array in the delegate method, it returns zero possibly because the tableview is created before Viewdidload is called.
Where should I create the array so that is known by the time numberofrows counts the number of rows in the array?
Edit:
After constructing the array, I save it to a property. However, I have discovered that this property is empty when I then log it to console in the numberofrowsinsection method so the problem seems to lie in how I am storing this array.
Right now, I have a property in the .h file and I've also tried it in the implementation but either way it is not persisting for some reason.
I'm not to familiar with obj-C, but I know you need to initialize your array outside your viewDidLoad() function. The reason why your .count is returning zero, is because your array is acting as a local variable to your viewDidLoad() function. Instead you could initialize the array as field in your UITableViewController class. This is how you would do so in swift, but it applies to obj-C as well:
class YourTableViewController: UITableViewController {
var yourArray = [AnyObject]()
override func viewDidLoad() {
//You can still do any programming to set up values and elements in yourArray[] here
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return yourArray.count
}
//Plus all your other tableView functions...
}
Also if you are passing information to your array between other UIViewController's you can add this function to your class, so every time you come back to your table view it loads the correct table cell count:
override func viewWillAppear(animated: Bool) {
self.tableView.reloadData()
}
I suggest to load the content of your array in viewDidLoad(), that is called once and before the table view use the array. The table view do not load the items before viewDidLoad. Are you doing something much different than this example structure below?
#implementation ViewController {
NSArray *arrayList;
}
- (void)viewDidLoad {
[super viewDidLoad];
arrayList = #[#"item 1", #"item 2"];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
cell.textLabel.text = [arrayList objectAtIndex:indexPath.row];
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return arrayList.count;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
Consider abstracting the creation of your data - the array instantiated in your tableview, into a model instead. This is generally considered to be a better software engineering practice (read : https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html ).
What I would do is have another file as a Class or Struct, and populate the data for the array used in your table view in there. I would also recommend having setter/getter methods in your array class/struct as well. When loading the tableview, we would then grab the data from the class/struct in your tableview viewDidLoad() method. The data from your array would also be available at any other point of your application as well, as it is no longer dependant on the tableview.
Side note : You can consider making the class/struct a singleton as well, if the model is supposed to only get instantiated once.
Related
I want to create 2 arrays, and populate my table view with that arrays. Then, if user click on a cell, that contain object from first array, i want to perform transition to my detail controller "one". Therefore, if user tap on cell, that contain text from second array, i want to perform segue for detail controller "two".
How to achieve that? I can't put tag on array objects and check it.
Edit: or, if NSDictionary suitable for that case, i could use them instead.
instead of having an array of data elements, you could have an array of MyDataClass, which has attributes for your data, and to identify the source.
You can use a single array to populate the table, and as suggested have different methods for populating the table cell based on the source.
Another way to do this would be create a third array with NSDictionary objects with two keys 'tag' and 'data'. 'tag' key will hold the information about which array and 'data' key will hold data from the array.
NSMutableArray *tableArray = [NSMutableArray arrayWithCapacity:0];
for (id obj in array1) {
[tableArray addObject:#{#"tag":#1, #"data":obj}];
}
for (id obj in array2) {
[tableArray addObject:#{#"tag":#2, #"data":obj}];
}
Then use this new array to populate your table
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
....
id obj = [[tableArray objectAtIndex:indexPath.row] objectForKey:#"data"];
....
}
and on didSelectRowAtIndexpath you can check value for 'tag' key to check whether it is from array1 or array2
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
....
NSNumber *tag = [[tableArray objectAtIndex:indexPath.row] objectForKey:#"tag"];
if (tag.intValue == 1) {
//controller 1
}
else if (tag.intValue == 2) {
//controller 2
}
....
}
There are multiple ways of looking to this problem. Firstly, you have to understand that you can populate a table view with only a single array; however this array can be made from multiple arrays.
Without getting ourselves dirty into multiple data structures that might provide a lot of redundancy than efficiency, a simple way would be to check for the array number in tableView:didSelectRowAtIndexPath:.
Example:
- (void) tableView: (UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *) indexPath
{
<Type> obj = tableViewArray[indexPath.row];
if (obj in array1)
{
// ...
}
else
{
// ...
}
}
The tableViewArray is probably array1+array2.
I struggle with tableview datasource and delegate methods. I've a tableview if user navigate to that table view controller I'm calling web service method which is block based service after the request finished successfully reloaded tableview sections but tableView:cellForRowAtIndexPath:indexPath called twice. Here's my code.
viewDidLoad
[[NetworkManager sharedInstance].webService getValues:self.currentId completion:^(NSArray *result, BOOL handleError, NSError *error) {
self.data = result[0];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 4)] withRowAnimation:UITableViewRowAnimationAutomatic];
});
}
but cellForRowAtIndexPath self.data value is null in the first time.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"%#", self.data); // first time it print null
}
So is there any ideas about this? Thank you so much!
Did you initialise the data array in viewDidLoad? If you didn't it will return null.
if you want to avoid two calls to the tableview try this:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if(!data)
return 0;
return yourNumberOfSections;
}
The tableview calls cellForRowAtIndexPath when it needs to render a cell on screen. If the tableview appears before it has any data, self.data will be empty.
In viewDidLoad set [[self.data = NSMutableArray alloc] init] (for example) and in the datasource/delegate methods of UIITableView it should correctly return numberOfRows etc as zero until your web service populates your data.
It sounds like -tableView:cellForRowAtIndexPath: is being called simply because the view is loaded and the table is trying to populate itself before you've received any data from your web service. At that point, you won't have set self.data (whatever that is) to any useful value, so you get null instead. When your web service returns some data, the completion routine causes the relevant sections to be reloaded, and the table draws the data.
I Have a one table view and i have two array. My arrays name AllItems and SpecialItems. I Use segment control. I wantto if segment value is 0 tableview load AllItems Array, When change segment value and value is = 1 than mytableview reload tada but SpecialItems array. Can u help me please. Thanks.
I solved this problem with table tag.
- (IBAction)segmentControlChanged:(UISegmentedControl *)sender {
if (sender.selectedSegmentIndex == 1) {
mytable.tag = 1;
}
else
{
mytable.tag = 0;
}
[mytable reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(tableView.tag==1)
{
return [specialItems count];
}
else
return [allItems count];
}
You could create two data source classes that implement all the UITableViewDataSource methods: one for AllItems and one for SpecialItems. To switch between the two, connect a valueChanged action. In the method that is called, set the data source and reload the table view.
- (void)valueChange:(UISegmentedControl *)sender
{
if (/* condition for all items */) {
self.tableView.dataSource = self.allItemsDataSource;
} else {
self.tableView.dataSource = self.specialItemsDataSource;
}
[self.tableView reloadData];
}
I would personally create an array which the data is loaded from. Put this in your implementation:
NSArray * _tableData
Then in your viewDidLoad just allocate this for the array which we want it to start on.
_tableData = [[NSArray alloc] initWithArray:allItems];
This initially loads the data we will always see as the segment control starts on index 0. We have to set the initial data somewhere so the tableView loads with some data in it.
Then set the number of rows and the cellForRowAtIndex to pick up from the _tableData array
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _tableData.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView_ cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell * cell = [tableView_ dequeueReusableCellWithIdentifier:bCell];
// Here we use the specific array as we would normally
return cell;
}
This step means the tableView will load with the array. Even if the array is empty the view will still load as the number of cells will be zero.
Now in our value changed function we can reset the array as we need to:
- (IBAction)segmentControlChanged:(UISegmentedControl *)sender {
if (sender.selectedSegmentIndex == 1) {
_tableData = allItems;
}
else {
_tableData = specialItems;
}
[self.tableView reloadData];
}
You just need to make sure the segment control changed is linked up in the XIB file (or programatically) and that you reload the table after choosing the array.
This kind of thing is actually really easy to do. I would definitely recommend working it through step by step if you're having trouble. Make sure each step is working before applying the next:
Get the tableView loading with both sets of data individually
Confirm that the segment control is calling the change function when clicked
Then that should do it
In the .m file ClassroomCollectionViewController, I have the following instance variable declared:
#implementation ClassroomCollectionViewController
{
NSMutableArray *students;
}
This array is populated in the following delegate method of the NSURLConnectionDataDelegate protocol, which ClassroomCollectionViewController implements.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if (connection == _getStudentsEnrolledInClassConnection)
{
// Parse the JSON that came in
NSError *error;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:_receivedData options:NSJSONReadingAllowFragments error:&error];
if (jsonArray.count > 0)
{
students = [[NSMutableArray alloc] init];
// Populate the students array
for (int i = 0; i < jsonArray.count; i++)
{
Student *studentInClass = [Student new];
studentInClass.name = jsonArray[i][#"name"];
studentInClass.profile = jsonArray[i][#"profile"];
studentInClass.profileImageName = jsonArray[i][#"profile_image_name"];
[students addObject:studentInClass];
}
}
}
}
In the following delegate method of another protocol, namely the UICollectionViewDelegate, the students array populated above is used to construct the individual cells of the collection view.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
ClassmateCollectionViewCell *myCell = [collectionView
dequeueReusableCellWithReuseIdentifier:#"ClassmateCell"
forIndexPath:indexPath];
UIImage *image;
long row = [indexPath row];
image = [UIImage imageNamed:[students[row] profileImageName]];
myCell.imageView.image = image;
myCell.classmateNameLabel.text = [students[row] name];
return myCell;
}
The problem is that the students array is not yet populated by the time that the second of the two delegate methods above executes, which results in there being no data for the cells in the collection view to display.
The obvious solution to this problem is to delay the execution of the second method until the first one has finished executing (thus ensuring that the students array will be populated by the time the cells in the collection view are constructed). But I couldn't for the life of me figure out how to make this so in this particular context - since I have no control over when the second delegate method is invoked. I have considered using blocks and multithreading in order to solve this, but have failed at coming up with a solution that is relevant for this specific problem.
Could anyone point me in the right direction?
Thanks very much,
Jay
Try this, connect an IBOutlet to collection view and
in your connectionDidFinishLoading: method
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//All other codes for populating `students` array
[self.collectionView reloadData];
}
To delay execution of the collection view, you need to wait until the connectionDidFinishLoading calls.
Its pretty simple you can reload collection view from connectionDidFinishLoading method.
Thats it.
This is design problem. It suppose to go like this (MVC patern):
some service is receiving data by NSURLConnectionDataDelegate sends results to model.
model enforces main thread, updates its data and posts some notification
view controller is registered for model notification and properly reacts on those notifications. Like [self.collectionView reloadData]; or [self.collectionView insertItemsAtIndexPaths: indexPaths]; and so on.
I want to create a TableView in a TableViewController and initialize it with a dic. The number of items in the dic should determine the number of rows of my TableView. In the Initializer, I display it's count and getting: "resource table created with 8 entries." but when the Table built and the method 'numberOfRowsInSection' is called it returns "Now: 0 entries."
I tried making the dictionary static with no success and also tried "#property(strong,nonatomic,retain)". How can I make the instance not to forget it's content?
edit: moved here: Variable set in the TableView initializer is forgotten when numberOfRowsInSection is called
edit 2: Problem solved in the already linked thread, so is this one.
Relevant code snipplets:
// somewhere in ViewController.m:
...
resourceTableViewController = [[ResourcesTableViewController alloc] initWithDictionary:dictionary];
// ResourcesTableViewController.h:
#interface ResourcesTableViewController : UITableViewController
#property(nonatomic,retain)NSDictionary *resourcesAsDictionary;
- (id)initWithDictionary:(NSDictionary*)dic;
// ResourcesTableViewController.m:
- (id)initWithDictionary:(NSDictionary*)dic {
if (self = [super init])
[self setResourcesAsDictionary:dic];
NSLog(#"resource table created with %i entries.",[_resourcesAsDictionary count]);
return self;
}
...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
NSLog(#"Now: %i entries.",[_resourcesAsDictionary count]);
return [_resourcesAsDictionary count];
}