UITableView Dynamic Height not changing iOS - ios

Drag and drop UITableViewHeader
You can look at an orange color UITextView.
I set the height constant of the tableview is zero.
After reloading the tableview total height of UITableView showing same as previous(as no UITextView showing)
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.heightConstCommentBox.constant = 0;
[self configureTableviewFooter];
}
-(void)configureTableviewFooter{
//More button Configure
height = 50 +[self recusiveDescription:self.viewFooter] ;
// Assign new frame
self.viewFooter.frame = CGRectMake(self.viewFooter.frame.origin.x,
self.viewFooter.frame.origin.y,self.viewFooter.frame.size.width , height); // viewFooter is container view of tableFooterView
self.tableView.tableFooterView = self.viewFooter;
[self.tableView reloadData];
}
- (float )recusiveDescription:(UIView *)view
{
NSString *s = #"";
float height = 0;
NSArray *subviews = [view subviews];
if ([subviews count] == 0) return 0;
for (UIView *subView in subviews) {
height = height + subView.frame.size.height ;
[self recusiveDescription:subView];
}
return height;
}
After reloading the tableview , table view footer size not changing.

In method recusiveDescription:(UIView *)view you use the frame property of the subview. But for the autolayout it is not correct. To get future size of the view, call:
- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize
withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority
verticalFittingPriority:(UILayoutPriority)verticalFittingPriority;
This method returns the optimal size for the view based on the provided constraint priorities.
Also I did not understand why do you call recusiveDescription in recusiveDescription: and do not use result value.

u can use following in case u used custom cell for table.
in following code replace postss[index.row].post with your label content and +230 is minimum height of your cell.
or u can say its height of other view other than label.
automatic dimension method will work only if u have 1 label or 1 textfield,1 textview.
and don't fix your label's height.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let heightOfRow = self.calculateHeight(inString: postss[indexPath.row].post)
return (heightOfRow + 230)
}
//custom function for calculate dynamic height
func calculateHeight(inString:String) -> CGFloat
{
let messageString = inString
let attributes : [NSAttributedString.Key : Any] = [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 15.0)]
let attributedString : NSAttributedString = NSAttributedString(string: messageString, attributes: attributes)
let rect : CGRect = attributedString.boundingRect(with: CGSize(width: 370, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil)
let requredSize:CGRect = rect
return requredSize.height
}

Related

GrowingTextView or expandable textview inside UItableview cell

