Changing a view programmatically to align left or right - swift

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;
}

Related

Fast enumeration does not give correct object type in objective-c?

I think this is pretty weird. So I have a UIView and I want to change the textcolor of all UILabel. Here is what I did:
for (UILabel *label in [self subviews]) { // self is the UIView
label.textColor = someColor;
}
When I run the code, it crashed with error like UIImageView: unrecognized selector setTextColor: sent to instance (some instance)
So it seems that the label in the fast enumeration is actually a UIImageView. By the way, I do have two UIImageViews in the UIView *self. However, shouldn't the fast enumeration give only UILabel only (because I specified UILabel *label instead of UIView *label)?
I think this is the problem because when I wrote the following code, it works.
for (UILabel *label in [self subviews]) { // self is the UIView
if ([label isKindOfClass:[UILabel class]]) {
label.textColor = someColor;
}
}
So in this code, when I check to guarantee that label is a UILabel and then set its textcolor, all UILabels in the view changes their color correctly.
Can someone explain why I need the if-statement to double-check the instance type?
Your 2 loops are very different. The first assumes that all subviews will be labels, the second checks each subview to ensure it is a label. The first should check too.
Fast enumeration is basically syntactic sugar, it's just a normal for loop. It doesn't magically filter or manipulate the list you give it to enumerate.
You're reading the enumeration as "loop for every UILabel in my subviews", but that's not how it really works-- fast enumeration doesn't do any smart filtering, it's just a shortcut. You should read it as "loop for every object in my subviews, which are UILabels". And because not every subview is in fact a UILabel, you have problems when trying to treat them all as one.
Every object in that array will be a UIView though, so the more telling syntax would be:
for (UIView * view in self.subviews) {
if ([view isKindOfClass:[UILabel class]]) {
UILabel * label = (UILabel *)view;
// do something with label...
}
}
in the first loop your are iterating for all subviews of the main view while in the second for loop you are still iterating for all the objects but modifying only UILabel type elements.
Your main view can contain all types of views like UIImageView UILabel etc.....Enumerating over UIlabel as a datatype doesn't actually change the datatype of it and not going to enumerate over only that type of elements..
More elegant: tag your labels.
#define kLabelOffset 100;
for (int i=kLabelOffset +1; i < totalLabels; i+) {
UILabel *label = (UILabel*) [self viewWithTag:i];
label.textColor = someColor;
}
[self subviews] returns an NSArray which contains (id) pointers to UIViews. These could be any type of View including UILabels. So if a particular subview does not support setTextColor you will get an unrecognised selector message. So you do need your isKindOfClass test or you could use a respondsToSelector test to be more general.
In objective C we do not have type casting. Just by using fast enumeration and assigning that variable to UILabel doesnot typecast it to UILabel. Thats the reason for the crash. We can either use respondsToSelector or as you have used isKindOfClass to make the code work as expected.

Large UICollectionViewCell stopped being displayed when scrolling

