Multiline UILabels With Auto layout - ios

I have been trying for the last 2 days to get a UITableViewCell to do the following manual calculation.
CGFloat availableWidthForValueLabel = availableWidth * 0.7f;
CGSize valueLabelFitSize = [_valueLabel sizeThatFits:CGSizeMake(availableWidthForValueLabel, MAXFLOAT)];
_valueLabel.frame = CGRectMake(availableWidth - valueLabelFitSize.width, 0, valueLabelFitSize.width, valueLabelFitSize.height);
CGFloat availableWidthForKeyLabel = availableWidth - CGRectGetWidth(_valueLabel.frame);
CGSize keyLabelFitSize = [_keyLabel sizeThatFits:CGSizeMake(availableWidthForKeyLabel, MAXFLOAT)];
CGFloat keyLabelX = CGRectGetMinX(_valueLabel.frame) - keyLabelFitSize.width;
I have two multi-lined labels (key, value) in a view.
The value is anchored to the right of the view the Key is anchored to the left of the view with right aligned text.
I want to evaluate the values width/height first with a maximum width of 0.7 * view width then want the key to take up the rest of the available space in the view.
This works fine for single lines labels, but as not for multi-lined labels.
I have tried setting the preferredMaxLayoutWidth of the value label in both the updateConstraints and layoutSubview in the cell but what seems to happen is the hight of the cell grow incorrectly so big gaps appear in the cell.
- (void)updateConstraints
{
[super updateConstraints];
// value width
CGFloat width = ceil(self.keyValueSection.bounds.size.width * kValueMaxLenghtOfParentView);
if (self.valueLabel.preferredMaxLayoutWidth != width )
{
self.valueLabel.preferredMaxLayoutWidth = width;
}
// dec width
if (!self.decSection.hidden)
{
width = ceil(self.holderSection.bounds.size.width * kDecMaxLengthOfParentView);
if (self.decLabel.preferredMaxLayoutWidth != width )
{
self.decLabel.preferredMaxLayoutWidth = width;
}
}
// key width
width = floor(self.keyValueSection.bounds.size.width - kKeyValueGap - self.valueLabel.bounds.size.width);
if(self.keyLabel.preferredMaxLayoutWidth != width)
{
self.keyLabel.preferredMaxLayoutWidth = width;
}
}
I have tried everything I can find on the web and it all nearly works. I have tried setting the hugging and compression ratio but.

While this might not be the most elegant solution, i have fond a way to do what i want.
I use Autolayout to position the labels using constraints for vertical spacing >=0 top and bottom and centre Y alignment. I then specific the constraints for leading and trailing between the labels.
I then give each label a height and width constraint, and use the sizeTofit method to workout what the constants should be and update them when I set the labels text/atttributed text properties.
self.keyLabel.text = model.lineKey;
if ([model.lineValue isKindOfClass:[NSString class]]) {
self.valueLabel.text = model.lineValue;
}
else if ([model.lineValue isKindOfClass:[NSAttributedString class]])
{
self.valueLabel.attributedText = model.lineValue;
}
else
{
self.valueLabel.text = nil;
self.valueLabel.attributedText = nil;
}
self.keyValueGap.constant = kKeyValueGap;
CGFloat availableWidth = CGRectGetWidth(self.keyValueSection.bounds);
CGFloat availableWidthForValueLabel = availableWidth * 0.5f;
CGSize valueLabelFitSize = [self.valueLabel sizeThatFits:CGSizeMake(availableWidthForValueLabel, MAXFLOAT)];
self.valueHeight.constant = valueLabelFitSize.height;
self.valueWidth.constant = valueLabelFitSize.width;
CGFloat availableWidthForkeyLabel = availableWidth - valueLabelFitSize.width - kKeyValueGap;
CGSize keyLabelFitSize = [self.keyLabel sizeThatFits:CGSizeMake(availableWidthForkeyLabel, MAXFLOAT)];
self.keyHeight.constant = keyLabelFitSize.height;
self.keyWidth.constant = availableWidthForkeyLabel;
If anyone else has any better ideas :) - i would love to hear them as i do not like manually calculations while using autolayout

Related

How to avoid losing image quality using UIImageView's contentmode UIViewContentModeScaleAspectFit keeping height constant?

