I have subclassed UITableViewCell to create a custom cell with a button and 2 labels. The cell definition is loaded from a xib using the pattern outlined in Dave Mark's Beginning iPhone Development. Here's the essential code:
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MarketItemCustomCell" owner:self options:nil];
for (id oneObject in nib)
{
if ([oneObject isKindOfClass:[MarketItemCustomCell class]])
{
cell = (MarketItemCustomCell *)oneObject;
break;
}
}
The labels and button display as expected but the indentation level is not respected. I have implemented indentationLevelForRowAtIndexPath like below, but the cell is still aligned all the way to the left.
- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath
{
<snip/>
return 5;
}
Note that the indentation level works fine when I'm not using a custom cell.
Any hints?
Update for iOS 8: The code below no longer works for some reason
Alternate suggestion for iOS 8:
Consider creating an AutoLayout constraint to represent the indenting left margin
At runtime, modify this constraint to change the indent level
If you have a custom UITableViewCell subclass, this subclass can have an IBOutlet mapped to the constraint. (Unfortunately Apple did not provide a findConstraintByTag/Id function)
The ironically named "constant" property of the NSLayoutConstraint can be modified to change the indent level.
For iOS 7, this works (but should no longer be recommended)
Based on vodkhang's suggestion, I implemented the following solution in my UITableViewCell sublass. Not sure if this is the best solution but it appears to work fine.
- (void)layoutSubviews
{
[super layoutSubviews];
float indentPoints = self.indentationLevel * self.indentationWidth;
self.contentView.frame = CGRectMake(indentPoints,
self.contentView.frame.origin.y,
self.contentView.frame.size.width - indentPoints,
self.contentView.frame.size.height);
}
I think that you need to customize the indentation level in your custom UITableViewCell. I guess this method (and methods like – tableView:heightForRowAtIndexPath:) will not affect in the case of UITableViewCell if your custom UITableViewCell already specified it
If you design a UITableViewCell in a xib, you'll be using the customView cell property, which does not utilize indentation automatically. You can subclass UITableViewCell and implement layoutSubviews to change your frame based on indentation or you can do the same from wherever you configure your cells in your table delegate or datasource. I wish that Apple implemented indentation for custom cells.
Related
I have been building this chat and I am stuck in something that I can't figure out yet.
I have been unsuccessfully trying to align the same view left or right according to the scenario but I haven't gotten the desired result yet.
Basically, I built the cell in interface builder and pinned to the left and made its width and height variable according to the contents of the labels inside.
What I have been trying to do is reusing the same cell and align to the right when the I am the one sending the message (yellow) because by default it will be aligned to the left when others send me a message (gray) see the following image for illustration:
What I want is:
others messages--------
--------------my message
To accomplish this, and under TroyT's suggestion, I activated and deactivated the leading/trailing constraints according to my needs.
So what I did was.
On my UITableViewCell Class, I created two #IBoulets for my two constraints like so:
#IBOutlet var bubbleViewLeading: NSLayoutConstraint!
#IBOutlet var bubbleViewTrailing: NSLayoutConstraint!
Later, in my tableViewController on the method cellForRowAtIndexPAth , I activate or deactivate one of the constraints according to my needs doing the following.
cell.bubbleViewTrailing.active = true
cell.bubbleViewLeading.active = false
or the opposite
cell.bubbleViewTrailing.active = false
cell.bubbleViewLeading.active = true
where the constraint set to "True" is set to 0. As I mentioned before, this work well for all cells except the first one.
This works PARTIALLY, because for some reason it does not affect the first row and both constraints stay activated, hence the row is stretched through the tableview's width like so:.
I tried several things like:
Using the init method from my UITableViewCell class to change the constraints
Changing the constraints from the awakeFromNib method within my UITableViewCell
Using the "User Defined Runtime Attributes" on the 3rd tab from the left on the storyboard, add a key path named "active", set the type to Bool, and set the value to false.
unticking "installed" in the attribute inspector when I select the constraints in the storyboard
However, none of these methods affect the constraints on the first cell but I can easily change everything else like the cell background or the text alignment in a label within the cell. It just the constraint that will not change for that first time that I use the cell. Even when I scroll down past the boundaries of the screen to "force" the cell reuse, the first cell becomes fixed:
I have been trying to figure this one out for days with little success. Hence, I decided to put a bounty on this question.
What you explain makes little sense to me, but if it is happening only with the first row, then I would try forcing the layout to redraw by doing:
cell.setNeedsLayout()
cell.layoutIfNeeded()
from the cellForRowAtIndexPath
BTW, I think a better approach would be to register 2 different cells, and create them both in IB. This approach will keep your code shorter and will allow you to modify not only the trailing/leading but also colors, fonts or whatever you need. You give them 2 different IDs, but keep the same implementation class, and you just deque the one you need on your cellForRowAtIndexPath.
Keeping a different cell per style is the standard way to do these things with IB.
removeConstraint: is deprecated, in favor of using the active property instead (or NSLayoutConstraint's deactivateConstraints:). While removeConstraint: still usually works, combining that with setting your constraints' active property makes this worse. Either use all activate and deactivate (preferred) or use add and remove.
So instead of cell.removeConstraint:, use this:
cell.bubbleViewTrailing.active = false
One thing I may note is that your leading and trailing is reversed. Leading is on the left and trailing is on the right, except for right-to-left localization.
Here is the Custom ChatMessageCell that I have implemented for you in which I have applied programmatically constrains using the KVConstraintExtensionsMaster library to apply constraints that I have implemented.
I have update the leading & trailing constraints Constant value instead of activate/deactivate constraint or remove/add constraint.
I hope this may help you.
Put below code in ChatMessageCell.h header file
typedef NS_ENUM(NSUInteger, Type) {
TypeSender,
TypeReceiver,
};
#interface ChatMessageCell : UITableViewCell
#property (assign, nonatomic) Type cellType;
#property (strong, nonatomic) UIView *msgBackgroundView;
#property (strong, nonatomic) UILabel *messageLabel;
#end
Put below code in ChatMessageCell.m file
#import "ChatMessageCell.h"
#import "KVConstraintExtensionsMaster.h"
#implementation ChatMessageCell
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
}
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self createAndConfigureViewHierarchy];
}
return self;
}
- (void)createAndConfigureViewHierarchy
{
_msgBackgroundView = [UIView prepareNewViewForAutoLayout];
[self.contentView addSubview:_msgBackgroundView];
_msgBackgroundView.backgroundColor = [UIColor clearColor];
[_msgBackgroundView.layer setCornerRadius:6.0];
_labelMessage = [UILabel prepareNewViewForAutoLayout];
[_labelMessage setLineBreakMode:NSLineBreakByTruncatingTail];
[_labelMessage setNumberOfLines:0];
[_labelMessage setTextAlignment:NSTextAlignmentLeft];
[_labelMessage setTextColor:[UIColor whiteColor]];
self.labelMessage.backgroundColor = [UIColor clearColor];
[_msgBackgroundView addSubview:_labelMessage];
[self applyConstraints];
}
-(void)applyConstraints
{
// now applying the constraints by using KVConstraintExtensionsMaster library
CGFloat padding = 8.0;
// adding Top and Bottom contraints of _msgBackgroundView
[_msgBackgroundView applyTopAndBottomPinConstraintToSuperviewWithPadding:padding];
// adding leading and trailing contraints of _msgBackgroundView
[_msgBackgroundView applyLeadingAndTrailingPinConstraintToSuperviewWithPadding:padding];
// adding Top and Bottom contraints of _msgBackgroundView
[_labelMessage applyTopAndBottomPinConstraintToSuperviewWithPadding:padding];
// adding leading and trailing contraints of _msgBackgroundView
[_labelMessage applyLeadingAndTrailingPinConstraintToSuperviewWithPadding:padding];
}
-(void)setCellType:(Type)cellType
{
switch (cellType) {
case TypeSender:
{
[_msgBackgroundView setBackgroundColor:[UIColor redColor]];
[_labelMessage setTextAlignment:NSTextAlignmentRight];
// this method will change the Leading Pin Constraint Constant value by 100.0
[_msgBackgroundView applyLeadingPinConstraintToSuperviewWithPadding:100.0];
// this method will increase the Leading Pin Constraint Constant value with proper ratio only iPad
[_msgBackgroundView updateAppliedConstraintConstantValueForIpadByAttribute:NSLayoutAttributeLeading];
break;
}
case TypeReceiver:
{
[_msgBackgroundView setBackgroundColor:[UIColor redColor]];
[_labelMessage setTextAlignment:NSTextAlignmentLeft];
// this method will change the Trailing Pin Constraint Constant value by 100.0
[_msgBackgroundView applyTrailingPinConstraintToSuperviewWithPadding:100.0];
// this method will increase the Leading Pin Constraint Constant value with proper ratio only iPad
[_msgBackgroundView updateAppliedConstraintConstantValueForIpadByAttribute:NSLayoutAttributeTrailing];
break;
}
}
[self.contentView setNeedsLayout];
[self.contentView updateModifyConstraints];
}
-(void)prepareForReuse
{
// this method will change the Leading And Trailing Pin Constraints Constant value by 8.0
[_msgBackgroundView applyLeadingAndTrailingPinConstraintToSuperviewWithPadding:8.0];
[super prepareForReuse];
}
#end
Put the below code in the viewDidLoad method ofyour ViewController is:
[self.tableView registerClass:ChatMessageCell.class forCellReuseIdentifier:#"KVChatMessageCell"];
self.tableView.rowHeight = UITableViewAutomaticDimension;
/* any estimated height but must be more than 2 */
self.tableView.estimatedRowHeight = 44.0;
Now implement UITableView DataSource in your ViewController
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section{
return messages.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"KVChatMessageCell";
ChatMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
[cell.labelMessage setText:messages[indexPath.row]];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
if (indexPath.row%2 == 0) {
// here all even cell are the sender type
[cell setCellType:TypeSender];
}
else {
// here all odd cell are the Receiver type
[cell setCellType:TypeReceiver];
}
return cell;
}
Basically I've been writing iOS apps for about 3 days (after reading a book to get the basic knowledge of design patterns and frameworks etc) and 1 entire day has been spent on this. I cannot get my CollectionView to display. I've got a storyboard that starts with a TabBarView and the two tabs (one is a TableView and the other is just a UIView) work, but the third tab (UICollectionView) just displays a black screen even after setting up.
How I set it up:
1) dragged a ViewController to the storyboard
2) made a relationship segue between my UITabBarController to the new ViewController
3) dragged a UICollectionView to the new ViewController
4) dragged 4 UICollectionViewCell's to the CollectionView
5) dragged 4 UILabels into said CollectionViewCell's
6) made a connection between the new CollectionView's delegate & data source and the header file of my CollectionView class (this might be where I went wrong, I did not make the connection to my ViewController.h class as I used this one for my UITableView and thought a new class was necessary)
7) declared the following methods in my CollectionView.m file
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
//Populates each cell using the data given (no data in this case).
//Should return a CollectionViewCell.
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
return cell;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
//Gets the number of cells in each section.
//Should return an integer value.
return 2;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
//Tells the collection view the number of sections it should have.
//Should return an integer value.
return 4;
}
I get the feeling there is something I missed out, as this logic makes sense to me as a programmer but, having such little experience in Xcode, maybe I forgot to link up this class to my CollectionView or something. Does anybody know where I went wrong?
BTW in case you're wondering, I'm using this CollectionView more of a navigation technique than to display wonderful images or anything, I am going to populate them with generic images later when they actually show up in the emulator.
thanks
It is not displaying anything because default color of label is black, you'll have to change the color of text to any light color to see text over cell.
Now,
->Add a new file which should be subclass of UICollectionViewCell, Ctrl+drag from label to this class.(for eg I took mainViewCell)and label property name I took labelText
->Declare reuseIdentifier for cell from Attribute Inspector,(mainCell, I took)
->Import cellClass to your UIViewController Class,
->Using this method to display any text
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
mainViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"mainCell" forIndexPath:indexPath];
cell.labelText.text = #"What-ever-you-want-display";
return cell;
}
hope this would help...
Did you tell your view controller(the one that contains the collection view) to use UICollectionViewDelegateFlowLayout? For example,
#interface RootViewController () <UICollectionViewDelegateFlowLayout, UICollectionViewDataSource>
Also, have you set the reuse identifier for your cell? In Interface Builder set this in the attributes inspector. Then go to the cellForItemAtIndexPath: method and change this line to use your identifier.
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"MyCell" forIndexPath:indexPath];
Check out UICollectionViewDelegateFlowLayout in the documentation for methods used to size cells and set margins and spacing.
The step 6) might be inadequate. Have you ctrl-dragged the collectionView to the controller icon beneath the main view directly? You would have been prompted in a black dialog box for connecting dataSource and delegate. The connections are made in XCode this way.
Alternatively you could explicitly set the dataSource as self in the controller implementation.
Remove registerClass code from the default view controller code snippet
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:reuseIdentifier];
I'm trying to make a UITableView like the native calendar app:
but I'm trying to learn the best way to do this. I'm able to get this for the most part with a switch statement in the cellForRowAtIndexPath method, but I'm having troubles changing the textColor when a cell is selected.
For some reason cell.isSelected is always NO, and I have no way to reload the tableview after another cell is selected anyway.
Should I subclass UITableViewCell for something this simple and store an array of cells?
Any help would be appreciated, thanks.
There is no need to subclass, as stated in the app doc the delegation function:
tableView:willSelectRowAtIndexPath:
should do the trick
EDIT:
Below you find some code that should demonstrate the idea of using the delegate. Please note that this code is untested, as i am currently not in front of my xcode.
-(void)willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (prevIndexPath != nil) {
UITableViewCell* prevCell = [self.tableView cellForRowAtIndexPath: prevIndexPath];
prevCell.textLabel.textColor = [UIColor black]; // your initial color here
}
UITableViewCell* cell = [self.tableView cellForRowAtIndexPath: indexPath];
cell.textLabel.textColor = [UIColor green];
prevIndexPath = indexPath;
}
Make sure to set the UITableViewDelegate protocol to your controlling class that manages your TableView and set the tableViews.delegate to that.
To make this code run, you also have to define a property or variable with the name prevIndexPath. This ones holds the previously selected cell that is needed to revert to the cell to its initial color.
I'm using a custom UITableViewCell. They load up great, but when they get reused instead of replacing the text in the labels, they are some how writing over the top of them. Any ideas how to stop this behaviour?
I assume I'm not reseting the view correctly before it gets reused. I'm currently empty the labels so they have just a blank #"" string. But I still get the old text plus the new (very messy).
I'm certain there's an obvious solution to this (presently i just don't reuse the cell, but this isn't best practice and is slow on old devices), so if someone can help I'd be very grateful.
Thanks
ED
As requested here is the method for amending the cell
static NSString *CellIdentifier = #"Cell";
TransactionCellView *cell = (TransactionCellView *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"TransactionCellView" owner:self options:nil];
cell = tblCell;
}
[cell resetCell]; // clears the labels
[cell setData:[dataArray objectAtIndex:[indexPath row]]; // replaces the data and updates the labels
return cell;
}
Solved this issue!!! Amazingly simple when you know what the problem is/was.
I was using IB and "Clear Graphics Context" was not selected on my UILabels. So that is why the next text was just overlaying the old.
THanks guys for trying to help.
I suspect that you are adding the label as a subview programatically in your cellForRowAtIndexPath method. You need to make sure that you are not adding that subview every time the cell is recycled. Instead create the label only when creating a new cell (not when recycling a cell) then assign a tag value to the label and then in the future, when it gets recycled, retrieve the label by tag value and change its text.
You are correct in that the cells are being reused. Your code should be set up roughly using the following pattern:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString* identifier = #"someidentifier";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(!cell) {
// create the cell and add all subviews to it
} else {
// update the cell and access appropriate subviews to modify what is displayed
}
return cell;
}
The cell will be created the first time the identifier is used. For all subsequent requests, the cell is pulled from the UITableView cache (via dequeueReusableCellWithIdentifier), and you can then access its subviews either by tag, index, type, or whatever mechanism you choose.
Somewhat related is that you can have multiple cell identifiers, which allows you to create multiple instances of different cells depending on the data that you have. In one of my projects, I have 4 different cells, each dependent upon the number of lines of data that they will display (anywhere between 1 and 4). This helps ensure a smooth scrolling experience regardless of how many lines the cell has since the renderer doesn't have to worry about dynamically changing the height of the cell on the fly.
Try this..
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
for (UIView* subView in cell.contentView.subviews)
{
[subView removeFromSuperview];
}
// Your Code to customize cell goes here.
}
The standard Grouped UITableView style allows UITableViewCells to be drawn with rounded corners at the top and bottom of each section. How is this accomplished? How does the cell know its own location within its section, and how does it know when to change its rounded edges?
I want to make my own rounded cells, and I have images to use, but don't know when to show which image
Note: I already know how the UITableView works, and I know how to use it. I just thought that since a UITableView is able to automatically draw rounded corners at the correct places, I should be able to as well, without needing to add anything to my data source or delegate.
NSIndexPath *indexPath = [(UITableView *)self.superview indexPathForCell: self];
int rows = [(UITableView *)self.superview numberOfRowsInSection:indexPath.section];
if (indexPath.row == 0 && rows == 1) {
// the one and only cell in the section
}
else if (indexPath.row == 0) {
//top
}
else if (indexPath.row != rows - 1) {
//middle
}
else {
//bottom
}
It's very simple. suppose cell is the object, whose position is to be found out.
UITableView* table = (UITableView *)[cell superview];
NSIndexPath* pathOfTheCell = [table indexPathForCell:cell];
NSInteger sectionOfTheCell = [pathOfTheCell section];
NSInteger rowOfTheCell = [pathOfTheCell row];
There is sectionLocation method of UITableViewCell that returns integer telling you what you need:
1 - middle cell
2 - top cell
3 - bottom cell
4 - single cell
I had no issues using this in several production apps since 2010.
UPDATE: one of our binaries was automatically rejected recently (end of 2018) because we were using 'sectionLocation' property, so it's not a good option anymore.
Add something like this into your header files and you can use it:
typedef NS_ENUM(NSInteger, MMMTableViewCellLocation) {
MMMTableViewCellLocationUndefined = 0,
MMMTableViewCellLocationMiddle = 1,
MMMTableViewCellLocationTop = 2,
MMMTableViewCellLocationBottom = 3,
MMMTableViewCellLocationSingle = 4
};
#interface UITableViewCell ()
/** Undocumented method of UITableViewCell which allows to know where within section the cell is located,
* so the cell can draw its borders properly. */
- (MMMTableViewCellLocation)sectionLocation;
/** Override this one to know when the value of sectionLocation changes. */
- (void)setSectionLocation:(MMMTableViewCellLocation)sectionLocation animated:(BOOL)animated;
#end
You can use
- (NSIndexPath *)indexPathForCell:(UITableViewCell *)cell
for this issue. In my example I am using this to scroll the cell (with custom content) to the top of the view.
If you need more robust and general stuff, take a look at http://cocoawithlove.com/2009/04/easy-custom-uitableview-drawing.html - Matt Gallagher shows what you need, pretty effectively. He basically recreates UITableViewController from UIViewController, while adding ability to use your own custom graphics. I'm just working on applying this to one my projects, so far it looks it would do the job.
Unfortunately, I have found no solution to this problem, and have resorted to subclassing UITableViewController and UITableViewCell into a generic solution that I can extend as necessary.
You don't do this in cell. Rounded corners are drawn in [tableView viewForHeaderInSection] and viewForFooterInSection.
The way I do it is to use Plain tableview style, then use these two views for roundness and cells are normal, no rounds.
Without getting into who draws what, you can know which cell is the last cell in its section inside of cellForRowAtIndexPath very easily.
You're passed in the indexPath of the cell you need to provide, right? You're also passed the tableView.
call [tableView numberofRowsInSection:indexPath.section] and if it's == ([indexPath.row]-1) you know you're being asked to supply the last cell in that section.
At the time that cellForRowAtIndexPath is being called, the cell is guaranteed to be at the indexPath passed in.
To expand upon Darren's answer (which I found most useful, thanks Darren!), what you can do is to iterate through all of the superviews' until you find the parent UITableView. This should be future proof since you do not rely on a fixed hierarchy of views.
I use a recursive method that will return the UITableView if it finds one or return nil if there is none.
- (UITableView *)parentTableViewOf:(UIView *)view {
Class class = [view.superview class];
NSLog(#"Class : %#", NSStringFromClass(class));
if([view.superview isKindOfClass:[UITableView class]]) {
return (UITableView *)view.superview;
} else {
return [self parentTableViewOf:view.superview];
}
return nil;
}
So far I've used this one and it seems to work without hiccups. Hope it helps! :)
The cells dont know where they go...The table view has cells, You are the one telling the table view WHAT goes in the cell. You do this in the DataSource where you implement cellForRowAtIndexPath...The way this works :
An index path has a row and a section
For a grouped table view
A section pertains to a group, and a row pertains to 1 entry in that section,
the way UITableView knows how many rows are in a section and how many sections there are is the DataSources methods numberOfSectionInTableView and the method numberOfRowsInSection, this will make the right calls to cellForRowAtIndexPath, it is up to you to recognize which section and row is being queried and you need to build your cell according to these specifications.
A good way to do this i s you can have a Dictionary with keys of section names and values of NSArray with the values that go in that section.
So you implementation for numberOfSectionsInRows would look like
return [[dictionary allKeys] count]
And the implmentation of numberOfRowsInSection would look like
NSString* key=[[dictionary allKeys] objectAtIndex:sectionNumber]
return [[dictionary objectForKey:key] count]
You can always refer to the UITableView programming guide at http://developer.apple.com/iphone/library/documentation/UserExperience/Conceptual/TableView_iPhone/Introduction/Introduction.html
Hope that helps
Simply add a property to your custom UITableViewCell (depending on implementation) class that contains an int, NSNumber, or an NSIndexPath specifying which one it is. In you're using a data structure instead, then put it in you element in that data structure. Then you simply set the property when you create the data structure, something like elt.id=i, and then you access it in the cellForRowAtIndexPath, something like if (elt.id == 0 || elt.id == n-1) where n is the number of rows in your section.
I might have totally missed your question, but if I did, just comment and I'll post again.