IOS: Weird animation on UICollectionView orientation - ios

The cell size of UICollectionView is the full screen size. And the user can swipe horizontally. The problem is during the orientation, I can see the next or previous cell. (Attached screenshot here. the red cell is current one, the purple cell is the next cell.) And sometimes after orientation, collection view will scroll to wrong cell index. (I use scrollToItemAtIndexPath to force change it back to correct one...)
I tried two ways to solve this problem.
1) use flow layout:
- (UICollectionViewLayoutAttributes*)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
// after orientation, scroll to the correct position.
UICollectionViewLayoutAttributes* attr = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];
[self.collectionView scrollToItemAtIndexPath:itemIndexPath atScrollPosition:UICollectionViewScrollPositionNone animated:NO];
return attr;
}
- (void)prepareForAnimatedBoundsChange:(CGRect)oldBounds
{
NSLog(#"%# prepare animated bounds change", self);
[super prepareForAnimatedBoundsChange:oldBounds];
}
- (void)finalizeAnimatedBoundsChange
{
NSLog(#"%# finalize animated bounds change", self);
[super finalizeAnimatedBoundsChange];
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
CGRect oldBounds = self.collectionView.bounds;
if (!CGSizeEqualToSize(oldBounds.size, newBounds.size)) {
if(oldBounds.size.width > newBounds.size.width)
{
// vertical cell size
self.itemSize = CGSizeMake(768, 968);
}
else{
// horizontal cell size
self.itemSize = CGSizeMake(1024, 712);
}
NSLog(#"%# should invalidate layout", self);
return YES;
}
return NO;
}
2) use in delegate call
// used in will rotate and did rotate, but both does not work well...
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
// do invalidate layout
[self.CollectionView.collectionViewLayout invalidateLayout];
}
// then change the cell size
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
UIInterfaceOrientation o = [[UIApplication sharedApplication] statusBarOrientation];
if (UIInterfaceOrientationIsPortrait(o)) {
return CGSizeMake(768, 968);
} else {
return CGSizeMake(1024, 712);
}
}
I do not want to use fake image to hide layout changes during animation. Thanks!! :)

Related

The UITableViewCell's expansion with long long content in iOS