The same behavior of UICollectionView as described here has been led to this question. Even though I decided to post my own one, because I did further investigations, I didn't want to post in a comment or in edit of the question mentioned above.
What happens?:
When large cells being displayed in a UICollectionView with a UICollectionViewFlowLayout, after scrolling the collection view to a certain offset, the cells will disappear.
When scrolling further until another cell comes into visible area, the vanished/hidden cell becomes visible again.
I tested with a vertical scrolling collection view and full-width-cells, but I'm rather sure, that it would also happen with similar setups for horizontal scrolling.
What are large cells?:
The described behavior happens with cells higher than twice the display height (960.f + 1.f on 3,5 inch displays, 1136.f + 1.f on 4 inch).
What exactly happens?:
When the scrolling offset of the collection view exceeds cell.frame.origin.y + displayHeightOfHardware the cells hidden property is set to YES and -collectionView:didEndDisplayingCell:forItemAtIndexPath: gets called (e.g. the first cell changes to hidden when scrollingOffset.y reaches 481.f on 3,5-inch-iPhone).
As described above, when scrolling until next cell comes into view, the hidden cell gets displayed again (i.e. hidden property changes to NO) and furthermore, when scrolling far enough the cell will never vanish again, when it shouldn't, no matter where you scroll to.
This changes when working with cells larger than triple-display-height (1441.f/1705.f). Those show the same behavior, but it stays the same, no matter how far they're being scrolled up and down.
What else?:
The situation can not be fixed by overriding -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds to return YES.
The cells cannot being forced to display with setting the hidden property to NO programmatically after they were hidden (in didEndDisplayingCell for example)
So, whats the question?:
I'm pretty sure, that this is a bug in UICollectionView/Controller/Cell/Layout and I'll submit a TSI at Apple. But for the meantime: Has anyone any ideas for a quick hack solution?
i have a VERY dirty and internal solution for this problem:
#interface UICollectionView ()
- (CGRect)_visibleBounds;
#end
#interface MyCollectionView : UICollectionView
#end
#implementation MyCollectionView
- (CGRect)_visibleBounds {
CGRect rect = [super _visibleBounds];
rect.size.height = [self heightOfLargestVisibleCell];
return rect;
}
- (float)heightOfLargestVisibleCell {
// do your calculations for current max cellHeight and return it
return 1234;
}
#end
I have a workaround that seems to be working for me and should not run amok of Apple's rules for iOS applications.
The key is the observation that the large cells bounds are the issue. I've worked around that by ensuring that one edge of the cell is within the viewable area of the scrollable content region. You'll obviously need to subclass the UICollectionViewFlowLayout class or UICollectionViewLayout depending on your needs and make use of the contentOffset value to track where you are in the UIScrollView.
I also had to ensure:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
returns YES or face a runtime exception indicating the layout was invalid. I keep the edge of the larger cell bound to the left edge in my case. This way you can avoid the erroneous bounds intersection detection for these larger cells.
This does create more work depending on how you would like the contents of the cell to be rendered as the width/height of the cell is being updated as you scroll. In my case, the subviews within the cell are relatively simple and do not require a lot of fiddling with.
As requested here is an example of my layoutAttributesInRect
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray* attributes = [NSMutableArray array];
NSArray *vertical = myVerticalCellsStore.cells;
NSInteger startRow = floor(rect.origin.y * (vertical.count)/ (vertical.count * verticalViewHeight + verticalViewSpacing * 2));
startRow = (startRow < 0) ? 0 : startRow;
for (NSInteger i = startRow; i < vertical.count && (rect.origin.y + rect.size.height >= i * verticalViewHeight); i++) {
NSArray *horizontals = myHorizontalStore.horizontalCells;
UICollectionViewLayoutAttributes *verticalAttr = [self layoutAttributesForSupplementaryViewOfKind:#"vertical" atIndexPath:[NSIndexPath indexPathForItem:0 inSection:i]];
if (CGRectIntersectsRect(verticalAttr.frame, rect)) {
[attributes addObject:verticalAttr];
}
BOOL foundAnElement = NO;
for (NSInteger j = 0 ; j < horizontals.count; j++) {
MYViewLayoutAttributes *attr = (MyViewLayoutAttributes *)[self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:j inSection:i]];
if (CGRectIntersectsRect(rect, attr.frame)) {
[attributes addObject: attr];
foundAnElement = YES;
}
else if (foundAnElement) {
break;
}
}
}
return attributes;
}
This is my sanitized code. Basically I calculate about were the first cell should be based on the cell height. In my case that is fixed, so the calculation is pretty easy. But my horizontal elements have various widths. So the inner loop is really about figuring out the right number of horizontal cells to include in the attributes array. There I'm using the CGRectIntersectsRect to determine if the cell intersects. Then the loop keeps going until the intersection fails. And if at least one horizontal cell has been found the loop will break. Hope that helps.
My solution is basically the same as Jonathan's but in a category, so you don't have to use your own subclass.
#implementation UICollectionView (MTDFixDisappearingCellBug)
+ (void)load {
NSError *error = nil;
NSString *visibleBoundsSelector = [NSString stringWithFormat:#"%#isib%#unds", #"_v",#"leBo"];
if (![[self class] swizzleMethod:NSSelectorFromString(visibleBoundsSelector) withMethod:#selector(mtd_visibleBounds) error:&error]) {
FKLogErrorVariables(error);
}
}
- (CGRect)mtd_visibleBounds {
CGRect bounds = [self mtd_visibleBounds]; // swizzled, no infinite loop
MTDDiscussCollectionViewLayout *layout = [MTDDiscussCollectionViewLayout castedObjectOrNil:self.collectionViewLayout];
// Don`t ask me why, but there's a visual glitch when the collection view is scrolled to the top and the max height is too big,
// this fixes it
if (bounds.origin.y <= 0.f) {
return bounds;
}
bounds.size.height = MAX(bounds.size.height, layout.maxColumnHeight);
return bounds;
}
#end
I found that this issue only occurred when using a subclassed UICollectionViewLayoutAttributes and when that attribute class did not have a correct isEqual: method.
So for example:
#implementation COGridCollectionViewLayoutAttributes
- (id)copyWithZone:(NSZone *)zone
{
COGridCollectionViewLayoutAttributes *attributes = [super copyWithZone:zone];
attributes.isInEditMode = _isInEditMode;
return attributes;
}
- (BOOL)isEqual:(id)other {
if (other == self) {
return YES;
}
if (!other || ![[other class] isEqual:[self class]]) {
return NO;
}
if ([((COGridCollectionViewLayoutAttributes *) other) isInEditMode] != [self isInEditMode]) {
return NO;
}
return [super isEqual:other];
}
#end
Worked but originally I had:
return YES;
This is on iOS 7.

UITableView Separator Style Question

I have a tableview that is blank by default. User can add cells to it.
I want the separator lines to be clear when there are no cells, and grey when there are cells.
I am using this code:
if ([[self.fetchedResultsController fetchedObjects] count] == 0)
{
self.routineTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.routineTableView.separatorColor = [UIColor clearColor];
}
else
{
self.routineTableView.separatorColor = [UIColor grayColor];
}
The problem is, when I launch the app with a blank table, and if I add cells, the grey lines are not there there until I restart the app. But if I start with cells there, then delete them, then re-add them, the lines are there. Any suggestions?
Maybe you are missing this?
...
else
{
self.routineTableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine; // or you have the previous 'None' style...
self.routineTableView.separatorColor = [UIColor grayColor];
}
EDIT :
You need this but not only this... According to Apple Documentation :
The value of this property is one of the separator-style constants described in UITableViewCell Class Reference class reference. UITableView uses this property to set the separator style on the cell returned from the delegate in tableView:cellForRowAtIndexPath:.
That means the style wont change for cells that are already loaded. Just scrolling the table to force cells to redraw should make separators appearing...
You then have to :
set it BEFORE cell is inserted
OR
reload tableView when the first cell is added
which is not easy to do with a NSFetchedResultsController, you should look into its delegate for a solution... or change direction, like hiding the tableView until you have a result maybe...
EDIT 2 : You can also simply add this :
[self.tableView reloadData];
but that's a dirty workaround that will just reload full tableView, losing most benefits of NSFetchedResultsController...
A quick fix I usually do is:
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if ([tableView respondsToSelector:#selector(setSeparatorStyle:)]) {
[tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
}
}
This changes the boolean flag of whether there will be a separator or not. Put this in viewDidLoad:
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
And to make sure you've really made it go away, set the seperatorColor property to whatever the background color of the view and cell would be:
// If the background is white
self.tableView.separatorColor = [UIColor whiteColor];
So then even if somehow the above does not get called and the separator is still persisting - it would be the same color as what is behind it, therefore invisible.
Good luck.

UISwitch and UITableViewCell iOS4

Caveat: I have looked for the answer to my question and several come close, but I'm still missing something. Here is the scenario:
I want a way to create UITableViewCells that contain UISwitches dynamically at run time, based on the data in a table (which I can do). The problem becomes connecting the switches such that I can get their value when that view is changed (navigated away, closed, etc). I have tried to use the events UIControlEventValueChanged to be notified, but have failed to specify it correctly, because it dumps when that switch is tapped. Also, there doesn't seem to be any way to uniquely identify the switch so that if all the events are handled by a single routine (ideal), I can't tell them apart.
So...
If I have a UITableView:
#interface RootViewController : UITableViewController
{
UISwitch * autoLockSwitch;
}
#property (nonatomic, retain) UISwitch * autoLockSwitch;
-(void) switchFlipState: (id) sender;
#end
// the .m file:
#implementation RootViewController
// ...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * CellIdentifier = #"Cell";
int row = 0;
NSString * label = nil;
TableCellDef_t * cell_def = nil;
row = indexPath.row;
cell_def = &mainMenuTableCellsDef[ row ];
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
label = (NSString *) mainMenuTableCellsDef[indexPath.row].text;
[cell.textLabel setText:(NSString *) mainMenuItemStrings[ indexPath.row ]];
if (cell_def->isSpecial) // call special func/method to add switch et al to cell.
{
(*cell_def->isSpecial)(cell ); // add switch, button, etc.
}
else
{
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
}
}
and this is the 'special' function:
-(void) autoLockSpecialItem :(UITableViewCell *) cell
{
autoLockSwitch = [[[UISwitch alloc] initWithFrame:CGRectZero] autorelease];
[autoLockSwitch addTarget:self action:#selector(switchFlipState:) forControlEvents:UIControlEventValueChanged ];
[cell addSubview:autoLockSwitch];
cell.accessoryView = autoLockSwitch;
}
and finally:
-(void) switchFlipState: (id) sender
{
NSLog(#"FLIPPED");
}
==============================================================
Questions:
Why would it crash (bad selector) when the switch was tapped? I believe that my code follows all the example code that I have seen, but obviously something is wrong.
I cannot put a instance method into a table as a function pointer; and it doesn't seem to like a class method either. If I make it a 'C/C++' function, how do I get access to the class/instance member variables? That is, if I want to put a call to autoLockSpecialItem into a static table (or reasonable facsimile) such that I can get autoLockSwitch member variable? If I make it a class method and the autoLockSwitch var a static, will that be valid?
More simply: how do I connect the UIControlEventValueChanged to my view (I have tried and failed) and can I differentiate at runtime within the event handler which switch has changed?
Is there a better way? I cannot believe that I am the first person to have to solve this type of problem.
Apologies for the length, appreciation for attention and grateful for any and all help.
:bp:
Don't know about why your method isn't connected, but a simple way to "differentiate at runtime within the event handler which switch has changed" is to take the (id)sender given to your event handler, walk your tableview, and compare the sender to any switches, if present, in each table item. If that's too slow, a hash table connecting senders to table cells, or something like that, is a possible optimization.
If you want to use C function pointers, you need to pass the object to the function to use it to call the object's property accessor methods within the function. (Or you could assign the object to a global variable if it's clearly a singleton, but that's a very politically incorrect answer.)
First, and easy way to define your different switches would be defining their tag based on the row number. When one of the switches is tapped you can access sender.tag to get the row number this way.
Also, you should probably be adding the switch the the cells content view, not the actual cell, [cell.contentView addSubview:autoLockSwitch]. Also the frame does need to be set (note CGRectZero, cocoa will ignore the width and height but uses the x,y coords to define where you want the switch in the cell.

Can't indent UITableViewCell subclass

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.