I'm trying to create a view similar to add/edit contact in iOS and there are a few things that are happening, and I'm not sure how they are implemented. Any help in understanding is greatly appreciated.
For each section in contacts i.e. name, phone number, email, etc are these each their own tableview or are these sections within a larger tableview?
When clicking done when adding or editing a contact, the unused tableview cells disappear. Is this using deleteRowsAtIndexPaths:withRowAnimation: or is there a hide method I haven't found? If it is using that method, then when clicking the edit contact button, how does the view brings back these unused tableview cells?
When clicking on a cell in the tableview cell when editing a contact, you are able to change the text. Is this a textfield within a tableview cell or is it actually modifying the label of the tableview cell?
I am not looking for any specific code, as a fairly new programmer I am just trying to understand the strategies/best way to implement these features.
I tried a lot different ways to implement that. the easiest one: Subclass UITableViewCell and overwrite setFrame:. note that this is easy to achieve for grouped tables, but hard for plain ones. in the datasource's tableView:cellForRowAtIndexPath: create an object of this custom cell for the first section. use another identifier for cells of that section, so that only the correct cells will be re-used.
yes, I assume that. The controller has some sort of definition how many cells has to be show in edit mode and how many are actually used with some sort of information. you can easily create a array of indexPaths that must be deleted.
I would do it in tableView:didSelectRowAtIndexPath: by fetching the cell via tableView:cellForRowAtIndexPath:, hide the label and unhide or add a textfield and make this first responder.
code for 1.
the cell
#interface InsetCell : UITableViewCell
#property(nonatomic)CGFloat inset;
#end
#implementation InsetCell
- (void)setFrame:(CGRect)frame {
CGFloat inset;
if (_inset == 0) {
inset = 70; //default value
} else {
inset = _inset;
}
frame.origin.x += inset;
[super setFrame:frame];
}
-(void)setInset:(CGFloat)inset
{
_inset = inset;
[self setNeedsLayout];
}
#end
a project that uses similar code
Related
What I'm doing?
I'm adding dynamic views to my UITableViewCell (which isn't subclassed).
Cell Hierarchy :
UITableView > UITableViewCell > cell.contentView > MainView > ([Number Of PointView] + [Options View]).
Here it is:
When a user will tap "Add Another Point", I'll add a PointView (which will be same as above) :
Y position [ ] X position [ ]
which will be look like this,
What is my logic to get this done?
I'm taking MainView from cell.contentView.
Then fetch last two views (Point View & Options View) added into MainView.
Add Another PointView in MainView.
Update frames for newly added PointView based on last PointView and also update frame of OptionsView.
Resizing height of MainView
And reloading particular cell.
I'm able to get it work, to confirm I've logged frame and subviews for that MainView. But once I tap on "Add Another Point" button again, I found that MainView isn't updated at all?
My simple question is, if I have a MainView (which I fetch from a Cell), can I update it directly or not?
P.S. I already have a poor solution for this is, to remove MainView and recreate new – which I found unnecessary. Any thoughts?
Short answer, yes you can.
cell.contentView is a placeholder. What you are likely running into is an entirely different problem: UITableViewCells are cached, then recycled and reused by the system.
Anytime you respond to cellForRowAtIndexPath, you basically need to reset the content.
I think what you should do. Just make this one cell class
This one other cell subclass
Use this method to insert your rows and also update your data source for table view that is the number of rows in section part
Your + button on click method can be like this..
//Use your own logic what you want to do.
Update your data source
NSIndexPath* index=[NSIndexPath indexPathForRow:[table count]-1 inSection:0];
NSMutableArray* indexPathArray=[[NSMutableArray alloc]init];
[indexPathArray addObject:index];
//index will be your new added point cell. Updating your datasource
[_myTableView insertRowsAtIndexPaths:indexPathArray
withRowAnimation:UITableViewRowAnimationTop];
Make this YES
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
// Return YES if you want the specified item to be editable.
return YES;
}
This question already has answers here:
How to know the UITableview row number
(10 answers)
How to get UITableView from UITableViewCell?
(21 answers)
Closed 8 years ago.
I've used this code for to get an UIButton from inside my UITableViewCell but it didn't work. So I've debugged it like half an hour to know what superview it in is.
UIButton *button = (UIButton *)sender;
UITableViewCell* cell = (UITableViewCell*)button.superview.superview.superview;
UITableView* tableView = (UITableView*) cell.superview.superview;
NSIndexPath* indexPath = [tableView indexPathForCell:cell];
The question for me is now: How can I know in the future in what hierarchy it is for a UITableView or others? Isn't there a list where I can see how much superviews I need to go without testing it 100 times and set BreakPoints to see what class it refers to?
Regards
You may just end up going with the superview hierarchy route, but know this is not a good idea because Apple could change their table view hierarchy any time. There are much better ways to get the index path for a table view cell given a button inside one of the cells.
If all the buttons are in one section of the table view, simply set each button's tag to the cell's index path row. Then you can recreate the index path knowing which row the button is in.
You can always subclass UITableViewCell and implement the action method for the button inside the subclass. The subclass might call a delegate method to whichever object wanted to know about the button tap.
So hopefully this gives you an idea of the alternatives to what you were doing before. As you said, it's time-consuming to figure out the superview hierarchy and it could change at any time.
If you want to check multiple superview hierarchy then check like that below:-
UIView * view=(UIView*)sender;
while ((view= [view superview])) {
if(viewisKindOfClass:[UITableView class]) {
//your code
return;
}
}
I want to remove specific views from my UITableViewCell based on some condition, for example:
if (!model.isThumbnail) {
// remove thumbnail UIImageView
[cell.thumbnailView removeFromSuperview];
}
but this will remove UIImageView from all following cells as well..
Is it possible to remove it only per one specific cell (without creating several different cells)?
Thanks!
Since UITableViewCells are reused somewhat automatically, removing individual views from cells would be problematic. Say that you're using two cell layouts, one, layout1, removes view1, the other, layout2, removes view2. Now if a cell is initially configured for layout1, view1 is removed. The next time it gets reused, it gets reused for layout2, so view2 is removed and view1 was removed the last time, so now it's missing both views.
As implied in CEAFDC's answer, a better approach might be to hide (and subsequently show) those views that aren't necessary.
IMO, a better approach would be to use completely different cell layouts (you can even use the same sub-class, just different use different prototypes in the storyboard) and create whichever layout you need at the time.
Maybe you can add a tag to the specific cell...
Can you show how you create the cell? or tell why you need to remove it?
Not sure if I understand your problem, but I think this would solve:
if (!model.isThumbnail) {
cell.tumbnailView.hidden = YES;
} else {
cell.tumbnailView.hidden = NO;
}
or
cell.tumbnailView.hidden = !model.isThumbnail;
try like this,
if (!model.isThumbnail) {
// remove thumbnail UIImageView
[cell.thumbnailView removeFromSuperview];
} else {
if there is no thumbnailView on cell {
// create thumbnailView and add to cell
}
}
But anyway this isn't good solution.
I have a UITableView and each UITableViewCell contains an editable UITextView.
My data source is a NSMutableArray containing NSMutableDictionary that holds the text value and some styling keys for the text.
How can I (efficiently) make it so that any changes a user makes to the UITextView are updated in the corresponding datasource NSMutableDictionary?
A rather simple way is to utilize the index path of the table, it is NOT the cleanest so it depends on the complexity of your datasource, and if you have multiple tables etc.
What you can do is when the user ends editing the textView or selects another row in tableView, you read the indexPath of the selected row (That requires that the row keeps actually being in the selected state while editing the textView which it should by default). From there you call your update method.
To catch the end of editing you implement
-(void)textViewDidEndEditing:(UITextView *)textView
{
NSIndexPath *selectedpath = [myTable indexPathForSelectedRow];
[self myUpdateMethodForIndexPath:selectedpath];
}
To catch deselect of the table row and the above doesnt get called, you implement
-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self myUpdateMethodForIndexPath:indexPath];
}
Your update method must then read the value of the textView at the corresponding cell at the indexPath and handle this in the datasource. To care for sections of course you need to correctly handle the indexPath, in the example just the row is used (1 section).
-(void)myUpdateMethodForIndexPath:(NSIndexPath *)editPath
{
UITableViewCell *editCell = [myTable cellForRowAtIndexPath:editPath];
NSString *newText = editCell.theTextView.text;
....
NSMutableDictionary *dict = [myDictArray objectAtIndex:editPath.row];
....
}
First of all, you must assign a tag to each UITextView, to know exactly which UITextView are you refering.
Then you must implement UITextViewDelegate in your view controller which holds the tableview. Then, make this view controller the delegate of each UITextView. Read here how to implement it: UITextViewDelegate reference.
Look for the protocol method that better fits your needs (probably – textView:shouldChangeTextInRange:replacementText:, wich is called each time the text changes in any range.
In the delegate method, you can read the text with UITextView.text property, and assign this value to your data model (the dictionary).
Another possible approach is to use KVO pattern, but it requires more coding and a better understanding both, the pattern and the implementation. Hope it helps!
Make your view controller the delegate of each text view. Listen for appropriate events to get the updated text. Then have the view controller update the data model with the updated text.
If you have custom cells then have the cell be the text view delegate. Then the cell should notify its delegate (the view controller) about the updated text. Of course this requires that your custom cell class define its own delegate protocol and the view controller should make itself the delegate of each cell.
That's as specific as an answer can be for such a vague question.
I have a UITableview cell that gets a tally from a core data database. The tallyTable is in a view controller inside a UITab view. I have an NSLog statement that prints out the tally value whenever it gets updated. Another tab has a list to change the source (different day) for the tallies. I am using iOS5 with ARC targeting iOS 4.2.
Here's the problem. When I load the application, the correct tallies for whatever the last selected day show up in the table tab. If I then go to the day tab and change the day and return to the tally tab there is no change in the display. However, the viewWillAppear on the tally tab runs and as the table cycles through cellForIndexPath, my NSLog statement prints out all the correct new values. If I then scroll the top label off the screen and back the label updates to the new value.
I've tried setNeedsLayout and setNeedsDisplay on the UILabel, the UITableViewCell, the UITableView and the view controller loading the table. I tried changing the CellReuse identifier so that it would never reuse a cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
CollectionItemTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[CollectionItemTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSUInteger row = [indexPath row];
cell.textLabel.text = [[self.collectionKeys objectAtIndex:row] valueForKey:#"collectionTitle"];
NSInteger test1 = indexPath.row + 150;
NSLog(#"tag = %i", test1);
cell.tallyButton.tag = test1;
NSNumber * questionID = [[self.collectionKeys objectAtIndex:row] valueForKey:#"answerID"];
cell.tallyLabel.text = [NSString stringWithFormat:#"%i",[self updatePointTotal:questionID]];
NSLog(#"Collection text should be = %#", [NSString stringWithFormat:#"%i",[self updatePointTotal:questionID]]);
[cell setNeedsLayout];
return cell;
}
I've read over a half dozen other similar questions. Got about three hours invested so far in trying to solve this.
EDIT: I thought I fixed it by using the navigation controller to repush the top level view controller on to the view again. I'll admit now this feels like a classically kludgy hack in every way. When the view is PUSHED everything updates and it is seamless. However, in order to have a fixed footer to make selection settings for the table buttons, I used a UIView with two subviews, a UITableView on top and a simple UIView with four buttons below.
The captions on the buttons need to change with the data source. Now when the view controller is pushed onto the view it obscures my fixed footer view. So, I inserted the fixed footer into the UITableview and everything appeared fine until I scrolled the UITableView and the footer scrolled up with it. The table is basically a tally sheet with buttons next to each item and in the footer is four buttons to note the color of the tallied item. Say the next item was a green lego, you would tap "green" in the footer and the button next to "lego" in the table. When I push the view controller with the two subviews the UITableview labels do not update. Thus the tableview needs to be pushed itself (as far as I can tell).
ANSWER: see comment below but ultimately I needed to reload both the visible UITableView data and the delegate UITableView controller data behind it.
I'll give it a shot. First, are you using ARC? If not, you need to add autorelease when you alloc/init a new cell. Otherwise, it's fine as is.
If I'm understanding your question correctly:
The tableView displays the correct data at app launch
You switch away from the tab with the tableView and change the tableView dataSource
You switch back to the tab with the tableView and you see (via NSLog) that the table cells are reloaded with the correct data yet the old data is still visible in the cells
If you scroll a cell off the display and back forcing it to refresh it contains the correct data
Some thoughts:
the tableView will not reload itself automatically when it's view appears. You need to call [tableView reloadData] whenever the dataSource changes. This is independent of whether the tableView is currently displayed or not. My guess is this alone will solve your problem.
You don't need to call setNeedsLayout on the cell unless you want the cell to relayout its subviews based on the data. You also don't need setNeedsDisplay.
I'm assuming there aren't other complicating factors (such as multiple tableViews displaying the same data) that could confuse things.
If you use prepare for reuse method, remember to over the original method with [super prepareForReuse];
Another method if the above way does not work is re setup cell as describe here.
I use the same method i applied for some of my collection view : we should remove/reset your subview where you create/add it to cell 's content. That mean we need set up data each cell completely for each row.
I move the code reset data value from prepare reuse to where i set value and I worked simply !
In my CustomCell.m :
- (void)configCellWith:(id)item At:(NSUInteger)row {
if (_scrollView) {
[[_scrollView subviews]
makeObjectsPerformSelector:#selector(removeFromSuperview)];
_scrollView = nil;
[_scrollView removeFromSuperview];
}
else {
CGFloat y = labelHeight+15;
float scrollHeight = _imgArray.count*200;
_scrollView=[[UIScrollView alloc]initWithFrame:CGRectMake(10, y,SCREEN_WIDTH-20, scrollHeight)];
_scrollView.scrollEnabled=YES;
_scrollView.userInteractionEnabled=YES;
_scrollView.layer.masksToBounds = YES;
[self.contentView addSubview:_scrollView]; } }
Remember to change your data source appropriately too.