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.

    • even if image retrieved when cell displayed, cell displays poorly , have scroll / down hide it, display again right size... until next time cell size has computed again. see below: enter image description here
  • 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

Popular posts from this blog

php - Wordpress website dashboard page or post editor content is not showing but front end data is showing properly -

How to get the ip address of VM and use it to configure SSH connection dynamically in Ansible -

javascript - Get parameter of GET request -