I want to display image in cell, and because I need to download it first so I would like to display (for question simplicity) black view with the same width and height like image should be displayed.
Because I want to stretch image to same width as cell width, I only need aspect ratio for setting height and this is provided in my code when cellForRowAt is called.
I decided to achieve "black view" before downloading image I only need one UIImageView with black background, and resizing it when cellForRowAt is called. But here is the problem, because using code from similiar questions is not working for me.
I tried something like this in cellForRowAt method:
var frame cell.imageView.frame
frame.size.height = CGFloat(aspectRatio) * frame.size.width
cell.imageView.frame = frame
EDIT
As a result I want something like facebook, where we have photo with the same width as cell and height accordingly to aspect ratio. For simplicity cell can only have UIImageView.
Since you're using auto layout, in the storyboard you should set two things:
First, set the width of the UIImageView to be the full width of the cell
Second, set the aspect ratio of the UIImageView to 1:1 - this says the height is always the same as the width.
If you're using the UITableView's Automatic Dimensions for cell heights, this should be all you need; no code anywhere, everything else is as normal.
If you're not using the automatic cell height feature, then either set the cell height once with self.tableView.rowHeight = self.tableView.bounds.size.width if they're all the same, or, if the cells can be different heights:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if (<indexPath IS IMAGE ROW>)
return tableView.bounds.size.width
return <default height>
}
Where I've written <indexPath IS IMAGE ROW>, you should replace that with whatever condition you need to detect if this particular cell is an image type.
Also, as Rikh pointed out, where I've written <default height>, you should replace that with the height of the cells that are not the image type.
Of course, you may have a much more complex design for your table; this just covers the case you were asking about.
here my solution
imageView.contentMode = UIViewContentModeCenter;
if (imageView.bounds.size.width > ((UIImage*)imagesArray[i]).size.width && imageView.bounds.size.height > ((UIImage*)imagesArray[i]).size.height) {
imageView.contentMode = UIViewContentMode.ScaleAspectFit;
}
Related
I have a UIImage of dimensions 300x600 (height x width). UIImage's relative hierarchy is like so :
Using AutoLayout, I have set the following constraints for the UIImageView, UIScrollView and UIVIew:
I have set relative height dimensions of the TableView as follows:
// Setting Last Row to height = 70 pixels and first row to fill balance of screen:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == 1 { return 70 }
else { return tableView.frame.size.height - 70 }
}
What I would like is for the height of the UIImage to exactly match the height of the UISCrollView that it sits in. I am not concerned about the eventual width of the UIImage, but would like it to follow confines of 'AspectFit'. So I would probably employ
imageView.contentMode = UIViewContentMode.ScaleAspectFit
... probably in
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
I would set the dynamic height and width parameters of the UIScrollView, and then set the UIImage height to follow the height of the UISCrollView and allow the ScaleAspectFitto automatically handle the width.
I have tried various renditions of code to achieve this, but have failed to achieve the desired result.
Question: Am I on the right track? Can anyone kindly get me started with some skeleton code so I can customise it to my needs.
Side Note: I am only allowing for portrait mode of my app. No landscape.
Many thanks in advance for your kind attention and time ;)
I would make a container UIView to hold the exact UIScrollView then I can create constraints of scroll view's child UIImageView to that of the container view i.e. equal heights.
As I suppose, these constraints are to their parents like
UIView <--> UIScrollView <--> UIImageView
but try them as
UIView <--> UIImageView
I have a custom tableview and under each cell i have image-view. I am receiving images of different sizes from the service and i want to adjust my frame according to that.
Whenever there is a picture smaller than the frame size i want to adjust the height according to its height.
So i am comparing the current frame size and image size and trying to update the imageview frame,but its not working.
cell.image.contentMode = UIViewContentModeScaleAspectFit;
if(cell.image.image.size.height<cell.image.frame.size.height)
{
cell.image.frame = CGRectMake(cell.image.frame.origin.x, cell.image.frame.origin.y,cell.image.frame.size.width, cell.image.image.size.height);
}
what needs to be done to overcome the problem??
You would require to change cell height according to image in UITableView datasource like this
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (image.size.width > CGRectGetWidth(self.view.bounds)) {
CGFloat ratio = image.size.height / image.size.width;
return CGRectGetWidth(self.view.bounds) * ratio;
} else {
return image.size.height;
}
}
EDIT
Incase you are looking out for resizing image and not tableview cell then these are Resize UIImage with aspect ratio? and Resize UIImage with aspect ratio? very good references. Also you should understand UIImageView Scaling Explained Visually
If the code above is in cellForRowAtIndexPath and the cell that you have initialized inside cellForRowAtIndexPath is an instance of ItemCell (for instance), then in your ItemCell class, you will have to override layoutSubviews and check the size of the image subview inside layoutSubviews and adjust the height of the frame of the cell accordingly inside layoutSubviews. This ensures that every time for cellForRowAtIndexPath is called, the layout of the UITableViewCell subclass (ItemCell) happens before the cell gets rendered by cellForRowAtIndexPath.
Btw, the fact that you have set the contentMode of the cell to ScaleAspectFit means that regardless of what you do to the frame of the imageView inside the cell, the image will always be scaled to fit the entire view available to it, i.e. in this case, the dimensions of the imageView inside the cell. Maybe you should try moving the contentMode line
cell.image.contentMode = UIViewContentModeScaleAspectFit;
after the conditional where you set the frame size of the imageView :
if(cell.image.image.size.height<cell.image.frame.size.height)
{
cell.image.frame = CGRectMake(cell.image.frame.origin.x, cell.image.frame.origin.y,cell.image.frame.size.width, cell.image.image.size.height);
}
cell.image.contentMode = UIViewContentModeScaleAspectFit;
Ofcourse, this would probably not solve the problem if you aren't overriding layoutSubview to adjust the dimensions of the imageView frame there.
In the interface builder, I'm trying to create a prototype cell with an image that covers the entire cell but it is not running how it is expected.
As you can see in the following screenshot of my interface builder, I have an image view covering the entire cell, and is constrained to each edge of the cell:
And in fact this is how I expect it to look on the simulator, but instead I get this:
Where as you can see, it is not anchored all the way to the sides, and it may be hard to see, but the image actually extends past the bottom of the cell (if you look hard enough you can see the separator striking through the bottom portion of the image.
This is really buggy and I really have no idea what's happening.
Perhaps adding aUIImageView inside of your cell in code.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//configure cell
let imageView = UIImageView(frame: self.cell.frame)
imageView.image = YOUR_IMAGE
imageView.center = cell.center
imageView.frame.size = cell.frame.size
imageView.contentMode = .ScaleAspectFill
cell.addSubview(imageView)
}
Hope this helps.
I think you accidentally disabled cell's Clip Subviews in code or in Storyboard, by default It should be enabled.
If it's not the cell, check it's Content View.
By the way, by disabling Clip Subviews for both Cell and it's Content view, I managed to reproduce your bug.
Seems that your image constraints are relative to cells contentView margins. You can disable it, see screenshot. Be sure that constant is 0
You need to do Clip Subviews (clipsToBounds) on cells contentView or imageView if you don't want aspect filled image to go beyond bounds. Otherwise you should use Aspect Fit, or Scale To Fill, or do the math manually
This is because you are setting constraint to margins.
When adding constraints to uiimageview. Uncheck constraint to margin.
I am currently working with a custom tableviewcell xib and want the size of it to be automatically resized to the tableview's width and it's row's height.
The size of the cell's view does not auto adjusts to the tableview. So I have tried to change the frame inside the tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) delegate no changes are reflected (if I print the frame size before and after it does in fact change.)
Does anyone know a solution for this? The best case would be to make the cell's frame auto adjust to the tableview's width and row height.
Edit: After further examination the problem probably has to do with iOS8 Size classes. When I resize the cell inside the xib, it will just expand to a greater width than the table view's size. The label is supposed to be in the center (had set them with constraints and programmatically, got the same results).
Here's a screenshot:
Found the problem, and it was my mistake. I never set constraints to the TableView inside the ViewController, so it maintained the default base size defined by Size Classes introduced in iOS 8.
That is why the width of the cell expanded about the double of the view controller's width.
What i understand from your Question is You want to set cell's width equal to table's width And cell's height equal to tableviewCell 's height!
By default The height of tableviewCell is 44. So you can set Cell's Height to 44! and you get tableview's Width by
self.tableview.frame.size.width
and You can set cell's Frame by cell.frame!
for Example
CGRect cellRect = CGRectMake(0, 0, self.tableview.frame.size.width ,44);
cell.frame = cellRect;
Hope all things Going Well and that may help you!
In iOS 8 the UICollectionViewFlowLayout supports automatically resizing cells based on their own content size. This resizes the cells in both width and height according to their content.
Is it possible to specify a fixed value for the width (or height) of all the cells and allow the other dimensions to resize?
For a simple example consider a multi-line label in a cell with constraints positioning it to the sides of the cell. The multi-line label could be resized different ways to accommodate the text. The cell should fill the width of the collection view and adjust it's height accordingly. Instead, the cells are sized haphazardly and it even causes a crash when the cell size is larger than the non-scrollable dimension of the collection view.
iOS 8 introduces the method systemLayoutSizeFittingSize: withHorizontalFittingPriority: verticalFittingPriority: For each cell in the collection view the layout calls this method on the cell, passing in the estimated size. What would make sense to me would be to override this method on the cell, pass in the size that is given and set the horizontal constraint to required and a low priority to the vertical constraint. This way the horizontal size is fixed to the value set in the layout and the vertical size can be flexible.
Something like this:
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
UICollectionViewLayoutAttributes *attributes = [super preferredLayoutAttributesFittingAttributes:layoutAttributes];
attributes.size = [self systemLayoutSizeFittingSize:layoutAttributes.size withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
return attributes;
}
The sizes given back by this method, however, are completely strange. The documentation on this method is very unclear to me and mentions using the constants UILayoutFittingCompressedSize UILayoutFittingExpandedSize which just represent a zero size and a pretty large one.
Is the size parameter of this method really just a way to pass in two constants? Is there no way to achieve the behavior I expect of getting the appropriate height for a given size?
Alternate Solutions
1) Adding constraints that will be specify a specific width for the cell achieves the correct layout. This is a poor solution because that constraint should be set to the size of the cell's collection view which it has no safe reference to. The value for that constraint could be passed in when the cell is configured, but that also seems completely counterintuitive. This is also awkward because adding constraints directly to a cell or it's content view is causing many problems.
2) Use a table view. Table views work this way out of the box as cells have a fixed width, but this would not accommodate other situations like an iPad layout with fixed width cells in multiple columns.
It sounds like what you are asking for is a way to use UICollectionView to produce a layout like UITableView. If that's really what you want, the right way to do this is with a custom UICollectionViewLayout subclass (maybe something like SBTableLayout).
On the other hand, if you're really asking if there is a clean way to do this with the default UICollectionViewFlowLayout, then I believe there is no way. Even with iOS8's self-sizing cells, it is not straightforward. The fundamental problem, as you say, is that the flow layout's machinery provides no way to fix one dimension and let another respond. (In addition, even if you could, there would be additional complexity around needing two layout passes to size the multi-line labels. This might not fit with how self-sizing cells want to compute all sizing via one call to systemLayoutSizeFittingSize.)
However, if you still want to create a tableview-like layout with a flow layout, with cells that determine their own size, and respond naturally to the collection view's width, of course it is possible. There is still the messy way. I have done it with a "sizing cell", i.e., a non-displayed UICollectionViewCell that the controller keeps only for calculating cell sizes.
There are two parts to this approach. The first part is for the collection view delegate to calculate the correct cell size, by taking in the collection view's width and using the sizing cell to calculate the cell's height.
In your UICollectionViewDelegateFlowLayout, you implement a method like this:
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
// NOTE: here is where we say we want cells to use the width of the collection view
let requiredWidth = collectionView.bounds.size.width
// NOTE: here is where we ask our sizing cell to compute what height it needs
let targetSize = CGSize(width: requiredWidth, height: 0)
/// NOTE: populate the sizing cell's contents so it can compute accurately
self.sizingCell.label.text = items[indexPath.row]
let adequateSize = self.sizingCell.preferredLayoutSizeFittingSize(targetSize)
return adequateSize
}
This will cause the collection view to set the width of the cell based on the enclosing collection view, but then ask the sizing cell to calculate the height.
The second part is to get the sizing cell to use its own AL constraints to calculate the height. This can be harder than it should be, because of the way multi-line UILabel's effectively require a two-stage layout process. The work is done in the method preferredLayoutSizeFittingSize, which is like so:
/*
Computes the size the cell will need to be to fit within targetSize.
targetSize should be used to pass in a width.
the returned size will have the same width, and the height which is
calculated by Auto Layout so that the contents of the cell (i.e., text in the label)
can fit within that width.
*/
func preferredLayoutSizeFittingSize(targetSize:CGSize) -> CGSize {
// save original frame and preferredMaxLayoutWidth
let originalFrame = self.frame
let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth
// assert: targetSize.width has the required width of the cell
// step1: set the cell.frame to use that width
var frame = self.frame
frame.size = targetSize
self.frame = frame
// step2: layout the cell
self.setNeedsLayout()
self.layoutIfNeeded()
self.label.preferredMaxLayoutWidth = self.label.bounds.size.width
// assert: the label's bounds and preferredMaxLayoutWidth are set to the width required by the cell's width
// step3: compute how tall the cell needs to be
// this causes the cell to compute the height it needs, which it does by asking the
// label what height it needs to wrap within its current bounds (which we just set).
let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
// assert: computedSize has the needed height for the cell
// Apple: "Only consider the height for cells, because the contentView isn't anchored correctly sometimes."
let newSize = CGSize(width:targetSize.width,height:computedSize.height)
// restore old frame and preferredMaxLayoutWidth
self.frame = originalFrame
self.label.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth
return newSize
}
(This code is adapted from the Apple sample code from the sample code of the WWDC2014 session on "Advanced Collection View".)
A couple points to notice. It's using layoutIfNeeded() to force layout of the entire cell, in order to compute and set the width of the label. But that's not enough. I believe you also need to set preferredMaxLayoutWidth so that the label will use that width with Auto Layout. And only then can you use systemLayoutSizeFittingSize in order to get the cell to compute its height while taking the label into account.
Do I like this approach? No!! It feels way too complex, and it does layout twice. But as long as performance doesn't become an issue, I'd rather perform layout twice at runtime than have to define it twice in code, which seems to be the only other alternative.
My hope is that eventually self-sizing cells will work differently and this will all get a lot simpler.
Example project showing it at work.
But why not just use self-sizing cells?
In theory, iOS8's new facilities for "self-sizing cells" should make this unnecessary. If you've defined a cell with Auto Layout (AL), then the collection view should be smart enough to let it size itself and lay itself out correctly. In practice, I haven't seen any examples that have gotten this to work with multi-line labels. I think this is partly because the self-sizing cell mechanism is still buggy.
But I'd bet it's mostly because of the usual trickiness of Auto Layout and labels, which is that UILabels require a basically two-step layout process. It's not clear to me how you can perform both steps with self-sizing cells.
And like I said, this is really a job for a different layout. It is part of flow layout's essence that it positions things that have a size, rather than fixes a width and lets them choose their height.
And what about preferredLayoutAttributesFittingAttributes: ?
The preferredLayoutAttributesFittingAttributes: method is a red herring, I think. That is only there to be used with the new self-sizing cell mechanism. So this isn't the answer as long as that mechanism is unreliable.
And what's up with systemlayoutSizeFittingSize:?
You're right the docs are confusing.
The docs on systemLayoutSizeFittingSize: and systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: both suggest that you should only pass UILayoutFittingCompressedSize and UILayoutFittingExpandedSize as the targetSize. However, the method signature itself, the header comments, and the behavior of the functions indicate that they are responding to the exact value of the targetSize parameter.
In fact, if you set the UICollectionViewFlowLayoutDelegate.estimatedItemSize, in order to enable the new self-sizing cell mechanism, that value seems to get passed in as the targetSize. And UILabel.systemLayoutSizeFittingSize seems to return the exact same values as UILabel.sizeThatFits. This is suspicious, given that the argument to systemLayoutSizeFittingSize is supposed to be a rough target and the argument to sizeThatFits: is supposed to be a maximum circumscribing size.
More Resources
While it is sad to think that such a routine requirement should require "research resources", I think it does. Good examples and discussions are:
http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html
http://devetc.org/code/2014/07/07/auto-layout-and-views-that-wrap.html
code for WWDC2014 session 232, "Advanced User Interfaces with Collection Views"
There's a cleaner way to do this than some of the other answers here, and it works well. It should be performant (collection views load fast, no unnecessary auto layout passes etc), and doesn't have any 'magic numbers' like a fixed collection view width. Changing the collection view size, e.g. on rotation, and then invalidating the layout should work great too.
1. Create the following flow layout subclass
class HorizontallyFlushCollectionViewFlowLayout: UICollectionViewFlowLayout {
// Don't forget to use this class in your storyboard (or code, .xib etc)
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = super.layoutAttributesForItemAtIndexPath(indexPath)?.copy() as? UICollectionViewLayoutAttributes
guard let collectionView = collectionView else { return attributes }
attributes?.bounds.size.width = collectionView.bounds.width - sectionInset.left - sectionInset.right
return attributes
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let allAttributes = super.layoutAttributesForElementsInRect(rect)
return allAttributes?.flatMap { attributes in
switch attributes.representedElementCategory {
case .Cell: return layoutAttributesForItemAtIndexPath(attributes.indexPath)
default: return attributes
}
}
}
}
2. Register your collection view for automatic sizing
// The provided size should be a plausible estimate of the actual
// size. You can set your item size in your storyboard
// to a good estimate and use the code below. Otherwise,
// you can provide it manually too, e.g. CGSize(width: 100, height: 100)
flowLayout.estimatedItemSize = flowLayout.itemSize
3. Use the predefined width + custom height in your cell subclass
override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
layoutAttributes.bounds.size.height = systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
return layoutAttributes
}
A simple way to do it in iOS 9 in a few lines of codes - the horizontal way exemple (fixing its height to its Collection View height) :
Init your Collection View Flow Layout with an estimatedItemSize to enable self-sizing cell :
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.estimatedItemSize = CGSizeMake(1, 1);
Implement the Collection View Layout Delegate (in your View Controller most of the time), collectionView:layout:sizeForItemAtIndexPath: . The goal here is to set the fixed height (or width) to the Collection View dimension. The 10 value can be anything, but you should set it to a value that doesn't break constraints :
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(10, CGRectGetHeight(collectionView.bounds));
}
Override your custom cell preferredLayoutAttributesFittingAttributes: method, this part actually calculate your dynamic cell width based on your Auto Layout constraints and the height you have just set :
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
UICollectionViewLayoutAttributes *attributes = [layoutAttributes copy];
float desiredWidth = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].width;
CGRect frame = attributes.frame;
frame.size.width = desiredWidth;
attributes.frame = frame;
return attributes;
}
Try fixing your width in the preferred layout attributes:
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
UICollectionViewLayoutAttributes *attributes = [[super preferredLayoutAttributesFittingAttributes:layoutAttributes] copy];
CGSize newSize = [self systemLayoutSizeFittingSize:CGSizeMake(FIXED_WIDTH,layoutAttributes.size) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
CGRect newFrame = attr.frame;
newFrame.size.height = size.height;
attr.frame = newFrame;
return attr;
}
Naturally you also want to ensure that you setup your layout correctly to:
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *) self.collectionView.collectionViewLayout;
flowLayout.estimatedItemSize = CGSizeMake(FIXED_WIDTH, estimatedHeight)];
Heres something I put on Github that uses constant width cells and supports dynamic type so the height of the cells updates as the system font size changes.
YES it can be done using auto layout programmatically and by setting constraints in storyboard or xib. You need to add constraint for width size to remain constant and set height greater than or equal to.
http://www.thinkandbuild.it/learn-to-love-auto-layout-programmatically/
http://www.cocoanetics.com/2013/08/variable-sized-items-in-uicollectionview/
Hope this will be helpful and solve your issue.