Getting collection view cells to update after a constraint change - ios

I'm having a problem with auto layout in a collection view cell in iOS 9.
During the collection view's :cellForItemAtIndexPath: I change a constraint:
[[self rootStack] setDistribution:UIStackViewDistributionFillEqually];
or
[[self rootStack] setDistribution:UIStackViewDistributionFillProportionally];
During the cell's preferredLayoutAttributesFittingAttributes: I try to force an additional round of layout:
- (UICollectionViewLayoutAttributes*) preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
[self setNeedsLayout];
[self layoutIfNeeded];
return layoutAttributes;
}
Still, when cells are displayed, some show the effect of the constraint change and some do not. If I scroll a cell that does not display the change offscreen and then scroll back to it, it then displays the change. Moving the constraint changes to the cell's updateConstraints resulted in none of the cells showing the change on first display.
I can fix this for the first cells that are displayed by calling reloadData on the collection view during the collection view controller's viewWillAppear:. But once I start scrolling some of the cells that come into view are not formatted correctly (unless I scroll past them and then scroll back to them).
Is there a way I can make sure that all cells are formatted correctly the first time they are displayed?

Related

Create a UICollectionView that scrolls with another UICollectionView

I have already built out 2 collection views, 1 horizontally scrolling (at the top) & 1 vertically scrolling (at the middle - bottom) that are used to view 2 different sets of content in my Objective-C iOS application similar to this Instagram screenshot:
I am trying to add functionality to make it so that the horizontally scrolling Collection View disappears when the user scrolls up on the vertically scrolling one. What is the best way to accomplish this task? I have looked up tutorials on adding a collection view in another collection view's cell but I cant find anything on just adding a collection view to the 1st cell of another collection view. What would be the best way to accomplish this functionality?
I think you should you use UITableView with UICollectionView. On the screeshot, I think horizontal collectionview is embedded in first cell of the tableview. And when use starts to scroll tableview, first row is gone as you want.
Edit
Create uitableview with 2 prototype cells. Create horizontall collectionview and embed it in first cell of tableview, this is first prototype cell. Then create second prototype cell for images. And when user will start scroll the tableview first cell will gone, as you want.
If you don't want to go with the other suggested method of embedding the horizontal collection view into the top cell of the vertical collection view, you could use the vertical collection view's scrolling callbacks (scrollViewDidScroll, since UICollectionView subclasses UIScrollView). When the vertical collection view scrolls, you can apply a transform to the top collection view to move it off the top of the screen based on the contentOffset of the vertical collection view, and then have it reappear once the contentOffset approaches 0.
Keep in mind that with this approach, the vertical collection view's frame will likely be the height of the screen minus the height of the horizontal collection view. Therefore, you will need a bit of extra logic to expand the vertical collection view's frame to take up the whole screen once the horizontal collection view has disappeared from sight. Otherwise, you will have an awkward blank bar at the top of the screen where the horizontal collection view initially was while you scroll.
You have two scroll view lets call it "cvHorizontal" and "cvVertical".
You can manage scrollViewDidScroll method to hide cvHorizontal when scrolled up and show cvHorizontal when scrolled down.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if (lastContentOffset > scrollView.contentOffset.y){
[self showCategory:YES];
lastContentOffset = scrollView.contentOffset.y;
} else if (lastContentOffset < scrollView.contentOffset.y) {
[self showCategory:NO];
lastContentOffset = scrollView.contentOffset.y;
}
}
-(void)showCategory:(BOOL)flag{
if(flag){
[UIView animateWithDuration:0.6 animations:^{
if(cvHorizontal.hidden ){
cvHorizontal.hidden=NO;
cvHorizontalHeight.constant=65.0f;//manage the cvHorizontal height and all the other constraints calculation if any
}
}];
}else {
[UIView animateWithDuration:0.6 animations:^{
cvHorizontal.hidden=YES;
cvHorizontalHeight.constant=0;
}];
}
}

UITableViewCell detail indicators flying around

I have a UITableView added to a UIViewController inside a UIView. The table view has constraints to fit the size of it's parent view. The view has constraints for it's position and size. If I animate the size of this view, the table view shows new rows, but the new elements, which about to appear, seem to fly around from their initial (not set) position. For example, the right detail indicator of the UITableViewCell flies from the left edge of the cell, the text labels from a slightly different position, etc.
I do the animation like this:
[self.tableView reloadData];
[UIView animateWithDuration:0.3f animations:^{
self.tableViewHeightConstraint.constant = blah;
[self.view layoutIfNeeded];
}];
As you can see, the data reloading takes place before the animation block. Once the animation was played, changing the view size does not animate the table view's contents anymore (I guess it does, but everything is in their place already.)
How can I prevent animating the elements and keep the animation of the frame?
Have a look at calling setAnimationsEnabled on the cells or maybe the cells accessory view.

