How do I create a UI TableView with multiple columns in Xcode? - ios

I am working on a iOS 8 app with Xcode.
I need to display a table with many columns and rows of data in one view.
Example:
Name Time In Time Out ETA
Johnnys Supplies 8:30AM 9:00AM 10:15AM
Franks Company 8:45AM 9:05AM 9:45AM
Another Inc. 10:12AM 11:00AM 12:04PM
All of the data would be read in with JSON/PHP.
I need it to work like a tableview where a user can scroll vertically, and select an index. Once that index is selected, the user can click a button to run additional queries based on the data in the selected cell (etc.).
What is the easiest way to implement this? There has to be a way xcode allows you to do this natively? Am I missing something?
All coding examples welcomed!
I have found two options but both require licensing fees:
http://www.ioscomponents.com/Home/IOSDataGrid <-- $400
http://www.binpress.com/app/ios-data-grid-table-view/586 <-- $130
Anyone familiar with these components?

What's the difference between two columns and two labels next to each other? A divider?
Is this a multi column table view?
Because it's a tableview with ordinary UITableViewCell that have 3 UILabels and 2 UIViews. The views pretend to be dividers by being 1 px wide.
Code should be self explanatory.
.h
#interface MultiColumnTableViewCell : UITableViewCell
#property (strong, nonatomic) UILabel *label1;
#property (strong, nonatomic) UILabel *label2;
#property (strong, nonatomic) UILabel *label3;
#end
.m
#interface MultiColumnTableViewCell ()
#property (strong, nonatomic) UIView *divider1;
#property (strong, nonatomic) UIView *divider2;
#end
#implementation MultiColumnTableViewCell
- (UILabel *)label {
UILabel *label = [[UILabel alloc] init];
label.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:label];
return label;
}
- (UIView *)divider {
UIView *view = [[UIView alloc] init];
view.translatesAutoresizingMaskIntoConstraints = NO;
[view addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:1.0/[[UIScreen mainScreen] scale]]];
view.backgroundColor = [UIColor lightGrayColor];
[self.contentView addSubview:view];
return view;
}
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
self.separatorInset = UIEdgeInsetsZero;
self.layoutMargins = UIEdgeInsetsZero;
self.preservesSuperviewLayoutMargins = NO;
self.divider1 = [self divider];
self.divider2 = [self divider];
self.label1 = [self label];
self.label2 = [self label];
self.label3 = [self label];
NSDictionary *views = NSDictionaryOfVariableBindings(_label1, _label2, _label3, _divider1, _divider2);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-5-[_label1]-2-[_divider1]-2-[_label2(==_label1)]-2-[_divider2]-2-[_label3(==_label1)]-5-|" options:NSLayoutFormatAlignAllCenterY metrics:nil views:views];
[self.contentView addConstraints:constraints];
NSArray *horizontalConstraints1 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_divider1]|" options:0 metrics:nil views:views];
[self.contentView addConstraints:horizontalConstraints1];
NSArray *horizontalConstraints2 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_divider2]|" options:0 metrics:nil views:views];
[self.contentView addConstraints:horizontalConstraints2];
return self;
}
#end
TableViewController:
#implementation MasterViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[MultiColumnTableViewCell class] forCellReuseIdentifier:#"Cell"];
self.tableView.separatorColor = [UIColor lightGrayColor];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 10;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MultiColumnTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.label1.text = [NSString stringWithFormat:#"Name %ld", (long)indexPath.row];
cell.label2.text = [NSString stringWithFormat:#"Start %ld", (long)indexPath.row];
cell.label3.text = [NSString stringWithFormat:#"End %ld", (long)indexPath.row];
return cell;
}
#end

iOS table views are always a single column. On Mac OS you can create what you are after directly.
That said, you could create custom table view cells that display the content you want. In fact it would be quite easy. All you would do is to subclass UITableViewCell and define views (probably UILabels) for each of you columns, then wire them up as outlet properties in the cell. Then rig your table view to register that cell class for the cell identifier you use.
You then write your cellForRowAtIndexPath method to install your data into the different outlet properties.
You could also use a UICollectionView, but it looks to me like a table view with custom cells is a better fit for this application.

Use UICollectionView, check Apple WWDC 2012 sessions
205 Introducing Collection Views
219 Advanced Collection Views and Building Custom Layouts
from https://developer.apple.com/videos/wwdc/2012/

Related

TableviewCells are not inserted as expected

I have a reusableTablViewCell that will be simply echoed to the tableview whenever a user gives an input in the textbook and clicks send button, my problem is that the cells are not behaving as expected few times the cells are inserted correctly and few times they won't.
Expected Behaviour: when the user clicks send button after he has finished typing some text in the text box the same value should be printed twice (like sending and receiving the same text).
//view somewhat looks like this on expected behaviour
'''''''''
' hi '
'''''''''
'''''''''
' hi '
'''''''''
Current Behaviour: Sometimes it does give me the expected behaviour, but some times both the cells are on the same side
EX:
//view when it doesn't work as expected
'''''''
' hi '
'''''''
'''''''
' hi '
'''''''
or something like
''''''''
' hi '
''''''''
''''''''
' hi '
''''''''
And sometimes when we scroll, the cells change their position(being odd Cell and Even-cell which u can see in the code) from sender to receiver and vice-versa.
My code
//FirstTableViewController.h
#import <UIKit/UIKit.h>
#class SecondViewController;
#interface FirstTableViewController : UITableViewController
#property (strong, nonatomic) IBOutlet UITableView *messageView;
#property (nonatomic,readwrite) NSInteger counter;
#property (nonatomic,readwrite) NSMutableArray *userInput;
#property (nonatomic,readwrite) NSMutableDictionary *heightAtIndexPath;
#property (nonatomic, assign) BOOL shouldScrollToLastRow;
+ (id)sharedInstance;
#end
#interface ChatMessageCellTableViewCell : UITableViewCell
#property (nonatomic, retain) UILabel *formLabel;
#property (nonatomic, retain) UIView *bubbleBackView;
#end
//FirstTableViewController.m
#import "FirstTableViewController.h"
BOOL isReceived;
#interface ChatMessageCellTableViewCell (){
NSLayoutConstraint *leadingConstraint;
NSLayoutConstraint *trailingConstraint;
}
#end
#implementation ChatMessageCellTableViewCell
-(void) loaded{
if(isReceived){
[self.bubbleBackView setBackgroundColor:[UIColor whiteColor]];
[self.formLabel setTextColor:[UIColor blackColor]];
}
else{
[[self bubbleBackView] setBackgroundColor:[UIColor colorWithRed:(66/255) green:(137/255.0) blue:1 alpha:1.0]];
[self.formLabel setTextColor:[UIColor whiteColor]];
}
}
-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
[self setBackgroundColor:[UIColor clearColor]];
self.formLabel = [UILabel new];
self.bubbleBackView = [UIView new];
//[self.bubbleBackView setBackgroundColor:[UIColor yellowColor]];
[self.bubbleBackView.layer setCornerRadius:12];
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if(self){
[[self contentView] addSubview:self.bubbleBackView
];
[self loaded];
[self.bubbleBackView setTranslatesAutoresizingMaskIntoConstraints:NO];
[[self contentView] addSubview:self.formLabel];
[self.formLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
if (#available(iOS 9.0, *)) {
[self.formLabel.topAnchor constraintEqualToAnchor:self.topAnchor constant:32].active=YES;
[self.formLabel.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-32].active=YES;
[self.formLabel.widthAnchor constraintLessThanOrEqualToConstant:250].active=YES;
[self.bubbleBackView.topAnchor constraintEqualToAnchor:_formLabel.topAnchor constant:-16].active=YES;
[self.bubbleBackView.bottomAnchor constraintEqualToAnchor:_formLabel.bottomAnchor constant:16].active=YES;
[self.bubbleBackView.trailingAnchor constraintEqualToAnchor:_formLabel.trailingAnchor constant:16].active=YES;
[self.bubbleBackView.leadingAnchor constraintEqualToAnchor:_formLabel.leadingAnchor constant:-16].active=YES;
leadingConstraint= [self.formLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:32];
trailingConstraint = [self.formLabel.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-32];
if(isReceived){
[leadingConstraint setActive:YES];
[trailingConstraint setActive:NO];
}
else{
[leadingConstraint setActive:NO];
[trailingConstraint setActive:YES];
}
} else {
// Fallback on earlier versions
}
[self.formLabel setLineBreakMode:NSLineBreakByWordWrapping];
[self.formLabel setNumberOfLines:0];
[self.formLabel sizeToFit];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-40-[bodyLabel]-40-|" options:0
metrics:nil
views:#{ #"bodyLabel":self.formLabel}]];
}
return self;
}
#end
#interface FirstTableViewController ()
{
NSArray *messages;
FirstTableViewController *classA;
}
#end
#implementation FirstTableViewController
+(id)sharedInstance
{
static FirstTableViewController *sharedClassA = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedClassA = [[self alloc] init];
});
return sharedClassA;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.heightAtIndexPath = [NSMutableDictionary new];
self.userInput = [[NSMutableArray alloc] init];
[self.tableView registerClass:[ChatMessageCellTableViewCell class] forCellReuseIdentifier:#"id"];
[[self tableView] setSeparatorStyle:UITableViewCellSeparatorStyleNone];
[self.tableView setBackgroundColor:[UIColor colorWithWhite:0.95 alpha:1]];
[[self navigationController] setTitle:#"Meetings"];
classA = [FirstTableViewController sharedInstance];
[classA setCounter:(classA.userInput.count)];
[classA setMessageView:(self.messageView)];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
classA.counter=classA.userInput.count;
return classA.counter;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *cellIdentifier = (indexPath.row % 2 == 0 ? #"EvenCell" : #"OddCell"); //just to differentiate the sending and receiving cell.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
ChatMessageCellTableViewCell *messageCell = (ChatMessageCellTableViewCell*) cell;
if (messageCell == nil) {
messageCell = [[ChatMessageCellTableViewCell alloc] initWithStyle: UITableViewCellStyleDefault
reuseIdentifier:cellIdentifier];
}
if(indexPath.row % 2 == 0) // simple logic to differentiate and apply my constraints to the sending and receiving cells.
{
isReceived =TRUE;
}
else{
isReceived = FALSE;
}
[[messageCell formLabel]setText:classA.userInput[indexPath.row]];
[messageCell setSelectionStyle:UITableViewCellSelectionStyleNone];
[[self tableView] setEstimatedRowHeight:50.0];
[self.tableView setRowHeight:UITableViewAutomaticDimension];
return messageCell;
}
-(void)viewWillLayoutSubviews{
if(classA.shouldScrollToLastRow){
[classA setShouldScrollToLastRow:NO];
dispatch_async(dispatch_get_main_queue(),^{
NSIndexPath *path = [NSIndexPath indexPathForRow:(self->classA.counter)-1 inSection:0];
//Basically maintain your logic to get the indexpath
[self->classA.messageView scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionBottom animated:NO];
});
}
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
}
-(void)dealloc{
NSLog(#"Dealloc!!!");
}
#end
//SecondViewController.m
//sendButtonClicked is the function from where the data is passed to the FirstViewController's tableview cell.
-(IBAction)sendButtonClicked{
NSString *input = self.ChatTextInput.text;
if([input isEqualToString:#""]){
NSLog(#"this is a nil ");
}
else{
[inputValues addObject:input];
[inputValues addObject:input];
[classA setUserInput:inputValues];
[classA setCounter:inputValues.count];
[self.ChatTextInput setText:nil];
[classA setShouldScrollToLastRow:YES];
[classA.messageView reloadData];
}
}
This is basically a chat view which I'm actually trying to achieve everything is good except this abnormal behaviour.
I hope anyone can take some time and correct me where am I wrong.
UPDATE: Any one who is looking for basic chatView in objective-C can use the code above as an reference, use the code above and correct the mentioned things in the accepted answer.
This is a typical cell reuse issue. In iOS all collections (UITableView/UICollectionView) reuses the cell and cells initWithStyle gets called only once the cell is initialised. Once tableView has enough cells with it, it will reuse the cell so initWithStyle will not get called all the time. Hence few of your cells (preferably initial ones) seems all right. As you set its constraints properly in init and for other cells which are not shown properly init was never called so your constraints were never updated. Hence shows wrong bubble.
Whats the solution?:
1. Use PrepareforReuse
every cell when it gets reused, iOS calls prepareForReuse on the cell to give developer a last chance to do all clean up
-(void) prepareForReuse {
[super prepareForReuse];
[self.formLabel setText: nil];
//set default background color or change bubble view
// do whatever clean up you wanna do here
}
2. Modify your cells method and ensure you update your constraints every time cell is shown and not just in init
lets say you add a method called:
-(void)configureView:(BOOL) isRecieved {
isReceived = isRecieved;
if(isReceived){
[leadingConstraint setActive:YES];
[trailingConstraint setActive:NO];
}
else{
[leadingConstraint setActive:NO];
[trailingConstraint setActive:YES];
}
//[self layoutIfNeeded]; might be needed here
[self loaded];
}
In your init remove code to set constraint based on isRecieved Value
-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
[self setBackgroundColor:[UIColor clearColor]];
self.formLabel = [UILabel new];
self.bubbleBackView = [UIView new];
//[self.bubbleBackView setBackgroundColor:[UIColor yellowColor]];
[self.bubbleBackView.layer setCornerRadius:12];
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if(self){
[[self contentView] addSubview:self.bubbleBackView
];
[self loaded];
[self.bubbleBackView setTranslatesAutoresizingMaskIntoConstraints:NO];
[[self contentView] addSubview:self.formLabel];
[self.formLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
if (#available(iOS 9.0, *)) {
[self.formLabel.topAnchor constraintEqualToAnchor:self.topAnchor constant:32].active=YES;
[self.formLabel.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-32].active=YES;
[self.formLabel.widthAnchor constraintLessThanOrEqualToConstant:250].active=YES;
[self.bubbleBackView.topAnchor constraintEqualToAnchor:_formLabel.topAnchor constant:-16].active=YES;
[self.bubbleBackView.bottomAnchor constraintEqualToAnchor:_formLabel.bottomAnchor constant:16].active=YES;
[self.bubbleBackView.trailingAnchor constraintEqualToAnchor:_formLabel.trailingAnchor constant:16].active=YES;
[self.bubbleBackView.leadingAnchor constraintEqualToAnchor:_formLabel.leadingAnchor constant:-16].active=YES;
leadingConstraint= [self.formLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:32];
trailingConstraint = [self.formLabel.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-32];
} else {
// Fallback on earlier versions
}
[self.formLabel setLineBreakMode:NSLineBreakByWordWrapping];
[self.formLabel setNumberOfLines:0];
[self.formLabel sizeToFit];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-40-[bodyLabel]-40-|" options:0
metrics:nil
views:#{ #"bodyLabel":self.formLabel}]];
}
return self;
}
Finally in cellForRowAtIndexPath call configureView with isReceived value
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *cellIdentifier = (indexPath.row % 2 == 0 ? #"EvenCell" : #"OddCell"); //just to differentiate the sending and receiving cell.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
ChatMessageCellTableViewCell *messageCell = (ChatMessageCellTableViewCell*) cell;
if (messageCell == nil) {
messageCell = [[ChatMessageCellTableViewCell alloc] initWithStyle: UITableViewCellStyleDefault
reuseIdentifier:cellIdentifier];
}
if(indexPath.row % 2 == 0) // simple logic to differentiate and apply my constraints to the sending and receiving cells.
{
isReceived =TRUE;
}
else{
isReceived = FALSE;
}
[messageCell configureView: isReceived];
[[messageCell formLabel]setText:classA.userInput[indexPath.row]];
[messageCell setSelectionStyle:UITableViewCellSelectionStyleNone];
[[self tableView] setEstimatedRowHeight:50.0];
[self.tableView setRowHeight:UITableViewAutomaticDimension];
return messageCell;
}
Hope it helps

UIsearchController inside UIViewController using Auto Layout

Has anyone been successful implementing a UIViewController that contais both a UISearchController searchBar and a UItableView while laying everything out using Auto Layout?
I'm trying to achieve something similar to what 1Password does on the iPhone: a fixed searchBar on top of a tableView (not part of its tableHeaderView). When the UISearchController that owns the searchBar gets activated, its searchBar animates to show the scope buttons and thus the tableView moves down a bit.
I have got the basics of this layout working correctly with this class:
//
// ViewController.m
// UISearchControllerIssues
//
// Created by Aloha Silver on 05/02/16.
// Copyright © 2016 ABC. All rights reserved.
//
#import "ViewController.h"
#interface ViewController () <UISearchResultsUpdating, UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, strong) UISearchController *searchController;
#property (nonatomic, strong) UITableView *tableView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupTableView];
[self setupSearchInterface];
[self setupConstraints];
self.edgesForExtendedLayout = UIRectEdgeNone;
self.extendedLayoutIncludesOpaqueBars = YES;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.searchController.searchBar sizeToFit];
}
- (void)setupTableView {
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
self.tableView.dataSource = self;
self.tableView.delegate = self;
self.tableView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.tableView];
}
- (void)setupSearchInterface {
self.definesPresentationContext = YES;
self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
self.searchController.dimsBackgroundDuringPresentation = NO;
self.searchController.hidesNavigationBarDuringPresentation = NO;
self.searchController.searchBar.scopeButtonTitles = #[#"One", #"Two"];
self.searchController.searchBar.translatesAutoresizingMaskIntoConstraints = NO;
self.searchController.searchResultsUpdater = self;
[self.view addSubview:self.searchController.searchBar];
}
- (void)setupConstraints {
NSDictionary *layoutViews = #{#"searchBar": self.searchController.searchBar, #"tableView": self.tableView, #"topLayoutGuide": self.topLayoutGuide};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[searchBar]|" options:0 metrics:nil views:layoutViews]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[tableView]|" options:0 metrics:nil views:layoutViews]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[searchBar(44)][tableView]|" options:0 metrics:nil views:layoutViews]];
}
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
NSLog(#"Update should happen here");
}
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 100;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellID = #"CellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellID];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellID];
}
cell.textLabel.text = [NSString stringWithFormat:#"Cell number %ld", (long)indexPath.row];
return cell;
}
#end
It is embedded in a UINavigationController instance and initially runs as expect, like the following screenshots show:
Trouble arises when the searchBar gets activated. It seems to disappear from screen, but after carefully inspecting the view, we determine that it is actually onscreen, but with a width of zero. Here's a picture showing what is presented at this time:
I'm not that experienced with Auto Layout, so I'm left thinking there must be something wrong with my constraints, although I don't mess with them when activating the UISearchController.
Is there any way of making this work?
The UISearchController moves the search bar around when it's tapped, so it doesn't always play well with your constraints.
Instead of setting your constraints directly on the search bar, add an empty placeholder view that will hold your search bar and then place the search bar in it procedurally in viewDidLoad(). Set your constraints on this placeholder instead. Just match the search bar's frame to the placeholder's bounds and leave translatesAutoresizingMaskIntoConstraints set to true.
Sorry, I'm not sure how this placeholder view will handle the size change with the scope buttons. Hopefully you can figure out how to get that part working with auto layout after the change.

Custom implementation of UICollectionViewCell makes cells invisible

For the sake of context I'm developing a calendar app with 3 different views. Day, Month and Year View.
In order to display the days I've decided to use a UICollectionView with a custom implementation of UICollectionViewCell (so that I'm able to customize the aspect of each cell).
On the init of my NSObject CtrlCalendar class I register 3 cell Classes like this:
//Used for the yearView
[self.grid registerClass:[CollectionViewCell class] forCellWithReuseIdentifier:#"monthCell"];
//Used for the monthView
[self.grid registerClass:[CollectionViewCell class] forC ellWithReuseIdentifier:#"dayCell"];
//Used for the dayView
[self.grid registerClass:[CollectionViewCell class] forCellWithReuseIdentifier:#"hourCell"];
Than, on collectionView:collectionView cellForItemAtIndexPath:indexPath I'm doing as follows:
CollectionViewCell *cell = nil;
if ([self.currentDisplayType isEqualToString:#"monthView"]) {
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"dayCell" forIndexPath:indexPath];
cell.backgroundColor = [UIColor grayColor];
self.month = [self.displayingViews get:indexPath.section];
if (indexPath.row + 1 >= self.month.firstWeekDay && indexPath.row + 1 < self.month.numberOfDays + (self.month.firstWeekDay)) {
NSDateComponents *tempDay = [self.calendar components:NSCalendarUnitDay fromDate:self.month.date];
cell.date = [self.dateHelper addToDate:self.month.date days:(int)(tempDay.day + indexPath.row - self.month.firstWeekDay)];
cell.cellTitle.text = [NSString stringWithFormat:#"%i", (int)(tempDay.day + indexPath.row + 1 - self.month.firstWeekDay)];
cell.cellTitle.textColor = [UIColor blackColor];
if ([[self.dateHelper addToDate:self.month.date days:(indexPath.row + 1 - self.month.firstWeekDay)] isEqualToDate:self.today]) {
cell.cellTitle.textColor = [UIColor redColor];
}
cell.userInteractionEnabled = YES;
cell.separator.hidden = NO;
cell.contentView.hidden = NO;
} else {
cell.userInteractionEnabled = NO;
}
And finally here is my implementation of CollectionViewCell:
#interface CollectionViewCell ()
#property (nonatomic, retain) UILabel *cellTitle;
#property (nonatomic, retain) NSString *titleText;
#property (nonatomic, retain) UILabel *separator;
#property (nonatomic, retain) NSDate *date;
#end
#implementation CollectionViewCell
#synthesize cellTitle;
#synthesize titleText;
#synthesize separator;
#synthesize date;
- (void)dealloc {
[cellTitle release];
[titleText release];
[separator release];
[date release];
[super dealloc];
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.cellTitle = [[[UILabel alloc] initWithFrame:self.bounds] autorelease];
self.cellTitle.textColor = [UIColor blackColor];
self.cellTitle.textAlignment = NSTextAlignmentCenter;
self.separator = [[[UILabel alloc] initWithFrame:CGRectMake(0, (self.frame.size.height - 0.25), self.frame.size.width, 0.5)] autorelease];
self.separator.backgroundColor = [UIColor lightGrayColor];
[self.contentView addSubview:self.separator];
[self.contentView addSubview:self.cellTitle];
}
return self;
}
- (void)prepareForReuse {
[super prepareForReuse];
self.cellTitle.text = nil;
self.separator.hidden = YES;
[self.cellTitle setTextColor:[UIColor blackColor]];
//the presence of this line is explained after the edit bellow
[self.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
}
#end
The first time I run the app, the Month View is configured as expected, but as soon as it performs the dequeue, the cells turn blank.
After some debugging I found that the CollectionViewCell's cellTitle property is correctly set. The problem resides in the fact that after the dequeue the labels are hidden for some reason.
I know this is long question, but if you can help in anyway or know someone who can help please let me know!
Thanks a lot!
For clarification of what the problem is I'm adding some screens
Before scrolling:
After scrolling:
UPDATE 1
As #Alessandro Chiarotto answered, the
[self.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)]
does make the cells empty. The caveat here is that I need that selector to remove from the cells some UILabels I'm adding to the yearView.
My yearView UICollection has 12 cells (one for each month). Each cell than has the number of UILabels as the days in each month. I'm adding this UILabels from the collectionView:collectionView cellForItemAtIndexPath:indexPath like this:
for (int d = 1; d < self.month.numberOfDays; d++) {
UILabel *day = [[[UILabel alloc] initWithFrame:dayRect] autorelease];
day.text = currentDay;
[cell addSubview:day];
}
If i don't perform the selector in prepareForReuse, after scrolling the view I have all the day labels in duplicate in each month cell.
If you scroll the collection then prepareForReuse will be called. In prepareForReuse you have the following call:
[self.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
this will remove all the subviews of the cell (UILabel and so on).
At this point your cell will "white"...
I've found the problem. As #Alessandro said in his answer the [self.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)]; was removing everything from the cell, therefore making it blank.
That would be it if it weren't for another aspect of the problem.
As I said in my question Update, I need that selector in order to remove some UILabels that are being added in the yearView. Those labels are the days in each month cell.
Solution:
I added a UIView property to the CollectionViewCell that has a frame equal to the cell's bounds. I than add the UILabels to this view and on the prepareForReuse I perform the removeFromSuperView on the self.labelsView.subviews. It works like a charm.
The correct implementation of prepareForReuse would be:
- (void)prepareForReuse {
[super prepareForReuse];
self.cellTitle.text = nil;
self.separator.hidden = YES;
[self.cellTitle setTextColor:[UIColor blackColor]];
[self.labelsView.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
}

Dynamic UITableViewCell Producing Undesired Results

I'm attempting to create a dynamically sized table view cell. I've read every SO question, website, article, and example Github project and can't get the layout I want without errors (in its current form, there are no errors, but the end result is as depicted in the last image).
I have a table with multiple sections. The first section has a single cell that is dynamically sized. My goal is to display this cell correctly and without errors. Here are the two different visual states the cell may have:
Here is the desired look of the cell with a Message at the bottom:
Here is desired look of the cell without the message at all:
For the code shown below, here is the result:
Here is the TableViewController:
//
// The TableViewController
//
#import <Masonry.h>
#import "CustomCell.h"
#import "MyViewController.h"
#interface MyViewController()
#property (retain, nonatomic) CheckoutHeaderView *headerView;
#property (retain, nonatomic) CustomCell *customCell;
#end
#implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView = [[UITableView alloc] init];
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.tableView.allowsSelection = NO;
[self.tableView registerClass:[CustomCell class] forCellReuseIdentifier:#"customCell"];
[self.view addSubview:self.headerView];
[self.view addSubview:self.tableView];
[self.headerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view);
make.left.equalTo(self.view);
make.right.equalTo(self.view);
}];
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.headerView.mas_bottom);
make.left.equalTo(self.view);
make.right.equalTo(self.view);
make.bottom.equalTo(self.view);
}];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return self.customerCell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
static dispatch_once_t onceToken;
static CustomCell *customCell;
dispatch_once(&onceToken, ^{
customCell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"customCell"];
self.customCell = customerCell;
});
self.customCell.model = self.model;
return [self calculateHeightForConfiguredSizingCell:self.customCell];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
- (CGFloat)calculateHeightForConfiguredSizingCell:(UITableViewCell *)sizingCell {
[sizingCell setNeedsUpdateConstraints];
[sizingCell updateConstraintsIfNeeded];
sizingCell.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(self.tableView.bounds), CGRectGetHeight(self.tableView.bounds));
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
CGFloat height = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
height += 1.0f;
return height;
}
#end
Here is the cell class:
#import "CustomCell.h"
#import <Masonry.h>
#import "Label.h"
#import "Order.h"
#import "Helper.h"
#import "Theme.h"
#interface CustomCell()
#property (assign, nonatomic) BOOL didSetupConstraints;
#property (retain, nonatomic) Label *dateOneLabel;
#property (retain, nonatomic) Label *dateTwoToLabel;
#property (retain, nonatomic) Label *messageLabel;
#property (retain, nonatomic) Label *dateOneValue;
#property (retain, nonatomic) Label *dateTwoToValue;
#property (retain, nonatomic) Label *messageText;
#property (retain, nonatomic) NSMutableArray *messageConstraints;
#property (retain, nonatomic) MASConstraint *pinBottomOfDateTwoLabelToBottomOfContentViewConstraint;
#end
#implementation CustomCell
- (NSMutableArray *)messageConstraints {
return _messageConstraints ? _messageConstraints : [#[] mutableCopy];
}
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self setup];
}
return self;
}
- (void)awakeFromNib {
[self setup];
}
- (void) setup {
self.didSetupConstraints = NO;
self.dateOneLabel = [UILabel new];
self.dateOneLabel.text = #"Date One";
self.dateTwoLabel = [UILabel new];
self.dateTwoLabel.text = #"Date Two";
self.messageLabel = [UILabel new];
self.messageLabel.text = #"Message";
self.dateOneValue = [UILabel new];
self.dateTwoToValue = [UILabel new];
// The actual message text label that spans numerous lines.
self.messageText = [UILabel new];
self.messageText.numberOfLines = 0;
self.messageText.adjustsFontSizeToWidth = NO;
[self.contentView addSubview:self.dateOneLabel];
[self.contentView addSubview:self.dateTwoToLabel];
[self.contentView addSubview:self.messageLabel];
[self.contentView addSubview:self.dateOneValue];
[self.contentView addSubview:self.dateTwoToValue];
[self.contentView addSubview:self.messageText];
}
- (void)layoutSubviews {
[super layoutSubviews];
[self.contentView setNeedsLayout];
[self.contentView layoutIfNeeded];
self.messageText.preferredMaxLayoutWidth = CGRectGetWidth(self.messageText.frame);
}
- (void)updateConstraints {
if (!self.didSetupConstraints) {
__weak typeof (self.contentView) contentView = self.contentView;
// Topmost label, pinned to left side of cell.
[self.dateOneLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(contentView).with.offset(14);
make.right.lessThanOrEqualTo(self.dateOneValue.mas_left).with.offset(-20);
make.top.equalTo(contentView).with.offset(14);
}];
// Second label, pinned to left side of cell and below first label.
[self.dateTwoToLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.dateOneLabel);
make.top.equalTo(self.dateOneLabel.mas_bottom).with.offset(6);
make.right.lessThanOrEqualTo(self.dateTwoToValue.mas_left).with.offset(-20);
}];
// First date value, pinned to right of cell and baseline of its label.
[self.dateOneValue mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(contentView).with.offset(-14).priorityHigh();
make.baseline.equalTo(self.dateOneLabel);
}];
// Second date value, pinned to right of cell and baseline of its label.
[self.dateTwoToValue mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.dateOneValue);
make.baseline.equalTo(self.dateTwoToLabel);
}];
self.didSetupConstraints = YES;
}
[super updateConstraints];
}
- (void)uninstallMessageConstraints {
[self.pinBottomOfDateTwoLabelToBottomOfContentViewConstraint uninstall];
for (MASConstraint *constraint in self.messageConstraints) {
[constraint uninstall];
}
[self.contentView mas_remakeConstraints:^(MASConstraintMaker *make) {
self.pinBottomOfDateTwoLabelToBottomOfContentViewConstraint = make.bottom.equalTo(self.dateTwoToLabel).with.offset(14);
}];
}
- (void)installMessageConstraints {
__weak typeof (self.contentView) contentView = self.contentView;
[self.pinBottomOfDateTwoLabelToBottomOfContentViewConstraint uninstall];
// Below, add constraints of `self.messageConstraints` into an array so
// they can be removed later.
[self.messageConstraints addObjectsFromArray:[self.messageLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.dateOneLabel);
make.top.equalTo(self.dateTwoToLabel.mas_bottom).with.offset(6);
}]];
self.messageConstraints = [[self.messageText mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.messageLabel);
make.top.equalTo(self.messageLabel.mas_bottom).with.offset(6);
make.right.equalTo(contentView).with.offset(-14);
}] mutableCopy];
[contentView mas_makeConstraints:^(MASConstraintMaker *make) {
self.pinBottomOfDateTwoLabelToBottomOfContentViewConstraint = make.bottom.equalTo(self.messageText).with.offset(14);
}];
}
- (void)setModel:(MyModel *)model {
if (!model.message || model.message.length < 1) {
[self uninstallMessageConstraints];
self.messageText.text = #"";
[self.messageLabel removeFromSuperview];
[self.messageText removeFromSuperview];
} else {
self.messageText.text = model.message;
if (![self.contentView.subviews containsObject:self.messageLabel]) {
[self.contentView addSubview:self.messageLabel];
}
if (![self.contentView.subviews containsObject:self.messageText]) {
[self.contentView addSubview:self.messageText];
}
[self installMessageConstraints];
}
self.dateOneValue.text = model.dateOne;
self.dateTwoValue.text = model.dateTwo;
[self.contentView setNeedsDisplay];
[self.contentView setNeedsLayout];
}
#end
I've been tinkering with this for two days, and at certain points, it looked as desired, but with Autolayout Errors. I have no idea where my errors lie, so my general question is: What is wrong with my code and what needs to change to produce the correct result?
Many thanks.
I think you need to add self.messageText.lineBreakMode = NSLineBreakByWordWrapping to force it to multiple lines.

