So, simply put, I have the following setup:
UIViewController - this is the main, parentController
Within this main view, I have a UIScrollView at the very top, and a UITableView below it.
The UITableView is populated with core data via NSFetchedResultsController...
UIScrollView (The UIViewController is the delegate of this UIScrollView)
|- Within this, I have 2 UIViews that I show: 1 contains a UISegmentedControl, the other just contains nothing but UILabels
When the segmentedControl's value changes (ie: A user clicks on it), I just want to reload the UITableView's list of rows by issuing a new fetchRequest (by just changing the predicate and refetching) to the NSFetchedResultsController.
However, that's exactly where I began having an issue.
Whenever I issue [tableView reloadData], the top-most UIScrollView (not the UITableView's ScrollView, but the extra one I added to the UIViewController) stop responding.
Before I ever issue [tableView reloadData], scrolling works just fine. After I issue it, the scrollView stops responding, and scrolling stops completely.
I've tried all of the following:
[self.topScrollView setScrollingEnabled:YES]
self.topScrollView.delegate = self;
[self.topScrollView setContentSize:CGSizeMake(640.0f, self.topScrollView.frame.size.height)];
[self.topScrollView setContentOffset:CGPointMake(0, 0)];
I removed the UISegmentedControl completely. What I did instead was put a button at the very top of the view, and just issued [tableView reloadData];. It is when the tableView is reloaded, that the UIScrollView stops responding.
Is this a bug? Am I completely missing something here? The only thing I can even begin to think of is that the UITableView inherits from UIScrollView, and so that when the table reloads, it must be doing something to cause the topScrollView to either lose all sense of contentOffset/Size and/or its delegate or something...
Has anyone ever tried this? Has anyone ever had this issue?
The only way I could solve this was by creating a ContainerView, splitting the header out into its own UIViewController, and then setting that UIViewController as the UIVScrolLView delegate and just handling the entire header there.
While this works - I'm kind of confused, and would really like to know if a better alternative exists...
Edit: Here is a basic code example of what I was doing before I split it out the header into a ContainerView and just loaded it via embededSegue:
-(void)viewDidLoad {
[super viewDidLoad];
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.topScrollView setContentSize:CGSizeMake(640.0f, self.topScrollView.frame.size.height)];
[self.topScrollView setContentOffset:CGPointMake(0, 0)];
}
// .....
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"listItemCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Load the cell with what is in core data at this index path..
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
#pragma mark SegmentedControl Switch
- (IBAction)segmentedControlValueChanged {
NSFetchRequest *aRequest = [[self fetchedResultsController] fetchRequest];
NSPredicate *predicate;
switch(self.segmentedControl.selectedSegmentIndex) {
case 0:
predicate = [NSPredicate predicateWithFormat:#"status = %#", #"incomplete"];
break;
case 1:
predicate = [NSPredicate predicateWithFormat:#"status = %#", #"complete"];
break;
case 2:
predicate = [NSPredicate predicateWithFormat:#"status = %#", #"deleted"];
break;
default:
predicate = [NSPredicate predicateWithFormat:#"status = %#", #"none"];
break;
}
[aRequest setPredicate:predicate];
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[self.tableView reloadData];
// self.topScrollView.delegate = self;
// [self.topScrollView setContentSize:CGSizeMake(640.0f, self.topScrollView.frame.size.height)];
// [self.topScrollView setContentOffset:CGPointMake(0, 0)];
// [self.topScrollView setScrollEnabled:YES];
}
Related
I have a ViewController in which I put another small UIView in the middle where I have placed a TableView but not sure how to display data from an array in this table, any help would be greatly appreciated. I'm trying to load the table using this code:
-(void) animateResults {
_resultsLabel.text = [NSString stringWithFormat:#"You Scored %d", runningScore ];
resultsTable.delegate = self;
resultsTable.dataSource = self;
[self.resultsTable registerClass:[resultsViewCell self] forCellReuseIdentifier:#"resultsListCell"];
[self.resultsTable reloadData];
[self.view layoutIfNeeded];
[UIView animateWithDuration:0.3 animations:^{
_resultsView.frame = self.view.frame;
} completion:^(BOOL finished){
NSLog(#"%#", questionsArray);
}];
}
I am using a Custom Cell in my TableView to load an NSMutableArray. I have tried Using this code for the table view:
-(NSInteger)numberOfSectionsInTableView:(resultsViewCell *)tableView {
//Return number of sections
return 1;
}
//get number of rows by counting number of challenges
-(NSInteger)tableView:(resultsViewCell *)tableView numberOfRowsInSection:(NSInteger)section {
return questionsArray.count;
}
//setup cells in tableView
-(resultsViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
//setup cell
static NSString *CellIdentifier = #"resultsListCell";
resultsViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSDictionary *results = [questionsArray objectAtIndex:indexPath.row];
NSString *resultsName = [results objectForKey:#"answers"];
BOOL correct = [[results objectForKey:#"correct"] boolValue];
if (!correct) {
cell.resultsIcon.image = [UIImage imageNamed:#"BlackIconLock.png"];
}
else{
cell.resultsIcon.image = nil;
}
cell.resultsName.text = resultsName;
return cell;
}
But the _resultsView does not load not sure why. I've got a lot of great help thus far and I really do appreciate it. I've been stuck at this point for about 2 weeks. Please help!
If you are using above xcode 7, you will use UIContainerView middle to ViewController in storyboard, its automatically Embed the UITableView to containerView, example
Try to reload data in animateResults in completion block. something like this,
completion:^(BOOL finished){
NSLog(#"%#", questionsArray);
[self.resultsTable reloadData];
}];
I FINALLY! got this figured out My intention was to have a UITableView load inside of a UIView. I tried to drag and drop the TableView delegate and datasource on the ViewController but every time the app loaded it crashed. So I moved the table view delegate and datasource to the UIView load method here: I tried using this Method to do so.
-(void) animateResults {
//this part of my method actually worked it displayed the final score from a predifined variable.
_resultsLabel.text = [NSString stringWithFormat:#"You Scored %d", runningScore ];
//i tried using this to set my UITableView delegate & dataSource when the method was initiated instead of UIViewController load.
resultsTable.delegate = self;
resultsTable.dataSource = self;
//used this code to register UIViewCell that was nonexistent,
//i declared *CellIdentifier = #"resultsListCell" and it should have
//been *CellIdentifier = #"resultsCell". so i received an error used this code to solve it.
//duh. cell don't exist must not be the cell you created.My Bad.
[self.resultsTable registerClass:[resultsViewCell self] forCellReuseIdentifier:#"resultsListCell"];
//this code worked but after debugging i found out there was no data to load.
[self.resultsTable reloadData];
//was desperate at this point not sure why i put this in.
[self.view layoutIfNeeded];
//this also worked loads my UIView.
[UIView animateWithDuration:0.3 animations:^{
_resultsView.frame = self.view.frame;
} completion:^(BOOL finished){
NSLog(#"%#", questionsArray);
}];
}
So My solution was frustrating in it's simplicity. The reason my app crashed when I tried to drag and drop the dataSource and delegate, was because of the mislabeled "static NSString *CellIdentifier", So make sure your cell identifier is correct! Not knowing this I attempted to load the UITableView dataSource and delegate in the above method. I have since modified the above method to this.
-(void) animateResults {
//populate label
_resultsLabel.text = [NSString stringWithFormat:#"You Scored %d", runningScore ];
//reload table
[self.resultsTable reloadData];
//Load View
[UIView animateWithDuration:0.3 animations:^{
_resultsView.frame = self.view.frame;
} completion:^(BOOL finished){
NSLog(#"%#", questionsArray);
}];
}
This trimmed down code works because I connected the dataSource and delegate to UIViewController. Doing this loads the table when the UIViewController loads so theres no need to do so programmatically. Also, correcting the mislabeled cell identifier eliminated the need for the registerClass variable. Hope this Helps.
I have a TableViewController (favTable.m) and with the help of NSFetchedResultsController it is filled with data extracted from a Core Data Model (1 entity with 2 attributes). I have included a delete button in the navigation bar, that when clicked it is supposed to delete the selected row of the table, as well as the respective object in the core data model. I am using the code below for the delete button but it returns error [favTable delItem]: unrecognized selector sent to instance. What should I fix?
UIBarButtonItem *delButton = [[UIBarButtonItem alloc] initWithTitle:#"Del"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(delItem)];
- (void)delItem:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
self.managedObjectContext = ((ecoAppDelegate *) [UIApplication sharedApplication].delegate).managedObjectContext;
FavoritesInfo*favoritesInfo = [self.fetchedResultsController objectAtIndexPath:indexPath];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[self.managedObjectContext deleteObject:favoritesInfo];
NSError *error= nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
[self.tableView reloadData];
}
You're getting the exception because your method's signature is: delItem:cellForRowAtIndexPath:, not just delItem.
In your case, I don't think you need neither tableView not indexPath to be parameters.
Instead, declare your tableView as a property (IBOutlet as well if done from storyboard).
#property (nonatomic) IBOutlet UITableView *tableView;
Then you have access to it using self.tableView.
As for the selected row, you can use indexPathForSelectedRow:
NSIndexPath *selectedIndexPath = self.tableView.indexPathForSelectedRow;
Now you can rewrite your UIButton's action to:
- (void) deleteSelectedItem {
NSIndexPath *selectedIndexPath = self.tableView.indexPathForSelectedRow;
//Delete the item using your code
}
I have table view set up in the traditional Master/Detail way in an iPhone app. When I click save on the detail screen (after adding the information I want), it switches back to the master list, however, the text that's supposed to appear in the cell in the Master list doesn't appear until I touch the cell with my finger, however, if I touch the cell with my finger it obviously segues to the detail screen and then I have to click "back" to get to the Master list where the cell now has the text that it's supposed to. Interestingly, the table view image is appearing immediately upon save - I don't have to touch the cell to make the image appear in the same way I have to make the text appear.
Clicking the "save" button in the detail screen runs this code. The mnemonicField.text gets saved to the currentTopic and I later set it to be the text that appears in the cell
- (IBAction)save:(id)sender {
[self.currentTopic setTopic:topicField.text];
[self.currentTopic setMnemonic:mnemonicField.text]; //this should appear in cell on master
[self.currentTopic setMood: self.mood];
[self.delegate AddTopicViewControllerDidSave];
}
The Master table view controller is the delegate referred to in the above method. Here is that method.
-(void)AddTopicViewControllerDidSave{
NSError *error = nil;
NSLog(#"saving topick");
NSManagedObjectContext *context = self.managedObjectContext;
if (![context save:&error]){
NSLog(#"Error: %#", error);
}
[self.navigationController popToRootViewControllerAnimated:YES];
}
In cellForRowAtIndexPath, I call another method to setup the cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"TopicCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier forIndexPath:indexPath];
[self configureCell:cell atIndexPath:indexPath];//sets up cell, see below
return cell;
}
Here's configureCell:atIndexPath which sets up the cell. Again, note that both the image and the textLabel.text are set in this method but that I have to touch the cell with my finger to actually make the text appear
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
// Configure the cell...
Topic *topic = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = topic.mnemonic;
NSLog(#"jokemood in configure cell %#", joke.mood);
UIImage *funnyimage = [UIImage imageNamed: #"nslaugh.png"];
UIImage *badimage = [UIImage imageNamed: #"nsbad.png"];
UIImage *crapimage = [UIImage imageNamed: #"nscrap.png"];
UIImage *mehimage = [UIImage imageNamed: #"nsmeh.png"];
UIImage *smileimage = [UIImage imageNamed: #"nssmile.png"];
switch ([topic.mood intValue]) {
case 0:
// do something
NSLog(#" topic table 0");
cell.imageView.image = crapimage ;
break;
case 1:
After some experimentation, I determined that the problem is this line cell.textLabel.text = topic.mnemonic; in the above function. If I take that line out, then the title shows in the cell in the master list immediately upon saving in the detail screen. However, if I take that line out, then when i start the application, the title is not getting assigned when the data's pulled from core data. So, either way there's a problem. If this line cell.textLabel.text = topic.mnemonic; is left in the above function, then I have to touch the cell in the master list (after saving in the detail) for the text to appear, but if I take that line out then the textLabel.text is not getting assigned when the application pulls from core data. Neither option is acceptable.
Do you know why this might be happening?
For the sake of completion here's prepareForSegue in the master view controller where I setup the currentTopic on the destination view controller based on the topic in the master view controller
if ([[segue identifier] isEqualToString:#"AddJoke"]){
MMAddTopicViewController *ajvc = (MMAddTopicViewController *)[segue destinationViewController];
ajvc.delegate = self;
ajvc.mood = nil;
Topic *newTopic = (Topic *)[NSEntityDescription insertNewObjectForEntityForName:#"Topic" inManagedObjectContext: [self managedObjectContext]];
NSMutableSet* relationship = [self.rootObject mutableSetValueForKey:#"subItems"];
NSManagedObject *lastObject = [self.fetchedResultsController.fetchedObjects lastObject];
double lastObjectDisplayOrder = [[lastObject valueForKey:#"displayOrder"] doubleValue];
[newTopic setValue:[NSNumber numberWithDouble:lastObjectDisplayOrder + 1.0] forKey:#"displayOrder"];
[relationship addObject:newJoke];
ajvc.currentTopic = newTopic; ///currentTopic in destination view controller is set to topic from master view controller
I added [self.tableView reloadData]; in the save method in the master view controller, which is called from the detail controller (master serves as the detail's delegate). The Lynda.com tutorial I copied it from didn't have to use self.tableView reloadData, not sure why my code did.
-(void)AddTopicViewControllerDidSave{
NSError *error = nil;
NSLog(#"saving cock");
NSManagedObjectContext *context = self.managedObjectContext;
if (![context save:&error]){
NSLog(#"Error: %#", error);
}
[self.navigationController popToRootViewControllerAnimated:YES];
[self.tableView reloadData];
}
Background: I'm working on my first iOS app that uses a UITableView for data being loaded dynamically from a web source. No, this is not homework.
Problem: When I scroll down the list bounces back to the top.
Things I've Tried:
This suggestion (reducing the size of the table view) works but now I have large space at the bottom of the screen.
I've tried using [tableView reloadData] but this doesn't changing the behavior. I even hooked this up to a button to make sure it was firing after the view was populated.
I also read a few post about contentSize and how to calculate based on the amount of data loaded in the table. This looks like the solution I need but am having trouble getting a clear explanation of how to implement it for my setup.
Code:
I started a new project with single view application template and added a UITableView to the default view in the main storyboard.
In view controller I have the following (relevant) code:
#interface ERViewController ()
#property (nonatomic, strong) NSMutableArray *myArray;
#property (nonatomic,retain) UITableView *tableView;
#end
#implementation ERViewController
{
NSMutableArray *tableData;
}
Here I am loading the data from Parse.com into my table and then calling [self loadView] to get the view to update after the query finishes.
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableArray *returnedData = [[NSMutableArray alloc] init];
PFQuery *query = [PFQuery queryWithClassName:#"Persons"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (PFObject *object in objects) {
//NSLog(#"display name: %#",object[#"firstName"]);
[returnedData addObject:[NSString stringWithFormat:#"%#, %#", object[#"lastName"],object[#"firstName"]]];
}
tableData = returnedData;
// Reload view to display data
[self loadView];
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableItem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
// Configure the cell
cell.textLabel.text = [tableData objectAtIndex:indexPath.row];
NSArray *itemArray = [NSArray arrayWithObjects: #"✔︎", #"?", #"X", nil];
UISegmentedControl *mainSegment = [[UISegmentedControl alloc] initWithItems:itemArray];
mainSegment.frame = CGRectMake(200,0, 100, 43);
self.navigationItem.titleView = mainSegment;
mainSegment.selectedSegmentIndex = [itemArray count];
[mainSegment addTarget:self
action:#selector(mainSegmentControl:)
forControlEvents: UIControlEventValueChanged];
mainSegment.tag = indexPath.row;
[cell.contentView addSubview:mainSegment];
return cell;
}
Sample screen shot:
Since I'm waiting for the data to load I'm not sure where to resize the view and how exactly I need to resize the view.
You should never ever manually call loadView.
Once you have your tableData call reloadData on the tableView.
You should also load you table data in viewDidAppear:, and if you only want to do it once, create a flag to see if its already been loaded. While waiting for the data to load, tableData will be nil so if you query the count of it, you will be messaging a nil object and therefore it would act like returning 0.
Finally you should not be touching the contentSize of the table view, it handles that itself. You just need to make sure that in Interface Builder, the table view's frame is the same as the root views frame.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSMutableArray *returnedData = [[NSMutableArray alloc] init];
PFQuery *query = [PFQuery queryWithClassName:#"Persons"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (PFObject *object in objects) {
//NSLog(#"display name: %#",object[#"firstName"]);
[returnedData addObject:[NSString stringWithFormat:#"%#, %#", object[#"lastName"],object[#"firstName"]]];
}
tableData = returnedData;
// Reload view to display data
[self.tableView reloadData];
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
If you just want a UIViewController with only a UITableView in it, consider using a UITableViewController.
Posted for others dealing with the same problem I had and can't just move to a different view controller.
To solve the problem with a UITableView within a general view controller (not UITableViewController) click the little icon below the view controller that looks like this:
The button is titled "Resolve Auto Layout Issues"
Select "Reset to suggested constraints in View Controller"
This fixed all my scrolling issues since I must have been incorrectly snapping to the auto layout edges.
I am working on app and simply editing the cell text field and at finish editing, I am trying to update coredata but app get stack and some time it give SIGSTOP and sometime it says unable to allocate more memory kinda error, please help me with this.
- (void)textFieldDidEndEditing:(UITextField *)textField
{
if ((textField.tag - 999) > 0) {
NSArray *visible = [self.productTable indexPathsForVisibleRows];
UITableViewCell* cell = [self.productTable cellForRowAtIndexPath:[visible objectAtIndex:(textField.tag - 999)]];
for (UIView* subview in [cell.contentView subviews]) {
if ([subview isKindOfClass:[UITextField class]]) {
textField = (UITextField*)subview;
Product* myProduct = (Product*)[self.productArray objectAtIndex:(textField.tag - 1000)];
[myProduct setValue:[NSNumber numberWithFloat:[textField.text floatValue]] forKey:#"quantity"];
[myProduct setValue:[NSNumber numberWithFloat:[myProduct.quantity floatValue] * [myProduct.rate floatValue]] forKey:#"amount"];
NSLog(#"Product %#", myProduct);
NSError *error; if (![self.managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
}
}
It seems that your subview loop is really not necessary.
But already your check by visible cells seems flawed.
You get the index paths of all visible cells.
You pick one of these index paths based on the text field's tag.
--> This has unpredictable results. If the table view has scrolled, the exact index of your cell in the array of visible cells is not clear. If the cell has scrolled off the screen, the array will be empty and you get a crash.
Why so complicated?
Isn't the text field you need exactly the text field that is passed into the delegate method?
All you need is this:
UITableViewCell *cell = (UITableViewCell*) textField.superview.superview;
Product *myProduct = [self.fetchedResultsController objectAtIndexPath:
[self.tableView indexPathForCell:cell]];
You are using a NSFetchedResultsController, right? If not, you definitely should!