I wish to implement an expandable textview inside the table cell, I found the GrowingTextView but I still failed to implement.I need to get input from user and the cell will auto resize when the users typing. Is there any easier implementation or guide on this? Thanks all
Actually you dont need 3rd party library to do this.
Change your GrowingTextView to UILabel
Config you UILabel to Top, Left, Right, and Bottom to the cell, and make "Title" label dependent on the UILabel
this is important because the cell size is dependant of the content of UILabel.
Set numberOfLines to 0 and
Set lineBreakMode to word wrapping or character warpping
Set tableView.rowHeight = UITableViewAutomaticDimension and tableView.estimatedRowHeight = 150 // or wtever you found reasonable
For task like these i suggest you can do more research on Internet instead of using a 3rd party library so fast, the keyword here is dynamic table view cell which by searching on Google there are lots of tutorials helping you out without using 3rd party library.
You can achieve it with native UITextView.
Implement this in the text view's delegate
- (void)textViewDidChange:(UITextView *)textView{
CGRect oldFrame = textView.frame;
CGSize constraint = CGSizeMake(yourTextViewWidth, MAXFLOAT);
CGRect rect = [textView.text boundingRectWithSize:constraint options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:textView.font} context:nil];
CGSize size = CGSizeMake(rect.size.width, rect.size.height);
[textView setFrame:CGRectMake(oldFrame.origin.x, oldFrame.origin.y, oldFrame.size.width, size.height + 5)];
// These 2 lines to force UITableView to redo the height calculation.
[self.yourTableView beginUpdates];
[self.yourTableView endUpdates];
}
Then, implement this in the table view's delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *yourTextViewText;
// You get the text either from the same way you did in cellForRowAtIndexPath dataSource
// or if you already referenced UITextView as your property you can easily retrieve the text by calling self.yourTextView.text
CGSize constraint = CGSizeMake(yourTextViewWidth, MAXFLOAT);
CGRect rect = [text boundingRectWithSize:constraint options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:[UIFont systemFontOfSize:yourFontSize]} context:nil];
CGSize size = CGSizeMake(rect.size.width, rect.size.height);
CGFloat height = MAX(size.height, yourDefaultCellHeight);
return height + 5;
}
UPDATE Swift version converted by Swiftify v4.1.6738 - https://objectivec2swift.com/
func textViewDidChange(_ textView: UITextView) {
let oldFrame: CGRect = textView.frame
let constraint = CGSize(width: yourTextViewWidth, height: MAXFLOAT)
let rect: CGRect = textView.text.boundingRect(with: constraint, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: textView.font], context: nil)
let size = CGSize(width: rect.size.width, height: rect.size.height)
textView.frame = CGRect(x: oldFrame.origin.x, y: oldFrame.origin.y, width: oldFrame.size.width, height: size.height + 5)
// These 2 lines to force UITableView to redo the height calculation.
yourTableView.beginUpdates()
yourTableView.endUpdates()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let yourTextViewText: String
// You get the text either from the same way you did in cellForRowAtIndexPath dataSource
// or if you already referenced UITextView as your property you can easily retrieve the text by calling self.yourTextView.text
let constraint = CGSize(width: yourTextViewWidth, height: MAXFLOAT)
let rect: CGRect = text.boundingRect(with: constraint, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: yourFontSize)], context: nil)
let size = CGSize(width: rect.size.width, height: rect.size.height)
let height: CGFloat = max(size.height, yourDefaultCellHeight)
return height + 5
}

How to resize table cell based on textview?

