Change scroll direction in UICollectionView - ios

I have a UICollectionView which I have setup, everything works fine (selection, headers, etc), however, I want to change the scroll direction in some situations.
In short if I go into the story board and change the scrollDirection it works fine but I want to do it programatically!
I have tried to change the scroll direction of the collection view directly with something like
[myCollectionView setScrollDirection:....and so on.......
But this does not work, I can not find scrollDirection or similar in there.
I have also tried to setup a flow layout but I am sure i am doing this wrong (i.e. trying to set a ViewLayout to a FlowLayout).
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc]init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
[myCollectionView setCollectionViewLayout:flowLayout];
This crashes with a lot of Constraint problems and I suspect I need to do a lot more work with the flowLayout (from what I have found it is a bit above me right now).
It should also be noted that I am using a custom cell, headers and footers.
In short is there an easy way to do this or not? OR does anyone know a good Flow Layout tutorial?
EDIT
I setup the collection view as such;
[myCollectionView setDataSource:self];
[myCollectionView setDelegate:self];
I implement these delegate methods and all work fine
viewForSupplementaryElementOfKind
numberOfSectionsInCollectionView
numberOfItemsInSection
cellForItemAtIndexPath
didSelectItemAtIndexPath
I have added the DataSource and Delegate to the .h and also the FlowLayout delegate BUT I am not sure what I should also have for the latter.
Most of the visual layout is done in Story Board, there are a few things such as Font, size and colour which I do programatically.
ANOTHER EDIT
This is the error when I try to change the FlowLayout, I also get this when I try and invalidate the layout.
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
"<NSAutoresizingMaskLayoutConstraint:0x89967f0 h=--& v=--& V:[menuCell:0x8991700(50)]>",
"<NSLayoutConstraint:0x8991200 menuCell:0x8991700.bottom == UILabel:0x8992be0.bottom + 100>",
"<NSLayoutConstraint:0x898fd50 UILabel:0x8992be0.top == menuCell:0x8991700.top + 3>"
Will attempt to recover by breaking constraint
Break on objc_exception_throw to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in may also be helpful.

do this:
- (IBAction)changeDirection:(UIButton *)sender
{
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)[self.collectionView collectionViewLayout];
if(layout.scrollDirection == UICollectionViewScrollDirectionHorizontal)
{
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
}
else
{
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
}
}
It works for me.

In swift you can do this:
//From the collection view subclass
if let layout: UICollectionViewFlowLayout = self.collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .Vertical
}

Ok, try calling invalidateLayout on your collection view like so:
[myCollectionView.collectionViewLayout invalidateLayout];
This forces the collection view to update its layout at that point (See apple documentation here). I'm not certain, but I don't imagine this is called when you change the scroll direction.
See if that gets you anywhere near!

You can do it with property observer didSet{} with your collection view outlet.
Assuming that the outlet for the collectionView is myCollectionView, in the code add a didSet property observer to the outlet and change the layout's direction. Something like-
#IBOutlet weak var myCollectionView:UICollectionView!{
didSet{
let layout = contentCollectionView.collectionViewLayout as!
UICollectionViewFlowLayout
layout.scrollDirection = .Vertical
}
}
Normally a collectionView is dropped in storyboard from object library, it automatically comes with FlowLayout. You can change this layout's flow direction by telling your code that when I get my collection view, I want its layout's scroll direction to be horizontal or vertical.

Related

UITableViewCell's contentView gets unwanted "height==44" constraint

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.

AutoLayout breaks constraints when layoutIfNeeded() is called

