What is the best practice to create custom cell like this? since the data will be vary each cell. if the phone number not exist in the data, the price label and time will go up below the address and so on. how to calculate the size of the cell? now i am using this code to automatically calculate the size of the cell but this code only use 1 label the address label no other labels included.
code from the custom cell :
- (void)awakeFromNib {
[super awakeFromNib];
self.titleLabel.translatesAutoresizingMaskIntoConstraints = YES;
self.addressLabel.translatesAutoresizingMaskIntoConstraints = NO;
CGFloat screenWidth = CGRectGetWidth([UIScreen mainScreen].bounds);
int gapWidth = 50;
if (screenWidth == 414) {
gapWidth = 20;
}
// The cell width is half the screen width minus the gap between the cells
// The gap should be slightly larger than the minium space between cells set
for the flow layout to prevent layout and scrolling issues
CGFloat cellWidth = (screenWidth - gapWidth);
[self.addressLabel addConstraint:[NSLayoutConstraint constraintWithItem:self.addressLabel
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:cellWidth]];
}
You could use UITableViewAutomaticDimension
This basically means your tableview will guess the height of your cell based on its content.
Here is the doc:
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSelf-SizingTableViewCells.html
And a great tutorial:
https://www.raywenderlich.com/129059/self-sizing-table-view-cells
So to help you on your case, you will need to nil the text present in your labels, so that it's height will be 0 based on the data you receive from your api. Also as you have icons on the side, you will need to hide them as well.
Be sure to set all the constraints from the top to the bottom of your cell, so that the tableview will be able to understand and guess the height.
Also don't forget to gave an estimatedRowHeight in viewdidload:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80
Related
I'm trying to use autolayout to dynamically determine the height of my table cells using this tutorial http://www.raywenderlich.com/73602/dynamic-table-view-cell-height-auto-layout
The problem is, I'm getting these weird automatic constraints that set the dimensions to the simulated metrics of my custom cell. I have done a lot of autolayout, and I thought the simulated metrics weren't supposed to affect the actual app at runtime. Did something change in the new SDK?
Anyway, here is the method I've been messing with...
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
static JMSocialFeedTableViewCell *sizingCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingCell = [self.tableView dequeueReusableCellWithIdentifier:#"cell"];
});
[self configureCell:sizingCell forIndexPath:indexPath];
//[sizingCell.contentView addConstraint:[NSLayoutConstraint constraintWithItem:sizingCell.contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeWidth multiplier:0 constant:self.tableView.frame.size.width]];
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
//CGSize size = [sizingCell.contentView sizeThatFits:CGSizeMake(self.tableView.frame.size.width, CGFLOAT_MAX)];
CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height + 1.0f; // Add 1.0f for the cell separator height
}
The commented out lines are different things I was trying. My simulated metrics are 300 x 389. The sizeThatFits method returns this size even though the table view is 320 wide. The systemLayoutSizeFittingSize returns 400 x 489 for whatever reason. In these cases, it is easy to see why I'm getting unsatisfiable constraints, as the height should be 409 (+1.0F for seperator maybe. I've been trying with and without that too).
I'm not setting any other constraints in code, but if I try adding the one I have commented in the code above I'm getting this weird constraint...
"<NSLayoutConstraint:0x1701ebf0 'UIView-Encapsulated-Layout-Width' H:[UITableViewCellContentView:0x1705a310(300)]>"
I don't know why the number 300 keeps showing up. I thought simulated metrics were supposed to be gone once the view is laid out.
Sorry if my question isn't 100% clear. I guess I'm confused on a few points. Basically I'm trying to get autolayout to determine the proper height of my cell given that the cell's width should be equal to the width of the table view (which is 320 on the device I'm testing on).
I'm trying to make UICollectionView work like UITableView (I want the extra flexibility of collection view instead of just going with table view, both for some current and possible future feature extensions); having a fixed width (screen width) and dynamic cell height (just like iOS 8's new table view feature). I've been struggling a lot to get it working. First, I've enabled automatic sizing by setting an estimated size on layout on my collection view subclass:
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout*)self.collectionViewLayout;
layout.estimatedItemSize = CGSizeMake(SCREEN_WIDTH, 100);
Then I've set up my constraints of my custom cell in Interface Builder to let it grow according to my text view's content.
I've also set up a width constraint equal to screen width programatically, otherwise my cells were having a default width of 50pt. Inside my custom cell's awakeFromNib:
[self addConstraint: [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:SCREEN_WIDTH]];
It seems to work for width. When I run my app, it breaks height constraints as my own constraints conflict with UIView-Encapsulated-Layout-Height which is set to 100 (this actually means that auto-height is not really working well).
I lower the priority from my vertical constraints, especially the one with textview (which grows depending on its content) from required (1000) to some lower number (900). I'm not getting broken constraints anymore, but that's because we don't need to break constraints anymore as UIView-Encapsulated-Layout-Height takes precedence. Result is exactly the same. Here is what I'm getting:
There is no problem with the GIF above. It really does jump at one point exactly as seen on the GIF.
How can I prevent this behavior and make my collection view cells grow in height dynamically (of course, without creating a dummy offscreen cell to calculate view sizes)?
I don't need to support iOS 7, so iOS 8-and-above-only solutions are welcome.
UPDATE: If I implement preferredLayoutAttributesFittingAttributes: method as seen on the answer to UICollectionView Self Sizing Cells with Auto Layout:
-(UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes{
UICollectionViewLayoutAttributes *attrs = [layoutAttributes copy];
CGRect frame = attrs.frame;
self.frame = frame;
[self setNeedsLayout];
[self layoutIfNeeded];
float desiredHeight = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
frame.size.height = desiredHeight;
attrs.frame = frame;
return attrs;
}
Then it's always returning the NIB's original view size that was in the Interface Builder even though I've set:
self.translatesAutoresizingMaskIntoConstraints = NO;
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
I have no idea why it's returning an incorrect value.
After spending hours and days, I've decided to go back to table view. While collection view offers great flexibility and is definitely the future, it's just not there yet.
Is there any way to change the height of table view according the size of array?I am using table view as a drop down so i need to change the height of table view according the number of elements in array.
First get the row height from the storyboard and create outlet of the tableView height
then use the code as follow
as if row height is 50
(Swift code )
let arrayCount = yourArray.count
let tableHeight = arrayCount * 50
tableHeightConstraint.constant = tableHeight
this will calculate the count of your tableView array and assign the same height to your tableView
Calculate the height of each row,and then
height = rowHeight*array.count;
tableview.frame = CGRectMake(x,y,width,height);
if you use autolayout,add a constrant like
[NSLayoutConstraint constraintWithItem:tableView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:height];
So first what I want. I want to have cell with dynamic height which is changing by lenght of text. Something like in image:
As you can see second cell is over the edge.
How I did it? First I have table with layout to edges:
I created custom SwitchCell with own xib file:
Label has these autolayout settings:
And switch these:
In viewDidLoad I set tableView properties:
tableView.registerNib(UINib(nibName: SwitchCellIdentifier, bundle: NSBundle.mainBundle()), forCellReuseIdentifier: SwitchCellIdentifier)
tableView.tableFooterView = UIView(frame: CGRectZero)
tableView.rowHeight = UITableViewAutomaticDimension;
tableView.estimatedRowHeight = 44.0;
And in cellForRowAtIndexPath I have this:
...
var cell:SwitchCell = tableView.dequeueReusableCellWithIdentifier(SwitchCellIdentifier) as SwitchCell
cell.titleLabel.text = "Receive payments via multipay and lorem ipsum bla bla"
return cell
So what I've done wrong? What should I change or add?
Perhaps set a max-width constraint on the label or a >= constraint from its trailing edge to the superview. Right now, everything horizontal is "Equal" to fixed values. You'd need to limit the label width, use an inequality constraint between it and the Switch, and/or play w/the constraint priority levels to keep the label from expanding. The estimatedRowHeight and automatic dimensions should still let it expand vertically. –
So for your label you can arrange auto shrink to minimum font size then you can avoid to disappear when the text is too long. And the problem is that you just give constraints in your xib not from xib to cell therefore it is appears like that and you should make it in code just examine below code it may give you an example how to do that.
NSDictionary *metrics = #{#"viewWidth":[NSNumber numberWithFloat:size.width],
#"viewHeight":[NSNumber numberWithFloat:size.height]};
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(yourXibView);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[yourXibView]-0-|"
options:0
metrics:metrics
views:viewsDictionary];
[self.view addConstraints:constraints];
and do not forget to add yourXibView.translatesAutoresizingMaskIntoConstraints = NO; before above code. This code just in objective c but don't worry it has same logic in swift.
Try increasing Content Hugging Priority of the label
Challenge time!
Imagine we have 2 content views:
UIView with dynamically height content (expandable UITextView) = RED
UIView as a footer = BLUE
This content is inside a UIScrollView = GEEN
How should I structure and handle the constraints with auto-layout to archive all the following cases?
I am thinking next basic structure to start with:
- UIScrollView (with always bounce vertically)
- UIView - Container
- UIView - DynamicHeightContent
- UIView - Sticky Footer
Keyboard handling should be done by code watching notifications UIKeyboardWillShowNotification and UIKeyboardWillHideNotification. We can chose to set the keyboard's end frame height to Container UIView bottom pin constraint or to the UIScrollView bottom contentInset.
Now, the tricky part is the sticky footer.
How we make sure the sticky footer UIView stays at the bottom if there is more screen available than the whole Container View?
How do we know the available screen space when the keyboard is shown/hidden? we'll surely need it.
Is is it right this structure I purpose?
Thank you.
When the text content of the UITextView is relatively short, the content view's subviews (i.e., the text view and footer) will not be able to dictate the size of their content view through constraints. That's because when the text content is short, the content view's size will need to be determined by the scroll view's size.
Update: The latter paragraph is untrue. You could install a fixed-height constraint either on the content view itself or somewhere in the content view's view hierarchy. The fixed-height constraint's constant could be set in code to reflect the height of the scroll view. The latter paragraph also reflects a fallacy in thinking. In a pure Auto Layout approach, the content view's subviews don't need to dictate the scroll view's contentSize; instead, it's the content view itself that ultimately must dictate the contentSize.
Regardless, I decided to go with Apple's so-called "mixed approach" for using Auto Layout with UIScrollView (see Apple's Technical Note: https://developer.apple.com/library/ios/technotes/tn2154/_index.html)
Some iOS technical writers, like Erica Sadun, prefer using the mixed approach in pretty much all situations ("iOS Auto Layout Demystified", 2nd Ed.).
In the mixed approach, the content view's frame and the scroll view's content size are explicitly set in code.
Here's the GitHub repo I created for this challenge: https://github.com/bilobatum/StickyFooterAutoLayoutChallenge. It's a working solution complete with animation of layout changes. It works on different sized devices. For simplicity, I disabled rotation to landscape.
For those who don't want to download and run the GitHub project, I have included some highlights below (for the complete implementation, you'll have to look at the GitHub project):
The content view is orange, the text view is gray, and the sticky footer is blue. The text is visible behind the status bar while scrolling. I don't actually like that, but it's fine for a demo.
The only view instantiated in storyboard is the scroll view, which is full-screen (i.e., underlaps status bar).
For testing purposes, I attached a double tap gesture recognizer to the blue footer for the purpose of dismissing the keyboard.
- (void)viewDidLoad
{
[super viewDidLoad];
self.scrollView.alwaysBounceVertical = YES;
[self.scrollView addSubview:self.contentView];
[self.contentView addSubview:self.textView];
[self.contentView addSubview:self.stickyFooterView];
[self configureConstraintsForContentViewSubviews];
// Apple's mixed (a.k.a. hybrid) approach to laying out a scroll view with Auto Layout: explicitly set content view's frame and scroll view's contentSize (see Apple's Technical Note TN2154: https://developer.apple.com/library/ios/technotes/tn2154/_index.html)
CGFloat textViewHeight = [self calculateHeightForTextViewWithString:self.textView.text];
CGFloat contentViewHeight = [self calculateHeightForContentViewWithTextViewHeight:textViewHeight];
// scroll view is fullscreen in storyboard; i.e., it's final on-screen geometries will be the same as the view controller's main view; unfortunately, the scroll view's final on-screen geometries are not available in viewDidLoad
CGSize scrollViewSize = self.view.bounds.size;
if (contentViewHeight < scrollViewSize.height) {
self.contentView.frame = CGRectMake(0, 0, scrollViewSize.width, scrollViewSize.height);
} else {
self.contentView.frame = CGRectMake(0, 0, scrollViewSize.width, contentViewHeight);
}
self.scrollView.contentSize = self.contentView.bounds.size;
}
- (void)configureConstraintsForContentViewSubviews
{
assert(_textView && _stickyFooterView); // for debugging
// note: there is no constraint between the subviews along the vertical axis; the amount of vertical space between the subviews is determined by the content view's height
NSString *format = #"H:|-(space)-[textView]-(space)-|";
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:#{#"space": #(SIDE_MARGIN)} views:#{#"textView": _textView}]];
format = #"H:|-(space)-[footer]-(space)-|";
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:#{#"space": #(SIDE_MARGIN)} views:#{#"footer": _stickyFooterView}]];
format = #"V:|-(space)-[textView]";
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:#{#"space": #(TOP_MARGIN)} views:#{#"textView": _textView}]];
format = #"V:[footer(height)]-(space)-|";
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:#{#"space": #(BOTTOM_MARGIN), #"height": #(FOOTER_HEIGHT)} views:#{#"footer": _stickyFooterView}]];
// a UITextView does not have an intrinsic content size; will need to install an explicit height constraint based on the size of the text; when the text is modified, this height constraint's constant will need to be updated
CGFloat textViewHeight = [self calculateHeightForTextViewWithString:self.textView.text];
self.textViewHeightConstraint = [NSLayoutConstraint constraintWithItem:self.textView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0f constant:textViewHeight];
[self.textView addConstraint:self.textViewHeightConstraint];
}
- (void)keyboardUp:(NSNotification *)notification
{
// when the keyboard appears, extraneous vertical space between the subviews is eliminated–if necessary; i.e., vertical space between the subviews is reduced to the minimum if this space is not already at the minimum
NSDictionary *info = [notification userInfo];
CGRect keyboardRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
keyboardRect = [self.view convertRect:keyboardRect fromView:nil];
double duration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
CGFloat contentViewHeight = [self calculateHeightForContentViewWithTextViewHeight:self.textView.bounds.size.height];
CGSize scrollViewSize = self.scrollView.bounds.size;
[UIView animateWithDuration:duration animations:^{
self.contentView.frame = CGRectMake(0, 0, scrollViewSize.width, contentViewHeight);
self.scrollView.contentSize = self.contentView.bounds.size;
UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, keyboardRect.size.height, 0);
self.scrollView.contentInset = insets;
self.scrollView.scrollIndicatorInsets = insets;
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
[self scrollToCaret];
}];
}
Although the Auto Layout component of this demo app took some time, I spent almost as much time on scrolling issues related to a UITextView being nested inside of a UIScrollView.
Instead of using a UIScrollView you would very likely be better off with a UITableView. It also might be better to not using auto-layout. At least, I've found it better to not use it for these sorts of manipulations.
Look into the following:
UITextView textViewDidChange
Change the size of the text view using sizeThatFits (limiting width and using FLT_MAX for height). Change the frame, not the contentSize.
Call UITableView beginUpdates/endUpdates to update the table view
Scroll to the cursor
UIKeyboardWillShowNotification notification
On NSNotification that comes through, you can call userInfo (a Dictionary), and the key UIKeyboardFrameBeginUserInfoKey. Reduce the frame of the table view based on the height of the size of the keyboard.
Scroll to cursor again (since the layouts will have all changed)
UIKeyboardWillHideNotification notification
The same as the show notification, just opposite (increasing the table view height)
To have the footer view stick to the bottom, you could add an intermediate cell to the table view, and have it change size depending on the size of the text and whether the keyboard is visible.
The above will definitely require some extra manipulation on your part - I don't fully understand all of your cases, but it should definitely get you started.
If I understand whole task, my solution is put "red" and "blue" views to one container view, and in the moment when you know size of dynamic content (red) you can calculate size of container and set scrollView content size.
Later, on keyboard events you can adjust white space between content and footer views