I have a UITextView in a custom UITableViewCell. The textview delegate is assigned in the tableviewcell custom class. Textview scrolling is disabled. Text loads into each textview and is multiline. But the text is always clipped because the cell height doesn't change.
I have the following in viewDidLoad of the tableview controller:
tableView.estimatedRowHeight = 56.0
tableView.rowHeight = UITableViewAutomaticDimension
Any idea why this isn't working?
Try my answer its work perfectly.
var record : NSArray = NSArray()
var hight: CGFloat = 0.0
Put this code in viewDidLoad()
record = ["I have a UITextView in a custom UITableViewCell. The textview delegate is assigned in the tableviewcell custom class." ,"Textview scrolling is disabled. Text loads into each textview and is multiline. But the text is always clipped because the cell height doesn't change.","I have the following in viewDidLoad of the tableview controller:"," have a UITextView in a custom UITableViewCell. The textview delegate is assigned in the tableviewcell custom class.","Textview scrolling is disabled. Text loads into each textview and is multiline. But the text is always clipped because the cell height doesn't change.","I have the following in viewDidLoad of the tableview controller:","i just give you one link at put place i use label and you can now use your textview and give same constrain that i give in that link and try it so your problem will be solve","I have the following in viewDidLoad of the tableview controller:"];
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return record.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Textviewcell", forIndexPath: indexPath)
let textview: UITextView = (cell.viewWithTag(5) as! UITextView)
textview.text = record.objectAtIndex(indexPath.row) as? String
return cell
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
// 7.1>
hight = self.findHeightForText(self.record.objectAtIndex(indexPath.row) as! String, havingWidth: self.view.frame.size.width - 10, andFont: UIFont.systemFontOfSize(14.0)).height
return 44 + hight
}
func findHeightForText(text: String, havingWidth widthValue: CGFloat, andFont font: UIFont) -> CGSize {
var size = CGSizeZero
if text.isEmpty == false {
let frame = text.boundingRectWithSize(CGSizeMake(widthValue, CGFloat.max), options: .UsesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
size = CGSizeMake(frame.size.width, ceil(frame.size.height))
}
return size
}
In Swift 3.0
func findHeightForText(text: String, havingWidth widthValue: CGFloat, andFont font: UIFont) -> CGSize {
var size = CGSize.zero
if text.isEmpty == false {
let frame = text.boundingRect(with: CGSize(width: widthValue, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
size = CGSize(width: frame.size.width, height: ceil(frame.size.height))//CGSizeMake(frame.size.width, ceil(frame.size.height))
}
return size
}
Here are some screen shot .Storyboard
Runtime tableview with UITextView
You should use the delegate method
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
to set the height for the row. Then, inside that method, return the height that the cell should be by calculating the height of the textview which will occupy the space. You should use the function:
-(CGFloat)heightForTextViewRectWithWidth:(CGFloat)width andText:(NSString *)text withBuffer:(float)buffer
{
UIFont * font = [UIFont fontWithName:#"YourFontName" size:15.0f]; // Replace with your font name and size
NSDictionary *attributes = #{NSFontAttributeName: font};
// this returns us the size of the text for a rect but assumes 0, 0 origin, width is the width of that your text box will occupy.
// Ex. If you text box has padding of 12 both trailing and leading to the cell, then width should be the cell's width minus 24.
CGRect rect = [text boundingRectWithSize:CGSizeMake(width, MAXFLOAT)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:attributes
context:nil];
// return height of rect
return rect.size.height;
}
This will determine the rect that your text view will occupy in the cell. The height that is returned is equal to the height of the text view, so if you want the cell to be taller than the text box, add that padding.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString * yourText = commentDict.text; // or however you are getting the text
float widthPadding = 24.0f;
float heightPadding = 16.0f;
float height = [self heightForTextViewRectWithWidth: (tableView.frame.size.width - widthPadding) andText:yourText];
return height + heightPadding;
}
Hope this helped!

Add read more to label and expanding UITableViewCell

I am populating a UITableView with labels. How can i add a "read more" button at the end of the label only if the number of characters in that label exceed 120 and make the UItableViewCell expand to fit the new big label? Please help. (SWIFT)
I think you will have to manually adjust the string as it goes into the label, turn it into a NSAttributedString with 120 characters (with the ....) then append a NSLinkAttributeName for the read more part with a dummy URL, override textView shouldInteractWithURL and put expand function there (which should be just reload the cell with full string)
Just a Swift 3 version of #VRAwesome answer:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
var height: CGFloat = 333
// assign initial height
let lblFrame = CGRect(x: CGFloat(40), y: CGFloat(50), width: CGFloat(194), height: CGFloat(50))
//assign initial frame
let strText = currentChallenge.description as NSString
let rect = strText.boundingRect(with: CGSize(width: lblFrame.size.width, height: CGFloat(Float.greatestFiniteMagnitude)), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
if rect.size.height > lblFrame.size.height || rect.size.height < lblFrame.size.height {
let diff = rect.size.height - lblFrame.size.height
height = height + diff + 20
}
if isRowOpen[indexPath.row] == true {
let rect = strSharedText.boundingRect(with: CGSize(width: lblFrame.size.width, height: CGFloat(Float.greatestFiniteMagnitude)), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
if rect.size.height > lblFrame.size.height || rect.size.height < lblFrame.size.height {
let diff = rect.size.height - lblFrame.size.height;
height = height + diff + 20;
}
}
return height
}
First calculate chars of string which you are going to assign to label. If that is more than 120 chars then create and add button(Read More) to the bottom right corner of cell.
And when press Read More then calculate size of string using boundingRectWithSize then reload that particular cell it will update size of cell.
Create one global boolean array :
bool isRowOpen[];
Your methods should be like :
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat height = 420; // assign initial height
CGRect lblFrame = CGRectMake(40, 50, 194, 50); //assign initial frame
NSString *strText = [someArray objectAtIndex:indexPAth.row];
CGRect rect = [strText boundingRectWithSize:CGSizeMake(lblFrame.size.width, FLT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil];
if (rect.size.height > lblFrame.size.height || rect.size.height < lblFrame.size.height) {
float diff = rect.size.height - lblFrame.size.height;
height = height+diff+20;
}
if (isRowOpen[indexPath.row] == TRUE) {
CGRect rect = [strSharedText boundingRectWithSize:CGSizeMake(lblFrame.size.width, FLT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil];
if (rect.size.height > lblFrame.size.height || rect.size.height < lblFrame.size.height) {
float diff = rect.size.height - lblFrame.size.height;
height = height+diff+20;
}
}
return height;
}
And last on Button Read More click event reload that particular cell. when you want expand, update isRowOpen[selectedRow] = TRUE. when you want collapse then again update isRowOpen[selectedRow] = FALSE. So it will show effect what you want.

How to create a centered UICollectionView like in Spotify's Player

I am have a lot of difficulty trying to create a UICollectionView like in Spotify's Player that acts like this:
The problem for me is two fold.
1) How do I center the cells so that you can see the middle cell as well as the one of the left and right.
If I create cells that are square and add spacing between each cell, the cells are correctly displayed but are not centered.
2) With pagingEnabled = YES, the collectionview correctly swipes from one page to another. However, without the cells being centered, it simply moves the collection view over a page which is the width of the screen. So the question is how do you make the pages move so you get the effect above.
3) how do you animate the size of the cells as they move
I don't want to worry about this too much. If I can get that to work it would be great, but the harder problems are 1 and 2.
The code I have currently is a simple UICollectionView with normal delegate setup and custom UICollectionview cells that are squares. Maybe I neeed to subclass UICollectionViewFlowLayout? Or maybe I need to turn pagingEnabled to NO and then use custom swipe events? Would love any help!
In order to create an horizontal carousel layout, you'll have to subclass UICollectionViewFlowLayout then override targetContentOffset(forProposedContentOffset:withScrollingVelocity:), layoutAttributesForElements(in:) and shouldInvalidateLayout(forBoundsChange:).
The following Swift 5 / iOS 12.2 complete code shows how to implement them.
CollectionViewController.swift
import UIKit
class CollectionViewController: UICollectionViewController {
let collectionDataSource = CollectionDataSource()
let flowLayout = ZoomAndSnapFlowLayout()
override func viewDidLoad() {
super.viewDidLoad()
title = "Zoomed & snapped cells"
guard let collectionView = collectionView else { fatalError() }
//collectionView.decelerationRate = .fast // uncomment if necessary
collectionView.dataSource = collectionDataSource
collectionView.collectionViewLayout = flowLayout
collectionView.contentInsetAdjustmentBehavior = .always
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
}
}
ZoomAndSnapFlowLayout.swift
import UIKit
class ZoomAndSnapFlowLayout: UICollectionViewFlowLayout {
let activeDistance: CGFloat = 200
let zoomFactor: CGFloat = 0.3
override init() {
super.init()
scrollDirection = .horizontal
minimumLineSpacing = 40
itemSize = CGSize(width: 150, height: 150)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepare() {
guard let collectionView = collectionView else { fatalError() }
let verticalInsets = (collectionView.frame.height - collectionView.adjustedContentInset.top - collectionView.adjustedContentInset.bottom - itemSize.height) / 2
let horizontalInsets = (collectionView.frame.width - collectionView.adjustedContentInset.right - collectionView.adjustedContentInset.left - itemSize.width) / 2
sectionInset = UIEdgeInsets(top: verticalInsets, left: horizontalInsets, bottom: verticalInsets, right: horizontalInsets)
super.prepare()
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let collectionView = collectionView else { return nil }
let rectAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes }
let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.frame.size)
// Make the cells be zoomed when they reach the center of the screen
for attributes in rectAttributes where attributes.frame.intersects(visibleRect) {
let distance = visibleRect.midX - attributes.center.x
let normalizedDistance = distance / activeDistance
if distance.magnitude < activeDistance {
let zoom = 1 + zoomFactor * (1 - normalizedDistance.magnitude)
attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1)
attributes.zIndex = Int(zoom.rounded())
}
}
return rectAttributes
}
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView else { return .zero }
// Add some snapping behaviour so that the zoomed cell is always centered
let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.frame.width, height: collectionView.frame.height)
guard let rectAttributes = super.layoutAttributesForElements(in: targetRect) else { return .zero }
var offsetAdjustment = CGFloat.greatestFiniteMagnitude
let horizontalCenter = proposedContentOffset.x + collectionView.frame.width / 2
for layoutAttributes in rectAttributes {
let itemHorizontalCenter = layoutAttributes.center.x
if (itemHorizontalCenter - horizontalCenter).magnitude < offsetAdjustment.magnitude {
offsetAdjustment = itemHorizontalCenter - horizontalCenter
}
}
return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
// Invalidate layout so that every cell get a chance to be zoomed when it reaches the center of the screen
return true
}
override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext
context.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size
return context
}
}
CollectionDataSource.swift
import UIKit
class CollectionDataSource: NSObject, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 9
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
return cell
}
}
CollectionViewCell.swift
import UIKit
class CollectionViewCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .green
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Expected result:
Source:
WWDC 2012 session 219 - "Advanced Collection Views and Building Custom Layouts"
Well, I made UICollectionview moving just like this, yesterday.
I can share my code with you :)
Here's my storyboard
make sure uncheck 'Paging Enabled'
Here's my code.
#interface FavoriteViewController () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
{
NSMutableArray * mList;
CGSize cellSize;
}
#property (weak, nonatomic) IBOutlet UICollectionView *cv;
#end
#implementation FavoriteViewController
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// to get a size.
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
CGRect screenFrame = [[UIScreen mainScreen] bounds];
CGFloat width = screenFrame.size.width*self.cv.frame.size.height/screenFrame.size.height;
cellSize = CGSizeMake(width, self.cv.frame.size.height);
// if cell's height is exactly same with collection view's height, you get an warning message.
cellSize.height -= 1;
[self.cv reloadData];
// setAlpha is for hiding looking-weird at first load
[self.cv setAlpha:0];
}
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self scrollViewDidScroll:self.cv];
[self.cv setAlpha:1];
}
#pragma mark - scrollview delegate
- (void) scrollViewDidScroll:(UIScrollView *)scrollView
{
if(mList.count > 0)
{
const CGFloat centerX = self.cv.center.x;
for(UICollectionViewCell * cell in [self.cv visibleCells])
{
CGPoint pos = [cell convertPoint:CGPointZero toView:self.view];
pos.x += cellSize.width/2.0f;
CGFloat distance = fabs(centerX - pos.x);
// If you want to make side-cell's scale bigger or smaller,
// change the value of '0.1f'
CGFloat scale = 1.0f - (distance/centerX)*0.1f;
[cell setTransform:CGAffineTransformMakeScale(scale, scale)];
}
}
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{ // for custom paging
CGFloat movingX = velocity.x * scrollView.frame.size.width;
CGFloat newOffsetX = scrollView.contentOffset.x + movingX;
if(newOffsetX < 0)
{
newOffsetX = 0;
}
else if(newOffsetX > cellSize.width * (mList.count-1))
{
newOffsetX = cellSize.width * (mList.count-1);
}
else
{
NSUInteger newPage = newOffsetX/cellSize.width + ((int)newOffsetX%(int)cellSize.width > cellSize.width/2.0f ? 1 : 0);
newOffsetX = newPage*cellSize.width;
}
targetContentOffset->x = newOffsetX;
}
#pragma mark - collectionview delegate
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return mList.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"list" forIndexPath:indexPath];
NSDictionary * dic = mList[indexPath.row];
UIImageView * iv = (UIImageView *)[cell.contentView viewWithTag:1];
UIImage * img = [UIImage imageWithData:[dic objectForKey:kKeyImg]];
[iv setImage:img];
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return cellSize;
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
CGFloat gap = (self.cv.frame.size.width - cellSize.width)/2.0f;
return UIEdgeInsetsMake(0, gap, 0, gap);
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
return 0;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
{
return 0;
}
Key code of make cell centered is
scrollViewWillEndDragging
insetForSectionAtIndex
Key code of animate the size is
scrollviewDidScroll
I wish this helps you
P.S.
If you want to change alpha just like the image that you uploaded, add [cell setalpha] in scrollViewDidScroll
As you have said in the comment you want that in the Objective-c code, there is a very famous library called iCarousel which can be helpful in completing your requirement.Link: https://github.com/nicklockwood/iCarousel
You may use 'Rotary' or 'Linear' or some other style with little or no modification to implement the custom view
To implement it you have implement only some delegate methods of it and it's working for ex:
//specify the type you want to use in viewDidLoad
_carousel.type = iCarouselTypeRotary;
//Set the following delegate methods
- (NSInteger)numberOfItemsInCarousel:(iCarousel *)carousel
{
//return the total number of items in the carousel
return [_items count];
}
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view
{
UILabel *label = nil;
//create new view if no view is available for recycling
if (view == nil)
{
//don't do anything specific to the index within
//this `if (view == nil) {...}` statement because the view will be
//recycled and used with other index values later
view = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200.0f, 200.0f)];
((UIImageView *)view).image = [UIImage imageNamed:#"page.png"];
view.contentMode = UIViewContentModeCenter;
label = [[UILabel alloc] initWithFrame:view.bounds];
label.backgroundColor = [UIColor clearColor];
label.textAlignment = NSTextAlignmentCenter;
label.font = [label.font fontWithSize:50];
label.tag = 1;
[view addSubview:label];
}
else
{
//get a reference to the label in the recycled view
label = (UILabel *)[view viewWithTag:1];
}
//set item label
label.text = [_items[index] stringValue];
return view;
}
- (CGFloat)carousel:(iCarousel *)carousel valueForOption:(iCarouselOption)option withDefault:(CGFloat)value
{
if (option == iCarouselOptionSpacing)
{
return value * 1.1;
}
return value;
}
You can check the full working demo from 'Examples/Basic iOS Example' which is included with the Github repository link
As it is old and popular you can find some related tutorials for it and it will also be much stable than the custom code implementation
I wanted similar behavior a little while back, and with the help of #Mike_M I was able to figure it out. Although there are many, many way to do this, this particular implementation is to create a custom UICollectionViewLayout.
Code below(gist can be found here: https://gist.github.com/mmick66/9812223)
Now it's important to set the following: *yourCollectionView*.decelerationRate = UIScrollViewDecelerationRateFast, this prevents cells being skipped by a quick swipe.
That should cover part 1 and 2. Now, for part 3 you could incorporate that in the custom collectionView by constantly invalidating and updating, but it's a bit of a hassle if you ask me. So another approach would be to to set a CGAffineTransformMakeScale( , ) in the UIScrollViewDidScroll where you dynamically update the cell's size based on it's distance from the center of the screen.
You can get the indexPaths of the visible cells of the collectionView using [*youCollectionView indexPathsForVisibleItems] and then getting the cells for these indexPaths. For every cell, calculate the distance of its center to the center of yourCollectionView
The center of the collectionView can be found using this nifty method: CGPoint point = [self.view convertPoint:*yourCollectionView*.center toView:*yourCollectionView];
Now set up a rule, that if the cell's center is further than x away, the size of the cell is for example the 'normal size', call it 1. and the closer it gets to the center, the closer it gets to twice the normal size 2.
then you can use the following if/else idea:
if (distance > x) {
cell.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
} else if (distance <= x) {
float scale = MIN(distance/x) * 2.0f;
cell.transform = CGAffineTransformMakeScale(scale, scale);
}
What happens is that the cell's size will exactly follow your touch. Let me know if you have any more questions as I'm writing most of this out of the top of my head).
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)offset
withScrollingVelocity:(CGPoint)velocity {
CGRect cvBounds = self.collectionView.bounds;
CGFloat halfWidth = cvBounds.size.width * 0.5f;
CGFloat proposedContentOffsetCenterX = offset.x + halfWidth;
NSArray* attributesArray = [self layoutAttributesForElementsInRect:cvBounds];
UICollectionViewLayoutAttributes* candidateAttributes;
for (UICollectionViewLayoutAttributes* attributes in attributesArray) {
// == Skip comparison with non-cell items (headers and footers) == //
if (attributes.representedElementCategory !=
UICollectionElementCategoryCell) {
continue;
}
// == First time in the loop == //
if(!candidateAttributes) {
candidateAttributes = attributes;
continue;
}
if (fabsf(attributes.center.x - proposedContentOffsetCenterX) <
fabsf(candidateAttributes.center.x - proposedContentOffsetCenterX)) {
candidateAttributes = attributes;
}
}
return CGPointMake(candidateAttributes.center.x - halfWidth, offset.y);
}
pagingEnabled should not be enabled as it needs each cell to be the width of you view which will not work for you since you need to see the edges of other cells. For your points 1 and 2. I think you'll find what you need here from one of my late answers to another question.
The animation of the cell sizes can be achieved by subclassing UIcollectionviewFlowLayout and overriding layoutAttributesForItemAtIndexPath: Within that modify the layout attributes provided by first calling super and then modify the layout attributes size based on the position as it relates to the window centre.
Hopefully this helps.
If you want to have uniform spacing between cells you can replace the following method in ZoomAndSnapFlowLayout from Imanou Petit's solution:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let collectionView = collectionView else { return nil }
let rectAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes }
let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.frame.size)
let visibleAttributes = rectAttributes.filter { $0.frame.intersects(visibleRect) }
// Keep the spacing between cells the same.
// Each cell shifts the next cell by half of it's enlarged size.
// Calculated separately for each direction.
func adjustXPosition(_ toProcess: [UICollectionViewLayoutAttributes], direction: CGFloat, zoom: Bool = false) {
var dx: CGFloat = 0
for attributes in toProcess {
let distance = visibleRect.midX - attributes.center.x
attributes.frame.origin.x += dx
if distance.magnitude < activeDistance {
let normalizedDistance = distance / activeDistance
let zoomAddition = zoomFactor * (1 - normalizedDistance.magnitude)
let widthAddition = attributes.frame.width * zoomAddition / 2
dx = dx + widthAddition * direction
if zoom {
let scale = 1 + zoomAddition
attributes.transform3D = CATransform3DMakeScale(scale, scale, 1)
}
}
}
}
// Adjust the x position first from left to right.
// Then adjust the x position from right to left.
// Lastly zoom the cells when they reach the center of the screen (zoom: true).
adjustXPosition(visibleAttributes, direction: +1)
adjustXPosition(visibleAttributes.reversed(), direction: -1, zoom: true)
return rectAttributes
}