I'm trying to implement a dynamic height UITaleViewCell in UITableView with XCode6 using Swift.
I laid out my cell as following, by setting up the constraints graphically(Screenshot is from XCode5, because of NDA on XCode6). I also set the BodyLabel's Line Break property to 'Word Wrap', and set the Line number to '0' to allow multiple lines.
Now if I just set up the cell's contents inside tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) method, then I get the dynamic height behavior correctly.
However, since I was following along tutorials available online(specifically this one), I added another method to determine the height of the cell with tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!).
In the tutorial that I was following, it told me to add cell.layoutIfNeeded(), so I added that too.
override func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
var cell = tableView!.dequeueReusableCellWithIdentifier(kCellIdentifier) as GroupFeedCell
// Configure the cell
if let frc = self.fetchedResultsController {
let feed = frc.objectAtIndexPath(indexPath) as GroupFeed
cell.titleLabel.text = feed.name
if let message = feed.message {
cell.bodyLabel.text = message
}
}
// Layout the cell
cell.layoutIfNeeded()
// Get the height
var height : CGFloat = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
return height
}
However, when I ran the program, although the table view still displayed the dynamic heights of the cells correctly, it displayed the errors like this:
2014-07-27 13:59:22.599 FBGroups[4631:979424] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSLayoutConstraint:0x17809e780 H:[UILabel:0x14fd12f50'Body Label Contents...']-(8)-| (Names: '|':UITableViewCellContentView:0x178185d70 )>",
"<NSLayoutConstraint:0x17809e7d0 H:|-(8)-[UILabel:0x14fd12f50'Body Label Contents...'] (Names: '|':UITableViewCellContentView:0x178185d70 )>",
"<NSLayoutConstraint:0x17809ea50 'UIView-Encapsulated-Layout-Width' H:[UITableViewCellContentView:0x178185d70(0)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x17809e780 H:[UILabel:0x14fd12f50'Body Label Contents...']-(8)-| (Names: '|':UITableViewCellContentView:0x178185d70 )>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
I tried to figure out what might have gone wrong, and spend good amount of time on it, and I figured out that whenever I delete cell.layoutIfNeeded() method, then the constraints error disappears.
And it seemed that among the constraints that are conflicting, UIView-Encapsulated-Layout-Width was not the one I added, and other than that, the all of the constraints looked innocent to me. I tried to search through what could generate UIView-Encapsulated-Layout-Width constraints, but I couldn't get the satisfying explanation in my situation. I would like to know what would be the cause of this error message and how to resolve this problem.
Also, can someone explain what would be the purpose of calling cell.layoutIfNeeded() method inside calculating height of the cell, and when it would be necessary? Most of the tutorials that were covering dynamic height UITableViewCell utilized this method, although my program still displayed cells correctly without that method call, and whenever I tried to use that method, it caused the exceptions.
In Xcode, set the priority of the very bottom vertical constraint to 750 (or anything less than a 1000).
It could be that the conflicting constraints are because the first two constraints define the width of the label as being 16pts less than the width of the cell, but the third constraint, the UIView-Encapsulated-Layout-Width wants to make the label a different size.
You might try to lower the values of the content compression resistance/hugging properties to allow the label to ignore its intrinsic size.
It seems like a bug of Apple. I use a work around that add width constraint at runtime like this.
- (void)configurateBodyLabelConstraint {
UIScreen *mainScreen = [UIScreen mainScreen];
CGFloat viewWidth = mainScreen.bounds.size.width;
CGFloat bodyLabelWidth = viewWidth - 76 - 8;
NSDictionary *metric = # {
#"bodyLabelWidth": [NSNumber numberWithFloat:bodyLabelWidth]
};
UILabel *bodyLabel = self.bodyLabel;
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"[bodyLabel(bodyLabelWidth)]"
options:0
metrics:metric
views:# {
#"bodyLabel": bodyLabel
}];
[self.contentView addConstraints:constraints];
}
cell.layoutIfNeeded() - no need to invoke.
Create IBOutlet an NSLayoutConstraint

Can you amend UICollectionView scrollDirection programmatically?

I have a UICollectionView that is created using Storyboard (given it has quite complicated cells). I want to programmatically change the scrollDirection to be horizontal (on 4 inch screens) and vertical (on 3.5 inch screens).
I see you can set scrollDirection upon initiation, but I cannot see how you can access this property from an already created UICollectionView.
Does anyone know if this is possible?
What you are looking for is the
UICollectionViewFlowLayout
This is how you would go about using it
UICollectionViewFlowLayout *horizontalLayout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
horizontalLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
[self.collectionView setCollectionViewLayout:horizontalLayout];
I assume that your collectionView is linked with the storyboard as self.collectionView
This is the quick and dirty way. What you should do is create your own layout and subclass the UICollectionViewFlowLayout class.
An example can be found here: UICollectionViewFlowLayoutExample by AshFurrow

UICollectionView cell disappears