1.When the content inside the cell is too long, cell expansion, Or then shrink, UITableView will scroll to the back of the cell position.
2.I want cell to roll back to where it started expansion.
my code:
((PartnershipsTableViewCell *)cell).commentSpreadButtonClickHandler = ^() {
// just call - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
[weakSelf.tableView beginUpdates];
[weakCell configUI];
[weakSelf.tableView endUpdates];
// if here use "[weakSelf.tableView reloadData]",
// it can be correct,
// Unless on the first cell which have the expansion button.
};
then update uitableview cell's height. but the result isn't what i want
- (void)configUI {
if ([self.baseModel isKindOfClass: [UserWorldDynamicModel class]]) {
self.model = (id)self.baseModel;
}
[self setupValue];
}
- (void)setupValue {
// setup the property value, and update the constraints with masonry
}
// the button : read less or read more
- (void)setSpreadButton {
NSString *text = self.model.isContentOpen ? #"read less" : #"read more";
if (!self.spreadButton) {
self.spreadButton = [MYSUtil createButtonWithTitle: text target: self sel: #selector(spreadButtonClick:) image: nil font: Font14 color: DarkBlueTextColor cornerRadius: 0];
self.spreadButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
}
if (self.model.shouldShowSpreadButton) {
if (!self.spreadButton.superview) {
[self.whiteBackgroudView addSubview: self.spreadButton];
}
[self.spreadButton setTitle: text forState: UIControlStateNormal];
self.spreadButton.selected = [self.model.isSpreadState intValue];
self.spreadButton.frame = CGRectMake(CGRectGetMinX(self.contentLabel.frame), CGRectGetMaxY(self.contentLabel.frame), 80, 30);
self.tempView = self.spreadButton;
} else {
if (self.spreadButton.superview) {
[self.spreadButton removeFromSuperview];
}
}
}
// calculate the height of the label and compare it with the fixed value
NSMutableAttributedString *string = [self createMutableAttibuteStringWithNSString: text withFont: font];
self.contentLabel.attributedText = string;
// here calculate maxContentLabelHeight with
CGFloat maxContentLabelHeight = self.contentLabel.font.pointSize * (numberOfLines + 1) + 16;
// here calculate the NSMutableAttributedString's height
YYTextContainer *container = [YYTextContainer containerWithSize:CGSizeMake(width, MAXFLOAT)];
YYTextLayout *textLayout = [YYTextLayout layoutWithContainer:container text: string];
CGSize size = textLayout.textBoundingSize;
CGFloat height = size.height;
// then compare the NSMutableAttributedString's height with the fixed value. if true, show the spreadButton
if (height > maxContentLabelHeight) {
self.model.shouldShowSpreadButton = YES;
// storage the real height and temp height, use to calculate the tableView's contentOffset, when cell from expansion state to shrinking state in block.
self.model.contentHeight = height;
self.model.tempContentHeight = maxContentLabelHeight;
}
// if height > maxContentLabelHeight and the property "isContentOpen" of the viewModel, the height value is maxContentLabelHeight, Or not, the height value is height
if (!self.model.isContentOpen && height > maxContentLabelHeight) {
height = maxContentLabelHeight;
}
// no matter cell is expansion state or shrinking state, reset label's frame.
self.contentLabel.frame = CGRectMake(x, CGRectGetMaxY(self.headerImageView.frame) + Margin_Top, width, height);
readMoreļ¼ readLess block
before tableView reloadData on mainQueue, record it's contentOffset, Used to calculate the position of the tableView need to scroll. like this:
CGPoint point = weakSelf.tableView.contentOffset;
reloadData : refresh tableView On mainQueue.
when reloadData complete, scroll tableView to the position which expanded. when tableView from expansion state to shrinking state, and the height of expansion state is greater than 70% of the Screen's height, scroll the tableView (70% is ma condition, you can change is according to your condition)
- (UITableViewCell *)tableView:(UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath {
// here is your code
PartnershipsTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: cellIdentifierString];
[cell config];
((PartnershipsTableViewCell *)cell).spreadButtonClickHandler = ^() {
CGPoint point = weakSelf.tb.contentOffset;
[weakSelf.tb reloadData];
if (!baseModel.isContentOpen && baseModel.contentHeight > SCREEN_HEIGHT * 0.7) {
point.y -= baseModel.contentHeight - baseModel.tempContentHeight;
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.tb.contentOffset = point;
});
}
};
return cell;
}
- (CGFloat)tableView:(UITableView *) tableView heightForRowAtIndexPath:(NSIndexPath *) indexPath {
return 70;
}
in my issue, I find a another interesting problem, when use the follow method in your code. if your cell have great changes, especially the height of the cell. when you use the method [tableView reloadData], you'd better not use the follow method.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 70;
}
then use the method [tableView reloadData] to refresh UI, tableView will scroll to any position, so I delete it. because, this method use to estimated height of cell, then use to estimate tableView's contentSize, if the height between estimated and actual is bigger difference, use the method [tableView reloadData], will cause the tableView scroll to anywhere.(don't ask me how to know, this is a painful process for me).
I have solved the problem, leave some notes to myself, and for every one, and hope my solution can help you too.
thanks for #pckill and #Theorist, without your suggestion, I can't solve my question so perfect, thank you very much.
#Theorist I have reedited my code in my mind. Perhaps, the readability is better now.

UIDatePickers height is not animating?