self-sizing uitableviewcell not recalculate content size when subview instrinsic content size has changed

everyone, i have issue about uitableviewcell
custom uitableview cell which has subviews, one of which is custom image view named CustomIntrinsicImageView, and code as below:
class CustomIntrinsicImageView: UIImageView {
var _preferredMaxLayoutWidth: CGFloat!
var preferredMaxLayoutWidth: CGFloat! {
get {
return _preferredMaxLayoutWidth
}
set {
_preferredMaxLayoutWidth = newValue
invalidateIntrinsicContentSize()
}
}
override func intrinsicContentSize() -> CGSize {
if let size = image?.size {
let width: CGFloat
let height: CGFloat
if let maxLayoutWidth = preferredMaxLayoutWidth where size.width > maxLayoutWidth {
width = maxLayoutWidth
height = maxLayoutWidth * size.height/size.width
} else {
width = size.width
height = size.height
}
return CGSizeMake(width, height)
}
return CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric)
}
}
the subviews have layout constraints to cell, and the image view update preferredMaxLayoutWidth in cell's layoutSubview body, code like this
override func layoutSubviews() {
super.layoutSubviews()
image🐶.preferredMaxLayoutWidth = CGRectGetWidth(contentView.bounds) - CGFloat(leftMarginImage + rightMarginImage)
}
the result of custom image view is perfect, but cell not recalculate height.
some of system UI like as UILable, UITextView has own instrinsicContentSize implement,which cooperate well with cell
so, the question is how to send information to cell, and let it know subview content size has change and recalculate height
thanks

Resources