I am using collectionView in my iPad application. I have used custom layout class which inherited UICollectionViewFlowLayout class.
I am using horizontal scroll directional. The problem is, whenever we use scroll for collection view, some times cell get disappear. I debugged the code, and found that in data source method after creating cell, its hidden property get automatically ON.
I tried using below line but its not working
cell.hidden = NO
I have below method also to invalidate layout on bound change.
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
But still I am facing problem. Any solution on this ?
Thanks in advance.
There is a known issue which may cause cells to disappear in UICollectionView. If their fix doesn't work for you, try PSTCollectionView,
which is an open source replacement for UICollectionView. If PSTCollectionView works, you should file a radar ticket to Apple.
In my project, the UICollectionViewCell disappear also ,the flowLayout i use is a custom layout named UICollectionViewLeftAlignedLayout . Its scroll direction is horizontal .I debug the project again and again, have tried every solution i can find through google.com. All that did not work for me.
At the end ,i delete the UICollectionViewLeftAlignedLayout and use native
UICollectionViewFlowLayout through:
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
[layout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
_collectionView.collectionViewLayout = layout;
_CollectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
And I set the cell spacing through storyboard(also you can set them through the layout's propery):
Then i run the project again , the UICollectionViewCell DO NOT DISAPPEAR anymore. Hope my case can help you .

UICollectionView's cellForItemAtIndexPath is not being called

Only my second time using UICollectionView's and perhaps I have bitten off more than I can chew but nevertheless:
I am implementing a UICollectionView (myCollectionView) that uses custom UICollectionViewCell's that I have subclassed. The subclassed cells (FullReceiptCell) contain UITableView's and are the size of the viewcontroller. I am trying to allow for horizontal scrolling between the FullReceiptCells.
The subclassed UICollectionViewController that contains myCollectionView is being pushed on to a nav controller stack. Currently, myCollectionView loas and horizontal scrolling is enabled. However, no cells are visible. I have confirmed that
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
has run and is returning an integer greater than 0. I have also confirmed that myCollectionView's delegate and datasource are properly set in IB to the subclassed UICollectionViewController.
The method where the cells are to be loaded:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
is not being called.
Here is where I push the UICollectionViewController and my viewDidLoad method within that controller (NOTE: initWithBill is an override of the normal initializer):
In the prior ViewControllers .m file:
FullReceiptViewController *test = [[FullReceiptViewController alloc] initWithBill:currentBill];
test.title = #"Review";
[self.navigationController pushViewController:test animated:YES];
In FullReceiptViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[self.myCollectionView registerClass:[FullReceiptCell class] forCellWithReuseIdentifier:#"FullReceiptCellIdentifier"];
self.myCollectionView.pagingEnabled = YES;
// Setup flowlayout
self.myCollectionViewFlowLayout = [[UICollectionViewFlowLayout alloc] init];
[self.myCollectionViewFlowLayout setItemSize:CGSizeMake(320, 548)];
[self.myCollectionViewFlowLayout setSectionInset:UIEdgeInsetsMake(0, 0, 0, 0)];
[self.myCollectionViewFlowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
self.myCollectionViewFlowLayout.minimumLineSpacing = 0;
self.myCollectionViewFlowLayout.minimumInteritemSpacing = 0;
[self.myCollectionView setCollectionViewLayout:myCollectionViewFlowLayout];
//testing to see if the collection view is loading
self.myCollectionView.backgroundColor = [UIColor colorWithWhite:0.25f alpha:1.0f];
Any clue as to why it is not being called?
For those who stumble here later.... the reason:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
was not being called was because of the itemSize for the collectionViewFlowLayout's height was too big.
[self.myCollectionViewFlowLayout setItemSize:CGSizeMake(320, 548)];
If I change the height to 410, it will execute cellForItemAtIndexPath.
In my case, it was because my layout class incorrectly subclassed from UICollectionViewLayout instead of UICollectionViewFlowLayout
The cellForItemAtIndexPath will not get called if you do not provide the content size information to collection view.
If you are using Flow layout: You need to set the item sizes properly.
If you have a custom layout subclassed from UICollectionViewLayout: Ensure you are returning a proper size from the collectionViewContentSize method.
In case of the latter, you will also observe that your layoutAttributesForElementsRect is also not called. The reason is that you have not specified what is your content size and by default the size will be CGSizeZero. This basically tell collection view that you don't have any content to paint so it does not bother asking you for attributes or cells.
So, just override collectionViewContentSize and provide a proper size there, it should solve your problem.
For a more complicated view hierachy please check this blog. It saved my life!
self.automaticallyAdjustsScrollViewInsets = NO;
Maybe I'm just overlooking it, but it appears your missing your delegate and data source. In your header file, make sure you have added these:
<UICollectionViewDelegate, UICollectionViewDataSource>
and in your viewDidLoad method add this:
self.myCollectionView.delegate = self;
self.myCollectionView.dataSource = self;
Also, if you are loading it via an .xib, make sure you are have connected the IBOutlet to the UICollectionView.
If your class is subclass from UICollectionviewController and you are creating collectionView programmatically then use
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .Vertical
layout.itemSize = CGSizeMake(50, 50)
collectionView?.setCollectionViewLayout(layout, animated: false)
I was using autolayout, and in my case height constraint was missing
In my case, what I had very stupidly forgotten to do was implement the UICollectionViewDataSource protocol method numberOfItemsInSection, so that is another thing to check.
My issue was that I had 2 collection views within the same view, and both were sharing a computed UICollectionViewFlowLayout instance. Once I created separate instances of the same flow layout, it worked fine.
I was having a similar problem. But instead cellForItemAtIndexPath was called when the number of items was > 1, but not when there was only a single item. I tried many of the proposed solutions here and similar to many I found that it had to do with item sizing. For me the solution was to change the estimate size of the UICollectionView from Automatic to Custom.
In my case I had the collectionView in a UIStackView with alignment = .center. So the collectionView did not have a width. When setting the stackView.alignment to .fill everything was fine.
Ten minutes ago I also encountered this problem, I made a silly mistake.
My intention is to use UICollectionViewFlowLayout,
But because of mistakes, I used UICollectionViewLayout.
I forgot to set the autoresizing mask to false on the collection view:
collectionView.translatesAutoresizingMaskIntoConstraints = false
Also, make sure that reloadData is not called while collection view is updating with animation (insert, delete, update or performBatchUpdates).
In my case, set collectionView.prefetchingEnabled = NO; solved my problem.
Only works on iOS 10.
It was happening for me when item height == 0 by fixing that cellForItem method was called.
In my case, changing UINavigationBar's translucent to NO solved the problem.
In Xcode 7.0.1, we got this problem when we copied a storyboard and accompanying code from another project. The collection view had a custom flow layout set in the storyboard. Solution was to:
Remove the flow layout in IB
Compile/run the app
Set the flow layout back
Now it worked :)
In storyboard/xib UICollectionView property cellSize MUST not be {0, 0}
If you're using collection view protocols you must connect the CollectionViewDataSource and CollectionViewDelegate protocols to your ViewController.
Make sure numberOfSectionsInCollectionView returns a positive integer as well.
There has to be at least one section in the collection.
Make sure -(NSInteger) collectionView:numberOfItemsInSection: is returning a positive (read, greater than zero) value. It may be a matter of placing [self.collectionView reloadData]; in the completion handler.
For me, I update the height of self.view (the collectionView's superview) via autolayout, and MUST call layoutIfNeeded after that.
[self.view mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(#(height));
}];
[self.view layoutIfNeeded];
When using UICollectionViewDelegateFlowLayout be careful on what this function returns. Both width and height should be greater than 0. I used window as frame reference but due to complicated view hierarchy window was nil at runtime. If you need adjust the width of the element based on screen width use UIScreen.main.bounds.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout:
UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {}
It can be realated layout problems. Check layout warnings from console, if exist.
In my case the collection view was not fully visible in its parent view due to wrong auto layout constraints. So fixing the constraints made the collection view all visible and cellForItemAtIndexPath was called.
Same happened to me. I had 2 UICollectionViews and I removed once since I didn't need that. After that I realised that the CellForItemAtIndexPath was not getting called but the other required methods. I tried all of the above but then I did the standard magic. Removed the collection view from storyboard and added again. Then it started working. Not sure why and I have no explanation but maybe some connection issues.
Just resolved this issue, for a somewhat specific situation.
In my case, I was manually initializing the UICollectionViewController in a parent ViewController, and then adding the UICollectionViewController's view as a subview.
I resolved the issue by calling 'setNeedsLayout' and/or 'setNeedsDisplay' on the collectionView in the parent's viewDidAppear:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[_collection.view setNeedsLayout];
[_collection.view setNeedsDisplay];
}
reloadSections instead of reloadData did it for me.
I don't know why reloadData did not work.
In my case I had to adjust the estimated Content size. content size did not do it.
let layout = collectionVC.collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = CGSize(width: 100, height: 100)
layout.estimatedItemSize = CGSize(width: 100, height: 100)

Resources