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.
Related
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.
I currently have a UITableView within my MatchCenterViewController, and I designed it to load up 10 rows for every section, but only show the first 4 by making the heightForRowAtIndexPath return a value of 0 for the rest. What I want to do is have a button on the bottom of every section that when pressed, will reload the data and show 10 instead of 4 for just that specific section.
I've started working on the framework for what happens when the button is pressed, I'm just having trouble with the syntax for rendering the button on the bottom and showing 10 rows for only that respective section. Here's how I have my UITableView laid out so far:
MatchCenterViewController.h:
#import <UIKit/UIKit.h>
#import <Parse/Parse.h>
#import "AsyncImageView.h"
#import "SearchViewController.h"
#import "WebViewController.h"
#import "SLExpandableTableView.h"
#interface MatchCenterViewController : UIViewController <UITableViewDataSource>
#property (strong, nonatomic) NSString *itemSearch;
#property (nonatomic, strong) NSArray *imageURLs;
#property (strong, nonatomic) NSString *matchingCategoryCondition;
#property (strong, nonatomic) NSString *matchingCategoryLocation;
#property (strong, nonatomic) NSNumber *matchingCategoryMaxPrice;
#property (strong, nonatomic) NSNumber *matchingCategoryMinPrice;
#property (strong, nonatomic) NSArray *matchCenterArray;
#property (strong, nonatomic) NSString *searchTerm;
#property (strong, nonatomic) NSString *itemURL;
#end
MatchCenterViewController.m:
#import "MatchCenterViewController.h"
#import <UIKit/UIKit.h>
#interface MatchCenterViewController () <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, strong) UITableView *matchCenter;
#property (nonatomic, assign) BOOL matchCenterDone;
#property (nonatomic, assign) BOOL hasPressedShowMoreButton;
#end
#implementation MatchCenterViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
_matchCenterDone = NO;
//self.matchCenter = [[SLExpandableTableView alloc] initWithFrame:self.view.bounds style:UITableViewCellStyleSubtitle];
self.matchCenter = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewCellStyleSubtitle];
self.matchCenter.frame = CGRectMake(0,50,320,self.view.frame.size.height-100);
_matchCenter.dataSource = self;
_matchCenter.delegate = self;
[self.view addSubview:self.matchCenter];
_matchCenterArray = [[NSArray alloc] init];
}
- (void)viewDidAppear:(BOOL)animated
{
self.matchCenterArray = [[NSArray alloc] init];
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);
[self.view addSubview: activityIndicator];
[activityIndicator startAnimating];
_matchCenterDone = NO;
// Disable ability to scroll until table is MatchCenter table is done loading
self.matchCenter.scrollEnabled = NO;
[PFCloud callFunctionInBackground:#"MatchCenter2"
withParameters:#{}
block:^(NSArray *result, NSError *error) {
if (!error) {
_matchCenterArray = result;
[activityIndicator stopAnimating];
[_matchCenter reloadData];
_matchCenterDone = YES;
self.matchCenter.scrollEnabled = YES;
NSLog(#"Result: '%#'", result);
}
}];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return _matchCenterArray.count;
}
//the part where i setup sections and the deleting of said sections
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 21.0f;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return 0.01f;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 21)];
headerView.backgroundColor = [UIColor lightGrayColor];
_searchTerm = [[[[_matchCenterArray objectAtIndex:section] objectForKey:#"Top 3"] objectAtIndex:0]objectForKey:#"Search Term"];
UILabel *headerLabel = [[UILabel alloc] initWithFrame:CGRectMake(8, 0, 250, 21)];
headerLabel.text = [NSString stringWithFormat:#"%#", _searchTerm];
headerLabel.font = [UIFont boldSystemFontOfSize:[UIFont systemFontSize]];
headerLabel.textColor = [UIColor whiteColor];
headerLabel.backgroundColor = [UIColor lightGrayColor];
[headerView addSubview:headerLabel];
UIButton *deleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
deleteButton.tag = section;
deleteButton.frame = CGRectMake(300, 2, 17, 17);
[deleteButton setImage:[UIImage imageNamed:#"xbutton.png"] forState:UIControlStateNormal];
[deleteButton addTarget:self action:#selector(deleteButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[headerView addSubview:deleteButton];
return headerView;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSDictionary *currentSectionDictionary = _matchCenterArray[section];
NSArray *top3ArrayForSection = currentSectionDictionary[#"Top 3"];
return top3ArrayForSection.count-1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Initialize cell
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
// if no cell could be dequeued create a new one
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
// No cell separators = clean design
tableView.separatorColor = [UIColor clearColor];
// title of the item
cell.textLabel.text = _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Title"];
cell.textLabel.font = [UIFont boldSystemFontOfSize:14];
// price of the item
cell.detailTextLabel.text = [NSString stringWithFormat:#"$%#", _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Price"]];
cell.detailTextLabel.textColor = [UIColor colorWithRed:0/255.0f green:127/255.0f blue:31/255.0f alpha:1.0f];
// image of the item
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:_matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Image URL"]]];
[[cell imageView] setImage:[UIImage imageWithData:imageData]];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row > 3 || self.hasPressedShowMoreButton){
return 0;
}
else{
return 65;
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (_matchCenterDone == YES) {
self.itemURL = _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row][#"Item URL"];
[self performSegueWithIdentifier:#"WebViewSegue" sender:self];
}
}
-(IBAction)pressedShowMoreButton{
self.hasPressedShowMoreButton = YES;
[self.matchCenter reloadData];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
WebViewController *controller = (WebViewController *) segue.destinationViewController;
controller.itemURL = self.itemURL;
}
#end
for this functionality you can either use special UITableviewCell or a footerView of UITableView
You can use property tableFooterView.
Below is how I use in showing load more option
UIView *v = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 65)];
v.backgroundColor = [UIColor clearColor];
int mySiz = 0;
// keep counter how many times load more is pressed.. initial is 0 (this is like index)
mySiz = [startNumberLabel.text intValue]+1;
// i have 15 bcz my index size is 15.
if ([feeds count]>=(15*mySiz)) {
NSLog(#"showing button...");
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setFrame:CGRectMake(10, 10, 296, 45)];
[button setBackgroundImage:[UIImage imageNamed:localize(#"loadmore")] forState:UIControlStateNormal];
[button addTarget:self action:#selector(loadMoreData:) forControlEvents:UIControlEventTouchUpInside];
[v addSubview:button];
mainTableView.tableFooterView = v;
} else {
mainTableView.tableFooterView = nil;
}
[mainTableView reloadData];
Now adjust code as per your necessity...
Ended up doing it like this:
// Create "more" button
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
UIView *view = [[UIView alloc] initWithFrame:CGRectZero];
view.backgroundColor = [UIColor whiteColor];
self.moreButton = [UIButton buttonWithType:UIButtonTypeCustom];
self.moreButton.frame = CGRectMake(0, 0, 320, 44);
[self.moreButton setImage:[UIImage imageNamed:#"downarrow.png"] forState:UIControlStateNormal];
[self.moreButton addTarget:self action:#selector(moreButtonSelected:) forControlEvents:UIControlEventTouchUpInside];
[view addSubview:self.moreButton];
return view;
}
// Load rest of items
- (void)moreButtonSelected:(id)sender {
if (_hasPressedShowMoreButton == NO){
self.hasPressedShowMoreButton = YES;
}
else if (_hasPressedShowMoreButton == YES){
self.hasPressedShowMoreButton = NO;
}
[self.matchCenter reloadData];
}
The problem in pictures:
Basically, my table view adds an empty cell between each other, and I don't know why. I already tried putting an NSLog statement in cellForRowAtIndexPath:, willDisplayCell:forRowAtIndexPath:, and didSelectRowAtIndexPath:, and the data source in each cell's indexPath is correct. It's just that the displayed cells are skipping in between, and I don't understand why.
My view controller and table view cell subclasses are also very simple. Here's the code:
PrivateMessagesViewController.m
static NSString * const kCellIdentifier = #"kCellIdentifier";
#interface PrivateMessagesViewController () <UITableViewDataSource, UITableViewDelegate>
#property (strong, nonatomic) NSMutableArray *messages;
#property (strong, nonatomic) SCPFInboxQuery *query;
#property (nonatomic) BOOL hasAlreadyFetchedBefore;
#property (strong, nonatomic) UITableView *tableView;
// This is a custom view that either shows a loading animation,
// a "No Results Found" label, or an error message with a Retry button
// at the center of the view of a view controller.
#property (strong, nonatomic) SCPCenterView *centerView;
#end
#implementation PrivateMessagesViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"Private Messages";
self.messages = [NSMutableArray array];
self.query = [[SCPFInboxQuery alloc] init];
self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, [UIScreen mainScreen].bounds.size.height - 113) style:UITableViewStylePlain];
self.tableView.dataSource = self;
self.tableView.delegate = self;
[self.tableView registerClass:[SCPPrivateMessageListCell class] forCellReuseIdentifier:kCellIdentifier];
self.tableView.alpha = 0; // The table view starts out invisible.
[self.view addSubview:self.tableView];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (!self.hasAlreadyFetchedBefore) {
[self.view addSubview:self.centerView];
[self.centerView showLoadingView];
__weak PrivateMessagesViewController *weakSelf = self;
[self.query runInBackgroundWithCompletion:^(NSArray *messages, NSError *error) {
PrivateMessagesViewController *innerSelf = weakSelf;
// If there is an error, handle it.
if (error) {
// ...
return;
}
// If there weren't any messages before and none were found,
// the user has no messages in the inbox.
if (!innerSelf.hasAlreadyFetchedBefore && messages.count == 0) {
[innerSelf.centerView showNoResultsLabel];
}
else {
if (!innerSelf.hasAlreadyFetchedBefore) {
[innerSelf.centerView fadeOutAndRemoveFromSuperview];
innerSelf.tableView.alpha = 1;
} else {
}
[innerSelf.messages addObjectsFromArray:messages];
[innerSelf.tableView reloadData];
}
innerSelf.hasAlreadyFetchedBefore = YES;
}];
}
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.messages.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
SCPPrivateMessageListCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
SCPFMessage *message = self.messages[indexPath.row];
cell.message = message;
return cell;
}
#pragma mark - Table view delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [SCPPrivateMessageListCell heightForMessage:self.messages[indexPath.row]];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
SCPPrivateMessageListCell *theCell = (SCPPrivateMessageListCell *)cell;
NSLog(#"Message at %d: %#", indexPath.row, theCell.message.rawData);
}
#pragma mark - Getters
- (SCPCenterView *)centerView
{
if (!_centerView) {
_centerView = [[SCPCenterView alloc] initWithParent:self.view];
_centerView.noResultsText = #"You don't have any private messages yet.";
}
return _centerView;
}
#end
SCPPrivateMessageListCell.m
static const CGFloat kInnerMargin = 10;
static const CGFloat kSpaceBetweenImageAndRightLabel = 10;
static const CGFloat kImageViewSize = 60;
static const CGFloat kRightLabelX = kInnerMargin + kImageViewSize + kSpaceBetweenImageAndRightLabel;
static const CGFloat kMaxRightLabelWidth = 320 - (kRightLabelX + kInnerMargin);
#interface SCPPrivateMessageListCell ()
#property (strong, nonatomic) UIImageView *thumbnailImageView;
#property (strong, nonatomic) UILabel *dateLabel;
#property (strong, nonatomic) UILabel *senderLabel;
#property (strong, nonatomic) UILabel *subjectLabel;
#property (strong, nonatomic) UILabel *summaryLabel;
#end
#implementation SCPPrivateMessageListCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
if (self) {
_thumbnailImageView = [[UIImageView alloc] init];
_thumbnailImageView.contentMode = UIViewContentModeScaleAspectFill;
_thumbnailImageView.layer.cornerRadius = kImageViewSize / 2;
_thumbnailImageView.clipsToBounds = YES;
_dateLabel = [[UILabel alloc] init];
_dateLabel.font = [UIFont systemFontOfSize:10];
_dateLabel.textAlignment = NSTextAlignmentCenter;
_dateLabel.numberOfLines = 1;
_dateLabel.lineBreakMode = NSLineBreakByTruncatingTail;
_senderLabel = [SCPPrivateMessageListCell senderLabel];
_subjectLabel = [SCPPrivateMessageListCell subjectLabel];
_summaryLabel = [SCPPrivateMessageListCell summaryLabel];
[self.contentView addSubview:_thumbnailImageView];
[self.contentView addSubview:_dateLabel];
[self.contentView addSubview:_senderLabel];
[self.contentView addSubview:_subjectLabel];
[self.contentView addSubview:_summaryLabel];
}
return self;
}
- (void)setMessage:(SCPFMessage *)message
{
_message = message;
[self.thumbnailImageView setImageWithURL:message.thumbnailURL];
self.dateLabel.text = message.dateSent;
self.senderLabel.text = message.nameOfSender;
self.subjectLabel.text = message.subject;
self.summaryLabel.text = message.summary;
[self setNeedsLayout];
}
- (void)layoutSubviews
{
self.thumbnailImageView.frame = CGRectMake(kInnerMargin, kInnerMargin, kImageViewSize, kImageViewSize);
[self.dateLabel sizeToFitWidth:kImageViewSize];
self.dateLabel.frame = CGRectMake(kInnerMargin, kInnerMargin + kImageViewSize, kImageViewSize, self.dateLabel.frame.size.height);
[self.senderLabel sizeToFitWidth:kMaxRightLabelWidth];
self.senderLabel.frame = CGRectMake(kRightLabelX, kInnerMargin, kMaxRightLabelWidth, self.senderLabel.frame.size.height);
CGFloat subjectLabelY = self.senderLabel.frame.origin.y + self.senderLabel.frame.size.height;
[self.subjectLabel sizeToFitWidth:kMaxRightLabelWidth];
self.subjectLabel.frame = CGRectMake(kRightLabelX, subjectLabelY, kMaxRightLabelWidth, self.subjectLabel.frame.size.height);
CGFloat summaryLabelY = self.subjectLabel.frame.origin.y + self.subjectLabel.frame.size.height;
[self.summaryLabel sizeToFitWidth:kMaxRightLabelWidth];
self.summaryLabel.frame = CGRectMake(kRightLabelX, summaryLabelY, kMaxRightLabelWidth, self.summaryLabel.frame.size.height);
CGFloat cellHeight = [SCPPrivateMessageListCell heightForMessage:self.message];
self.contentView.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, cellHeight);
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, cellHeight);
}
#pragma mark - Class methods
+ (UILabel *)senderLabel
{
UILabel *label = [[UILabel alloc] init];
label.font = [UIFont boldSystemFontOfSize:17];
label.textColor = [UIColor colorFromHex:0x0076be];
label.numberOfLines = 1;
label.lineBreakMode = NSLineBreakByTruncatingTail;
return label;
}
+ (UILabel *)subjectLabel
{
UILabel *label = [[UILabel alloc] init];
label.font = [UIFont systemFontOfSize:14];
label.numberOfLines = 1;
label.lineBreakMode = NSLineBreakByTruncatingTail;
return label;
}
+ (UILabel *)summaryLabel
{
UILabel *label = [[UILabel alloc] init];
label.font = [UIFont systemFontOfSize:12];
label.textColor = [UIColor grayColor];
label.numberOfLines = 3;
label.lineBreakMode = NSLineBreakByTruncatingTail;
return label;
}
+ (CGFloat)heightForMessage:(SCPFMessage *)message
{
CGFloat height = kInnerMargin;
UILabel *senderLabel = [SCPPrivateMessageListCell senderLabel];
senderLabel.text = message.nameOfSender;
[senderLabel sizeToFitWidth:kMaxRightLabelWidth];
height += senderLabel.frame.size.height;
UILabel *subjectLabel = [SCPPrivateMessageListCell subjectLabel];
subjectLabel.text = message.subject;
[subjectLabel sizeToFitWidth:kMaxRightLabelWidth];
height += subjectLabel.frame.size.height;
UILabel *summaryLabel = [SCPPrivateMessageListCell summaryLabel];
summaryLabel.text = message.summary;
[summaryLabel sizeToFitWidth:kMaxRightLabelWidth];
height += summaryLabel.frame.size.height;
height += kInnerMargin;
return height;
}
#end
There. I'm not doing anything non-standard. The subviews of the cells also disappear when I scroll, but when I scroll them back in, the content becomes displayed, still with a skipping empty cell. Anyone have any idea why this is happening?
The problem was in my table view cell's layoutSubviews.
self.contentView.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, cellHeight);
That should have been:
self.contentView.frame = CGRectMake(self.contentView.frame.origin.x, self.contentView.frame.origin.y, self.contentView.frame.size.width, cellHeight);
I found out by setting a different color for self.backgroundColor and self.contentView.backgroundColor from inside the init method.
CustomCell.h
#interface CustomCell : UITableViewCell
//Properties created in Code, not via IB.
#property (nonatomic, strong) UILabel *labelUsername;
#property (nonatomic, strong) UIView *circle;
//Properties Created through IB by control+drag
#property (nonatomic, strong) UILabel *labelFirstName;
#property (nonatomic, strong) UILabel *labelLastName;
#end
CustomCell.m
#implementation CustomCell
-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
//Creating properties in code
self.circle = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 40.0f, 40.0f)];
[self.circle setBackgroundColor:[UIColor brownColor];
self.labelUsername = [[UILabel alloc] initWithFrame:CGRectMake(15, 20, 200.0f, 50.0f)];
self.labelUsername.textColor = [UIColor blackColor];
[self.contentView addSubview:self.labelUsername];
[self.contentView addSubview:self.circle];
}
return self;
}
tableView.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *customCellIdentifier = #"CustomCell";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:customCellIdentifier];
if (cell == nil) {
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:inviteCellIdentifier];
}
cell.labelFirstName.text = #"FirstName";
cell.labelLastName.text = #"LastName";
return cell;
}
The code above continued to show labelUsername and circle. However, the properties created using the IB (labelFirstName and labelLastName) did not appear.
So in tableView.m in viewDidLoad I registered the Nib with the following code:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:#"CustomCell"
bundle:[NSBundle mainBundle]]
forCellReuseIdentifier:#"CustomCell"];
}
Now labelFirstName and labelLastName appear, but the properties created with code (labelUsername and circle) DO NOT appear.
How can I get all 4 properties to show?
Try this in CustomCell.m,
- (void)awakeFromNib
{
[super awakeFromNib];
self.circle = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 40.0f, 40.0f)];
[self.circle setBackgroundColor:[UIColor brownColor];
self.labelUsername = [[UILabel alloc] initWithFrame:CGRectMake(15, 20, 200.0f, 50.0f)];
self.labelUsername.textColor = [UIColor blackColor];
[self.contentView addSubview:self.labelUsername];
[self.contentView addSubview:self.circle];
}
Try this after self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
NSArray *views = [[NSBundle mainBundle]loadNibNamed:#"yourNibName" owner:self options:NULL];
[self addSubview:[views lastObject]];
I have a custom cell loaded from my table, with an image and a label the label shows ok but the image doesn't show
CustomCell.m
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier: (NSString*)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
[self initLabels];
CGRect vintageScreenRect = CGRectMake(25, 0.0f, 100, 100);
self.iconImage = [[UIImage alloc]init];
UIImageView *vintageScreen = [[UIImageView alloc] initWithFrame:vintageScreenRect];
//[vintageScreen setImage:[UIImage imageNamed:#"vidButtonImg.png"]];
// [vintageScreen setImage:self.iconImage];
[vintageScreen setImage:[UIImage imageNamed:self.tingo]];
vintageScreen.opaque = YES; // explicitly opaque for performance
[self.contentView addSubview:vintageScreen];
[vintageScreen release];
NSLog(#"tingo ::%#", self.tingo);
}
return self;
}
UsingTable.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.tingo = [NSString stringWithFormat:#"picsButtonImg.png"];
cell.iconName.text = #"d";
}
return cell;
}
So the label shows ok , but the image doesn't show, I have tried with sending an uiImage and an NSString,
what is missing? thanks!
self.tingo probably is nil in initWithStyle method. You are asigning self.tingo property after initWithStyle call.
That cause [UIImage imageNamed:self.tingo] is also nil, and the image simply doesn't exist.
You can fix it for example by custom setter of tingo property, or by making vintageScreen as property and set image "from outside".
Your variable tingo is null when you first instantiate the cell. Doing cell.tingo = [NSString stringWithFormat:#"picsButtonImg.png"]; sets it to a value, but then your cell never knows to reset the image.
Move vintageScreen into a variable on your cell class, then override your tingo setter to reload the image if you want to keep the code you have in cellForRowAtIndexPath:
CustomCell.h
#interface CustomCell : UITableViewCell {
UIImageView *vintageImage;
NSString *tingo;
}
#property (nonatomic, retain) UIImageView *vintageImage;
#property (nonatomic, retain) NSString *tingo;
#end
CustomCell.m
#implementation CustomCell
#synthesize vintageImage, tingo;
-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString*)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self initLabels];
[self addSubview:[self vintageScreen]];
}
return self;
}
-(UIImageView *)vintageScreen {
if(vintageScreen == nil) {
CGRect vintageScreenRect = CGRectMake(25, 0.0f, 100, 100);
[self setVintageScreen:[[[UIImageView alloc] initWithFrame:vintageScreenRect] autorelease]];
[vintageScreen setImage:[UIImage imageNamed:self.tingo]];
vintageScreen.opaque = YES;
}
return vintageScreen;
}
-(void)setTingo:(NSString *)newTingo {
[tingo release];
tingo = [newTingo retain];
[[self vintageScreen] setImage:[UIImage imageNamed:tingo]];
}