I'm using Core Data to manage Thing(s) and then UITableViewController/UITableViewCell to present a list of Things. However, a Thing object has more data than just what I display in the text property of the UITableViewCell, and when I segue from the UITableViewController to a more detailed view controller of Thing, what I really want is a pointer to the Thing object so I can pass it on to the next view controller.
For example:
-(UITableViewCell*)tableView:(UITAbleView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ENTRY_LOG;
// Ask the NSFetchedResultsController for the NSManagedUserObject at the row in question
Thing* thing = [self.fetchedResultsController objectAtIndexPath:indexPath];
// For brevity skip dequeueReusable...
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.textLabel.text = thing.name;
EXIT_LOG;
return cell;
Now, assume I assume the segue connected appropriately:
- (void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender {
ENTRY_LOG;
UITableViewCell* cellSender = (UITableViewCell*)sender;
// I want to do this:
Thing* thingHandle = [cellSender someMessageToGetThingReference];
[segue.destinationViewController setThing:thing];
// and I don't want to do this
// setup NSFetchRequest and predicate to get thing where 'name = cellSender.text'
[segue.destinationViewController setThing:thing];
EXIT_LOG;
}
If I could get the indexPath that the cell is at I could then use my NSFetchResultsController to at least get the objectAtIndexPath again, perhaps call - (NSIndexPath *)indexPathForSelectedRow in the prepareForSegue against the UITableView and proceed from there.
Perhaps I am missing something obvious, but to get the cell's index path, assuming you are segueing from a UITableViewController, can't you use:
NSIndexPath *indexPath = [[self tableView] indexPathForCell:cellSender];
Related
I know that dequeueReusableCellWithIdentifier:forIndexPath is called by tableView method inside the tableviewcontroller. And if I understand correctly, tableView method is called several times until all the cells are populated. But what I don't know is where do you get the value for the parameter IndexPath? I want to use dequeueReusableCellWithIdentifier:forIndexPath for a method that I created because I want to access my cell to copy some values of its properties.
NOTE:
I already populated my cell, which means that I successfully used the method tableView.
(Edit) ADDITIONAL INFO:
I'm trying to create a profile and edit profile tableviews. Inside the profile tableview, I displayed the name, address, contact#, etc., of the user. Also, I have a segue called edit profile. In the edit profile, I have textfields for each category (name, address, etc.). What I want to do is, if I edit the contents of the textfields, I should be able to display the new contents in my profile tableview. An example case would be: in the profile view I'm displaying -> name:human, address:earth (each in its own cell). Now if I go to editprofile tableview, I will edit the contents such that -> name:alien, address:mars. After that, there is a button called 'apply' to end editing of contents and go back to profile tableview. If I go back to profile view, the display should now be name:alien, address:mars and not name:human, address:earth.
Here is some code if it's any help. The code is called by a button in tableviewcontroller. "MyCell" is the class of my cell. This code is not working properly. I hope someone can help me fix this.
- (IBAction)updateCopies:(id)sender {
static NSString *ident = #"MyCell";
NSIndexPath *indexPath;
//create cell
MyCell *cell = [self.tableView dequeueReusableCellWithIdentifier:ident forIndexPath:indexPath];
//create variable for accessing cells
int row = [indexPath row];
_labelValues[row] = cell.textField.text
}
You should only use dequeueReusableCellWithIdentifier when you need to supply the table view with a cell to display. If you want to get the UITableViewCell object at a certain index, you should use cellForRowAtIndexPath.
Your problem
What you really need is a model class. You can then pass this to the edit controller, which changes the properties. Then when you return to the tableView, you can reload it and display the new properties.
What you could also do is create a delegate protocol for your edit profile controller, something like EditProfileViewControllerDelegate with something like:
protocol EditProfileViewControllerDelegate {
- (void)editProfileViewController:(EditProfileViewController *)controller didUpdateName:(NSString *)name address:(NSString *)address;
}
You can implement this delegate in your table view controller and use it to update the values when the text is changed. However, this quickly becomes unwieldy, I would not recommend it over using a proper model class.
You can get indexPath using CGPoint..You can use dequeueResusableCell for reusability of the cell..
- (IBAction)updateCopies:(id)sender {
CGPoint position = [sender convertPoint:CGPointZero
toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:position];
//create variable for accessing cells
MyCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
int row = [indexPath row];
_labelValues[row] = cell.textField.text
}
Hope it helps you..
Use this
- (IBAction)updateCopies:(id)sender {
MyCell *parentCell = (MyCell *)sender.superview;
while (![parentCell isKindOfClass:[MyCell class]]) { // iOS 7 onwards the table cell hierachy has changed.
parentCell = parentCell.superview;
}
UIView *parentView = parentCell.superview;
while (![parentView isKindOfClass:[UITableView class]]) { // iOS 7 onwards the table cell hierachy has changed.
parentView = parentView.superview;
}
UITableView *tableView = (UITableView *)parentView;
NSIndexPath *indexPath = [tableView indexPathForCell:(MyCell *)parentCell];
NSLog(#"indexPath = %#", indexPath);
}
Well I got what you want to accomplish.
Firstly, there is a delegate which is being called when you click/select a cell and go to the Edit Profile page. That delegate is
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
///
}
Make a global variable, say selectedIndexPath which holds the current cell index path which is being edited. Update this value each time when you go to edit profile page.
Like this
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
selectedIndexPath = indexPath;
// code to go to edit page...
}
Now in your updateCopies Method, do like this
- (IBAction)updateCopies:(id)sender {
//get the existing cell with the indexPath
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:#[selectedIndexPath]];
[self.tableView endUpdates];
//rest of your code goes here...
}
I have two table views, one called mainTableViewController (mtvc), the other called detailTableViewController (dtvc). It's very typical click the accessory button on the main tableview cell bring you to the detail tableview kinda thing.
In the prepareForSegue method, the data passed from the main tableview to detail tableview is a NSMutableArray called item.
And this is how I got it displayed: cell.detailTextLabel.text = self.item[indexPath.row];
The cool thing is I managed to do in-place editing on the detail table view cell (overwrote the NSTableViewCell, added a UITextField as subview to each cell).
everything works, the last thing I spent whole day cannot figure out is how do I update the NSMutableArray item after in-place editing taken place, the ultimate goal is in-place editing, and the main tableview data shall reflect the change.
I tried to use delegation and protocol but it does not work (the in-place edited content didn't got passed back, part of the reason is I don't know how to capture the edited content, it's not like it's a text field with a name, I can't just do updatedContent = self.myTextField.text to grab the change)
I'm running out of ideas, any help would be highly appreciated, thanks.
Here's the prepareForSegue in the main tableview controller
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"toInventoryDetail"]) {
NSMutableArray *selectedItem = nil;
if (self.searchDisplayController.active) {
selectedItem = _searchResults[[sender row]];
} else {
selectedItem = _appDelegate.items[[sender row]];
}
UPFInventoryDetailTableViewController *idtvc = segue.destinationViewController;
idtvc.item = selectedItem;
}
}
and here's the cellForRowAtIndex at the detail tableview controller
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UPFEditableUITableViewCell *cell = [[UPFEditableUITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:CellIdentifier];
cell.textLabel.text = _appDelegate.title[indexPath.row];
cell.detailTextLabel.text = self.item[indexPath.row];
[cell showEditingField:YES];
return cell;
}
I wrote the delegation but delete them after cause they didn't work.
I had an idea, still using delegation and protocol obviously: when the 'done' button in the detail tableview hit, I go grab all the row contents and build a new array, using delegation to pass this new array back to the main tableview controller, add this new array into the model meanwhile delete the old one. The tricky thing is still HOW CAN I GRAB ALL THE CONTENTS in the detail tableview?
update:
Haha! I think solved half of the puzzle !
here's the solution for the detail tableview controller
- (IBAction)doneUpdate:(UIBarButtonItem *)sender {
[self.delegate addItem:[self newItem]];
}
- (NSMutableArray *)saveItem
{
NSMutableArray *newItem = [[NSMutableArray alloc] init];
NSArray *indexPathes = [self.tableView indexPathsForVisibleRows];
for (NSIndexPath *indexPath in indexPathes) {
UPFEditableUITableViewCell *cell = (UPFEditableUITableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
[newItem addObject:cell.editField.text];
}
return newItem;
}
and here's the main tableview controller
- (void)addItem:(NSArray *)item
{
//take the updated item then insert the items array as new item
[_appDelegate.items addObject:item];
//remove the selected item (the one being updated) from the items array
[_appDelegate.items removeObject:_appDelegate.selectedItem];
[self.tableView reloadData];
[self.navigationController popViewControllerAnimated:YES];
}
When you creating a cell - give tags to your UITextFields
You can collect data entered by its delegate methods - you can either make NSDictionary/ key value pairs or you can add it to NSArray.
- (void)textFieldDidEndEditing:(UITextField *)textField {
if(textField.tag == 11) {
// you can add it to your desired array/dictionary
}
}
OR
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if(textField.tag == 11) {
// you can add it to your desired array/dictionary
}
}
You can use Delegation/Protocol or store this values in NSUserDefault and get it back on mainViewController.
Do you have a separate data model class(classes) for your selectedItem? That would be the appropriate way to persist data between the two TableViewControllers. It can be Core Data or simply a NSMutableArray that lives in memory. The DetailViewController updates the item and saves the changes, then the mainTableViewController reloads the TableView (or even just the data backing the previously edited cell.
Perhaps even consider the Model-View-Controller-Store pattern promoted by BigNerdRanch.
I am currently making a tableView for my app, and I've got my cells from a .plist file. However I want to be able to enter my second view controller no matter, what cell I click on, the difference should be that the UIText for my second viewController will depend on which cell I click.
So far, I have imagined something like;
if(tableview.tag == 1){
myUIText.text = #"a";
}else if(tableview.tag == 2){
myUIText.text = #"b";
etc.......
Will this work using the same view controller, same UIText? If not, then how?
And how can I set the tags for my array in my .plist file?
I would appreciate your answers
EDIT: Here is a sample of two methods;
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.items count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString * id = #"plistdata";
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:id forIndexPath:indexPath];
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:id];
}
cell.textLabel.text = self.items[indexPath.row];
return cell;
}
More information is certainly needed by I will make some assumptions on your code and see if it helps any. I assume the the text you want to send is the title of the cell the person clicked on. Now how you send that to the next VC will differ depending on if you are using a segue or not. If you are using a segue you can do something like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"mySegue"]) {
UITableViewCell *cell = (UITableViewCell *)sender;
UpcomingViewController *viewController = segue.destinationViewController;
viewController.textView.text = cell.chapterText;
}
}
However if you are using didSelectRowAtIndexPath then:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UpcomingViewController *viewController = [[UpcomingViewController alloc] init];
viewController.textView.text = self.arrayOfChapterText[indexPath.row];
[self.navigationController pushViewController:viewController animated:YES];
}
Now again because your question wasn't very clear on your scenarios I have taken some liberties with what I think you are probably trying to do. I assume that the view controller that will show after you choose an item will have the UITextView as a public property so that you can set it before pushing or segueing to your new view. I also assume in the didSelectRowAtIndexPath that you are using a UINavigationController.
Hope this helps.
I need more help with my first Table View app.
I have .plist with continents, countries and other info:
I work with a StoryBoard and I have 3 View Controllers (MasterVC - for continents, MiddleVC - for countries, DetailVC - for detailed info). I have already continents displayed in UITableView on my MysterVC. Now I want to pass information about what was pushed to the MiddleVC, for example that Europe was pushed and the to display European countries in a table view on my MiddleVC.
I guess that I should do it in didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
//is it correct way to catch the info about which continent was pressed?
NSString *ContinentName = [[world allKeys] objectAtIndex:indexPath.row];
//I don't know how to pass the info forward
}
Or maybe I should use segue for passing data?
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showMiddle"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSDate *object = _objects[indexPath.row];
//??is everything what needed here??
[[segue destinationViewController] setDetailItem:object];
}
}
And I am not sure how to deal with the passed info from MasterVC, here in MiddleVC. Although I prepared some initial coding:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
//I think countries arrays will be needed here
NSArray *namesOfAfricanCountries = [africa allKeys];
NSString *AfricanCountryName = [namesOfAfricanCountries objectAtIndex:indexPath.row];
NSArray *namesOfEuropeanCountries = [europe allKeys];
NSString *EuropeanCountryName = [namesOfEuropeanCountries objectAtIndex:indexPath.row];
//how to feel cells only with the proper continent's countries?
cell.textLabel.text = ???;
return cell;
}
Will appreciate any help.
Are you using a UINavigationController? If so:
Setup a 'push' segue from your Master VC to your Detail VC and have a property in your details VC to hold the 'continent' dictionary.
implement 'prepareForSegue', here you can use the indexPathForCell: on the sender argument to find out which cell was tapped.
Use the row in the index path to locate the country in your dictionary
In your prepare for segue, use segue.destinationViewController to get a handle on your detail VC and set it's continent property to the country dictionary from your data source. (you only want to pass forward the continent dictionary containing the countries in that continent, not your whole data source).
Then in the detail VC' cellForRowAtIndexPath:
then if you only want to display the country name in the cell assuming the names are keys...
NSString *key = [self.continentDict allKeys][indexPath.row];
cell.textLabel.text = key;
I created a universal master-detail application. I have a table view within a custom class (MasterViewController class). This is a UITableViewController class.
Using Storyboard, I created a custom cell in my iPhone table view. The custom cell contains 3 labels and 1 image.
In StoryBoard, this table view datasource and delegate is set to the MasterViewController class.
My custom table view cell has 'User Interaction Enabled' in the View section of the attribute inspector.
In my MasterViewController class, the UITableViewDataSource methods such as 'numberOfSectionsInTableView' are working fine.
I have a seque defined between the table view and a detailed view.
When I run the application and select a cell in the table view, the didSelectRowAtIndexPath is not called for the iPhone. My prepareForSegue method executes and the segue occurs.
I have read quite a number of posts on didSelectRowAtIndexPath not being executed, but I have not been able to solve my problem.
Any hints would be very much appreciated.
My custom cell identifier is ConversationCell. The segue identifier is ShowConversationDetails.
I debug with a breakpoint on the first line of numberOfRowsInSection and this method is never entered.
The segue works and the detail view displays my label with 'Hello' text.
Some Code:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
//NSLog(#"Rows %lu",(unsigned long)[sectionInfo numberOfObjects]);
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ConversationCell";
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
[[self.fetchedResultsController sections] count]);
NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
self.detailViewController.detailItem = object;
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"ShowConversationDetails"]) {
NSIndexPath *selectedIndex = [self.tableView indexPathForSelectedRow];
Event *e = nil;
e = [self.fetchedResultsController objectAtIndexPath:selectedIndex];
UIViewController *destinationViewController = [segue destinationViewController];
[segue.destinationViewController setLabel:#"Hello"];
}
}
MASTERVIEWCONTROLLER:
Here is my interface for above;
#interface MasterViewController : UITableViewController <NSFetchedResultsControllerDelegate,ASIHTTPRequestDelegate,UITableViewDelegate, UITableViewDataSource>
{
NSMutableArray *eventsArray;
}
This may be a clue to my problem. When I add the following code to prepareForSeque
NSIndexPath *selectedIndex = [self.tableView indexPathForSelectedRow];
NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:selectedIndex];
NSString *messageTableID = [[object valueForKey:#"messagetableid"] description];
selectedIndex is empty as if a row is no longer selected when prepareForSeque is executing.
By the way, just to be safe, I removed the app from the simulator and then did a Clean on the project and nothing changed.
Tim
In addition to the checks you have already mentioned, here are a few other ones you might want to consider:
Check that the method is exactly this (no difference in letter cases) :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Check that the controller implements the UITableViewDelegate protocol
Check that interaction does work (change the selection style option so that the cell changes color on touch events)
Check that you don't have any view inside that is first responder and takes away the interaction (remove any view in the cell to test)
Try to programmatically allow selection on the table [tableView setAllowsSelection:YES];
Are you sure that your UITableViewDelegate object is correctly being set as the table view's delegate? (This is separate from setting it as the data source.)
I ended up deleting my MasterViewController and DetailViewController. I recreated them and things are now working. Not sure what I did wrong the first time. Thanks for everyone that offered advice.
Tim