I am using a collectionview inside a tableview for showing images which are being downloaded asynchronously. What I need to do is to show those images without squeezing. I am keeping height of the collectionview constant, and calculating the images' sizes after they are downloaded. I am keeping content mode of the UIIamgeView UIViewContentModeScaleAspectFit. This is what I have done to achieve my intended result.
- (void)configureMediaCell:(MediaContainerViewCell *)weakCell withMediaItem:(MSZWallPostMedia *)mediaItem withDownloadImage:(UIImage *)image{
if (mediaItem.isFrameSet == NO) {
weakCell.isFrameSet = YES;
weakCell.cellImageView.image = image;
weakCell.cellImageView.contentMode = UIViewContentModeScaleAspectFit;
CGRect frame = weakCell.cellImageView.frame;
//Image Scaling
CGSize scaleSize = [self imageScale:weakCell.cellImageView];
scaleSize.width = scaleSize.width * image.size.width;
scaleSize.height = scaleSize.height * image.size.height;
frame.size.width = scaleSize.width;
frame.size.height = scaleSize.height;
weakCell.cellImageView.frame = frame;
mediaItem.frame = frame;
mediaItem.isFrameSet = YES;
[weakCell.activityIndicatorView stopAnimating];
[self.collectionView reloadItemsAtIndexPaths:#[weakCell.indexPath]];
} else {
[weakCell.activityIndicatorView stopAnimating];
weakCell.cellImageView.image = image;
weakCell.cellImageView.frame = mediaItem.frame;
weakCell.cellImageView.contentMode = UIViewContentModeScaleAspectFit;
}
}
For Image Scaling:
- (CGSize)imageScale : (UIImageView *)imageView {
CGFloat sx = imageView.frame.size.width / imageView.image.size.width;
CGFloat sy = imageView.frame.size.height / imageView.image.size.height;
CGFloat s = 1.0;
switch (imageView.contentMode) {
case UIViewContentModeScaleAspectFit:
s = fminf(sx, sy);
return CGSizeMake(s, s);
break;
case UIViewContentModeScaleAspectFill:
s = fmaxf(sx, sy);
return CGSizeMake(s, s);
break;
case UIViewContentModeScaleToFill:
return CGSizeMake(sx, sy);
default:
return CGSizeMake(s, s);
}
}
The problem in this approach is that I get the correct widths and heights but some images are shown smaller which is obvious because I am using Aspectfit property of imageview which is shown in the attached screen.
Any help regarding this would be appreciated. I need to keep images as they are keeping height of collectionviewitem fixed and variable width.
If I understand you correctly. You just need to find image width/height ratio and multiply desired height.
Example:
Image
width = 30, height = 10
Cell
width = ?, height = 7
Ratio = 30 / 10
cell width = 7 * (30/10) = 21
Cell size will become (21,7)
Hope this is what you meant. But you will face the problem if the image is smaller than cell. Then it might become distorted.

Autolayout with dynamic height

I am trying to create a subclass of UIView that will show a list of fixed sized UIImages similar to how a UILabel displays letters.If all the images won't fit on one line, the images are arranged on multiple lines.
How can I achieve this using autolayouts so that if I put this view in a UIStackView the images will be listed correctly?
Here is a sample if I did it using fixed position :
- (void) layoutSubviews{
[super layoutSubviews];
CGRect bounds = self.bounds;
CGRect imageRect = CGRectMake(0, 0, 30, 30);
for (UIImageView* imageView in self.imageViews){
imageView.frame = imageRect;
imageRect.origin.x = CGRectGetMaxX(imageRect);
if (CGRectGetMaxX(imageRect) > CGRectGetMaxX(bounds)){
imageRect.origin.x = 0.0;
imageRect.origin.y = CGRectGetMaxY(imageRect);
}
}
}
Update:
Here is a sample project to show the issue.
https://github.com/datinc/DATDemoImageListView
Here the link for ImageListView
https://github.com/datinc/DATDemoImageListView/blob/master/DATDemoImageListView/DATDemoImageListView.m
You should overwrite intrinsicContentSize returning the preferred content size of your view.
The problem is that in intrinsicContentSize the view has not it's final bounds. You can use an internal height constraint, and overwrite updateConstraints:
- (void)updateConstraints {
CGFloat theWidth = CGRectGetWidth(self.bounds);
NSUInteger theCount = [self.subviews count];
CGFloat theRows = ceilf(theCount / floorf(theWidth / 30.0));
self.heightConstraint.constant = 30.0 * theRows;
[super updateConstraints];
}
In viewDidAppear: and on layout changes (e. g. rotation) you have to call setNeedsUpdateConstraints to get a proper initial layout of the image views.

sizeToFit in a custom View

I am trying to write a custom view, which is a subclass of a UILabel
I dragged a UILabel to the storyboard, set it to the class of the given view.
I then tried to override draw in rect, only to find out that the label does not adjust its height to what i need.
After some further reading, i overrode the "sizeThatFits:" method with this
-(CGSize)sizeThatFits:(CGSize)size{
if(!self.bg){
return [super sizeThatFits:size];
}
CGFloat width = self.bg.size.width;
CGFloat height = self.bg.size.height;
CGFloat length = [self.text length];
CGFloat padding = self.paddingBetweenLetter * (length -1);
size.width = width*(length+padding);
size.height = size.height;
return size;
}
-(void)setBg:(UIImage *)bg{
_bg = bg;
[self sizeToFit];
[self setNeedsDisplay];
}
however, the height remains the same.
What am i missing ?

Dyamic frame calculation for UIImages in UITableViewCell

I am putting few icons on UITableViewCell. The number of icons can vary from 1 to 6. Icons should not have fixed X position and instead they should be dynamically placed considering the cell space. So if there are 3 icons they should look placed centric to the cell. Also, the icon spacing should also vary. For instance when it is 6 icons all of them will be placed with less margins in between and when there are 3 then margin will be more and so will be the X position.
Please suggest some quick way to calculate this frame. I am running the app both on iOS 6 and iOS 7.
This is what I have tried by far but this does not seems to work well with icon count variation. Also, in between space is not dynamic with this.
int maxIconTypes = 6;
CGFloat innerPadding = ([self isIOS7]) ? 15.0f : 9.0f;
CGRect adjustedBoundsIOS6 = [self bounds];
CGFloat adjustedWidth = ([self isIOS7]) ? self.bounds.size.width : adjustedBoundsIOS6.size.width;
CGFloat xOrigin = innerPadding;
CGFloat iconViewSize = 25.0f;
CGFloat ySpacer = (self.bounds.size.height - iconSize) / 2;
CGFloat xSpacer = ((adjustedWidth - (innerPadding * 2)) - (maxRequestTypes * iconViewSize)) / (maxIconTypes - 1);
for (NSString *icon in iconList) {
UIImageView *anIconView = [[UIImageView alloc] initWithImage:[UIImage icon]];
if (anIconView.image) {
anIconView.frame = CGRectMake(xOrigin + originPadding, ySpacer, anIconView.image.size.width, anIconView.image.size.height);
[self.contentView addSubview:anIconView];
xOrigin += anIconView.frame.size.width + xSpacer;
}
}
However many items you have, the number of spaces is the same if you want equal spacing with half spacing at the start and end:
half width full width half
half width full width full width half
So, you just need to know the full width available and the combined width of the items. A simple multiply (to get the combined width of the items) and subtract (from the full width available) gives you the remaining width for the 'spacers'. Divide by the number of items to get the xSpacer, set the initial xOrigin to xSpacer * 0.5
Here is an example of what Wain is explaining:
//Im doing this in a UIView rather than in a UITableViewCell but idea is the same
int cellWidth = CGRectGetWidth(self.view.bounds);
//num of icons
int iconCount = 6;
//size of icon
int iconSize = 25;
//The max padding we can have
float maxPadding = (cellWidth - (iconSize * iconCount)) / iconCount;
//Our offset
float xOrigin = maxPadding / 2;
//Loooop
for (int i = 0; i < iconCount; i++) {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(xOrigin, self.view.center.y, iconSize, iconSize)];
xOrigin += (iconSize + maxPadding);
label.backgroundColor = [UIColor blueColor];
[self.view addSubview:label];
}

