Dynamic UITableViewCell Producing Undesired Results - ios

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.

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

UIButton doesn't resize to fit its titleLabel

I'm working with iOS8 Self-Sizing cell and I want the cell resize to fit its subview, messageBodyButton and profileImageView.
profileImageView's size is fixed. As you can see in the picture below, the messageBodyButton doesn't resize to fit its titleLabel.
Why did this happen? How to fix this bug using Auto Layout?
And when the titleLabel's content changed(say, the model's data changed), how should I update constraints so that the cell height can be calculated correctly?
Here is my code:
RXTChatCell.m:
#property (nonatomic, strong) UILabel *timeLabel;
#property (nonatomic, strong) UIImageView *profileImageView;
#property (nonatomic, strong) UIButton *messageBodyButton;
#property (nonatomic, assign) BOOL needUpdateConstraints;
#property (nonatomic, assign) BOOL didSetUpConstraints;
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
_timeLabel = [[UILabel alloc] init];
_timeLabel.text = #"19:00";
[self.contentView addSubview:_timeLabel];
_profileImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"placeholder"]];
[self.contentView addSubview:_profileImageView];
_messageBodyButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_messageBodyButton.titleLabel setLineBreakMode:NSLineBreakByTruncatingTail];
[_messageBodyButton.titleLabel setTextAlignment:NSTextAlignmentRight];
_messageBodyButton.titleLabel.numberOfLines = 0;
_messageBodyButton.titleLabel.backgroundColor = UIColor.grayColor;
[self.contentView addSubview:_messageBodyButton];
_timeLabel.backgroundColor = UIColor.redColor;
_profileImageView.backgroundColor = UIColor.greenColor;
_messageBodyButton.backgroundColor = UIColor.blueColor;
self.contentView.backgroundColor = UIColor.orangeColor;
}
return self;
}
- (void)updateConstraints
{
if (!self.didSetUpConstraints) { // set up constraints
[_timeLabel makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.contentView.topMargin);
make.centerX.equalTo(self.contentView);
}];
[_profileImageView makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_timeLabel.bottom).offset(10);
make.right.equalTo(self.contentView).offset(-10);
make.width.and.height.equalTo(50);
make.bottom.lessThanOrEqualTo(self.contentView.bottomMargin);
}];
[_messageBodyButton makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_profileImageView);
make.right.equalTo(_profileImageView.left).offset(-10);
make.left.equalTo(self.contentView).offset(10);
make.bottom.lessThanOrEqualTo(self.contentView.bottomMargin);
}];
self.didSetUpConstraints = YES;
}
if (self.needUpdateConstraints){
// here, how should I update constraints?
}
[super updateConstraints];
}
- (void)setMessage:(RXTChatMessage *)message
{
EMTextMessageBody *body = (EMTextMessageBody *)message.messageBody.body;
[self.messageBodyButton setTitle:body.text forState:UIControlStateNormal];
self.needUpdateConstraints = YES;
[self setNeedsUpdateConstraints];
}
RXTChatViewController.m:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.tableView.estimatedRowHeight = 44;
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
RXTChatMessage *message = self.messages[indexPath.row];
RXTChatCell *cell = [tableView dequeueReusableCellWithIdentifier:RXTChatCellId];
if (cell == nil) {
cell = [[self alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:RXTChatCellId];
}
cell.message = message;
return cell;
}
Select Your Button -> Go to Editor -> Size to fit content.
Remove fixed height constraint for your button (only set leading,trailing,top bottom) constraint (Superview should be your cell view).
This should solve your problem
let me know if it solves your issue.

UITableview/UIView Delegate Confusion

