In a CollectionView, some cells should have an additional subview or layer. The CollectionView can be told to resize it's cells, thus all content needs to resize appropriately.
Currently, the cell is initialized from a nib containing a cell with imageview; the cell nib is linked to a custom UICollectionViewCell subclass, that only does the init. Autoresize subviews is checked.
The CollectionView is told to resize the cell by a value derived and returned in sizeForItemAtIndexPath:. I have subclassed a FlowLayout but it only specifies ScrollDirection and Insets.
All of that is working fine. Problem: How do I add subview/layer to the cell so it also resizes correctly? I tried adding subviews and layers with translatesAutoresizingMaskIntoConstraints off, but these do not automatically change size at all. Also tried to use code frame/view instead of nib.
The best I got now is a cell.contentView.layer sublayer which I add in cellForItemAtIndexPath:; that is "manually" resized by storing the cell's frame.size from sizeForItemAtIndexPath:, which is not only ugly but also ends up with the sublayer having various sizes for different cells.
Any help appreciated!
I ran into the same issue just now.
When using the UICollectionViewFlowLayoutDelegate method to set the cell size depending on device and device-orientation, the size would be calculated properly but subviews would not resize to fill the newly size cell. The effect was a large blank cell with small subviews that don't fill the cell's bounds / remain the size equal to their current value in the xib file.
I solved this by doing the following in awakeFromNib:
- (void)awakeFromNib
{
[super awakeFromNib];
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.contentView.translatesAutoresizingMaskIntoConstraints = YES;
}
Prior to doing this, the contentView mask was nil.
*override func awakeFromNib() {
super.awakeFromNib()
self.contentView.autoresizingMask.insert(.FlexibleHeight)
self.contentView.autoresizingMask.insert(.FlexibleWidth)
}
This worked for me.. This code goes inside of your subclassed UICollectionViewCell.swift file (where your code involving the custom cell is located)
Swift solution*
As an alternative to enabling AutoResizingMask, for custom UICollectionViewLayouts that have variable height for example where you are setting some constraints manually and need translatesAutoresizingMaskIntoConstraints to remain NO, you can add the following to layoutSubviews in the cell:
self.contentView.frame = self.bounds;
This worked to fix all of my custom collection view layouts that had problens with Xcode 6.
In another project without xibs i subclassed UICollectionViewCell and did this for the same effect:
#import "CVcell.h"
#implementation CVcell
#synthesize cellImage;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
CGFloat cellSize = self.contentView.bounds.size.width;
cellImage = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, cellSize, cellSize)];
[cellImage setClipsToBounds:YES];
cellImage.translatesAutoresizingMaskIntoConstraints = NO;
cellImage.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
[self.contentView addSubview:cellImage];
}
return self;
}
#end
I always prefer autolayout when possible.
But Sometimes usings frames and bounds just is a timesaver when a view is determined straightforward by only its superview.
In the case of UICollectionViewCell I set an image to be the cells frame +
self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
But when I had different sizes of cells in the collectionView it messed up things and the image sometimes took the size of a different cell.
So I turned to working with the the cells bounds and - ye that acually worked out fine.
So maybe give that a try?
A simple auto layout solution is to set constraints to the container view.
So if we have an image view with a parent view, we basically want to tell the subview (the image view) to maintain a leading, trailing, bottom, and top space distance of 0 to the container view.
#Alfie Hanssen solution (here) didn't work properly for me, according with this article:
The size of the cell view in the XIB is 50 x 50 points, which is the default size of the collection view cells as set in the flow layout. Even if it’s a bit hard to work with a cell this small in Interface Builder, it’s better to not change the default size. The problem is that Auto Layout considers the manually set size as being fixed and generates a NSAutoresizingMaskLayoutConstraint error when it tries to adjust the cells height automatically
I have inspected the UICollectionViewCell and I found that there is a view between the cell and the contentView, and that view has intrinsic width and height constraints. Instead of the AutoresizingMask I'm just updating as below and seems working for me.
override func layoutSubviews() {
contentView.superview?.frame = bounds
super.layoutSubviews()
}
I'm adding subView and constraint programmatically, the following code works for me:
lazy var imageView: UIImageView = { [unowned self] in
let imageView = UIImageView(frame: self.contentView.frame)
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
return imageView
}()
func updateCellWith(image: UIImage) {
contentView.subviews.forEach { $0.removeFromSuperview() }
contentView.addSubview(imageView)
imageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0).isActive = true
imageView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 0).isActive = true
imageView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: 0).isActive = true
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0).isActive = true
self.imageView.autoresizingMask.insert(.flexibleHeight)
self.imageView.autoresizingMask.insert(.flexibleWidth)
imageView.image = image
}
The solution was to turn off AutoConstraints for the cell xib and activate the flexible width/height arrows in the AutoResize for the imageviews.
I had the same problem. Switching between two layouts did not resize the Pictures (UIImage) inside my cells. My Cells where build without a xib. And I used two different cell classes for each CollectionViewCustomLayout.
I fixed this programatically with this:
self.autoresizesSubviews = YES;
in my UICollectionViewCell subclasses.
But this only worked for me by adding the Picture as a cells backgroundpicture like this:
cell.backgroundView[[UIImageView alloc] initWithImage: SumDummyImage ];
Related
I've got my custom UITableViewCell working now, with 'dynamic' height using Auto Layouts.
However, these row-dividers are kind of off.
It's a UITableViewController. The width of the image is the full width of the iPhone in the simulator.
Anyone have a clue? It's kind of a UITableViewController right of the shelf, not much code in it, mostly code for datasource/delegation.
To clearify I want the separators, but I want them equally indented on both sides. The default indention is fine, which is on the left side, but not the right side.
As Fogmeister mentioned, you could remove the separators entirely and just add a separator view on your custom table cells or you could extend the separators by setting the
cell.separatorInset = UIEdgeInsetsZero
cell.layoutMargins = UIEdgeInsetsZero
tableView.separatorInset = UIEdgeInsetsZero
tableView.layoutMargins = UIEdgeInsetsZero
note that this is only available for iOS 8 onwards.
These are called separators.
You can turned them off in Interface Builder as a property on the tableView.
Select the tableview and select the "None" property for the separator.
The default for the UITableViewCell separators is to be indented. However, by digging into your UITableViewCell's subviews, you can move and size the separator by altering its frame.
In your custom UITableViewCell class, override the layoutSubviews() method so you can grab the separator object as the cell's subviews are being laid out by iterating through your cell's subviews and check for a subview of the UITableViewCellSeparator type. If you want to make the separator span the entire cell's width, for example, change its frame's origin.x to 0 and make the separator the full width of the cell's contentView.
override func layoutSubviews() {
super.layoutSubviews()
for subview in self.subviews {
if subview.dynamicType == NSClassFromString("_UITableViewCellSeparatorView") {
var newFrame = subview.frame
newFrame.origin.x = 0
newFrame.size.width = self.contentView.frame.width
separator.frame = newFrame
}
}
}
I've been looking for days for a (working) tutorial or even an example app that uses UIScrollView to scroll vertically, programatically that is. There's tons of tutorials on using storyboard, leaving me at a loss.
I looked through apple's documentation, and their "guide" still not having a solid example or hint as to where to start. What I've attempted so far, is doing some of the following.
Making my view a scrollview Directly in the class
let scrollView = UIScrollView(frame: UIScreen.mainScreen().bounds)
Then assigning it to the view in my viewDidLoad function
self.view = scollView
Attempting to change the content size.
self.scrollView.contentSize = CGSize(width:2000, height: 5678)
Trying to enable scrolling with
scrollView.scrollEnabled = true
And the last suggestion I could find on doing this programatically
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.frame = view.bounds
}
Currently I havn't tried to start adding my objects to the scrollview, (I don't need to zoom, just do vertical scrolling), but I havn't managed to get anything working whatsoever :( In fact, running the app with those additions simply causes UIProblems, The screen is moved up weirdly, and it doesn't fit the entire width of my screen? I tried fixing that making the frame bounds equal to the width, but still didn't work
I'm not getting any errors.
I would love to see an example viewcontroller that you can scroll vertically in! Or any help would be hugely appreciated!
Here is a screenshot of the disaster, attempting to make my view scrollable causes.
(I made the scrollview background red, to see if it was even showing up correctly. Which it seems to be. Yet I can't scroll anywhere
As suggested in the comments, instead of doing self.view = self.scrollview, I tried adding scrollview as a subview of self.view instead, but with no positive results.
Adding
scrollView.contentSize = CGSize(width:2000, height: 5678)
to viewDidLayoutSubviews, as suggested in the comments below made my view scrollable!
However my layout still looks like a complete mess for some reason (it looks as it's supposed to, before I made it scrollable).
Here's an example constraint for my topBar (blue one), that's supposed to take up the entire horizontal space.
self.scrollView.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[topBar]|", options: nil, metrics: nil, views: viewsDictionary))
Any ideas why this doesn't work?
Swift 4.2
I make simple and complete example of scroll a stack view using auto layout.
All view are in code and don't need story board.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.addSubview(scrollViewContainer)
scrollViewContainer.addArrangedSubview(redView)
scrollViewContainer.addArrangedSubview(blueView)
scrollViewContainer.addArrangedSubview(greenView)
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollViewContainer.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
scrollViewContainer.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
scrollViewContainer.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
scrollViewContainer.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
// this is important for scrolling
scrollViewContainer.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
}
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
return scrollView
}()
let scrollViewContainer: UIStackView = {
let view = UIStackView()
view.axis = .vertical
view.spacing = 10
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let redView: UIView = {
let view = UIView()
view.heightAnchor.constraint(equalToConstant: 500).isActive = true
view.backgroundColor = .red
return view
}()
let blueView: UIView = {
let view = UIView()
view.heightAnchor.constraint(equalToConstant: 200).isActive = true
view.backgroundColor = .blue
return view
}()
let greenView: UIView = {
let view = UIView()
view.heightAnchor.constraint(equalToConstant: 1200).isActive = true
view.backgroundColor = .green
return view
}()
}
hope this help!
Strictly I feel problem is at below line.
self.view = scollView
It should be self.view.addSubview(scollView)
Then add all label, buttons, etc in scrollview and then give content size.
Content size is the parameter that will tell scrollview to scroll.
Put self.scrollView.contentSize = CGSize(width:2000, height: 5678) inside viewDidLayoutSubviews.
Thanks to Tom Insam who helped me put this answer together:
You need to add all constraints as you usually do i.e.
add constraints for the scrollview and its superview.
add constraints for the contents of the scrollview.
Then pause. Understand that at this point, even if your scrollView's frame is known to be e.g. 400 * 600, still the size of its content is unknown. It could be 400 * 6000 or 400 * 300 or any other size.
There's no other edge (leading, trailing, left, right, top, bottom, margins) based constraint that you can use to calculate the scrollview's content size.
Unless your views have some intrinsicContentSize, then at this point if you run the code, you'll get a run-time error saying: ScrollView Content size is ambiguous. Continue reading to learn more about intrinsicContentSize.
What's the solution?
https://developer.apple.com/library/archive/technotes/tn2154/_index.html
To use the pure autolayout approach do the following:
Set translatesAutoresizingMaskIntoConstraints to NO on all views involved.
Position and size your scroll view with constraints external to the scroll view.
Use constraints to lay out the subviews within the scroll view, being sure that the constraints tie to all four edges of the scroll view and do not rely on the scroll view to get their size.
The part I bolded is tho whole focus of my answer. You need to set a (horizontal/vertical) constraint that's independent of edges. Because otherwise it would rely on the scrollview to get its size.
Instead you need to explicitly set constraints on the width, height or attach to the center of the axis. The 3rd bullet is unneeded if the view has intrinsicContentSize e.g. labels or textviews can calculate their contentsize based on font, character length, and line breaks. To dig deepinter into intrinsicContentSize, see here
To say things differently:
ScrollView needs:
external constraints against its superview
internal constraints for its content
edge-to-edge / chained subviews in a way that its height (if scrolling vertically can be calculated). To the Layout engine setting a contentSize with a height of 1000 shouldn't be any different from chaining multiple subviews where the height of the all the content can be calculated as 1000. e.g. you have two labels together they have 400 lines. Each lines takes 2points and there's 100points of line space and 100 points of space between the two labels equaling to 1000 ( 400 * 2 + 100 + 100) points.
You see, the OS can calculate that without looking into the surroundings of the label. That's what I mean by calculating the size without relying on the scrollview
Also see docs from here as well.
I'm creating my UI entirely in code and using Masonry to constrain the cell's content view's subviews to the appropriate height. I'm using
[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height
on iOS 7 for the row height, while iOS 8 handles it automatically.
Everything looks exactly as it should on screen, but in the console I get trainloads of warnings for conflicting constraints, which all seem to be caused by an unasked and unnecessary height constraint on the cell's content view (e.g. <NSLayoutConstraint UITableViewCellContentView.height == 44>).
On iOS 8 I'm setting the table view's rowHeight as UITableViewAutomaticDimension (effectively -1) but still I get this constraint. I'm only adding constraints between the content view and its own subviews, so no constraints between the content view and the cell itself.
Any idea where this constraint comes from and how to make it go away?
Edit: Now I actually found a "solution" of sorts – initially setting the content view's frame to something ridiculous, like CGRectMake(0, 0, 99999, 99999), before adding subviews or constraints, seems to make the warnings go away. But this doesn't exactly smell like the right way to do it, so can anyone tell of a better approach?
I had the same issue and fixed it setting the auto resizing mask of the cell like this:
override func awakeFromNib() {
super.awakeFromNib()
self.contentView.autoresizingMask = .flexibleHeight
}
Also in the controller I set the estimated height and tell the table view to use automatic dimension (in the viewDidLoad method:
self.tableView.estimatedRowHeight = 120
self.tableView.rowHeight = UITableView.automaticDimension
These links helped:
http://useyourloaf.com/blog/2014/08/07/self-sizing-table-view-cells.html
Auto layout constraints issue on iOS7 in UITableViewCell
Hope this helps!
To tack on to the accept answer- after months of trying to get iOS 8's automatic cell sizing to work I discovered an important caveat. The 'estimatedRowHeight' property MUST be set. Either via the tableView directly or by implementing the delegate methods. Even if there's no way to determine a valid estimate, simply providing a value other than the default (0.0) has demonstrably allowed iOS 8's cell layout to work in my testing.
Regarding to the "solution" mentioned in the edit in the question (setting the contentView frame to something big temporarily), here's proof this is a good "solution":
https://github.com/smileyborg/TableViewCellWithAutoLayoutiOS8/blob/master/TableViewCellWithAutoLayout/TableViewController/TableViewCell.swift
// Note: if the constraints you add below require a larger cell size than the current size (which is likely to be the default size {320, 44}), you'll get an exception.
// As a fix, you can temporarily increase the size of the cell's contentView so that this does not occur using code similar to the line below.
// See here for further discussion: https://github.com/Alex311/TableCellWithAutoLayout/commit/bde387b27e33605eeac3465475d2f2ff9775f163#commitcomment-4633188
// contentView.bounds = CGRect(x: 0.0, y: 0.0, width: 99999.0, height: 99999.0)
It's hacky but it seems to work.
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
//self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
self.itemView = [CustomItemView new];
[self.contentView addSubview:self.itemView];
}
return self;
}
set translatesAutoresizingMaskIntoConstraints to NO is not work for me
, but autoresizingMask = UIViewAutoresizingFlexibleHeight is well.
you should also making constraints like this:
- (void)updateConstraints {
[self.itemView mas_updateConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.top.equalTo(0);
//make.bottom.equalTo(0);
make.bottom.lessThanOrEqualTo(0);
}];
[super updateConstraints];
}
bottom constraints not just equalTo contentView's bottom, you should use lessThanOrEqualTo
hope this is work to you!
I found out that in some cases, setting an estimatedHeight that is many times bigger the height of my average cell fixed most if not all warnings and had no negative impact on the display.
i.e.:
Setting self.tableView.estimatedRowHeight = 500.0f while most rows are only about 100.0f in height fixed my issue.
My UICollectionView cells don't get displayed on iOS6, because my delegate method cellForItemAtIndexPath doesn't get called. I suspect because of this warning:
the behavior of the UICollectionViewFlowLayout is not defined because:
the item height must be less that the height of the `UICollectionView`
minus the section inset's top and bottom values.
I don't get the warning on iOS7, and all the cells display correctly there too.
I've set my collectionView frame to height 270 in the .xib and there are no insets defined.
I've set my cell height to 270 in the .xib.
I can print out my collectionView frame at runtime and it's 271.
Also, my collectionview is actually inside a custom tableview cell.
Any ideas?
Thanks!
Try to set self.automaticallyAdjustsScrollViewInsets = NO
This was introduced in ios7 so you might want to wrap that with an ios version check, if you are supporting ios6 and below.
This fixed my problem! In my .xib, setting my Collection View Size Cell Size to a smaller value.
My setup is that I have this collectionview inside a custom tableview cell and
I do return the height of my tableview cell programatically (depending on the content). So it could be that my warnings had to do with my collectionview not fitting inside the tableview cell. So setting the initial collectionview to a smaller value fixed it.
I was on the wrong path thinking that the problem was with my collectionview and its colletionview cell.
Maybe?
self.automaticallyAdjustsScrollViewInsets = NO
actually did the trick. it also resolved my issue in swift, where the cells of a horizontal flow layout had a frame top of -32 (???) and did not fit into the collection view properly.
I found that I had to manually set self.collectionView.collectionViewLayout.itemSize in viewWillLayoutSubviews.
- (void)viewWillLayoutSubviews {
self.collectionView.collectionViewLayout.itemSize = CGRectMake(...);
}
Another possibility to generate the same trick would be to implement the method
collectionView:layout:sizeForItemAtIndexPath:
I have the same issue, in my case the size of collectionCell in storyboard is 96x96 and also under -(CGSize)collectionView:layout:sizeForItemAtIndexPath:
the solution was removing this delegate:
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
UIEdgeInsets insets = {.left = 10, .right = 10, .top = 5, .bottom = 5};
return insets;
}
And by the way this is under ios7, it's late but hope this will help some.. Cheers..
Set:
self.itemSize = CGSizeMake(1, 1);
Have a custom table cell with it's own .XIB and .h and .m. Nothing too fancy. When the device rotates, the cell width does not change. The tables cell for index path is called, but width is not changes (say from portrait to landscape). What would be preventing this?
Don't want to hand set the frame size.
Will post code if need to, but maybe we can answer with out code on this one.
Even if starting the app in landscape also does not set the larger width for the cell.
[Addition]
Also does not work with non-custom cells.
self.tableViewSelection.autoresizesSubviews = true;
self.tableViewSelection.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.view.autoresizesSubviews = true;
XIB for the table has the resizing seemed to be setup correctly (non-IOS6 Support).
If you are overriding the layoutSubviews or another method, don't forget to call it's super:
override func layoutSubviews() {
//...
super.layoutSubviews()
}
Not doing this will result in the problem you are facing.
The cell will automatically be the same width as the table view. So what is not resizing might be the table view. You need to give it appropriate constraints (if using Autolayout, the default in iOS 6) or autoresizing mask (otherwise) so that it will resize in response to the top-level view resizing to compensate for device rotation.
I also faced this same issue while autoresizing custom cells on rotating. After fighting I got the solution as authorize cell.contentView, because I am adding my views as subview into cell.contentview.
I used following code and my code works fine :)
cell.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
For swift 2, it will be:
cell.contentView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
In Swift 4, the below works.
cell.cellUIView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
Note: cellUIView is a UIView I added to the contentView of the prototype cells.