UILabel gets clipped when transform applied

I have a horizontal UICollectionView with a UICollectionViewCell with a horizontally and vertically centered UILabel. As the label contains text of different lenght I calculate the width of the label like so. I give it a little margin (10) here to be on the save side.
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSString *text = [_collectionViewProvider collectionViewController:self titleForRow:indexPath.row];
CGSize size = CGSizeMake(MIN(_maxWidth, [text sizeWithFont:self.font].width + 16 + 10), CGRectGetHeight(collectionView.bounds));
return size;
}
As I like to have the users having the impression of a horizontal scroll wheel I apply a transform and adjust the zIndex of the cells depending on the distance from the center. It works almost as expected but as the frame width gets shrinked when the transform is apllied on the far left/right I have the effect that the text gets clipped which looks rather strange.
Here is an image that shows the effect and when the cell is in the center.
Has anybody an idea how to solve this?
Here is my code to calculate the layout.
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *array = [super layoutAttributesForElementsInRect:rect];
CGRect visibleRect = CGRectZero;
visibleRect.origin = self.collectionView.contentOffset;
visibleRect.size = self.collectionView.bounds.size;
CGFloat activeDistance = floorf(CGRectGetWidth(visibleRect) / 2);
CGFloat midX = ceilf(CGRectGetMidX(visibleRect));
CGFloat baseAngle = M_PI/180 * 40;
for (UICollectionViewLayoutAttributes *attributes in array)
{
if (CGRectContainsRect(visibleRect, attributes.frame)) {
CGFloat distance = midX - attributes.center.x;
attributes.alpha = (activeDistance - ABS(distance))/activeDistance;
CGFloat value = -distance/activeDistance;
value *= baseAngle;
attributes.transform3D = CATransform3DMakeRotation(value, 0, 1, 0);
attributes.zIndex = ABS(distance)/activeDistance * 200;
}
else {
attributes.alpha = 0;
}
}
return array;
}

Resources