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
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.
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
I have a custom UIView with a UICollectionView.
On screen rotation I am trying to get the UICollectionView to stretch across the screen, and then redraw its cells.
After I had the data downloaded I tried both [grid setNeedsLayout] and [grid setNeedsDisplay] but that didn't work.
This is what I want to happen:
Portrait
Landscape
(This is also how it appears when the app is started in landscape, but if you change to portrait it doens't update.)
But this is what I get if I start in Portrait mode and switch to Landscape.
I am creating these views programmatically. I am not using any Storyboards.
I have tried:
-(void)viewDidLayoutSubviews {
grid = [[MyThumbnailGridView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height/2, self.view.frame.size.width, self.view.frame.size.height/2)];
}
I have also tried toying with:
- (void) viewWillLayoutSubviews {
UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
if (UIInterfaceOrientationIsLandscape(interfaceOrientation))
{
//LANDSCAPE
if(grid){
NSLog(#"Grid Needs Landscape Layout");
grid.frame =CGRectMake(0, self.view.frame.size.height/2, self.view.frame.size.width, self.view.frame.size.height/2);
[grid refreshData];
}
}else {
//PORTRIAT
if(grid){
NSLog(#"Grid Needs Portrait Layout");
grid.frame =CGRectMake(0, self.view.frame.size.height/2, self.view.frame.size.width, self.view.frame.size.height/2);
[grid refreshData];
}
}
}
But I can't get it to stretch.
Any help?
MyThumbnailGridView
#interface ViewController () <UINavigationControllerDelegate> {
MyThumbnailGridView *grid;
NSMutableArray * arrImages;
}
- (void)viewDidLoad {
[super viewDidLoad];
arrImages = [NSMutableArray new];
grid = [[MyThumbnailGridView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height/2, self.view.frame.size.width, self.view.frame.size.height/2)];
//grid = [[MyThumbnailGridView alloc] initWithFrame:CGRectZero];
NSLog(#"showThumbnailGrid Grid View Size: %#", NSStringFromCGRect(grid.frame));
[self.view addSubview:grid];
[self getListOfImages];
}
-(void) getListOfImages {
//Do background task to get images and fill arrImages
[self onDownloadImageDataComplete];
}
- (void) onDownloadImageDataComplete{
grid.imageDataSource = arrImages;
// [grid setNeedsLayout];
// [grid setNeedsDisplay];
}
//...
#end
*MyThumbnailGridView.h
#interface MyThumbnailGridView : UIView
-(id)initWithFrame:(CGRect)frame;
-(void) refreshData;
#property (nonatomic,strong) NSArray *imageDataSource;
#end
*MyThumbnailGridView.m
#interface MyThumbnailGridView () <UICollectionViewDelegate, UICollectionViewDataSource>{
UICollectionView *collectionView;
}
#end
#implementation MyThumbnailGridView
- (instancetype) initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if(self){
[self customInit];
}
return self;
}
- (void) customInit {
collectionView = [[UICollectionView alloc] initWithFrame:self.bounds collectionViewLayout:[[MyFlowLayout alloc] init]];
collectionView.delegate = self;
collectionView.dataSource = self;
collectionView.allowsMultipleSelection = NO;
collectionView.showsVerticalScrollIndicator = YES;
[collectionView setBackgroundColor:[UIColor darkGrayColor]];
[collectionView registerClass:[MyCollectionViewCell class] forCellWithReuseIdentifier:#"MyId"];
[self addSubview:collectionView];
}
- (void) refreshData {
NSLog(#"Refresh Grid Data");
[collectionView reloadData];
}
////other code
#end
MyFlowLayout
#interface MyFlowLayout : UICollectionViewFlowLayout
#end
#implementation MyFlowLayout
- (instancetype)init{
self = [super init];
if (self)
{
self.minimumLineSpacing = 1.0;
self.minimumInteritemSpacing = 1.0;
self.scrollDirection = UICollectionViewScrollDirectionVertical;
}
return self;
}
- (CGSize)itemSize {
NSInteger numberOfColumns = 3;
CGFloat itemWidth = (CGRectGetWidth(self.collectionView.frame) - (numberOfColumns - 1)) / numberOfColumns;
return CGSizeMake(itemWidth, itemWidth);
}
#end
MyCollectionViewCell
#interface MyCollectionViewCell : UICollectionViewCell
#property (strong, nonatomic) UIImageView *imageView;
#end
#implementation MyCollectionViewCell
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.imageView = [UIImageView new];
[self.imageView setContentMode:UIViewContentModeScaleAspectFill];
[self.imageView setClipsToBounds:YES];
[self.imageView setBackgroundColor:[UIColor darkGrayColor]];
[self.contentView addSubview:self.imageView];
}
return self;
}
- (void)prepareForReuse {
[super prepareForReuse];
self.imageView.image = nil;
[self.imageView setHidden:NO];
}
- (void)layoutSubviews {
[super layoutSubviews];
[self.imageView setFrame:self.contentView.bounds];
}
#end
This can be solved with
either you can change the frame of MyThumbnailGridView view in delegate function of orientation or create the view with constraints like this
(void)viewDidLoad {
[super viewDidLoad];
arrImages = [NSMutableArray new];
grid = [[MyThumbnailGridView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height/2, self.view.frame.size.width, self.view.frame.size.height/2)];
[self.view addSubview:grid];
[self getListOfImages];
}
-(void)viewDidLayoutSubviews
{
if(Once){
Once = NO;
// adding constraints
MyThumbnailGridView.translatesAutoresizingMaskIntoConstraints = NO;
[self.MyThumbnailGridView.widthAnchor constraintEqualToConstant:self.view.frame.size.width].active = YES;
[self.MyThumbnailGridView.heightAnchor constraintEqualToConstant:self.view.frame.size.height/2].active = YES;
[self.MyThumbnailGridView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES;
[self.MyThumbnailGridView.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:self.view.frame.size.height/2].active = YES;
}
}
Also implement this
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[self.view layoutIfNeeded];
[MyThumbnailGridView.collectionView invalidate];
// Do view manipulation here.
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}
The problem is that the collection view itself is not changing size when the app rotates. You have given the collection view a fixed frame and then just walked away. So it never changes. So never lays itself out again.
You should give your MyThumbnailGridView and its collection view subview autolayout contraints to their superviews, so that they change size correctly when the app rotates.
I have a UIViewController with a SegmentedControl, UITableView and a UISearchController. The SegmentedControl is at the top of the main View with the tableView just beneath it. The searchController's searchBar is placed in the tableView.tableHeaderView and looks like this:
When the searchBar is tapped (made active) it moves down leaving a gap just above:
Also, if the searchBar is active and then the segmentedConrol is tapped (filtering the table data and reloading the tableView) then the tableView loads but with a gap at the top. (I have purposely set the searchBar to hidden when the 'Category' filter is selected.
If the segmentedControl 'Category' is selected when the searchBar is not active this is how it looks (and should look):
I need two things (I think they are related), 1) for the searchBar to NOT move when active and 2) for the searchBar to not be present when 'Category' is selected and for the tableView to have no gap at the top.
.h:
#interface ExhibitorViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UISearchControllerDelegate, UISearchBarDelegate, UISearchResultsUpdating>
{
// DATA
NSMutableArray *arrayOfExhibitors;
NSMutableArray *arrayOfExhibitorsFiltered;
NSMutableArray *arrayOfCategories;
NSMutableArray *arrayOfCategoriesFiltered;
// VARS
int selectedSegment;
float searchBarHeight;
float tableViewY;
NSString *currentCategory;
CGRect tableViewStartRect;
// UI
UISegmentedControl *segmentedControl;
UIView *categorySelectedView;
UIView *headerView;
}
#property (nonatomic, strong) UITableView *tableView;
#property (nonatomic, strong) UISearchController *searchController;
#property (nonatomic, readonly) NSArray *searchResults;
#property (strong, nonatomic) NSString *sponsorsOnly;
#end
.m:
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:NO animated:NO];
if (selectedSegment == 0) {
self.searchController.searchBar.hidden = FALSE;
}
if (!_searchController.searchBar.superview) {
self.tableView.tableHeaderView = self.searchController.searchBar;
}
}
-(void)loadTableView
{
[self printStats:#"loadTableView START"];
searchBarHeight = self.searchController.searchBar.frame.size.height;
Settings *settingsInstance = [Settings new];
if(!_tableView) {
segmentedControl = [UISegmentedControl new];
segmentedControl = [[UISegmentedControl alloc]initWithItems:[NSArray arrayWithObjects:#"Exhibitor", #"Category", nil]];
[segmentedControl setFrame:CGRectMake(0, 0, self.view.frame.size.width, 35)];
segmentedControl.selectedSegmentIndex = 0;
[segmentedControl addTarget:self action:#selector(segmentedControlHasChangedValue) forControlEvents:UIControlEventValueChanged];
self.automaticallyAdjustsScrollViewInsets = YES;
self.edgesForExtendedLayout = UIRectEdgeNone;
self.searchController.hidesNavigationBarDuringPresentation = NO;
//self.definesPresentationContext = NO;
float tvX = self.view.frame.origin.x;
float tvY = self.view.frame.origin.y + segmentedControl.frame.size.height;
float tvWidth = self.view.frame.size.width;
float frameHeight = self.view.frame.size.height;
float tvHeight = self.view.frame.size.height - segmentedControl.frame.size.height;
tableViewStartRect = CGRectMake(tvX, tvY, tvWidth, tvHeight);
_tableView = [UITableView new];
_tableView = [[UITableView alloc] initWithFrame:tableViewStartRect];
//_tableView.contentInset = UIEdgeInsetsMake(0, 0, 44, 0);
_tableView.separatorColor = [UIColor clearColor];
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
[self.view addSubview:segmentedControl];
[self.view addSubview:_tableView];
[_tableView setTag:1];
[_tableView setDataSource:self];
[_tableView setDelegate:self];
}
if (!categorySelectedView) {
float levelOneStart = (0);
categorySelectedView = [[UIView alloc] initWithFrame:CGRectMake(0, levelOneStart, self.view.frame.size.width, (screenHeight * 0.05))];
[categorySelectedView setBackgroundColor:[UIColor grayColor]];
[categorySelectedView setTag:4];
MyLabel *catSelectedLabel = [[MyLabel alloc] initWithFrame:categorySelectedView.frame];
[catSelectedLabel setFont:[UIFont systemFontOfSize:[settingsInstance getFontSizeFor:#"Label"]]];
[catSelectedLabel setTag:5];
[catSelectedLabel setBackgroundColor:[UIColor lightTextColor]];
[catSelectedLabel setTextColor:[UIColor darkGrayColor]];
UIButton *categoryBackButton = [[UIButton alloc] initWithFrame:CGRectMake((screenWidth * 0.6), levelOneStart, (screenWidth * 0.4), (screenHeight * 0.05))];
[categoryBackButton setTitle:#"^ Back ^" forState:UIControlStateNormal];
[categoryBackButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal];
[categoryBackButton addTarget:self action:#selector(resetTableViewCategories) forControlEvents:UIControlEventTouchUpInside];
[catSelectedLabel addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(resetTableViewCategories)]];
[categoryBackButton.titleLabel setFont:[UIFont systemFontOfSize:[settingsInstance getFontSizeFor:#"Label"]]];
[categorySelectedView addSubview:catSelectedLabel];
[categorySelectedView addSubview:categoryBackButton];
[categorySelectedView setHidden:TRUE];
}
if (!headerView) {
headerView = [[UIView alloc] initWithFrame:CGRectMake(0, (0), screenWidth, (searchBarHeight))];
[headerView addSubview:categorySelectedView];
[self.view addSubview:headerView];
[headerView setBackgroundColor:[UIColor purpleColor]];
[self.view sendSubviewToBack:headerView];
}
[self.view setTag:11];
tableViewY = _tableView.frame.origin.y;
[self printStats:#"loadTableView END"];
}
-(UISearchController*)searchController
{
if (!_searchController) {
_searchController = [[UISearchController alloc]initWithSearchResultsController:nil];
_searchController.searchResultsUpdater = self;
_searchController.dimsBackgroundDuringPresentation = NO;
_searchController.searchBar.delegate = self;
[_searchController.searchBar sizeToFit];
}
return _searchController;
}
-(void)segmentedControlHasChangedValue
{
[self.searchController setActive:NO];
if ((segmentedControl.selectedSegmentIndex == 0)) {
selectedSegment = 0;
currentCategory = #"";
[self resetTableViewExhibitors];
[_tableView setContentOffset:CGPointMake(0, -1) animated:NO];
} else {
selectedSegment = 1;
[self resetTableViewCategories];
[_tableView setContentOffset:CGPointMake(0, -1) animated:NO];
//[_tableView setContentOffset:CGPointMake(0, 56) animated:NO];
[_tableView setTableFooterView:nil];
}
[_tableView reloadData];
}
I have tried changing the insets of various views and forcing a manual changes to the frames of various views (this is the closest thing to a fix but seems very hacky). What am I doing wrong?
Edit: Have also tried :
-(void)segmentedControlHasChangedValue
{
[self.searchController setActive:NO];
if ((segmentedControl.selectedSegmentIndex == 0)) {
selectedSegment = 0;
currentCategory = #"";
[self resetTableViewExhibitors];
[_tableView setContentOffset:CGPointMake(0, -1) animated:NO];
} else {
selectedSegment = 1;
[_searchController dismissViewControllerAnimated:NO completion^() {
[self resetTableViewCategories];
[_tableView setContentOffset:CGPointMake(0, -1) animated:NO];
[_tableView setTableFooterView:nil];
}];
}
[_tableView reloadData];
}
Because you use UISearchController so searchBar will always move when it actives. To avoid it, use UISearchBar. And when you use UISearchBar, it's easy to hide when you select Category tab