ios - Using Auto Layout in UITableViewCell containing a UICollectionView with asynchronous image load -
tl;dr: height calculated autolayout engine when displaying cell containing collection view wrong first time. calling reloaddata()
on table view fixes issue makes table view jump , unusable. idea?
explanations: have table view many different cells of different heights.
one of these cells image gallery can contain 1 or more images loaded asynchronously.
the constraints have following:
- if gallery contains 1 image , if image oriented in landscape, height of gallery should height of image
- else, gallery has fixed height.
the problems face following:
i super annoying encapsulated-height-layout issue thing when table view tries display gallery cell first time. encapsulated height has wrong value, though height constraint on collection view has been updated.
the table view consistently never gets cell's size right @ first try.
- the way can force table view display cell correctly when call reloaddata on table view once image loaded first time... makes table view jump , unusable.
i'm using kingfisher retrieve images, here's code:
uicollectionviewcell data source:
func collectionview(collectionview: uicollectionview, cellforitematindexpath indexpath: nsindexpath) -> uicollectionviewcell { let cell = collectionview.dequeuereusablecellwithreuseidentifier("carouselcollectionviewcell", forindexpath: indexpath) as! carouselcollectionviewcell guard let collectionview = collectionview as? carouselcollectionview else { return cell } if let imagesurls = data.imagesurls { let url = imagesurls[indexpath.item] if let smallurl = url.small { kingfishermanager.sharedmanager.retrieveimagewithurl( smallurl, optionsinfo: kingfisheroptionsinfo(), progressblock: nil, completionhandler: { (image, error, cachetype, imageurl) -> () in if let image = image { self.delegate?.imageisreadyforcellatindexpath(image, collectionview: collectionview, screenshotindex: indexpath.row) cell.imageview.image = image } }) } } return cell }
here happens when delegate gets called on rooviewcontroller in imageisreadyforcellatindexpath(image: uiimage, collectionview: uicollectionview, screenshotindex: int)
:
func imageisreadyforcellatindexpath(image: uiimage, collectionview: uicollectionview, screenshotindex: int) { guard let collectionview = collectionview as? carouselcollectionview else { return } guard let collectionviewindexpath = collectionview.indexpath else { return } guard let screenshotscount = feed?.articles?[collectionviewindexpath.section].content?[collectionviewindexpath.row].data?.imagesurls?.count else { return } let key = self.cachedsizesindexpath(collectionviewindexpath: collectionviewindexpath, cellindexpath: nsindexpath(foritem: screenshotindex, insection: 0)) var sizetocache: cgsize! if screenshotscount == 1 { // resize collectionview fit landscape image: if image.isorientedinlandscape { sizetocache = image.scaletomaxwidthandmaxheight(maxwidth: constants.maximagewidth, maxheight: constants.maximageheight) } else { sizetocache = image.scaletoheight(constants.maximageheight) } if collectionviewcellscachedsizesobject.dict[key] == nil { let flowlayout = collectionview.collectionviewlayout as! uicollectionviewflowlayout let imagewidth = sizetocache.width let sidesinset = (collectionview.frame.width - imagewidth) / 2 print("sidesinset: ", sidesinset) flowlayout.sectioninset = uiedgeinsets(top: 0, left: sidesinset, bottom: 0, right: sidesinset) collectionviewcellscachedsizesobject.dict[key] = sizetocache collectionview.heightconstraint.constant = sizetocache.height collectionview.collectionviewlayout.invalidatelayout() collectionview.setneedsupdateconstraints() tableview.reloaddata() } } else { let sizetocache = image.scaletoheight(constants.maximageheight) if collectionviewcellscachedsizesobject.dict[key] == nil { // && collectionviewcellscachedsizesobject.dict[key] != sizetocache { collectionviewcellscachedsizesobject.dict[key] = sizetocache collectionview.collectionviewlayout.invalidatelayout() } } }
here how set collection view:
class carouselelement: element { let collectionview: carouselcollectionview func cachedsizesindexpath(collectionviewindexpath acollectionviewindexpath: nsindexpath, cellindexpath acellindexpath: nsindexpath) -> string { return "\(acollectionviewindexpath), \(acellindexpath)" } override init(style: uitableviewcellstyle, reuseidentifier: string?) { let layout = uicollectionviewflowlayout() layout.sectioninset = uiedgeinsets(top: 0, left: constants.horizontalpadding, bottom: 0, right: constants.horizontalpadding) layout.scrolldirection = .horizontal collectionview = carouselcollectionview(frame: cgrectzero, collectionviewlayout: layout) collectionview.translatesautoresizingmaskintoconstraints = false collectionview.registerclass(carouselcollectionviewcell.self, forcellwithreuseidentifier: "carouselcollectionviewcell") collectionview.allowsmultipleselection = false collectionview.allowsselection = true collectionview.backgroundcolor = constants.backgroundcolor collectionview.showshorizontalscrollindicator = false super.init(style: style, reuseidentifier: reuseidentifier) addsubview(collectionview) addconstraints(nslayoutconstraint.constraintswithvisualformat( "|[collectionview]|", options: nslayoutformatoptions(), metrics: nil, views: ["collectionview":collectionview])) addconstraints(nslayoutconstraint.constraintswithvisualformat( "v:|[collectionview]-verticalpadding-|", options: nslayoutformatoptions(), metrics: ["verticalpadding":constants.verticalpadding], views: ["collectionview":collectionview])) collectionview.heightconstraint = nslayoutconstraint( item: collectionview, attribute: .height, relatedby: .equal, toitem: nil, attribute: .notanattribute, multiplier: 1.0, constant: 200) addconstraint(collectionview.heightconstraint) } required init?(coder adecoder: nscoder) { fatalerror("init(coder:) has not been implemented") } func setcarouseldatasourcedelegate(datasourcedelegate: carouseldatasourcedelegate?, indexpath: nsindexpath, cachedheight: cgfloat?) { collectionview.indexpath = indexpath if let height = cachedheight { collectionview.heightconstraint.constant = height } collectionview.datasource = datasourcedelegate collectionview.delegate = datasourcedelegate collectionview.reloaddata() } override func prepareforreuse() { super.prepareforreuse() collectionview.contentoffset = cgpointzero }}
and custom cell holding it:
class carouselcollectionviewcell: uicollectionviewcell { let imageview: uiimageview override init(frame: cgrect) { imageview = uiimageview.autolayoutview() as! uiimageview imageview.image = constants.placeholderimage imageview.contentmode = .scaleaspectfit super.init(frame: frame) translatesautoresizingmaskintoconstraints = false addsubview(imageview) addconstraints( nslayoutconstraint.constraintswithvisualformat( "|[imageview]|", options: nslayoutformatoptions(), metrics: nil, views: ["imageview":imageview])) addconstraints( nslayoutconstraint.constraintswithvisualformat( "v:|[imageview]|", options: nslayoutformatoptions(), metrics: nil, views: ["imageview":imageview])) } override func prepareforreuse() { imageview.image = constants.placeholderimage } required init?(coder adecoder: nscoder) { fatalerror("init(coder:) has not been implemented") } }
last not least, set height constraint of collection view in tableview(_:willdisplaycell:forrowatindexpath:)
i tried to set in cellforrowatindexpath(_:)
doesn't change anything.
sorry massive code chunk, driving me insane.
when warning appears. tell's had break 1 of constraints in order fix it. probabily, (the height constraint) fix warning make constraint's priority 999 (if 1000).
for updating cell after setting image it. try this:
dispatch_async(dispatch_get_main_queue()) { () -> void in self.delegate?.imageisreadyforcellatindexpath(image, collectionview: collectionview, screenshotindex: indexpath.row) cell.imageview.image = image // content offset. let offset = self.tableview.contentoffset let visiblerect = cgrect(origin: offset, size: cgsize(width: 1, height: 1) // reload cell tableview.beginupdates() tableview.endupdates() // prevent jumping may scroll same visible rect without animation. self.tableview.scrollrecttovisible(visiblerect, animated: false) }
and remove tableview.reloaddata()
please give me feedback after try it.
Comments
Post a Comment