I have two labels in a UITableViewCell subclass. I want both labels to be multiline labels, so I have set the number of lines to zero for both of the labels. But the problem is that the labels keep getting truncated. For example, I have made the text size of the right label much larger so that it is taller but then I have added a lot of text to the first label on the left. But, rather than keep wrapping and adding more lines, it just truncates when it is the same size as the label on the right.
But the label on the left should have had a lot more text displayed and it should have taken several more lines, but instead it is truncated when it gets to the height of the other label. It is almost like I have a height constraint between the two labels, but I do not.
Here is the code I am using for this. The first part is the table view cell subclass that has the two labels - firstLabel (the one on the left) and secondLabel (the one on the right). Next, I have the code for the view that has the table view and shows how the table view is configured. Finally, there is a UITableView subclass that sets the intrinsicContentSize of the table to be the content size of the table. I added this because without this the table frame always stayed at zero and so the table view data source methods were not getting called. If anyone knows a better way to do this too, that would be much appreciated.
UITableViewCell subclass
This is the implementation for my UITableViewCell. I have vertical content hugging and content compression priorities for both labels, but I have tried this with and without these priorities and the result is the same.
#interface MyTableViewCell ()
#property (strong, nonatomic) UILabel *firstLabel;
#property (strong, nonatomic) UILabel *secondLabel;
#end
#implementation MyTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self initialize];
}
return self;
}
- (void)initialize {
[self.contentView setBackgroundColor:[UIColor systemTealColor]];
[self.backgroundView setBackgroundColor:[UIColor systemGrayColor]];
[self.contentView addSubview:self.firstLabel];
[self.contentView addSubview:self.secondLabel];
NSLayoutConstraint *heightConstraint = [self.heightAnchor constraintEqualToConstant:1.0f];
[heightConstraint setPriority:50];
[NSLayoutConstraint activateConstraints:#[
[self.firstLabel.leadingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.leadingAnchor],
[self.firstLabel.topAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.topAnchor],
[self.firstLabel.trailingAnchor constraintEqualToAnchor:self.centerXAnchor constant:-4.0f],
[self.secondLabel.leadingAnchor constraintEqualToAnchor:self.centerXAnchor constant:4.0f],
[self.secondLabel.topAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.topAnchor],
[self.secondLabel.trailingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.trailingAnchor],
[self.contentView.layoutMarginsGuide.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.firstLabel.bottomAnchor constant:20.0f],
[self.contentView.layoutMarginsGuide.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.secondLabel.bottomAnchor constant:20.0f],
heightConstraint
]];
}
- (UILabel *)firstLabel {
if (!self->_firstLabel) {
self->_firstLabel = [[UILabel alloc] init];
self->_firstLabel.translatesAutoresizingMaskIntoConstraints = NO;
self->_firstLabel.numberOfLines = 0;
self->_firstLabel.userInteractionEnabled = NO;
self->_firstLabel.contentMode = UIViewContentModeScaleToFill;
[self->_firstLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
[self->_firstLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisVertical];
[self->_firstLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[self->_firstLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
self->_firstLabel.textAlignment = NSTextAlignmentNatural;
self->_firstLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self->_firstLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
self->_firstLabel.adjustsFontSizeToFitWidth = NO;
self->_firstLabel.backgroundColor = [UIColor orangeColor];
}
return self->_firstLabel;
}
- (UILabel *)secondLabel {
if (!self->_secondLabel) {
self->_secondLabel = [[UILabel alloc] init];
self->_secondLabel.translatesAutoresizingMaskIntoConstraints = NO;
self->_secondLabel.numberOfLines = 0;
self->_secondLabel.userInteractionEnabled = NO;
self->_secondLabel.contentMode = UIViewContentModeScaleToFill;
[self->_secondLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
[self->_secondLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisVertical];
[self->_secondLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[self->_secondLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
self->_secondLabel.textAlignment = NSTextAlignmentNatural;
self->_secondLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self->_secondLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
self->_secondLabel.adjustsFontSizeToFitWidth = NO;
self->_secondLabel.backgroundColor = [UIColor yellowColor];
}
return self->_secondLabel;
}
- (void)setData:(MyModel *)data {
self.firstLabel.text = data.first;
self.secondLabel.text = data.second;
[self invalidateIntrinsicContentSize];
}
#end
Primary View - has a table view as a child view
This is the actual view that is displayed. As part of the larger application, this view is then displayed in a vertical UIStackView.
This view has a table view as the only subview and the table view is pinned to the edges of this view with AutoLayout. The UITableView is actually an instance of another class called "AutosizingTableView" to get the table view to autosize (without this, the frame of the table stays at zero and the table view data source method, tableView:cellForRowAtIndexPath:, was never being called since the table height was zero. The code for this table view is included after this section.
#interface MyView ()
#property (strong, nonatomic) AutosizingTableView *tableView;
#end
#implementation MyView
- (instancetype)init {
return [self initWithFrame:CGRectZero];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
[self initialize];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self initialize];
}
return self;
}
- (void)initialize {
[self addSubview:self.tableView];
[NSLayoutConstraint activateConstraints:#[
[self.tableView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
[self.tableView.topAnchor constraintEqualToAnchor:self.topAnchor],
[self.tableView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
[self.tableView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
[self.tableView.widthAnchor constraintEqualToAnchor:self.widthAnchor]
]];
}
- (UITableView *)tableView {
if (!self->_tableView) {
self->_tableView = [[AutosizingTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self->_tableView.translatesAutoresizingMaskIntoConstraints = NO;
self->_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self->_tableView.rowHeight = UITableViewAutomaticDimension;
self->_tableView.estimatedRowHeight = UITableViewAutomaticDimension;
self->_tableView.allowsSelection = NO;
self->_tableView.scrollEnabled = NO;
self->_tableView.delegate = self;
self->_tableView.dataSource = self;
[self->_tableView registerClass:[MyTableViewCell class] forCellReuseIdentifier:#"myTableViewCell"];
}
return self->_tableView;
}
- (void)setdata:(NSArray<MyData *> *)data {
self->_data = data;
[self.tableView reloadData];
}
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.data.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"myTableViewCell" forIndexPath:indexPath];
MyData *data = self.data[indexPath.row];
cell.detail = detail;
return cell;
}
#end
AutosizingTableView
As was mentioned in the previous section, this is just to make it so the table view autosizes. Without this, the table view height was zero and stayed at zero since the tableView:cellForRowAtIndexPath: method was never getting called because of the table view height.
#implementation AutosizingTableView
- (CGSize)intrinsicContentSize {
return self.contentSize;
}
- (void)setContentSize:(CGSize)contentSize {
[super setContentSize:contentSize];
[self invalidateIntrinsicContentSize];
}
#end
A bit difficult to know for certain, because you didn't provide a complete working example, but this may fix your issue.
See the comments for changes:
#interface MyTableViewCell ()
#property (strong, nonatomic) UILabel *firstLabel;
#property (strong, nonatomic) UILabel *secondLabel;
#end
#implementation MyTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self initialize];
}
return self;
}
- (void)initialize {
[self.contentView setBackgroundColor:[UIColor systemTealColor]];
[self.backgroundView setBackgroundColor:[UIColor systemGrayColor]];
[self.contentView addSubview:self.firstLabel];
[self.contentView addSubview:self.secondLabel];
// not needed
//NSLayoutConstraint *heightConstraint = [self.heightAnchor constraintEqualToConstant:1.0f];
//[heightConstraint setPriority:50];
[NSLayoutConstraint activateConstraints:#[
[self.firstLabel.leadingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.leadingAnchor],
[self.firstLabel.topAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.topAnchor],
[self.firstLabel.trailingAnchor constraintEqualToAnchor:self.centerXAnchor constant:-4.0f],
[self.secondLabel.leadingAnchor constraintEqualToAnchor:self.centerXAnchor constant:4.0f],
[self.secondLabel.topAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.topAnchor],
[self.secondLabel.trailingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.trailingAnchor],
[self.contentView.layoutMarginsGuide.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.firstLabel.bottomAnchor constant:20.0f],
[self.contentView.layoutMarginsGuide.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.secondLabel.bottomAnchor constant:20.0f],
// not needed
//heightConstraint
]];
}
- (UILabel *)firstLabel {
if (!self->_firstLabel) {
self->_firstLabel = [[UILabel alloc] init];
self->_firstLabel.translatesAutoresizingMaskIntoConstraints = NO;
self->_firstLabel.numberOfLines = 0;
self->_firstLabel.userInteractionEnabled = NO;
// not needed
//self->_firstLabel.contentMode = UIViewContentModeScaleToFill;
//[self->_firstLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
//[self->_firstLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisVertical];
//[self->_firstLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
//[self->_firstLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
self->_firstLabel.textAlignment = NSTextAlignmentNatural;
// word-wrapping, not truncating
//self->_firstLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self->_firstLabel.lineBreakMode = NSLineBreakByWordWrapping;
self->_firstLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
self->_firstLabel.adjustsFontSizeToFitWidth = NO;
self->_firstLabel.backgroundColor = [UIColor orangeColor];
}
return self->_firstLabel;
}
- (UILabel *)secondLabel {
if (!self->_secondLabel) {
self->_secondLabel = [[UILabel alloc] init];
self->_secondLabel.translatesAutoresizingMaskIntoConstraints = NO;
self->_secondLabel.numberOfLines = 0;
self->_secondLabel.userInteractionEnabled = NO;
// not needed
//self->_secondLabel.contentMode = UIViewContentModeScaleToFill;
//[self->_secondLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
//[self->_secondLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisVertical];
//[self->_secondLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
//[self->_secondLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
self->_secondLabel.textAlignment = NSTextAlignmentNatural;
// word-wrapping, not truncating
//self->_secondLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self->_secondLabel.lineBreakMode = NSLineBreakByWordWrapping;
self->_secondLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
self->_secondLabel.adjustsFontSizeToFitWidth = NO;
self->_secondLabel.backgroundColor = [UIColor yellowColor];
}
return self->_secondLabel;
}
- (void)setData:(MyData *)data {
self.firstLabel.text = data.first;
self.secondLabel.text = data.second;
// this is needed
[self.firstLabel layoutIfNeeded];
[self.secondLabel layoutIfNeeded];
// not needed
//[self invalidateIntrinsicContentSize];
}
#end
Related
I have a UICollectionView where when cells are selected, they change to a color selected by the user. To paint the full picture: The color is selected by the user from a color wheel (UIImageView) with a tap gesture attached to it.
That said, when the user taps a new color, say purple (and resets the defined rString, bString & gString...) after selecting 3 cells and making them green, I want to reload the color they're using without wiping the initial 3 selected green cells from the Collection View. How can I accomplish this?
See code below.
ViewController.m
#interface ViewController () {
CGPoint lastPoint;
NSInteger rString;
NSInteger bString;
NSInteger gString;
UIColor *colour;
}
#property (strong, nonatomic, nullable) NSIndexPath *trackingCellIndexPath;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.ringCollectionView.allowsMultipleSelection = YES;
UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(tapGesture:)];
[self.colorWheel addGestureRecognizer:tapRecognizer];
self.colorWheel.userInteractionEnabled = YES;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"RingCollectionViewCell" forIndexPath:indexPath];
if (!cell.selectedBackgroundView) {
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:cell.bounds];
cell.selectedBackgroundView.backgroundColor = [UIColor grayColor];
} else {
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:cell.bounds];
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithRed:rString/255.0 green:gString/255.0 blue:bString/255.0 alpha:1.0];
}
if ((indexPath.row >=9 && indexPath.row <=14) || ((indexPath.row >=17 && indexPath.row < 23) || (indexPath.row >=25 && indexPath.row <=30) || (indexPath.row >=33 && indexPath.row <=38))) {
NSLog(#"NOT AVAILABLE SORRY");
cell.backgroundColor = [UIColor whiteColor];
[cell setUserInteractionEnabled:NO];
}
return cell;
}
-(void)tapGesture:(UITapGestureRecognizer *)recognizer {
CGPoint location = [recognizer locationInView:recognizer.view];
CGPoint p = { round(location.x), round(location.y) };
_colorView.backgroundColor = [self colorInViewAtPoint:p];
UIColor *mylovelycolor = [self colorInViewAtPoint:p];
const CGFloat *components = CGColorGetComponents(mylovelycolor.CGColor);
NSLog(#"Red: %f", components[0]);
NSLog(#"Green: %f", components[1]);
NSLog(#"Blue: %f", components[2]);
NSLog(#"Alpha: %f", CGColorGetAlpha(mylovelycolor.CGColor));
int red = components[0] * 255;
int green = components[1] * 255;
int blue = components[2] * 255;
NSString *red1 = [#(red) stringValue];
NSString *green1 = [#(green) stringValue];
NSString *blue1 = [#(blue) stringValue];
NSInteger redInt = [red1 integerValue];
NSInteger greenInt = [green1 integerValue];
NSInteger blueInt = [blue1 integerValue];
rString = [red1 integerValue];
bString = [blue1 integerValue];
gString = [green1 integerValue];
self.redValue.text = red1;
self.greenValue.text = green1;
self.blueValue.text = blue1;
NSMutableString *str1 = [NSMutableString string];
for(NSInteger numberCopy = redInt; numberCopy > 0; numberCopy >>= 1)
{
[str1 insertString:((numberCopy & 1) ? #"1" : #"0") atIndex:0];
}
NSMutableString *str2 = [NSMutableString string];
for(NSInteger numberCopy = greenInt; numberCopy > 0; numberCopy >>= 1)
{
[str2 insertString:((numberCopy & 1) ? #"1" : #"0") atIndex:0];
}
NSMutableString *str3 = [NSMutableString string];
for(NSInteger numberCopy = blueInt; numberCopy > 0; numberCopy >>= 1)
{
[str3 insertString:((numberCopy & 1) ? #"1" : #"0") atIndex:0];
}
self.binaryString = [NSString stringWithFormat:#" %# %# %#", str1, str2, str3];
}
You need to track your user-selected colors in your data model.
In cellForItemAtIndexPath you want to set the cell's background color (or whatever element you're using) to the data element color.
When the user has one or more cells selected, and taps your "colorWheel," update your data model and then either set the cell elements directly or reload those cells.
Here is a very simple example...
MyDataObject.h
//
// MyDataObject.h
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface MyDataObject : NSObject
#property (strong, nonatomic) UIColor *userColor;
#end
NS_ASSUME_NONNULL_END
MyDataObject.m
//
// MyDataObject.m
//
#import "MyDataObject.h"
#implementation MyDataObject
#end
MyCollectionViewCell.h
//
// MyCollectionViewCell.h
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface MyCollectionViewCell : UICollectionViewCell
#property (strong, nonatomic) UILabel *label;
#end
NS_ASSUME_NONNULL_END
MyCollectionViewCell.m
//
// MyCollectionViewCell.m
//
#import "MyCollectionViewCell.h"
#implementation MyCollectionViewCell
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit {
_label = [UILabel new];
_label.textAlignment = NSTextAlignmentCenter;
_label.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0];
_label.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:_label];
UILayoutGuide *g = [self.contentView layoutMarginsGuide];
[NSLayoutConstraint activateConstraints:#[
[_label.widthAnchor constraintEqualToAnchor:g.widthAnchor multiplier:0.8],
[_label.heightAnchor constraintEqualToAnchor:g.heightAnchor multiplier:0.8],
[_label.centerXAnchor constraintEqualToAnchor:g.centerXAnchor],
[_label.centerYAnchor constraintEqualToAnchor:g.centerYAnchor],
]];
self.contentView.layer.borderColor = [UIColor yellowColor].CGColor;
}
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
self.contentView.layer.borderWidth = selected ? 2 : 0;
}
#end
MyTestViewController.h
//
// MyTestViewController.h
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface MyTestViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegate>
#end
NS_ASSUME_NONNULL_END
MyTestViewController.m
//
// MyTestViewController.m
//
#import "MyTestViewController.h"
#import "MyCollectionViewCell.h"
#import "MyDataObject.h"
#interface MyTestViewController ()
{
NSMutableArray <MyDataObject *>*myCellData;
UICollectionView *collectionView;
}
#end
#implementation MyTestViewController
- (void)viewDidLoad {
[super viewDidLoad];
UICollectionViewFlowLayout *fl = [UICollectionViewFlowLayout new];
fl.itemSize = CGSizeMake(50, 50);
fl.scrollDirection = UICollectionViewScrollDirectionHorizontal;
collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:fl];
collectionView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:collectionView];
UILayoutGuide *g = [self.view safeAreaLayoutGuide];
[NSLayoutConstraint activateConstraints:#[
[collectionView.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:20.0],
[collectionView.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:-20.0],
[collectionView.heightAnchor constraintEqualToConstant:240.0],
[collectionView.centerYAnchor constraintEqualToAnchor:g.centerYAnchor],
]];
// a few color views to tap, and an
// "Instructions" label
UILabel *label = [UILabel new];
label.text = #"Tap a color to change the selected cells:";
UIStackView *stack = [UIStackView new];
NSArray *colors = #[
[UIColor redColor],
[UIColor greenColor],
[UIColor blueColor],
[UIColor systemYellowColor],
[UIColor systemTealColor],
];
for (UIColor *c in colors) {
UIView *v = [UIView new];
v.backgroundColor = c;
UITapGestureRecognizer *t = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(gotTap:)];
[v addGestureRecognizer:t];
[stack addArrangedSubview:v];
}
stack.spacing = 20.0;
stack.distribution = UIStackViewDistributionFillEqually;
label.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:label];
stack.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:stack];
[NSLayoutConstraint activateConstraints:#[
[stack.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:20.0],
[stack.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:-20.0],
[stack.bottomAnchor constraintEqualToAnchor:g.bottomAnchor constant:-20.0],
[stack.heightAnchor constraintEqualToConstant:40.0],
[label.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:20.0],
[label.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:-20.0],
[label.bottomAnchor constraintEqualToAnchor:stack.topAnchor constant:-4.0],
]];
collectionView.dataSource = self;
collectionView.delegate = self;
collectionView.allowsMultipleSelection = YES;
[collectionView registerClass:MyCollectionViewCell.class forCellWithReuseIdentifier:#"c"];
// create 50 objects for our data
myCellData = [NSMutableArray new];
for (int i = 0; i < 50; i++) {
MyDataObject *obj = [MyDataObject new];
obj.userColor = [UIColor redColor];
[myCellData addObject:obj];
}
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return myCellData.count;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// dequeue a cell
MyCollectionViewCell *c = (MyCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:#"c" forIndexPath:indexPath];
// get the data object
MyDataObject *obj = (MyDataObject *)[myCellData objectAtIndex:indexPath.item];
// set cell's contentView.backgroundColor to the data object's .userColor
c.contentView.backgroundColor = obj.userColor;
// set the cell's label text
c.label.text = [NSString stringWithFormat:#"%ld", indexPath.item];
return c;
}
- (void)gotTap:(UITapGestureRecognizer *)g {
if (collectionView.indexPathsForSelectedItems.count == 0) {
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:#"Error"
message:#"No cells are selected!"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okButton = [UIAlertAction
actionWithTitle:#"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
}];
[alert addAction:okButton];
[self presentViewController:alert animated:YES completion:nil];
return;
}
UIView *v = g.view;
if (v) {
// get the background color from the tapped view
UIColor *color = v.backgroundColor;
// loop through selected cells
for (NSIndexPath *p in collectionView.indexPathsForSelectedItems) {
// update the object in our data
[myCellData objectAtIndex:p.item].userColor = color;
// get a reference to the cell
MyCollectionViewCell *c = (MyCollectionViewCell *)[collectionView cellForItemAtIndexPath:p];
// set its background color
c.contentView.backgroundColor = color;
// if we want to auto-deselect the cells
[collectionView deselectItemAtIndexPath:p animated:YES];
}
}
}
#end
So,
our data object has just a single property: userColor
our cell class has a centered label
our controller
creates a horizontal scrolling collection view
creates an array of 50 data objects, with default userColor of red
adds 5 color views to select from
When a cell is selected, it will be outlined in yellow. When a color view is tapped, we:
update the data model for the currently selected cells
set the background color of the contentView of the currently selected cells
deselect the currently selected cells
It looks like this:
then we select cells 5, 9, 14:
tap on the Green view:
then we select cells 16, 17, 18:
tap on the Blue view:
then we scroll a little and select cells 17, 21, 24, 25, 26:
tap on the Yellow view:
and so on.
I have two labels in a UITableViewCell subclass. I want both labels to be multiline labels, so I have set the number of lines to zero for both of them. But the problem is that my table view cell is not expanding. In fact, the labels are getting squished so much even though I have the compression resistance priority set to 1000 for both labels in both directions.
I have set the background of the firstLabel UILabel property to orange and the background of the secondLabel UILabel property to yellow to make it easier to see the labels. There is also a divider between each row to make it so the heights are easier to see.
Here is what the view looks like when run on a device. The labels are all compressed and the rows never increase in size no matter how much padding I add to the constraints or how much content I put in the labels. Additionally, the first label in the first row has enough content that it should be more than one line, but it is getting truncated with ....
Here is the code I am using for this. The first part is the table view cell subclass that has the two labels - firstLabel (the one on the left) and secondLabel (the one on the right). Next, I have the code for the view that has the table view and shows how the table view is configured. Finally, there is a UITableView subclass that sets the intrinsicContentSize of the table to be the content size of the table. I added this because without this the table frame always stayed at zero and so the table view data source methods were not getting called. If anyone knows a better way to do this too, that would be much appreciated.
UITableViewCell subclass
This is the implementation for my UITableViewCell. It is used programmatically, so I am
#interface MyTableViewCell ()
#property (strong, nonatomic) UILabel *firstLabel;
#property (strong, nonatomic) UILabel *secondLabel;
#end
#implementation MyTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self initialize];
}
return self;
}
- (void)initialize {
[self.contentView setBackgroundColor:[UIColor systemTealColor]];
[self.backgroundView setBackgroundColor:[UIColor systemGrayColor]];
[self addSubview:self.firstLabel];
[self addSubview:self.secondLabel];
NSLayoutConstraint *heightConstraint = [self.heightAnchor constraintEqualToConstant:1.0f];
[heightConstraint setPriority:50];
[NSLayoutConstraint activateConstraints:#[
[self.firstLabel.leadingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.leadingAnchor],
[self.firstLabel.topAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.topAnchor],
[self.firstLabel.trailingAnchor constraintEqualToAnchor:self.centerXAnchor constant:-4.0f],
[self.secondLabel.leadingAnchor constraintEqualToAnchor:self.centerXAnchor constant:4.0f],
[self.secondLabel.topAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.topAnchor],
[self.secondLabel.trailingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.trailingAnchor],
[self.contentView.layoutMarginsGuide.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.firstLabel.bottomAnchor constant:20.0f],
[self.contentView.layoutMarginsGuide.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.secondLabel.bottomAnchor constant:20.0f],
heightConstraint
]];
}
- (UILabel *)firstLabel {
if (!self->_firstLabel) {
self->_firstLabel = [[UILabel alloc] init];
self->_firstLabel.translatesAutoresizingMaskIntoConstraints = NO;
self->_firstLabel.numberOfLines = 0;
self->_firstLabel.userInteractionEnabled = NO;
self->_firstLabel.contentMode = UIViewContentModeScaleToFill;
[self->_firstLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
[self->_firstLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisVertical];
[self->_firstLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[self->_firstLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
self->_firstLabel.textAlignment = NSTextAlignmentNatural;
self->_firstLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self->_firstLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
self->_firstLabel.adjustsFontSizeToFitWidth = NO;
self->_firstLabel.backgroundColor = [UIColor orangeColor];
}
return self->_firstLabel;
}
- (UILabel *)secondLabel {
if (!self->_secondLabel) {
self->_secondLabel = [[UILabel alloc] init];
self->_secondLabel.translatesAutoresizingMaskIntoConstraints = NO;
self->_secondLabel.numberOfLines = 0;
self->_secondLabel.userInteractionEnabled = NO;
self->_secondLabel.contentMode = UIViewContentModeScaleToFill;
[self->_secondLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
[self->_secondLabel setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisVertical];
[self->_secondLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[self->_secondLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
self->_secondLabel.textAlignment = NSTextAlignmentNatural;
self->_secondLabel.lineBreakMode = NSLineBreakByTruncatingTail;
self->_secondLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
self->_secondLabel.adjustsFontSizeToFitWidth = NO;
self->_secondLabel.backgroundColor = [UIColor yellowColor];
}
return self->_secondLabel;
}
- (void)setData:(MyModel *)data {
self.firstLabel.text = data.first;
self.secondLabel.text = data.second;
[self invalidateIntrinsicContentSize];
}
#end
Primary View - has a table view as a child view
This is the actual view that is displayed. As part of the larger application, this view is then displayed in a vertical UIStackView.
This view has a table view as the only subview and the table view is pinned to the edges of this view with AutoLayout. The UITableView is actually an instance of another class called "AutosizingTableView" to get the table view to autosize (without this, the frame of the table stays at zero and the table view data source method, tableView:cellForRowAtIndexPath:, was never being called since the table height was zero. The code for this table view is included after this section.
#interface MyView ()
#property (strong, nonatomic) AutosizingTableView *tableView;
#end
#implementation MyView
- (instancetype)init {
return [self initWithFrame:CGRectZero];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
[self initialize];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self initialize];
}
return self;
}
- (void)initialize {
[self addSubview:self.tableView];
[NSLayoutConstraint activateConstraints:#[
[self.tableView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
[self.tableView.topAnchor constraintEqualToAnchor:self.topAnchor],
[self.tableView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
[self.tableView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
[self.tableView.widthAnchor constraintEqualToAnchor:self.widthAnchor]
]];
}
- (UITableView *)tableView {
if (!self->_tableView) {
self->_tableView = [[AutosizingTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self->_tableView.translatesAutoresizingMaskIntoConstraints = NO;
self->_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self->_tableView.rowHeight = UITableViewAutomaticDimension;
self->_tableView.estimatedRowHeight = UITableViewAutomaticDimension;
self->_tableView.allowsSelection = NO;
self->_tableView.scrollEnabled = NO;
self->_tableView.delegate = self;
self->_tableView.dataSource = self;
[self->_tableView registerClass:[MyTableViewCell class] forCellReuseIdentifier:#"myTableViewCell"];
}
return self->_tableView;
}
- (void)setdata:(NSArray<MyData *> *)data {
self->_data = data;
[self.tableView reloadData];
}
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.data.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"myTableViewCell" forIndexPath:indexPath];
MyData *data = self.data[indexPath.row];
cell.detail = detail;
return cell;
}
#end
AutosizingTableView
As was mentioned in the previous section, this is just to make it so the table view autosizes. Without this, the table view height was zero and stayed at zero since the tableView:cellForRowAtIndexPath: method was never getting called because of the table view height.
#implementation AutosizingTableView
- (CGSize)intrinsicContentSize {
return self.contentSize;
}
- (void)setContentSize:(CGSize)contentSize {
[super setContentSize:contentSize];
[self invalidateIntrinsicContentSize];
}
#end
Try this:
- (void)initialize {
[self.contentView setBackgroundColor:[UIColor systemTealColor]];
[self.backgroundView setBackgroundColor:[UIColor systemGrayColor]];
[self.contentView addSubview:self.firstLabel];
[self.contentView addSubview:self.secondLabel];
NSLayoutConstraint *heightConstraint = [self.heightAnchor constraintEqualToConstant:1.0f];
[heightConstraint setPriority:50];
[NSLayoutConstraint activateConstraints:#[
[self.firstLabel.leadingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.leadingAnchor],
[self.firstLabel.topAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.topAnchor],
[self.firstLabel.trailingAnchor constraintEqualToAnchor:self.centerXAnchor constant:-4.0f],
[self.secondLabel.leadingAnchor constraintEqualToAnchor:self.centerXAnchor constant:4.0f],
[self.secondLabel.topAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.topAnchor],
[self.secondLabel.trailingAnchor constraintEqualToAnchor:self.contentView.layoutMarginsGuide.trailingAnchor],
[self.contentView.layoutMarginsGuide.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.firstLabel.bottomAnchor constant:20.0f],
[self.contentView.layoutMarginsGuide.bottomAnchor constraintGreaterThanOrEqualToAnchor:self.secondLabel.bottomAnchor constant:20.0f],
heightConstraint
]];
}
You should be adding the labels to the contentView and no the cell directly.
As shown in the following image, I want to add a tip(subview of the biggest UIView) that left align with the UIStackView in UITableViewCell. The width of UIStackView in UITableViewCell is dynamic, so I need to place the tip view after the UITableViewCell layout its subviews.
I've tried to add the tip view in willDisplayCell function, but the position I get there is not the final position.
Also, I tried to set the tip view's position in the UITableViewCell's layoutSubviews function. Since this function is called multiple times, the tip will go to the expected place eventually, but it will flicker at the beginning.
So in which function should I add the tip view?
--------Edited--------
Add a code snippet to make it clearer. I just want to know where should I put the following code, so that rect.origin.x is the final position of the stackview.
UIView *view = self.view;
CGRect rect = [cell.stackView convertRect:cell.stackView.bounds toView:view];
[self.tipView.leadingAnchor constraintEqualToAnchor:view.leadingAnchor constant:rect.origin.x].active = YES;
[self.tipView.trailingAnchor constraintLessThanOrEqualToAnchor:view.trailingAnchor constant:-marginConstant].active = YES;
[self.tipView.bottomAnchor constraintEqualToAnchor:self.tableView.bottomAnchor constant:-TSkBubbleClickAreaHeight-marginConstant].active = YES;
[view layoutIfNeeded];
Edit
To try and directly answer your question, you know when the layout of the content of the cell is completely finished when whatever you're doing to the cell is done. So, to know exactly when your cell's Stack View has been laid-out, you could subclass the stack view and implement layoutSubviews in that subclass.
Since it's still not clear exactly what you're doing, I'll offer a couple approaches...
If you are calling reloadData on the table view, you could try this:
[tableView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
[self showTipView:pth];
});
If you are calling insertRowsAtIndexPaths, you could try this:
[CATransaction begin];
[CATransaction setCompletionBlock:^{
[self showTipView:pth];
}];
[self->tableView insertRowsAtIndexPaths:#[pth] withRowAnimation:UITableViewRowAnimationRight];
[CATransaction commit];
Here's an example (I've tried to keep it simple, and provide clear comments):
ExampleTableViewCell.h
//
// ExampleTableViewCell.h
// Created by Don Mag on 9/15/20.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface ExampleTableViewCell : UITableViewCell
- (void) configureCell:(NSString *)s;
- (UIStackView *)getStackRef;
#end
NS_ASSUME_NONNULL_END
ExampleTableViewCell.m
//
// ExampleTableViewCell.m
// Created by Don Mag on 9/15/20.
//
#import "ExampleTableViewCell.h"
#interface ExampleTableViewCell ()
{
UIStackView *stackView;
UILabel *label;
}
#end
#implementation ExampleTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self commonInit];
}
return self;
}
- (void) commonInit {
label = [UILabel new];
label.backgroundColor = [UIColor yellowColor];
stackView = [UIStackView new];
stackView.translatesAutoresizingMaskIntoConstraints = NO;
[stackView addArrangedSubview:label];
[self.contentView addSubview:stackView];
UILayoutGuide *g = self.contentView.layoutMarginsGuide;
[NSLayoutConstraint activateConstraints:#[
// constrain stack view Top: 0 / Trailing: 0 / Bottom: 0
[stackView.topAnchor constraintEqualToAnchor:g.topAnchor constant:0.0],
[stackView.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:0.0],
[stackView.bottomAnchor constraintEqualToAnchor:g.bottomAnchor constant:0.0],
]];
}
- (void) configureCell:(NSString *)s {
label.text = s;
}
- (UIStackView *)getStackRef {
return stackView;
}
#end
ExampleViewController.h
//
// ExampleViewController.h
// Created by Don Mag on 9/15/20.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface ExampleViewController : UIViewController
#end
NS_ASSUME_NONNULL_END
ExampleViewController.m
//
// ExampleViewController.m
// Created by Don Mag on 9/15/20.
//
#import "ExampleViewController.h"
#import "ExampleTableViewCell.h"
#interface ExampleViewController () <UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate>
{
NSMutableArray *myData;
NSMutableArray *sampleStrings;
UIView *tipView;
UIButton *reloadButton;
UIButton *insertButton;
UITableView *tableView;
NSInteger idx;
NSInteger insertRow;
}
#end
#implementation ExampleViewController
- (void)viewDidLoad {
[super viewDidLoad];
// row number to insert and show the tipView
insertRow = 7;
// init some sample data
myData = [NSMutableArray new];
for (int i = 0; i < 30; i++) {
NSString *s = [NSString stringWithFormat:#"Row %i", i];
[myData addObject:s];
}
// a few example strings
sampleStrings = [NSMutableArray new];
[sampleStrings addObject:#"Short text example."];
[sampleStrings addObject:#"A little more text example."];
[sampleStrings addObject:#"Considerably longer text for this example."];
// index for sampleStrings array
idx = -1;
// create a "tip view"
// red background view with
// green background label
// label is inset 4-pts on each side
tipView = [UIView new];
tipView.backgroundColor = [UIColor redColor];
UILabel *tipLabel = [UILabel new];
tipLabel.backgroundColor = [UIColor greenColor];
tipLabel.text = #"This is the tip!";
tipView.translatesAutoresizingMaskIntoConstraints = NO;
tipLabel.translatesAutoresizingMaskIntoConstraints = NO;
[tipView addSubview:tipLabel];
[NSLayoutConstraint activateConstraints:#[
[tipLabel.topAnchor constraintEqualToAnchor:tipView.topAnchor constant:4.0],
[tipLabel.leadingAnchor constraintEqualToAnchor:tipView.leadingAnchor constant:4.0],
[tipLabel.bottomAnchor constraintEqualToAnchor:tipView.bottomAnchor constant:-4.0],
[tipLabel.trailingAnchor constraintEqualToAnchor:tipView.trailingAnchor constant:-4.0],
]];
// init buttons
reloadButton = [UIButton new];
[reloadButton setTitle:#"Reload" forState:UIControlStateNormal];
[reloadButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[reloadButton setTitleColor:[UIColor grayColor] forState:UIControlStateHighlighted];
[reloadButton setBackgroundColor:[UIColor blueColor]];
insertButton = [UIButton new];
[insertButton setTitle:#"Insert" forState:UIControlStateNormal];
[insertButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[insertButton setTitleColor:[UIColor grayColor] forState:UIControlStateHighlighted];
[insertButton setBackgroundColor:[UIColor blueColor]];
// init table view
tableView = [UITableView new];
for (UIView *v in #[reloadButton, insertButton, tableView]) {
v.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:v];
}
UILayoutGuide *g = self.view.safeAreaLayoutGuide;
[NSLayoutConstraint activateConstraints:#[
// buttons at top
[reloadButton.topAnchor constraintEqualToAnchor:g.topAnchor constant:20.0],
[reloadButton.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:24.0],
[insertButton.topAnchor constraintEqualToAnchor:g.topAnchor constant:20.0],
[insertButton.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:-24.0],
[insertButton.leadingAnchor constraintEqualToAnchor:reloadButton.trailingAnchor constant:20.0],
[insertButton.widthAnchor constraintEqualToAnchor:reloadButton.widthAnchor],
// constrain tableView Top: 20-pts from buttons / Leading: 0 / Trailing: 0 / Bottom: 0
[tableView.topAnchor constraintEqualToAnchor:reloadButton.bottomAnchor constant:20.0],
[tableView.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:0.0],
[tableView.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:0.0],
[tableView.bottomAnchor constraintEqualToAnchor:g.bottomAnchor constant:0.0],
]];
[tableView registerClass:[ExampleTableViewCell class] forCellReuseIdentifier:#"exCell"];
tableView.delegate = self;
tableView.dataSource = self;
[reloadButton addTarget:self action:#selector(btnTap:) forControlEvents:UIControlEventTouchUpInside];
[insertButton addTarget:self action:#selector(btnTap:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[tipView removeFromSuperview];
}
- (void)btnTap:(UIButton *)btn {
// remove tipView if it's showing
[tipView removeFromSuperview];
NSString *s = sampleStrings[++idx % 3];
if (btn == reloadButton) {
[self reloadMethod:s];
} else {
[self insertMethod:s];
}
}
- (void)insertMethod:(NSString *)s {
// IndexPath for cell you want
__block NSIndexPath *pth = [NSIndexPath indexPathForRow:insertRow inSection:0];
[myData insertObject:s atIndex:pth.row];
[CATransaction begin];
[CATransaction setCompletionBlock:^{
[self showTipView:pth];
}];
[self->tableView insertRowsAtIndexPaths:#[pth] withRowAnimation:UITableViewRowAnimationRight];
[CATransaction commit];
}
- (void)reloadMethod:(NSString *)s {
// IndexPath for cell you want
__block NSIndexPath *pth = [NSIndexPath indexPathForRow:insertRow inSection:0];
[myData insertObject:s atIndex:pth.row];
[tableView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
[self showTipView:pth];
});
}
- (void)showTipView:(NSIndexPath *)pth {
// get a reference to the cell
ExampleTableViewCell *cell = [tableView cellForRowAtIndexPath:pth];
// if row is not visible
if (!cell) {
return;
}
// get a reference to the cell's stack view
UIStackView *stack = [cell getStackRef];
// add tipView to self.view
[self.view addSubview:tipView];
// constrain tipView
// Leading to stack view's Leading
// Bottom to cell's Top - 4
[NSLayoutConstraint activateConstraints:#[
[tipView.leadingAnchor constraintEqualToAnchor:stack.leadingAnchor constant:0.0],
[tipView.bottomAnchor constraintEqualToAnchor:cell.topAnchor constant:-4.0],
]];
// if we want to "fade-in" the tipView
[tipView setAlpha:0.0];
[UIView animateWithDuration:0.3
animations:^{
[self->tipView setAlpha:1.0];
}];
}
- (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
ExampleTableViewCell *c = (ExampleTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"exCell" forIndexPath:indexPath];
[c configureCell:myData[indexPath.row]];
return c;
}
- (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [myData count];
}
#end
I seem to be having trouble adding a subview to a view within my UICollectionViewCell subclass.
I have an abstract UICollectionViewCell subclass titled MessageItem, which looks like this:
I've created a few classes that inherit from this (since they all use the same logic for the header and footer). However I can't seem to add any subviews into MessageItem's blue view from within the child subclasses.
For example one of the child views is called TextItem. I'm trying to add a label to it's parent messageView (the blue view) but it only works if I do it in my UIViewController's cellForItemAtIndexPath:(NSIndexPath *)indexPath method, and not in my custom subclass.
This is how I'm trying to add it in my child subclass:
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
//Setup Message Label
[self setupMessageLabel];
}
return self;
}
#pragma mark - Setup Methods
- (void)setupMessageLabel {
NSLog(#"Setting up label");
//Setup Message Label
self.messageLabel = [TTTAttributedLabel new];
self.messageLabel.verticalAlignment = TTTAttributedLabelVerticalAlignmentCenter;
self.messageLabel.textInsets = UIEdgeInsetsMake(8, 8, 8, 8);
self.messageLabel.numberOfLines = 0;
[self.messageContentView addSubview:self.messageLabel];
[self.messageContentView autoPinEdgesToSuperviewEdges];
//Update Label Color
self.messageLabel.backgroundColor = FlatRed;
}
Note: I'm not using storyboard or xibs. Could that be the problem?
Update
This is what my MessageItem class is implemented:
MessageItem.h
#import <UIKit/UIKit.h>
#class Message;
#interface MessageItem : UICollectionViewCell
#property (nonatomic, strong) Message *message;
#property (nonatomic, strong) UIView *messageContentView;
#end
MessageItem.m
#interface MessageItem ()
#property (nonatomic, strong) TTTAttributedLabel *headerLabel;
#property (nonatomic, strong) TTTAttributedLabel *footerLabel;
#end
#implementation MessageItem
#synthesize message = _message;
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
//Setup Main View
[self setupMainView];
}
return self;
}
#pragma mark - Setup Methods
- (void)setupMainView {
//Setup Header
[self setupHeaderLabel];
//Setup Message
[self setupMessageView];
//Setup Footer View
[self setupFooterLabel];
}
- (void)setupHeaderLabel {
//Setup Header Label
self.headerLabel = [[TTTAttributedLabel alloc] initForAutoLayout];
self.headerLabel.font = [UIFont fontWithName:#"Lato-Bold" size:12.0];
self.headerLabel.textColor = FlatGray;
self.headerLabel.textAlignment = NSTextAlignmentCenter;
self.headerLabel.verticalAlignment = TTTAttributedLabelVerticalAlignmentCenter;
self.headerLabel.textInsets = UIEdgeInsetsMake(0, 8, 0, 8);
self.headerLabel.backgroundColor = FlatPurple;
[self.contentView addSubview:self.headerLabel];
[self.headerLabel autoSetDimension:ALDimensionHeight toSize:20.0];
[self.headerLabel autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsZero excludingEdge:ALEdgeBottom];
}
- (void)setupMessageView {
//Setup Message View
self.messageContentView = [UIView new];
self.messageContentView.backgroundColor = [UIColor blueColor];
[self.contentView addSubview:self.messageContentView];
[self.messageContentView autoSetDimension:ALDimensionHeight toSize:30 relation:NSLayoutRelationGreaterThanOrEqual];
[self.messageContentView autoPinEdgeToSuperviewEdge:ALEdgeLeading];
[self.messageContentView autoPinEdgeToSuperviewEdge:ALEdgeTrailing];
[self.messageContentView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.headerLabel];
}
- (void)setupFooterLabel {
//Setup Footer Label
self.footerLabel = [[TTTAttributedLabel alloc] initForAutoLayout];
self.footerLabel.font = [UIFont fontWithName:#"Lato-Bold" size:10.0];
self.footerLabel.textColor = FlatGray;
self.footerLabel.backgroundColor = FlatGreen;
self.footerLabel.textAlignment = NSTextAlignmentLeft;
self.footerLabel.textInsets = UIEdgeInsetsMake(0, 8, 0, 8);
[self.contentView addSubview:self.footerLabel];
[self.footerLabel autoSetDimension:ALDimensionHeight toSize:10.0];
[self.footerLabel autoPinEdgeToSuperviewEdge:ALEdgeLeading];
[self.footerLabel autoPinEdgeToSuperviewEdge:ALEdgeTrailing];
[self.footerLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom];
[self.footerLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.messageContentView];
}
TextItem.m
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
//Setup Message Label
[self setupMessageLabel];
}
return self;
}
#pragma mark - Setup Methods
- (void)setupMessageLabel {
//Setup Message Label
self.messageLabel = [[TTTAttributedLabel alloc] initWithFrame:CGRectMake(0, 0, 320, 100)];
self.messageLabel.verticalAlignment = TTTAttributedLabelVerticalAlignmentCenter;
self.messageLabel.textInsets = UIEdgeInsetsMake(8, 8, 8, 8);
self.messageLabel.numberOfLines = 0;
[self.messageContentView addSubview:self.messageLabel];
//Update Label Color
self.messageLabel.backgroundColor = FlatRed;
}
#pragma mark - Setter Methods
- (void)setMessageText:(NSString *)text {
//Incoming Text Message
NSMutableAttributedString *textString = [[NSMutableAttributedString alloc] initWithString:text];
[textString addAttribute:NSForegroundColorAttributeName value:[UIColor darkGrayColor] range:NSMakeRange(0, textString.length)];
[textString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:16 weight:UIFontWeightLight] range:NSMakeRange(0, textString.length)];
//Set Paragraph Style
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.minimumLineHeight = 20;
paragraphStyle.maximumLineHeight = 20;
[textString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, textString.length)];
//Update Message Label
[self.messageLabel setText:textString];
NSLog(#"Set Message Label Text");
}
- (void)setMessage:(Message *)message {
//Super
[super setMessage:message];
//Update Message Text
[self setMessageText:message.text];
}
This is what my collectionView looks like:
I would at least expect the color of the messageLabel to reflect the change in TextItem, but it doesn't.
Have you implement initWithCoder?
- (id)initWithCoder:(NSCoder*)aDecoder
{
if(self = [super initWithCoder:aDecoder]) {
// Do something
}
return self;
}
I don't have all your code, but you code looks good to me. Maybe the problem was how you init the TextItem.
Here is a demo using your code, it works fine to me. https://www.dropbox.com/s/7qp9ayqnyacf57j/CustomCellView.zip?dl=0
I create a custom IB-designable view (see code below) which renders correctly in IB and also works fine when running it. However, in get this warning about the view being misplaced and I cannot manually resize the view in Interface Builder (when touching a resize handle, the view will jump around in its container).
I get the same or similar behavior for all kinds of different layouts. Do you have an idea if I'm doing something wrong here, or is this just a bug in IB?
(PS: I cannot just ignore the warning)
EDIT: added screenshot of constraints:
Here is the code (header):
IB_DESIGNABLE
#interface AKATestView : UIView
#end
Implementation:
#interface AKATestView()
#property(nonatomic)BOOL subviewsCreated;
#property(nonatomic)BOOL subviewConstraintsCreated;
#property(nonatomic)NSDictionary* views;
#end
#implementation AKATestView
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self setupAfterInit];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setupAfterInit];
}
return self;
}
- (void)setupAfterInit
{
[self createSubviews];
}
- (void)createSubviews
{
if (!self.subviewsCreated)
{
self.translatesAutoresizingMaskIntoConstraints = NO;
UILabel* labelView = [[UILabel alloc] initWithFrame:CGRectZero];
labelView.text = #"Name";
labelView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:labelView];
UITextField* textField = [[UITextField alloc] initWithFrame:CGRectZero];
textField.borderStyle = UITextBorderStyleRoundedRect;
textField.placeholder = #"Enter some text";
textField.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:textField];
UILabel* errorMessageLabel = [[UILabel alloc] initWithFrame:CGRectZero];
errorMessageLabel.text = #"Error message";
errorMessageLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:errorMessageLabel];
self.views = #{ #"label": labelView, #"editor": textField, #"errorMessageLabel": errorMessageLabel };
self.subviewsCreated = YES;
[self setNeedsUpdateConstraints];
}
}
- (void)updateConstraints
{
if (!self.subviewConstraintsCreated)
{
NSDictionary* metrics =
#{ #"pt": #(4), #"pr": #(4), #"pb": #(4), #"pl": #(4),
#"labelWidth": #(100),
#"errorPl": #(4 + 100 + 4),
#"hsLabelEditor": #(4), #"vsEditorError": #(2)
};
NSArray* specs =
#[ #{ #"format": #"H:|-(pl)-[label(labelWidth)]-(hsLabelEditor)-[editor]-(pr)-|",
#"options": #(NSLayoutFormatAlignAllFirstBaseline) },
#{ #"format": #"V:|-(pt)-[editor]-(vsEditorError)-[errorMessageLabel]-(pb)-|",
#"options": #(NSLayoutFormatAlignAllLeading|NSLayoutFormatAlignAllTrailing) }
];
for (NSDictionary* spec in specs)
{
NSString* format = spec[#"format"];
NSUInteger options = ((NSNumber*)spec[#"options"]).unsignedIntegerValue;
NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:format
options:options
metrics:metrics
views:self.views];
[self addConstraints:constraints];
}
self.subviewConstraintsCreated = YES;
}
[super updateConstraints];
}
#end
Try removing self.translatesAutoresizingMaskIntoConstraints = NO; in your createSubviews method. IB seems to be relying on this translation to come up with correct measurement on the designer. I had the exact same problem and this fixed it.
I still have translatesAutosizingMaskIntoConstraints to NO for subviews. I confirmed that there aren't any extra constraints generated even with this set to YES. Hope it's the case for you too!