I am making my own drop down menu because I am not able to find an open source one that does exactly what I need.
I have implemented the dropdown as a UIView and am adding it to the superview of the button that is tapped in order to show it.
Code:
ViewController.m
#import "ViewController.h"
#import "MenuView.h"
#interface ViewController () <MenuViewDelegate>
#property (weak, nonatomic) IBOutlet UIView *fakeHeader;
#property (nonatomic, strong) MenuView *menuView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)btnTapped:(id)sender {
NSArray *array = #[#"Item 1", #"Item 2", #"Item 3", #"Item 4"];
NSArray *imgArray = nil;
if (self.menuView == nil) {
self.menuView = [[MenuView alloc] showDropDownWith:sender txtArr:array imgArr:imgArray direction:#"down" delegate:self];
self.menuView.delegate = self;
} else {
[self.menuView hideDropDown:sender];
self.menuView = nil;
}
}
- (void) menuDelegateMethod:(MenuView *)sender {
self.menuView = nil;
}
#end
MenuView.h
#import <UIKit/UIKit.h>
#class MenuView;
#protocol MenuViewDelegate
- (void)menuDelegateMethod:(MenuView *)sender;
#end
#interface MenuView : UIView
#property (nonatomic, retain) id <MenuViewDelegate> delegate;
- (id)showDropDownWith:(UIButton *)button txtArr:(NSArray *)txtArr imgArr:(NSArray *)imgArr direction:(NSString *)direction delegate:(id)delegate;
- (void)hideDropDown:(UIButton *)button;
#end
MenuView.m
#import "MenuView.h"
#import "QuartzCore/QuartzCore.h"
#interface MenuView () <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, strong) UITableView *table;
#property (nonatomic, strong) UIButton *btnSender;
#property (nonatomic, retain) NSString *animationDirection;
#property (nonatomic, retain) NSArray *list;
#property (nonatomic, retain) NSArray *imageList;
#end
#implementation MenuView
- (id)showDropDownWith:(UIButton *)button txtArr:(NSArray *)txtArr imgArr:(NSArray *)imgArr direction:(NSString *)direction delegate:(id)delegate {
CGFloat width = [UIScreen mainScreen].bounds.size.width;
CGFloat origin = [UIScreen mainScreen].bounds.origin.x;
CGFloat realHeight = 40 * txtArr.count;
self.btnSender = button;
self.animationDirection = direction;
self.table = (UITableView *)[super init];
if (self) {
// Initialization code
CGRect btn = button.frame;
self.list = [NSArray arrayWithArray:txtArr];
self.imageList = [NSArray arrayWithArray:imgArr];
if ([direction isEqualToString:#"up"]) {
self.frame = CGRectMake(origin, (btn.origin.y - btn.size.height) , width, 0);
self.layer.shadowOffset = CGSizeMake(0, 1);
} else if ([direction isEqualToString:#"down"]) {
self.frame = CGRectMake(origin, (btn.origin.y + btn.size.height + 10), width, 0);
self.layer.shadowOffset = CGSizeMake(0, 1);
}
self.layer.masksToBounds = YES;
self.layer.shadowOpacity = 0.2;
self.table = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, width, 0)];
self.table.delegate = delegate;
self.table.dataSource = self;
self.table.backgroundColor = [UIColor colorWithRed:0.239 green:0.239 blue:0.239 alpha:1];
self.table.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
self.table.separatorColor = [UIColor lightGrayColor];
self.table.backgroundColor = [UIColor whiteColor];
self.table.userInteractionEnabled = YES;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
if ([direction isEqualToString:#"up"]) {
self.frame = CGRectMake(origin, (btn.origin.y - realHeight), width, realHeight);
} else if([direction isEqualToString:#"down"]) {
self.frame = CGRectMake(origin, (btn.origin.y + btn.size.height + 10), width, realHeight);
}
self.table.frame = CGRectMake(0, 0, width, realHeight);
[UIView commitAnimations];
[button.superview addSubview:self];
self.table.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
[self addSubview:self.table];
}
return self;
}
- (void)hideDropDown:(UIButton *)button {
CGRect btn = button.frame;
CGFloat width = [UIScreen mainScreen].bounds.size.width;
CGFloat origin = [UIScreen mainScreen].bounds.origin.x;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
if ([self.animationDirection isEqualToString:#"up"]) {
self.frame = CGRectMake(origin, btn.origin.y, width, 0);
} else if ([self.animationDirection isEqualToString:#"down"]) {
self.frame = CGRectMake(origin, (btn.origin.y + btn.size.height + 10), width, 0);
}
self.table.frame = CGRectMake(0, 0, width, 0);
[UIView commitAnimations];
}
#pragma mark - Table View DataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.list count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.textLabel.font = [UIFont systemFontOfSize:15];
cell.textLabel.textAlignment = NSTextAlignmentLeft;
}
cell.textLabel.text = [self.list objectAtIndex:indexPath.row];
cell.backgroundColor = [UIColor colorWithRed:48.0f/255.0f green:48.0f/255.0f blue:48.0f/255.0f alpha:1.0f];
cell.textLabel.textColor = [UIColor lightTextColor];
return cell;
}
#pragma mark - Table View Delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 40;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self hideDropDown:self.btnSender];
[self myDelegate];
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
// Remove seperator inset
if ([cell respondsToSelector:#selector(setSeparatorInset:)]) {
[cell setSeparatorInset:UIEdgeInsetsZero];
}
// Prevent the cell from inheriting the Table View's margin settings
if ([cell respondsToSelector:#selector(setPreservesSuperviewLayoutMargins:)]) {
[cell setPreservesSuperviewLayoutMargins:NO];
}
// Explictly set your cell's layout margins
if ([cell respondsToSelector:#selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
}
#pragma mark - View Delegate
- (void)myDelegate {
[self.delegate menuDelegateMethod:self];
}
#end
Shows up perfectly but the didSelect method is never called.
I don't have any views over the top of it that would be stealing the touch events.
It seems that UIViews might not be able to be UITableviewDelegates. If that's true I don't know why, when I make the calling view controller the delegate, it still fails to didSelect.
NOTE: I am aware of the animation faux pas by not using newer methods. This is based on old example code. I will update the animation after I get this issue worked out.
Questions:
Is it true that UIViews can not be UITableView Delegates?
If so, how does one make a calling view controller the delegate for the table view that resides in the UIView? Other than the process of setting it up as a UITableViewDelegate and assigning the calling view controller as the delegate at the time of the creation of the table.
Did I miss something in the way I set this up that steals the cell taps so that didSelect does not get called, either in the view or the viewController?
Thanks for the help.
Agree with #Piotr that the menu must be the table's delegate, so replace self.table.delegate = delegate; with self.table.delegate = self; in MenuView.m.
But additionally, MenuView.m never invokes its delegate, which it should upon selection in the tableview....
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self hideDropDown:self.btnSender];
// remove this, since it does nothing
//[self myDelegate];
// replace it with
[self.myDelegate menuDelegateMethod:self];
}
That last line says, tell the delegate of the menu that something happened.
Another problem is that the menu doesn't really tell the delegate what happened. Certainly the delegate will be interested in which item is selected. Consider changing the protocol to something like:
#protocol MenuViewDelegate
- (void)menuView:(MenuView *)sender didSelectOptionAtIndex:(NSInteger)index;
#end
// calling it
[self.myDelegate menuView:self didSelectOptionAtIndex:indexPath.row];
Another alternative is to hand back the selected string to the delegate. This can be found in the tableview's datasource at the indexPath.row.
Finally, its good practice to not-retain your delegate since the customer of the Menu might retain it, resulting in a retain cycle. Instead, declare the delegate:
// notice "weak"
#property (nonatomic, weak) id <MenuViewDelegate> delegate;
As I can see, you are passing ViewController (it is MenuViewDelegate) to showDropDownWith method, and then use it as table delegate. This is not correct.
You should pass self there (same as with data source), because you want MenuView to be delegate of table, not ViewController, right?
self.table.delegate = self;

Delegate method is being called but doesn't execute properly

I'm having a problem with one of my delegate methods. I have a collectionViewController to which I have added a UILongPressGestureRecognizer, which is calling a delegate method fadeOutLabels in my UICollectionViewCell. I can confirm the delegate method being called by a NSLog statement NSLog(#"fadeOutLabels was called");. But the other code inside that function isn't being executed. I'm quite sure I'm missing something completely obvious, but I can't seem to figure it out myself. Code as follows:
FOFPhotoCell.h
#protocol FOFPhotoCellDelegate <NSObject>
#required
-(void)fadeOutLabels;
#end
#interface FOFPhotoCell : UICollectionViewCell {
id delegate;
}
#property (nonatomic, weak) id<FOFPhotoCellDelegate> delegate;
#property (nonatomic) UIImageView *imageView;
#property (nonatomic) NSDictionary *photo;
#property (nonatomic) NSArray *fetchPhotos;
#property (nonatomic) UILabel *titleLabel;
#end
FOFPhotoCell.m
#implementation FOFPhotoCell
#synthesize delegate = _delegate;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
CGFloat widthFromBounds = self.contentView.bounds.size.width;
self.imageView = [[UIImageView alloc] init];
[self.contentView insertSubview:self.imageView atIndex:0];
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 30, widthFromBounds, 60)];
self.titleLabel = titleLabel;
self.titleLabel.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.3];
self.titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
self.titleLabel.textColor = [UIColor whiteColor];
self.titleLabel.textAlignment = NSTextAlignmentCenter;
[self.contentView insertSubview:self.titleLabel atIndex:1];
}
return self;
}
-(void)fadeOutLabels
{
NSLog(#"fadeOutLabels was called");
[UIView animateWithDuration:1.0
delay:0.0 /* do not add a delay because we will use performSelector. */
options:UIViewAnimationOptionCurveEaseIn
animations:^ {
self.titleLabel.alpha = 0.0;
}
completion:^(BOOL finished) {
[self.titleLabel removeFromSuperview];
}];
}
FOFPhotosViewController.h
#import <UIKit/UIKit.h>
#import "FOFPhotoCell.h"
#interface FOFPhotosViewController : UICollectionViewController <FOFPhotoCellDelegate>
#end
FOFPhotosViewController.m
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
FOFPhotoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"photo" forIndexPath:indexPath];
NSArray *photosArray = [self.dishes valueForKeyPath:#"avatar_url"];
NSArray *nameArray = [self.dishes valueForKeyPath:#"name"];
// NSLog(#"photoURL %#", _responseDictionary);
cell.backgroundColor = [UIColor lightGrayColor];
[cell.imageView setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://xx.yy.zz.qq:4000%#",[photosArray objectAtIndex: indexPath.row]]]];
cell.titleLabel.text = [NSString stringWithFormat:#"%#", [nameArray objectAtIndex:indexPath.row]];
UILongPressGestureRecognizer *tapAndHold = [[UILongPressGestureRecognizer alloc] initWithTarget:cell action:#selector(fadeOutLabels)];
tapAndHold.minimumPressDuration = 0.5;
[self.collectionView addGestureRecognizer:tapAndHold];
[self.collectionView reloadItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
return cell;
}
I would really appreciate if some one could help me with this issue.
Thanks in advance
Chris
I believe your problem comes from the LongPressGestureRecognizer that should be instantiated in your custom cell and not in your view controller.
Basically, if you have a hundred cell how the long press gesture recognizer is supposed to know which cell you have pressed and which label to fade out.
You can try this instead, in your custom cell :
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UILongPressGestureRecognizer *tapAndHold = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(fadeOutLabels)];
tapAndHold.minimumPressDuration = 0.5;
[self addGestureRecognizer:tapAndHold];
....
}
}
And of course remove those lines in the view controller.

tableview:cellForRowAtIndexPath never called

I'm very confusing with this.
I'm trying to create an UITableView grouped programmatically, using the single view template from Xcode. Looking at internet for some examples, there is no secret, the approach to do this is very simple. I'm not sure if my implementation are wrong or right, because the method tableView:cellForRowAtIndexPath: is never called, but the other ones are called and return a integer > 0.
There is code, any suggestions are welcome.
Thanks,
#import "ViewController.h"
#import "SimpleCell.h"
#interface ViewController () <UITableViewDelegate, UITableViewDataSource>
#property (nonatomic, strong) UITableView *table;
#property (nonatomic, strong) NSArray *sections;
#property (nonatomic, strong) NSArray *section1;
#property (nonatomic, strong) NSArray *section2;
#property (nonatomic, strong) NSArray *section3;
#end
#implementation ViewController
static NSString * const CellIdentfier = #"Cell";
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self.view addSubview:self.table];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[table]|"
options:0
metrics:nil
views:#{#"table": self.table}]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.table reloadData];
});
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (UITableView *)table
{
if(!_table)
{
_table = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
_table.delegate = self;
_table.dataSource = self;
_table.translatesAutoresizingMaskIntoConstraints = NO;
_table.rowHeight = 34.0f;
_table.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
_table.backgroundColor = [UIColor grayColor];
_table.showsVerticalScrollIndicator = NO;
[_table registerClass:[SimpleCell class] forCellReuseIdentifier:CellIdentfier];
}
return _table;
}
- (NSArray *)sections
{
if(!_sections)
{
_sections = #[#"Section1", #"Section2", #"Section3"];
}
return _sections;
}
- (NSArray *)section1
{
if(!_section1)
{
_section1 = #[#"Player a", #"Player b", #"Player c"];
}
return _section1;
}
- (NSArray *)section2
{
if(!_section2)
{
_section2 = #[#"Zone a", #"Zone b", #"Zone c"];
}
return _section2;
}
- (NSArray *)section3
{
if(!_section3)
{
_section3 = #[#"Area a", #"Area b", #"Area c"];
}
return _section3;
}
#pragma mark - UI Table View Delegate impl
#pragma mark - UI Table View Datasource impl
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
DLog();
SimpleCell *cell = [self.table dequeueReusableCellWithIdentifier:CellIdentfier];
if(!cell)
cell = [[SimpleCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentfier];
cell.label.text = [NSString stringWithFormat:#"Section: %i Row: %i", indexPath.section, indexPath.row];
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger total = 0;
if(section == 0) total = self.section1.count;
if(section == 1) total = self.section2.count;
if(section == 2) total = self.section3.count;
DLog(#"Rows: %i", total);
return total;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
DLog();
return self.sections.count;
}
#end
Delete this:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[table]|"
options:0
metrics:nil
views:#{#"table": self.table}]];
It should be called then.
EDIT:
Change this as well:
_table = [[UITableView alloc] initWithFrame:self.view.frame style:UITableViewStyleGrouped];
This seems to fix it but I'm not sure why. I'm guessing that it has something to do with the table frame having to be larger than 0.

Resources