I have a UIDatePicker in a static UITableViewCell with auto layout applied (to the datePicker). The datePicker is hidden until a button is selected. When the button is selected, the datePicker becomes visible, and the cells height gets bigger.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 1)
{
if (self.datePickerView.hidden == YES) {
return 252 - self.datePickerView.bounds.size.height;
}
else return 252;
}
tableView.estimatedRowHeight = 150;
return UITableViewAutomaticDimension;
}
- (IBAction)datePicker:(id)sender
{
if (self.datePickerView.hidden == YES)
{
[self.datePickerView setHidden:NO];
CGFloat originalHeight = 162;
[self.datePickerView setFrame:CGRectMake(self.datePickerView.frame.origin.x, self.datePickerView.frame.origin.y, self.datePickerView.frame.size.width, 0)];
[UIDatePicker animateWithDuration:1.0 animations:^(){
[self.datePickerView setFrame:CGRectMake(self.datePickerView.frame.origin.x, self.datePickerView.frame.origin.y, self.datePickerView.frame.size.width, originalHeight)];
}];
} else {
[self.datePickerView setHidden:YES];
}
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
When the button gets selected, the cells height is animated, but the datePicker isn't. It just appears, and gets in the way of everything until the cell is finished animating.
How can I get the datePicker to animate its height, so it should animate together with the cell?
Updating frames would not work with auto-layout. you'll have to update your constraint to do the animation.
In your case, you should have separate cell for your datePickerview and insert/ remove the cell from your tableview in order to hide and show the pickerview with animation.
[Edited]
Sample Code here: http://www.filedropper.com/tableviewsample

UIImageView autolayout issue in UICollectionViewCell on iOS7 but on iOS8 it's ok

Have problem with reducing image size in ImageView.
In the CollectionViewCell have ImageView with constraints: two horizontal and two vertical spaces.
First screen is iOS7 and second screen is iOS8.
ImageURL it's custom class for load image by URL, it works ok, also set a image like
_clothesImageView.image = [UIImage imageNamed:#"imageName.png"];
- (void)configCellWithClothesModel:(ClothesModel *)clothesModel
{
[_clothesImageView setImageURL:[NSURL URLWithString:clothesModel.imageURL]];
}
ClothesModel:
- (instancetype)initWithDict:(NSDictionary *)dict
{
self = [super init];
if (self)
{
_isShop = #(YES);
self.title = [self checkNullAndNill:dict[kTitle]];
self.info = [self checkNullAndNill:dict[kDescription_Short]];
self.artNumber = [self checkNullAndNill:dict[kArtNumber]];
self.afLink = [self checkNullAndNill:dict[kDeeplink1]];
self.imageURL = [self checkNullAndNill:dict[kImg_url]];
_isCart = #(NO);
}
return self;
}
//==============================================================================
- (id)checkNullAndNill:(id)object
{
if (object == nil)
return #"";
else if (object == [NSNull null])
return #"";
else
return object;
}
//==============================================================================
- (UICollectionViewCell *)configCellWithType:(NSString *)type collectionView:(UICollectionView *)collectionView indexPath:(NSIndexPath *)indexPath
{
ClothesCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[type isEqualToString:#"Outfits"] ? #"CellOutfits" : #"Cell" forIndexPath:indexPath];
[cell configCellWithClothesModel:self];
return cell;
}
//==============================================================================
In Xcode6 Interface builder doesn't show you the content view of the Cell.
CollectionViewCell has content view in iOS7 and its frame is set to default values (0,0,50,50).
You need to add this code in CollectionViewCell subclass, so contentView will resize itself
- (void)awakeFromNib
{
[super awakeFromNib];
self.contentView.frame = self.bounds;
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
It can really depends on Autolayout settings.
UIImageViews usually set their sizes based on their intrinsic content size that is set to be the size of the image they are hosting.
If your collection view cell has not the sufficient number of constraints what you see can depend on the autolayout engine implementation on different iOS.
Also check the content mode of the image view as someone stated in the comments.

UICollectionViewFlowLayout ignoring section insets for items of different heights - bug or expected?

I'm using UICollectionViewFlowLayout and wanted to apply section insets like below. All my items have the same width but varying heights. The insets work when items within the same section are the same height but not when they are different heights in the same section. Is this expected behaviour for this layout? Do I need to subclass and make a custom one or is something missing?
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
UIEdgeInsets insets = UIEdgeInsetsZero;
CGFloat big = 30;
CGFloat small = 10;
if (section < 5) {
insets = UIEdgeInsetsMake(0, big, 0, small);
} else {
insets = UIEdgeInsetsMake(0, small, 0, big);
}
return insets;
}
For some reason the UICollectionViewFlowLayout has the behaviour to center the cells if you have a single item each row. It ignores the section insets then.
You can solve this issue by overriding the UICollectionViewFlowLayout and change the following two methods:
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *attribute = [super layoutAttributesForItemAtIndexPath:indexPath];
[self fixLayoutAttributeInsets:attribute];
return attribute;
}
And
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *results = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *attribute in results)
{
[self fixLayoutAttributeInsets:attribute];
}
return results;
}
The magic:
- (void)fixLayoutAttributeInsets:(UICollectionViewLayoutAttributes *)attribute
{
if ([attribute representedElementKind])
{ //nil means it is a cell, we do not want to change the headers/footers, etc
return;
}
//Get the correct section insets
UIEdgeInsets sectionInsets;
if ([[[self collectionView] delegate] respondsToSelector:#selector(collectionView:layout:insetForSectionAtIndex:)])
{
sectionInsets = [(id<UICollectionViewDelegateFlowLayout>)[[self collectionView] delegate] collectionView:[self collectionView] layout:self insetForSectionAtIndex:[[attribute indexPath] section]];
}
else
{
sectionInsets = [self sectionInset];
}
//Adjust the x position of the view, the size should be correct or else do more stuff here
CGRect frame = [attribute frame];
frame.origin.x = sectionInsets.left;
[attribute setFrame:frame];
}
This solution does not work (and is not needed) if you have multiple cells on a single row so you should check for that case with a boolean or whatever you like... This is just a simple example for this scenario
Doesn't seem to be possible with varying heights, so I made the cells full width and mange the padding logic inside the cell class itself.

CollectionView orientation issues

I currently have a collection view with a grid of images on it, when selecting an image this segues to another collection view with a full screen image on it.
To do this I am using:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"collectionView"])
{
QuartzDetailViewController *destViewController = (QuartzDetailViewController *)segue.destinationViewController;
NSIndexPath *indexPath = [[self.collectionView indexPathsForSelectedItems] objectAtIndex:0];
destViewController.startingIndexPath = indexPath;
[destViewController.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
[self.collectionView deselectItemAtIndexPath:indexPath animated:NO];
}
}
and then in my detail view:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (_startingIndexPath) {
NSInteger currentIndex = floor((scrollView.contentOffset.x - scrollView.bounds.size.width / 2) / scrollView.bounds.size.width) + 1;
if (currentIndex < [self.quartzImages count]) {
self.title = self.quartzImages[currentIndex][#"name"];
}
}
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
layout.itemSize = self.view.bounds.size;
[self.collectionView scrollToItemAtIndexPath:self.startingIndexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
}
when I rotate to landscape the image disappears and the title at the top changes, if I go back to the grid and then select a cell again it will segue again to the detail view but the title is for a different cell and the image shows half and half between two different images.
I am getting the following message as well on the log when I rotate the device:
2013-06-04 17:39:53.869 PhotoApp[6866:907] the behavior of the UICollectionViewFlowLayout is not defined because:
2013-06-04 17:39:53.877 PhotoApp[6866:907] the item height must be less that the height of the UICollectionView minus the section insets top and bottom values.
How can I correct this so it will support orientation.
Thanks
You need to invalidate collection view's layout since sizes of cells in portrait orientation are too big for landscape orientation.
Writing something like this in your parent UIViewController should fix your issue:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self.collectionView.collectionViewLayout invalidateLayout];
}

Resources