scrollToItemAtIndexPath destroys UICollectionViewCell layout

I am creating a paginated UICollectionView that scrolls horizontally and whose cells are as big as the collection view so that only one cell is shown at a time. I also want to display the last item first when the collection view first appears, so that the other items are revealed from the left instead of the default where the next items come in from the right.
To do that, I call scrollToItemAtIndexPath: in my view controller's viewDidLayoutSubviews as suggested by this answer. If I put the call to scrollToItemAtIndexPath in viewWillAppear or viewDidLoad, the collection view does not at all scroll to the last item.
However, this call destroys the layout of my collection view cells. For example, if I don't call scrollToItemAtIndexPath:, the collection view (the white area below) looks like the left one--correctly laid out but showing the first item. If I call scrollToItemAtIndexPath:, the collection view does initially display the last item, but the layout is messed up like in the right (the date isn't even showing anymore).
What am I doing wrong?
More info:
I see this error both in iOS 7 and iOS 8.
I use size classes in Xcode 6.1.
The code for viewDidLayoutSubviews:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:self.unit.readings.count - 1 inSection:0];
[self.readingCollectionView scrollToItemAtIndexPath:lastIndexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];
}
Whenever I've had this issue and I have a constant size cell I've worked around it by just setting the contentOffset manually and ignoring the collectionView methods.
self.collectionView.contentOffset = (CGPoint){
.x = self.collectionView.contentSize.width - cell.width,
.y = 0,
};
I put the following in viewWillLayoutSubviews (also works in viewDidLayoutSubviews):
[self.readingCollectionView scrollToItemAtIndexPath:lastIndexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];
[self.readingCollectionView reloadData];
A UI issue persists, however: when the view controller appears, it displays the first item, THEN immediately refreshes to display the last item instead.
To get around this seemingly unresolvable problem, I hacked the UI instead: display a "Loading" label before the view controller appears, and show the collection view in viewDidAppear.

Change height of the "header view" inside UITableView

I added a view "Header View" inside a table view (see structure in screenshot). It has fixed height. I tried to change view's frame size, view is getting smaller, but there is still empty space between header and tableview cells.
I need to change header height dynamically in code. Any suggestions?
I suggest you move the Header View out as a subview of View, set a tag to it, then in viewForHeaderInSection, use the tag to get it. Set the proper height in heightForHeaderInSection. When height changes, refresh tableView. The reason for that is Header View is no longer a subview of the table view, it will always stick to the first table view row.
Also with the current design you can make the Header View a cell, then in the viewForHeaderInSection you can reuse the cell, if you have multiple sections this is the way to go.
In your tableview's delegate, you have to use
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return /*desired height*/;
}
EDIT
According to your comment, if you don't want to change your interface, what you can do is to set a height constraint to that view.
You then make a reference of the constraint to the header of the controller managing the view.
In the code, you can then change the height by changing the value of the constraint and updating the view :
[UIView animateWithDuration:/*animation time in seconds*/ animations:^{
yourHeightConstraint.constant = /*desired height*/;
[self.view layoutIfNeeded];
}];
[self.view updateConstraints];

Animating auto layout constraint change on UICollectionView

Following on from a previous SO question which I asked, I am attempting to animate the change in height of a UICollectionView (which is yellowBox in the example). This change in height is being triggered by modifying the auto layout constraints on the collection view with the following code:
- (IBAction)expandYellowBox:(id)sender {
[UIView animateWithDuration:0.5
animations:^{
self.yellowBoxHeightConstraint.constant += 50;
[self.view layoutIfNeeded];
}];
}
However, when I call [self.view layoutIfNeeded] this results in the collection view being reloaded, so it flashes visibly to the user. However, I don't want the collection view to reload but instead just to animate its height change. Is there any way to either avoid -layoutIfNeeded reloading the collection view, or an alternative way to animate the constraint height change which doesn't call a method which has the side-effect of reloading the collection view?
In case anyone else encounters this problem, it was because I was actually calling [self.collectionView reloadData] a few lines above the animation block (and didn't notice!). It seems that calling -reloadData and -layoutIfNeeded causes the collection view to flash. Removing the call to -reloadData resolves the problem.

Resources