I am trying to display all the cells on my UICollectionView on a single row with an hozizontal scroll.
For this, I am using the built in UICollectionView class but I created a subclass of UICollectionViewFlowLayout with the following code:
#implementation MemberCollectionViewFlowLayout
- (id)init {
if ((self = [super init])) {
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.minimumLineSpacing = 10000.0f;
}
return self;
}
- (CGSize)collectionViewContentSize
{
NSArray *memberArray = [[NSUserDefaults standardUserDefaults] objectForKey:#"currentGroupMembers"];
return CGSizeMake([memberArray count]*50 , self.collectionView.frame.size.height);
}
#end
As a result I get a scrollable row but the dont display if the row is larger than the screen. If I increase the height on the contensize I can see that the remaining cells are added on a second line.
How can I make sure that all my cells are on a single line?
Many thanks for your help
In the flow layout documentation it says:
For a horizontally scrolling grid, this value represents the minimum spacing between successive columns.
So if you are adding a huge value horizontally and blowing the horizontal dimensions up - much larger than the content size you set. Therefore, the flow layout will try to start the next line.
Maybe it is not necessary to subclass at all. You could set the properties of the flow layout without subclassing and let the collection view take care of the content size. With the constraints of the view and the scroll direction, it should have the desired effect.
Related
Is there any simple way which can help me to change position of dependent views dynamically using their content size?
I want to show several views in column which all have varying content. And I want them to be placed one after another (I've created layout using constraints which looks like this)
But whenever I change content of labels and call sizeToFit, system seems to ignore layout.
At the moment I'm interested only in height property, I know that constraining rect can be used too and in the past I wrote many categories on UIView to change sizes dynamically (I guess everyone did). But maybe there is a simple way which I don't know?
-sizeToFit should not be called if you are using auto-layout. That's part of the 'old' system.
It looks like IB has inserted explicit heights into your constraints (the vertical bars next to the labels indicate this). Try selecting the labels and hitting Cmd+= to clear these.
For multiline labels you will also need to do the following in your view controller to make everything work correctly when rotating/resizing the view:
- (void)updateLabelPreferredMaxLayoutWidthToCurrentWidth:(UILabel *)label
{
label.preferredMaxLayoutWidth =
[label alignmentRectForFrame:label.frame].size.width;
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self updateLabelPreferredMaxLayoutWidthToCurrentWidth:self.label1];
[self updateLabelPreferredMaxLayoutWidthToCurrentWidth:self.label2];
[self updateLabelPreferredMaxLayoutWidthToCurrentWidth:self.label3];
[self.view layoutSubviews];
}
Multiline labels expose one of the weaknesses of auto-layout. We have to update preferredMaxLayoutWidth to force the label to reflow and adjust its height, otherwise if the view is resized/rotated, auto-layout does not realize the label needs to be reflowed and resized.
If you still want to use Auto Layout Constraint for your Label. This is a solution:
[self.lblBadgeValue sizeToFit];
self.constraintWidthBadgeLabel.constant = self.lblBadgeValue.frame.size.width;
[self.lblBadgeValue needsUpdateConstraints];
[self.lblBadgeValue layoutIfNeeded];
Explain more:
sizeToFit -> make label fit height, width with
content of it.
So in runtime you need to update constraint height,
or width for label
After that you need to say for compiler know
that what need update constraint.
And in the end you need to call
layout if have change on constraint.
This works too.
https://github.com/jszumski/auto-layout-table-cells/blob/master/DynamicCellHeights/JSLabel.m
#interface JSLabel : UILabel
#end
#implementation JSLabel
- (id)init {
self = [super init];
// required to prevent Auto Layout from compressing the label (by 1 point usually) for certain constraint solutions
[self setContentCompressionResistancePriority:UILayoutPriorityRequired
forAxis:UILayoutConstraintAxisVertical];
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.preferredMaxLayoutWidth = CGRectGetWidth(self.bounds);
[super layoutSubviews];
}
#end
I have a UITableView that scrolls up too far, to the point where the list of items in the UITableView scroll up and then a large amount of whitespace follows it.
How can I stop UITableView from scrolling that extra space?
Hope I described it well enough.
I think this may work for you. Run this code after calling reloadData on the table :
if (table.contentSize.height < table.frame.size.height)
{
table.scrollEnabled = NO;
}
else
{
table.scrollEnabled = YES;
}
Here ,table.frame.size.height is the actual size of the object (you can see this in Interface Builder) displayed on the screen, whereas table.contentSize.height is the height of the header, the footer, and the height of every cell added together.
I found it here : How to disable scrolling in UITableView table when the content fits on the screen
Hope you can get something from the answers given their also.
This will fix your problem
TableView.bounces=NO;
This will remove empty cells
TableView.separatorStyle = UITableViewCellSeparatorStyleNone;
I am currently testing in xcode a UICollectionView with just one horizontal row like a kinda cover flow. Basically I have my own Custom Cell class and xib file for the cell and then on each cell I am adding another UIView with a xib. In case you are wondering why, it is so I can add different UIViews to the cell. Right now I am only adding one.
Edit I have followed the WWDC 2012 video on creating a linelayout of a UICollectionViewCell with one difference. Instead of the cell in the middle getting bigger all the other cells get smaller.
Everything below is new to my question.
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *array = [super layoutAttributesForElementsInRect:rect];
CGRect visibleRect;
visibleRect.origin = self.collectionView.contentOffset;
visibleRect.size = self.collectionView.bounds.size;
for (UICollectionViewLayoutAttributes *attributes in array){
if (CGRectIntersectsRect(attributes.frame, rect)) {
CGFloat distance = CGRectGetMidX(visibleRect) - attributes.center.x;
CGFloat normalizedDistance = distance / ACTIVE_DISTANCE;
if (ABS(distance) < ACTIVE_DISTANCE) {
//THIS WOULD MAKE THE MIDDLE BIGGER
//CGFloat zoom = 1 + ZOOM_FACTOR *(1- ABS(normalizedDistance));
//attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1.0);
//attributes.zIndex = round(zoom);
} else {
//THIS MAKES ALL THE OTHERS NOT IN THE RECT SMALLER
CGFloat zoom = 1 + ZOOM_FACTOR *(1- ABS(normalizedDistance));
attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1.0);
attributes.zIndex = round(zoom);
}
}
}
return array;
}
The problem can be seen in the attached image.
Pink = Collection View Size
Brown = Cell size
Green = Cells Content size and an attached xib to the content size.
The problem I THINK I have is with the layout. When the sell is dequeued it is made smaller by the above code. Then when it is reused the CELL gets bigger again but the content view does not.
I have tired to manually set the frame of the content view but that does nothing.
UPDATE 1: This also only happens when I add a xib to the Cells content view. If there is no subview to the content view then there is no problem
UPDate 2: It appears that the subview of the cell, my xib is not resizing. I have tried to manually change its frame size but the only place this helps is in the cells drawrect method which feels like a hack to me.
reused cell not able to redraw itself so give call to
-(void)setFrame:(CGRect)frame {
[super setFrame:frame];
[self setNeedsDisplay]; // force drawRect:
}
from cellForItemAtIndexPath of the UICollectionView.
also have a look at this link
and this question
My answer is very specific and I am not sure it will help anyone.
The problem was that I had a constraint on the bottom of the grey view. After I changed this constraint to a less than or equal too then for some reason it worked.
Now I know this does not explain why it was not happening to every cell but it fixed my problem.
As such Harsh's answer might also be worth looking at if you have landed here after doing a search.
Edit: there also appears to be some bugs in the 6.0 UiCollectionView controller which seem to be fixed in 6.1
The row height beyond the last row in TableView (the empty placeholder rows that are just there for visual detail if the number of items is less than the number of rows a TableView can display at once) is always the same as the last row height.
Is it possible to change this without resorting to adding a dummy last row?
The private UITableView instance method _spacingForExtraSeparators returns the height of the placeholder rows. If you're not writing an app for the App Store, just override that. It returns a CGFloat.
Here's an different approach that is App Store-compliant (as far as I know), and might be easier than creating a dummy row.
UITableView sends itself the layoutSubviews message a lot. It does this whenever it adds, removes, or rearranges cells, and whenever it scrolls (and probably other times too). So let's override layoutSubviews to draw a view that starts at the bottom edge of your table view's last section. The view will be filled with a repeating pattern that looks like placeholder cells, with a height you define.
Create a subclass of UITableView. Give your subclass a UIView *_bottomView instance variable. We need to override layoutSubviews, so we'll initialize _bottomView lazily in that method.
#implementation MyTableView
{
UIView *_bottomView;
}
Override layoutSubviews. The first thing you do in your layoutSubviews is call [super layoutSubviews] so you'll continue to act like a proper UITableView.
- (void)layoutSubviews
{
[super layoutSubviews];
The next thing you do is lazily initialize _bottomView.
if (!_bottomView) {
You need a pattern to fill _bottomView, and it needs to look like placeholder cells. Make an image context that's 1 pixel wide, and as tall as you want a placeholder cell to be. Fill the context with white, and paint a separator line (pixel, really) at the bottom. Get the image from the context.
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, placeholderHeight), YES, 0);
[[UIColor whiteColor] setFill];
UIRectFill(CGRectMake(0, 0, 1, placeholderHeight));
[self.separatorColor setFill];
UIRectFill(CGRectMake(0, placeholderHeight - 1, 1, 1));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Next, create _bottomView, set its background "color" to that image as a repeating pattern, and add it to yourself as a subview.
_bottomView = [[UIView alloc] initWithFrame:CGRectZero];
_bottomView.opaque = YES;
_bottomView.backgroundColor = [UIColor colorWithPatternImage:image];
[self addSubview:_bottomView];
} // end of if-block
Finally, find the rect of your last section. Use that rect to compute a frame for _bottomView that starts at the bottom of the last section, is plenty tall (twice your own height is more than enough), and is as wide as the last section's rect.
int lastSectionIndex = [self.dataSource numberOfSectionsInTableView:self] - 1;
if (lastSectionIndex < 0)
lastSectionIndex = 0;
CGRect lastSectionRect = [self rectForSection:lastSectionIndex];
CGRect bottomViewFrame = CGRectMake(lastSectionRect.origin.x, CGRectGetMaxY(lastSectionRect), lastSectionRect.size.width, self.bounds.size.height * 2);
_bottomView.frame = bottomViewFrame;
Finally, make sure _bottomView is your frontmost subview so it will overdraw UITableView's placeholder cells.
[self bringSubviewToFront:_bottomView];
} // end of layoutSubviews
The end.
#end
I don't think there is a public API to do so. It is reasonable to use the last row height as the placeholder rows height.
If you insist on changing the height of placeholder rows, as I know, add an empty dummy cell at the end of your cells is the only way to do so.
Just as you've done.
I want to add a subview in the top of my view, I have to recalculate the origin y value for all of other views and re-position them to leave space for the new added view.
It is very boring, as I know android have relativelayout or linearlayout can help automatically do that.
How to solve this problem easily in ios development?
I've created a library to solve just this problem: CSLinearLayoutView
You use it like this:
// create the linear layout view
CSLinearLayoutView *linearLayoutView = [[[CSLinearLayoutView alloc] initWithFrame:self.view.bounds] autorelease];
linearLayoutView.orientation = CSLinearLayoutViewOrientationVertical;
[self.view addSubview:linearLayoutView];
// create a layout item for the view you want to display and add it to the layout view
CSLinearLayoutItem *item = [CSLinearLayoutItem layoutItemForView:someView];
item.padding = CSLinearLayoutMakePadding(5.0, 10.0, 5.0, 10.0);
item.horizontalAlignment = CSLinearLayoutItemHorizontalAlignmentCenter;
item.fillMode = CSLinearLayoutItemFillModeNormal;
[linearLayoutView addItem:item];
// add more items
I've been trying to do a relative (linear) layout for a while and finally decided to just subclass UIScrollView to get it done.
I started out just replacing layoutSubviews with a simple loop through the subviews that reset the origins while keeping a running Y. But, some unexpected things are added to the scrollview, including UIInlineAutoCorrect views from textfields/views, which means these things were being mangled by the layout. So I added a little bit of logic that uses the tag property of a UIView to determine if I should lay it out:
-(void) layoutSubviews{
CGFloat runningY = 0.0f;
CGFloat widestWidth = 0.0f;
for (UIView *view in self.subviews) {
if (view.tag != 1999) {
continue;
}
view.origin = CGPointMake(view.origin.x, runningY);
runningY += view.height;
if ([view autoresizingMask] == UIViewAutoresizingFlexibleWidth) {
view.width = self.width;
}
if (view.width > widestWidth) {
widestWidth = view.width;
}
}
[self setContentSize:CGSizeMake(widestWidth, runningY)];
}
If you would still like to use unique tags for your views, you should just specify a range of tags that will be included in the layout instead of a single value.
It's not much work to subclass UIView to make sense of methods like -(void)addView:toRightOfView: etc. You could do this as you go, porting only the methods you need. You could then call these in your override of layoutSubviews as Benjamin indicates.
Views can be built using IB or they can be written programmatically; Android scores well here in making layouts readable and you can bring that benefit to iOS views created programmatically. That there are few iOS devices means beyond readability there are not (yet?) many practical benefits to this pattern.
NB. A "XIB" file is an XML file. Open it up in your favourite text editor and take a look.
** EDIT.
Here's a quick example I knocked up. It has not been tested but some thing like this will work in your subclass of UIView (call it UIRelativeView perhaps).
- (void) addSubview:(UIView *) viewOne
toRightOfSubview:(UIView *) viewTwo
{
if (viewTwo == nil ||
[self.subviews contains:viewTwo] == NO)
{
[self addSubview:viewOne];
}
else
{
CGRect frameTwo = viewTwo.frame;
CGPoint originOne = CGPointMake(frameTwo.origin.x + frameTwo.size.width,
frameTwo.origin.y);
CGRect frameOne = CGRectZero;
frameOne.origin = originOne;
frameOne.size = viewOne.frame.size;
[viewOne setFrame:frameOne];
[self addSubview:viewOne];
}
}
- (void) moveSubview:(UIView *) viewOne
toRightOfSubview:(UIView *) viewTwo
{
if (viewTwo == nil ||
[self.subviews contains:viewTwo] == NO)
{
[self addSubview:viewOne];
}
else if ([self.subviews contains:viewOne] == NO)
{
[self addSubview:viewOne toRightOfSubview:viewTwo];
}
else
{
CGRect frameTwo = viewTwo.frame;
CGPoint originOne = CGPointMake(frameTwo.origin.x + frameTwo.size.width,
frameTwo.origin.y);
CGRect frameOne = CGRectZero;
frameOne.origin = originOne;
frameOne.size = viewOne.frame.size;
[viewOne setFrame:frameOne];
}
}
You've got no luck here. iOS doesn't have provisions for positioning the views in different layouts like Android. You need to reposition all the other subviews to make the way for the new view.
There are some view resizing methods like sizeToFit and autoResizingMask but they won't help you in your case here.
iOS is much more focused on pixel accuracy than Android it is, which uses relative layouts as it has to deal with multiple screen sizes. However, in iOS, the Interface Builder is an incredibly good tool included in XCode, which you can use.
Also, if you are just adding subviews in a repetitive manner, you could override the layoutSubviews method and use that to handle to manual labour for you. You mention having to "recalculate the origin y value for all of other views and re-position them to leave space for the new added view" ... You could code that into your layoutSubviews so you don't have to do it yourself each time.
Unfortunately, though, the SDK doesn't have any of this included by default. autoresizingMask's are great but you can't use that for initial layout; it's for automatic really it when rotating only.
As of iOS 9 you can use UIStackView, which works very similarly to LinearLayout: you add views and the stack view arranges them as needed based on your sizing preferences:
Fill will leave three of them their natural size, and make the fourth one take up the most space. It uses Auto Layout's content hugging priority to decide which one to stretch.
Fill Equally will make each subview the same size so they fill all the space available to the stack view.
Fill Proportionally uses the intrinsic content size of each subview to resize them by an equal amount. So view 1 was designed to have twice as much height as views 2, 3 and 4, that ratio will remain when they are resized – all the subviews get proportionally bigger or smaller.
Equal Spacing does not resize the subviews, and instead resizes the spacing between the subviews to fill the space.
Equal Centering is the most complicated, but for many people also the most aesthetically pleasing. It attempts to ensure the centers of each subview are equally spaced.
You can also set spacing between views in the stack view, adding some padding.
WARNING: When adding stack view child views in code you should always use addArrangedSubview() like this:
stackView.addArrangedSubview(someView)
If you try to use plain old addSubview() it won't work correctly, because the stack view won't know to arrange it.
As for removing, you need to be careful to use stackView.removeArrangedSubview(someView) and someView.removeFromSuperview() otherwise the view won't be removed correctly.
You might find my UIStackView tutorial useful.