Self sizing cell just doesn't work for me

I tried to follow the WWCD 2014 session 226, which introduced the way to realize self sizing cells in iOS 8 using auto layout, and it just doesn't work as it should.
HHTableViewCell.h
#import <UIKit/UIKit.h>
#interface HHTableViewCell : UITableViewCell
#property (strong, nonatomic)UILabel *title;
#end
HHTableViewCell.m
#import "HHTableViewCell.h"
#implementation HHTableViewCell
#synthesize title = _title;
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// configure control(s)
#pragma mark -- title Lable
_title = [[UILabel alloc] initWithFrame:CGRectInset(self.bounds, 15.0, 0.0)];
_title.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
_title.numberOfLines = 0;
[self.contentView addSubview:_title];
#pragma mark -- constraints
NSMutableArray *constraints = [[NSMutableArray alloc]init];
UIView *contentView = self.contentView;
[constraints addObject:[NSLayoutConstraint
constraintWithItem:_title
attribute:NSLayoutAttributeFirstBaseline
relatedBy:NSLayoutRelationEqual
toItem:contentView
attribute:NSLayoutAttributeTop
multiplier:1.8
constant:3.0]];
[constraints addObject:[NSLayoutConstraint
constraintWithItem:_title
attribute:NSLayoutAttributeFirstBaseline
relatedBy:NSLayoutRelationEqual
toItem:contentView
attribute:NSLayoutAttributeTop
multiplier:1.8
constant:3.0]];
[constraints addObject:[NSLayoutConstraint
constraintWithItem:_title
attribute:NSLayoutAttributeFirstBaseline
relatedBy:NSLayoutRelationEqual
toItem:contentView
attribute:NSLayoutAttributeTop
multiplier:1.8
constant:3.0]];
[constraints addObject:[NSLayoutConstraint
constraintWithItem:contentView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:nil
attribute:0
multiplier:1.0
constant:44.0]];
[constraints addObjectsFromArray:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|-15-[_title]-15-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(_title)]];
[self.contentView addConstraints:constraints];
}
return self;
}
#end
MMTableViewController.h
#import <UIKit/UIKit.h>
#interface MMTableViewController : UITableViewController
#end
MMTableViewController.m
#import "MMTableViewController.h"
#import "HHTableViewCell.h"
#interface MMTableViewController ()
#end
#implementation MMTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[HHTableViewCell class] forCellReuseIdentifier:#"HiCell"];
[self.tableView registerClass:[HHTableViewCell class] forCellReuseIdentifier:#"HCell"];
self.tableView.estimatedRowHeight = 44.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
#warning Potentially incomplete method implementation.
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
#warning Incomplete method implementation.
// Return the number of rows in the section.
return 10;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"HiCell";
HHTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
cell.title.text = #"Hello Apple. Hello Apple. Hello Apple. Hello Apple. Hello Apple. Hello Apple. Hello Apple. Hello Apple. Hello Apple. Hello Apple. Hello Apple. Hello Apple. Hello Apple. ";
// Configure the cell...
NSLog(cell.title.text);
return cell;
}
#end
The cell is of fixed height and wrapping two lines of text. Looks like this:
Hello Apple. Hello Apple. Hello Apple. Hello
Aplle. Hello Apple. Hello Apple. Hello Apple...
Constraints and subviews are added programatically.
Simulator is running iOS 8.3 in Xcode 6.3.1.
For UILabel to work with constraints, looking at Apple's documentation, I think you need to set the preferredMaxLayoutWidth property:
This property affects the size of the label when layout constraints
are applied to it. During layout, if the text extends beyond the width
specified by this property, the additional text is flowed to one or
more new lines, thereby increasing the height of the label.
However, unless you want some specific cell customization, you can use the default UITableViewCell, and set numberOfLines = 0 on the provided titleLabel. It will work with UITableViewAutomaticDimension, although I've only tested it in conjunction with heightForRowAtIndexPath:.
UPDATE:
From what I've learned, you also need to set estimatedRowHeight to something in the viewDidLoad (the value doesn't even needs to be accurate/important it seems).
Here is a working example using default UITableViewCells:

Resources