I'm trying to build a UI so the user can edit the attributes of a core data entity. When the user taps the edit button, selecting a row will push the listDetailViewController, which is just a table view that displays the attributes. It uses a custom table view cell with a label and a UITextField. The listDetailViewController displays the attributes properly, and will accept text as its supposed to, but I can't figure out how to get the user-inputted text to save.
If I'm not explaining clearly, here's an example. I want to change the list's name, so I tap Edit, tap the list, tap the List Name row, the keyboard pops up, I type in the new name, tap Done and it pops me back to the RVC with none of the changes saved. I've been banging my head on this for a few days and would love some help!
Here's the relevant code from ListDetailViewController:
- (void)viewDidLoad {
[super viewDidLoad];
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:#selector(done)];
self.navigationItem.rightBarButtonItem = doneButton;
[doneButton release];
self.tableView.allowsSelection = NO;
self.tableView.allowsSelectionDuringEditing = NO;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return 3;
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *DetailCellIdentifier = #"DetailCell";
ListDetailCell *cell = (ListDetailCell *)[tableView dequeueReusableCellWithIdentifier:DetailCellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"ListDetailCell" owner:self options:nil];
cell = listDetailCell;
self.listDetailCell = nil;
// Configure the cell...
// list name
if (0 == indexPath.row) {
cell.label.text = #"List Name";
cell.textField.text = self.selectedList.listName;
cell.textField.placeholder = #"Name";
}
// Detail 1
if (1 == indexPath.row) {
cell.label.text = #"Detail 1";
cell.textField.text = selectedList.detail1;
cell.textField.placeholder = #"Detail 1";
}
// Detail 2
if (2 == indexPath.row) {
cell.label.text = #"Detail 2";
cell.textField.text = selectedList.detail2;
cell.textField.placeholder = #"Detail 2";
}
}
return cell;
}
- (void)done {
[self.listDetailCell resignFirstResponder];
[self.navigationController popViewControllerAnimated:YES];
}
The ivars label and textField are declared in ListDetailCell, which is the table cell nib I mentioned earlier.
Not sure if I've got your problem correct or if my answer is the best method, but it's what I did recently. I have a TableViewController and UITableView which display a series of custom cells for editing data. This is basically replicating what I've seen a number of other applications do to create data editing screens.
Each of the custom cells has a UITextField. When the user finishes editing a cell, the UITextField triggers a message to a UITextFieldDelegate. So I added the UITextFieldDelegate protocol to the TableViewController and when setting up the custom cell in cellForRowAtIndexPath, I set the TableViewController as the UITextFields delegate. Then when the user finishes editing the end editing message is sent and I can then get the value from the UITextField and store it back in the managed entity object.
I apologise, but I don't have access to my code right now or I'd cut and paste an example for you.
Anyway, some things to watch out for:
In the code for the delegate message you need to first identify the UITextField that has triggered the call. The best way to do this is to set the Tag property on the UITextField when you create the UITableCell that contains it. Then in the delegate method you can use a switch statement to select which entity field to store the value in.
Getting ride of the keyboard when a user taps on a non-editable area of a UITableView can be tricky. You need to store a list of objects that can have a keyboard, and when a click happens, loop through them and do the resign first responder to remove the keyboard from the display.
tapping a save button on the navigation bar or something else outside of the UITableView will not remove the keyboard or resign the first responder, so the delegate of the field currently being edited does not get called. You need to add code to trigger the save sequence.
If you have any UITextView's they use a different delegate.
To add the delegate you will need to do something like this (taken from your code above):
#interface MyTableViewController : UITableViewController <UITextFieldDelegate>
....
if (0 == indexPath.row) {
cell.label.text = #"List Name";
cell.textField.text = self.selectedList.listName;
cell.textField.placeholder = #"Name";
cell.textField.delegate = self; // Setting controller as text field delegate.
cell.textField.tag = 1; // Really should use an enum here for clarity.
}
....
-(void) textFieldDidEndEditing:(UITextField *) textField {
switch(textField.tag) {
case 1: //Again with the enum.
// Save field 1.
entity.someProperty = textField.text;
....
}
}
This is for dealing with a number of text fields. Another solution I found was to store the changed values in a dictionary and only update the entity when the user taps save. With my solution above, you would also have to reset the entities properties if the user cancels. So it's horse for courses.
Related
I have a UITableView of 'people' for my iOS app. I have created an 'add' screen to add new people to my persistent store, which works perfectly.
This add view uses a form created with a UITableView and subclassed UITableViewCells, with a UITextField and UILabel, which although works well, I am very new to iOS programming and feel that this may not be the most efficient way.
I am trying to re-use this add view to be my detail, add, and edit view, and I can successfully set a 'Person' entity as a property in the detail view. I have the following code in my viewDidLoad method, where the adding property is set in the prepareForSegue method in the previous ViewController :
if (self.adding)
{
self.editing = YES;
self.title = #"Add Person";
}
else
{
self.title = self.person.name;
[self setDetail];
}
My problem is that when I try to pre-populate my detail view's fields (my setDetail method), I am unable to set my UITextField text with the name property from my person entity. Here's the code I'm using to retrieve the UITextField and set it's text property with:
form is the UITableView;
UITableViewCell *nameCell = [form cellForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
UITextField *nameField = (UITextField *)[nameCell viewWithTag:100];
nameField.text = self.person.name;
If I NSLog nameCell it returns (null)
I hope that's enough explanation. Any pointers would help a lot!
Instead of setting in viewDidLoad:, do it in your table view data source methods. i.e
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Deque the nameCell and prepare the cell if its not available.
UITextField *nameField = (UITextField *)[nameCell viewWithTag:100];
nameField.text = self.person.name;
return nameCell.
}
I don't really understand your question, but cellForRowAtIndexPath may return nil because
Return Value
An object representing a cell of the table or nil if the cell is not visible or indexPath is out of range.
Official Document: cellForRowAtIndexPath:
I'm brand new to Xcode and Objective-C, and fairly new to programming in general so please correct me if I have a severe misunderstanding.
Here's what I'd like to accomplish:
Create a Table View (empty upon startup)
Have user add and name cells via a Bar Button (pushes to a different view with a name Text Field and "Create" button I'm assuming)
Each created cell pushes to a new Table View with similar add/naming functionality
Then each of those cells push to a Text View
It seems to me that the best way to accomplish the first part is by populating a string array, then assigning those elements to the corresponding cells. That's where I get stuck.
How do I populate a Table View with an array?
Should I create a new secondary Table View for every cell, or just populate the same one differently depending on which parent cell is selected?
And same question as previous for the Text Views at the end of the chain...multiple Text Views or just different text passed each time?
If you got this far, I sincerely thank you and please let me know if I need to clarify anything.
I hope you know how to create a table view.
Now you should create a NSMutableArray to save your strings.
#property (retain) NSMutableArray *stringsArray = _stringsArray;
so your viewDidLoad will look like this.
- (void)viewDidLoad
{
[super viewDidLoad];
UITableView *myTableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
myTableView.dataSource = self;
myTableView.delegate = self;
[self.view addSubview:myTableView];
[myTableView release];
self.stringsArray = [NSMutableArray array];
}
Now the table view delegates
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [self.stringsArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tmpTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"MainCell";
UITableViewCell *cell = [tmpTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (nil == cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
cell.textLabel.text = [self.stringsArray objectAtIndex:indexPath.row];
return cell;
}
Now when you click on your barbutton to add new user, you must add it to strings array and just call [tableview reloadData];
I didn't understand why you want a text view.
Hope this helps!
Read from here for complete reference of uitableview UITableView Tutorial : Introduction to iPhone TableView
I have a custom UITableView cell that I've added a textbox to for editing, that shows and hides based on the edit mode. I've also tried adding a vertical line that shows when editing, and it does that, but I'm running into some drawing issues. I just added a green checkmark rightView to start working on input validation feedback, and I'm seeing similar issues.
Here is the code for the cell, and part of my cellForRowAtIndexPath.
#import <UIKit/UIKit.h>
#interface EditableCellStyle2 : UITableViewCell {
CGRect editRect;
UITextField *editField;
UIView *lineView;
}
#property (nonatomic, readonly, retain) UITextField *editField;
#property (nonatomic, readonly, retain) UIView *lineView;
#end
#import "EditableCellStyle2.h"
#implementation EditableCellStyle2
#synthesize editField;
#synthesize lineView;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code.
editRect = CGRectMake(83, 12, self.contentView.bounds.size.width-83, 19);
editField = [[UITextField alloc] initWithFrame:editRect];
editField.font = [UIFont boldSystemFontOfSize:15];
editField.textAlignment = UITextAlignmentLeft;
editField.textColor = [UIColor blackColor];
editField.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[self.contentView addSubview:editField];
self.editField.enabled = NO;
self.editField.hidden = YES;
lineView = [[UIView alloc] initWithFrame:CGRectMake(80, 0, 1, self.contentView.bounds.size.height)];
self.lineView.backgroundColor = [UIColor lightGrayColor];
[self.contentView addSubview:lineView];
self.lineView.hidden = YES;
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state.
}
-(void)layoutSubviews
{
[super layoutSubviews]; // layouts the cell as UITableViewCellStyleValue2 would normally look like
editRect = CGRectMake(83, 12, self.contentView.frame.size.width-self.detailTextLabel.frame.origin.x-10, 19);
editField.frame = editRect;
}
- (void)willTransitionToState:(UITableViewCellStateMask)state {
[super willTransitionToState:state];
if (state & UITableViewCellStateEditingMask) {
self.detailTextLabel.hidden = YES;
self.editField.enabled = YES;
self.lineView.hidden = NO;
self.editField.hidden = NO;
}
}
- (void)didTransitionToState:(UITableViewCellStateMask)state {
[super didTransitionToState:state];
if (!(state & UITableViewCellStateEditingMask)) {
self.editField.enabled = NO;
self.editField.hidden = YES;
self.lineView.hidden = YES;
self.detailTextLabel.hidden = NO;
self.editField.text = self.detailTextLabel.text;
}
}
- (void)dealloc {
[editField release];
[lineView release];
[super dealloc];
}
#end
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// handling every section by hand since this view is essentially static. Sections 0, 1, 2, and 4 use a generic editable cell.
// Section 3 uses the multiline address cell.
static NSString *CellIdentifier = #"Cell";
EditableCellStyle2 *cell = (EditableCellStyle2 *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (indexPath.section == 0 || indexPath.section == 1 || indexPath.section == 2 || indexPath.section == 4) {
if (cell == nil) {
cell = [[[EditableCellStyle2 alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:CellIdentifier] autorelease];
}
}
// Configure the Odometer
if (indexPath.section == 0) {
NSArray *array = [sectionsArray objectAtIndex:indexPath.section];
NSDictionary *dictionary = [array objectAtIndex:indexPath.row];
cell.textLabel.text = #"Odometer";
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#", [dictionary objectForKey:#"Odometer"]];
cell.tag = kOdometer;
cell.editField.text = cell.detailTextLabel.text;
cell.editField.placeholder = #"Odometer";
cell.editField.tag = kOdometer;
cell.editField.keyboardType = UIKeyboardTypeNumberPad;
// Create a view for the green checkmark for odometer input validation and set it as the right view.
UIImage *checkImage = [UIImage imageNamed:#"tick.png"];
UIImageView *checkImageView = [[[UIImageView alloc] initWithImage:checkImage] autorelease];
cell.editField.rightView = checkImageView;
cell.editField.rightViewMode = UITextFieldViewModeAlways;
}
return cell;
}
There is more to it but all the cells are built the same way.
The problems are that, when in edit mode, the vertical lines will display properly. When I leave edit mode, any cells that were off screen when I go to normal mode still have the vertical line (it doesn't get hidden). Also, now that I've added the imageView for the checkmark indicator, any cells that are off screen when switching modes gain the checkmark. (only section 0 sets it up).
I've also noticed that if i do cell.setNeedsDisplay, the text label and detail text label won't update if the data source has been updated. I have to do [self.tableView reloadData] which skips any active animations.
I'm sure these issues are related to me using a custom cell + dequeueReusableCellWithIdentifier, but I can't find exactly what.
Any feedback or a push in the right direction would be appreciated.
Edit:
Not using reusable cells seems to have resolved the above issues. I'm still open to feedback on the cell code.
I forgot one other issue that may or may not be related. One of my cells has a "tap to view list" button. If I enter data into the cells while in edit mode, then hit that button to choose some info from a list (it displays a modal table view), when I dismiss the modal view, all of the cells' edited data has reverted to their original state. I'm not calling reload data when I dismiss the modal view controller. I thought this might be fixed by not using reusable cells but it isn't.
You need to prepare the cell for reuse. Try adding this to the EditableCellStyle2 implementation:
- (void)prepareForReuse {
[super prepareForReuse];
[self didTransitionToState:UITableViewCellStateDefaultMask];
}
Maybe you trimmed too much for your post, but in the posted code your reusable cell handling is all wrong.
First of all, each different type of cell needs its own CellIdentifier. In your case (judging from your code comment), that means at least a different identifier for section 3 versus sections 0, 1, 2, and 4. You may also want to do a separate identifier for section 0, so you don't have to keep removing and readding that checkmark. The different identifier needs to be used for both the dequeueReusableCellWithIdentifier: and initWithStyle:reuseIdentifier:` for the appropriate sections.
The second problem is that you are not resetting the cells correctly. There are two "kinds" of initialization that must be done to a UITableViewCell: initialization that is the same for every cell of its type, and initialization that depends on the specific row being displayed. The first kind can (and should) only be done once, when a new cell is allocated. The second kind must be done every time through tableView:cellForRowAtIndexPath:. You seem to be doing the first correctly for your EditableTableCell2 class in its init method, but I see nowhere in there where you do the per-row initialization: you never reset selected, or the cell state, or the contents of the edit field, or remove the checkImageView since you are using the same kind of cell for section 0 versus the other sections. If you want, the reset selected, state, and clearing out the checkbox image and field contents can be done in prepareForReuse on your EditableTableCell2 class.
The third problem, which is almost certainly due to over-trimming, is that you never create this "multiline address" cell for section 3. You'll end up maybe reusing a random EditableTableCell2, or maybe crashing on an exception from the framework when you return nil from tableView:cellForRowAtIndexPath:.
I have a UITableView with 15 cells, each with a separate text box in it.
I have implemented UITextViewDelegate and I am able to received changed textview data using textViewDidChange (etc). But I have one big problem still, how do I know WHICH textview sent this, (i.e. in which cell was the textview altered?)
Its interesting to have so much working, yet not know precisely where it comes from.
A whole bunch of code is available if required.
Regards #norskben
Code
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
//Big Text Box
UITextView *detailLabel = [[UITextView alloc] initWithFrame:CGRectMake(30, 80, CONST_Cell_width, 150)];
detailLabel.tag = 20;
[cell.contentView addSubview:detailLabel];
}
UITextView * detailLabel = (UITextView *) [cell.contentView viewWithTag:20];
You can assign tags (integers) to the different views and query the tag number to see which view called the method. Look for the tag property on the view:
tag
The receiver’s tag, an integer that you can use to identify view objects in your application.
#property(nonatomic) NSInteger tag
see here
Not at my development machine, but when you create the UITextView you should be able to assign it a tag. I think it is [myTextView setTag:x]; where x is an integer.
Then, in the TextViewDidChange use
if (textview.tag == x) {
//do something
} else if (textview.tag == y) {
//do something else and so on
}
Hope that helps a little.
The text views pass a reference to themselves in every delegate method so you know which one sent it. To make a connection to the cell, I'd set each text view's tag property to a different value that corresponds to the row of the cell they're in.
Here's an important question: Are your text boxes static, or can they change over time? If they won't change (the user can't alter the number of cells or add more later), then you can declare a new textField for each cell. I have something similar in my apps. I have two text boxes, and depending on which textField is currently active, the delegate does something different.
Declare separate text fields in your header
UITextField *textField1;
UITextField *textField2;
UITextField *textField3;
in the delegate method, use if statement blocks to find out which textField is changing:
if (textField == textField1) {
//do something
} else if (textField == myTextField2) {
//something else
}
Note that this really only works if your view is static.
Hope this helps
Have a great day
When you're searching the UITableView's cells for the event source UITextView, only iterate over the cells that the user can currently see. This can be obtained using the following UITableView method:
- (NSArray *)visibleCells
I added several cells to a tableview and each cell has a textField in the right to let users input texts. I find that when I scroll down and go back, the input of the first few lines will disappear. Does anybody know what's the problem?
The following is a piece of my codes:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath
*)indexPath
{
//init cell
static NSString *TableIdentifier = #"MyIdentifier";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:TableIdentifier];
if(cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
reuseIdentifier:TableIdentifier] autorelease];
}
//set cell for each row
if([indexPath row] == 0)
{
cell.textLabel.text = #"Row A";
txt_A = [self CreateTextField]; //create textField
[cell.contentView addSubview:txt_A]; //add textField to cell
}
else if([indexPath row] == 1)
{
cell.textLabel.text = #"Row B";
txt_B = [self CreateTextField];
[cell.contentView addSubview:txt_B];
}
else{...} }
You mean the user input will disappear? If so this is probably because cellForRowAtIndexPath: reuses the cell and will execute the [self CreateTextField] again.
So, unless you store the user input before the cell disappears, your cells will be redrawn empty and reinitialized empty.
If you do store the user input, you could reinitialize the cell with the right user input.
I believe that when a cell leaves the screen (due to scrolling) the iPhone immediately releases it to save memory (thus loosing the user input). Now when you scroll back up, it creates a new cell with a new UITextField in the old position with
[self CreateTextField];
You have to store the user input for each text field separately. For example, you could become the text field's delegate and catch
- (BOOL)resignFirstResponder
to be notified when the user leaves the text field focus. At this point you can assume that the user is finished with this particular field and store it.
You can use method to save user inputs:
- (void)textFieldDidEndEditing:(UITextField *)textField
This methods called then user end editing the field. Save user inputs to array, for example.