ios - UIScrollView animation to targetContentOffset erratic -
i've implemented uiscrollview within uitableviewcell enables user scroll left , right reveal buttons in same fashion ios mail app. original implementation set frames , positions explicitly worked i've refactored code use autolayout throughout. animation hide/reveal 'container' buttons on left (accessory buttons) works animation brings scrollview rest when right container (edit buttons) slows before reaching desired offset before jerking final position.
all calculations use same math transformed (e.g. + rather - value, > rather < in tests) depending on side container located , values displayed logging correct. can't see obvious code errors , there no constraints cells set in ib. bug or there obvious i've missed through staring @ code last hour?
class swipeytableviewcell: uitableviewcell { // mark: constants private let thresholdvelocity = cgfloat(0.6) private let maxclosureduration = cgfloat(40) // mark: properties private var buttoncontainers = [buttoncontainertype: buttoncontainer]() private var leftcontainerwidth: cgfloat { return buttoncontainers[.accessory]?.containerwidthwhenopen ?? cgfloat(0) } private var rightcontainerwidth: cgfloat { return buttoncontainers[.edit]?.containerwidthwhenopen ?? cgfloat(0) } private var buttoncontainerrightanchor = nslayoutconstraint() private var isopen = false // mark: subviews private let scrollview = uiscrollview() // mark: lifecycle methods override func awakefromnib() { super.awakefromnib() // initialization code scrollview.delegate = self scrollview.showshorizontalscrollindicator = false scrollview.showsverticalscrollindicator = false contentview.addsubview(scrollview) scrollview.translatesautoresizingmaskintoconstraints = false scrollview.topanchor.constraintequaltoanchor(contentview.topanchor).active = true scrollview.leftanchor.constraintequaltoanchor(contentview.leftanchor).active = true scrollview.rightanchor.constraintequaltoanchor(contentview.rightanchor).active = true scrollview.bottomanchor.constraintequaltoanchor(contentview.bottomanchor).active = true let scrollcontentview = uiview() scrollcontentview.backgroundcolor = uicolor.cyancolor() scrollview.addsubview(scrollcontentview) scrollcontentview.translatesautoresizingmaskintoconstraints = false scrollcontentview.topanchor.constraintequaltoanchor(scrollview.topanchor).active = true scrollcontentview.leftanchor.constraintequaltoanchor(scrollview.leftanchor).active = true scrollcontentview.rightanchor.constraintequaltoanchor(scrollview.rightanchor).active = true scrollcontentview.bottomanchor.constraintequaltoanchor(scrollview.bottomanchor).active = true scrollcontentview.widthanchor.constraintequaltoanchor(contentview.widthanchor, constant: 10).active = true scrollcontentview.heightanchor.constraintequaltoanchor(contentview.heightanchor).active = true buttoncontainers[.accessory] = buttoncontainer(type: .accessory, scrollcontentview: scrollcontentview) buttoncontainers[.edit] = buttoncontainer(type: .edit, scrollcontentview: scrollcontentview) bc in buttoncontainers.values { scrollcontentview.addsubview(bc) bc.widthanchor.constraintequaltoanchor(contentview.widthanchor).active = true bc.heightanchor.constraintequaltoanchor(scrollcontentview.heightanchor).active = true bc.topanchor.constraintequaltoanchor(scrollcontentview.topanchor).active = true bc.containertocontentconstraint.active = true } scrollview.contentinset = uiedgeinsetsmake(0, leftcontainerwidth, 0, rightcontainerwidth) } func closecontainer() { scrollview.contentoffset.x = cgfloat(0) } } extension swipeytableviewcell: uiscrollviewdelegate { func scrollviewwillenddragging(scrollview: uiscrollview, withvelocity velocity: cgpoint, targetcontentoffset: unsafemutablepointer<cgpoint>) { let xoffset: cgfloat = scrollview.contentoffset.x isopen = false bc in buttoncontainers.values { if bc.iscontaineropen(xoffset, thresholdvelocity: thresholdvelocity, velocity: velocity) { targetcontentoffset.memory.x = bc.offsetrequiredtoopencontainer() nslog("target offset \(targetcontentoffset.memory.x)") isopen = true break /// 1 container can open @ time cn exit here } } if !isopen { nslog("closing container") targetcontentoffset.memory.x = cgfloat(0) let ms: cgfloat = xoffset / velocity.x /// if scroll isn't on fast path zero, animate closed if (velocity.x == 0 || ms < 0 || ms > maxclosureduration) { nslog("animating closed") dispatch_async(dispatch_get_main_queue()) { scrollview.setcontentoffset(cgpointzero, animated: true) } } } } /** defines position of container view buttons assosicated swipeytableviewcell - edit: identifier uiview acts container buttons right of cell - accessory: identifier uiview acts container buttons left of vell */ enum buttoncontainertype { case edit, accessory } extension buttoncontainertype { func getconstraints(scrollcontentview: uiview, buttoncontainer: uiview) -> nslayoutconstraint { switch self { case edit: return buttoncontainer.leftanchor.constraintequaltoanchor(scrollcontentview.rightanchor) case accessory: return buttoncontainer.rightanchor.constraintgreaterthanorequaltoanchor(scrollcontentview.leftanchor) } } func containeropenedtest() -> ((scrollviewoffset: cgfloat, containerfullyopenwidth: cgfloat, thresholdvelocity: cgfloat, velocity: cgpoint) -> bool) { switch self { case edit: return {(scrollviewoffset: cgfloat, containerfullyopenwidth: cgfloat, thresholdvelocity: cgfloat, velocity: cgpoint) -> bool in (scrollviewoffset > containerfullyopenwidth || (scrollviewoffset > 0 && velocity.x > thresholdvelocity)) } case accessory: return {(scrollviewoffset: cgfloat, containerfullyopenwidth: cgfloat, thresholdvelocity: cgfloat, velocity: cgpoint) -> bool in (scrollviewoffset < -containerfullyopenwidth || (scrollviewoffset < 0 && velocity.x < -thresholdvelocity)) } } } func transformoffsetforcontainerside(containerwidthwhenopen: cgfloat) -> cgfloat { switch self { case edit: return containerwidthwhenopen case accessory: return -containerwidthwhenopen } } } /// uiview subclass acts container buttongs associated swipeytablecellview class buttoncontainer: uiview { private let scrollcontentview: uiview private let type: buttoncontainertype private let maxnumberofbuttons = 3 let buttonwidth = cgfloat(65) private var buttons = [uibutton]() var containerwidthwhenopen: cgfloat { // return cgfloat(buttons.count) * buttonwidth return buttonwidth // todo: multiple buttons not yet implements - cause bug!! } var containertocontentconstraint: nslayoutconstraint { return type.getconstraints(scrollcontentview, buttoncontainer: self) } var offsetfromcontainer = cgfloat(0) { didset { let delta = abs(oldvalue - offsetfromcontainer) containertocontentconstraint.constant = offsetfromcontainer if delta > (containerwidthwhenopen * 0.5) { /// number arbitary - can more formal? animateconstraintwithduration(0.1, delay: 0, options: uiviewanimationoptions.curveeaseout, completion: nil) /// ensure large changes animated rather snapped } } } // mark: initialisers init(type: buttoncontainertype, scrollcontentview: uiview) { self.type = type self.scrollcontentview = scrollcontentview super.init(frame: cgrectzero) backgroundcolor = uicolor.bluecolor() translatesautoresizingmaskintoconstraints = false } required init?(coder adecoder: nscoder) { fatalerror("init(coder:) has not been implemented") } // mark: public methods func iscontaineropen(scrollviewoffset: cgfloat, thresholdvelocity: cgfloat, velocity: cgpoint) -> bool { let closure = type.containeropenedtest() return closure(scrollviewoffset: scrollviewoffset, containerfullyopenwidth: containerwidthwhenopen, thresholdvelocity: thresholdvelocity, velocity: velocity) } func offsetrequiredtoopencontainer() -> cgfloat { return type.transformoffsetforcontainerside(containerwidthwhenopen) } }
ok - found error , typo left earlier experimentation uiscrollview. clue in earlier comment 'snap' occurring within 10pt of desired targetcontentoffset...
the scrollcontentview width constraint set incorrectly follows:
scrollcontentview.widthanchor.constraintequaltoanchor(contentview.widthanchor, constant: 10).active = true
before found out force uiscrollview scroll setting contentinset, made subview larger cell's contentview uiscrollview pinned to. i've been refactoring code verbatim use new anchor properties, old code propagated , got bug!
so, not ios @ fault...just me not paying attention. lesson learnt! have other ideas on how implement things might little tidier.
Comments
Post a Comment