IGListKit
IGListKit copied to clipboard
Cell expanding animation
New issue checklist
- [x] I have reviewed the
READMEand documentation - [x] I have searched existing issues and this is not a duplicate
I tried to make cell expanding animation, but got this: https://imgur.com/a/DtCEHis
I saw implementation in examples with updating labels frame in layoutSubviews (mine with constraints and self-sizing cells), it works a bit better, but I think there should be another way.
I want it to work like in Instagram app in photo descriptions)
Bump
Does anyone have a solution to this problem?
@Recouse, do you have more info on how you got to this result? Were you following a guide to help you get here? Without much more info about what's going on here I can't really give you any useful help.
I used expanding cell code from examples. But I wanted to do this with self-sizing cells.
AuthorDescriptionCell.swift:
import UIKit
class AuthorDescriptionCell: UICollectionViewCell {
static let insets = UIEdgeInsets(top: 0, left: Global.UI.edgeInset, bottom: 0, right: Global.UI.edgeInset)
static let font = UIFont.systemFont(ofSize: 14)
static var singleLineHeight: CGFloat {
return font.lineHeight
}
var dataSource: Author? {
didSet {
updateData()
}
}
var shouldUpdateSize: Bool = false
let descriptionTextView: ReadMoreTextView = {
let textView = ReadMoreTextView()
textView.textColor = Asset.Colors.dark.color
textView.font = .systemFont(ofSize: 14)
textView.shouldTrim = true
textView.maximumNumberOfLines = 3
textView.contentInset = .zero
let readMoreStyle: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: 14, weight: .medium),
.foregroundColor: Asset.Colors.clearBlue.color
]
textView.attributedReadMoreText = NSAttributedString(string: " Ko‘proq", attributes: readMoreStyle)
textView.attributedReadLessText = NSAttributedString(string: " Kamroq", attributes: readMoreStyle)
return textView
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(descriptionTextView)
descriptionTextView.snp.makeConstraints {
$0.top.equalToSuperview()
$0.left.right.equalToSuperview().offset(Global.UI.edgeInset).inset(Global.UI.edgeInset)
$0.bottom.equalToSuperview().priority(250)
}
descriptionTextView.readMoreDelegate = self
descriptionTextView.onSizeChange = { [unowned self] _ in
guard self.shouldUpdateSize else { return }
self.shouldUpdateSize = false
self.delegate?.sizeChanged()
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var newFrame = layoutAttributes.frame
newFrame.size.height = ceil(size.height)
layoutAttributes.frame = newFrame
return layoutAttributes
}
func updateData() {
guard let author = dataSource else { return }
descriptionTextView.text = author.biography
}
private func updateSize() {
let bounds = contentView.bounds
descriptionTextView.frame = bounds.inset(by: AuthorDescriptionCell.insets)
}
static func textHeight(_ text: String, width: CGFloat) -> CGFloat {
let constrainedSize = CGSize(width: width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude)
let attributes = [ NSAttributedString.Key.font: font ]
let options: NSStringDrawingOptions = [.usesFontLeading, .usesLineFragmentOrigin]
let bounds = (text as NSString).boundingRect(with: constrainedSize, options: options, attributes: attributes, context: nil)
return ceil(bounds.height) + insets.top + insets.bottom
}
}
extension AuthorDescriptionCell: ReadMoreTextViewDelegate {
func textWasCollapsed() {
shouldUpdateSize = true
}
func textWasExpanded() {
shouldUpdateSize = true
}
}
AuthorDescriptionSectionController.swift:
import UIKit
import IGListKit
class AuthorDescriptionSectionController: ListSectionController {
var object: KeyedModel<Author>?
override init() {
super.init()
inset = UIEdgeInsets(top: 14, left: 0, bottom: 24, right: 0)
}
override func didUpdate(to object: Any) {
self.object = object as? KeyedModel<Author>
}
override func sizeForItem(at index: Int) -> CGSize {
let width = collectionContext!.containerSize.width
let height = AuthorDescriptionCell.singleLineHeight * 3
return CGSize(width: width, height: height)
}
override func cellForItem(at index: Int) -> UICollectionViewCell {
guard let cell = collectionContext?.dequeueReusableCell(
of: AuthorDescriptionCell.self,
for: self,
at: index
) as? AuthorDescriptionCell else {
fatalError()
}
cell.dataSource = object?.model
cell.delegate = self
return cell
}
func toggle() {
collectionContext?.invalidateLayout(for: self)
}
}
extension AuthorDescriptionSectionController: BookDescriptionCellDelegate {
func sizeChanged() {
toggle()
}
}
Bump
No any progress?
Nope, still don’t know how to solve this.
@Recouse do you have a minimal example that reproduces this problem? It'd help if it's more narrowed down
@joetam I posted an example here https://github.com/Instagram/IGListKit/issues/1345#issuecomment-518505712
@Recouse the animation looks like it's funky because after collectionContext?.invalidateLayout(for: self) is called in the section controller, the size for the cell is the same as the previous layout (AuthorDescriptionCell.singleLineHeight * 3) and THEN preferredLayoutAttributesFitting is triggering a resize.
Here is an IGListKit example of an expanding section controller.
Based on the above example, here are some changes I recommend:
- add a local
expandedproperty in the section controller and toggle this property before callinginvalidateLayoutin the section controller - in the section controller's
sizeForItem, calculate the desired height for your cell based on theexpandedstate. (i.e.height = expanded ? AuthorDescriptionCell.textHeight(text:width:) : AuthorDescriptionCell.singleLineHeight * 3(this can be cleaned up even further by passingexpandedtotextHeightand doing the calculation in there)) - wrap
invalidateLayoutin an animation block - remove the cell's
preferredLayoutAttributesFitting(you don't really need this in most cases with IGListKit) - simplify your delegates - you can get rid of the
ReadMoreTextViewDelegate(and the cell'sshouldUpdateSize), and instead assign a tap target to "read more" in the collection view cell. when user has tapped read more, directly trigger the cell's delegate toinvalidateLayoutfrom the section controller. - after updating the cell height, make sure to also update
descriptionTextView's maximum number of lines as needed
Hope the above helps!