iosswiftuikituikit-dynamics

Adjust offset with UIKit Dynamics


Reproduction can be found here

I'm making a chat view that's pretty similar to Messages.app. A part of this is the bouncy messages made possible by UIKit Dynamics. I have gotten my implementation working pretty well after consuming every bit of publicly available knowledge, but I have one remaining problem - I can't adjust the contentOffset without everything going bad.

The chat requires adjusting scroll offset for features like maintaining the vertical scroll when adding more pages above, maintaining bottom offset when adding new messages, or scrolling to the bottom via button or a linked message programatically.

When I do any manual offset adjustment, issues start to arise. At best, some cells appear to swap attributes and become out of order. At worst, the dynamics go bananas and start displaying undefined behaviour, sometimes disappearing entirely.

I've tried everything I can think of, including returning normal flow layout attributes before/during offset adjustments, but this hasn't brought me to completion.

Here's a poorly downscaled gif diplaying an example of the issue - note how the messages containing 1-10 enumerated becomes out of order after using scrollToItem(at: lastIndexPath).

enter image description here

Has anyone managed to adjust offset programatically while using UIKit Dynamics? I'd love any pointers on how to proceed.


Solution

  • I posted here a solution starting from your sample code and your idea of disabling dynamics while the content offset is being adjusted programmatically. This solution was tested only on a simulator.

    For animated content offset changes, I am using the scroll view delegate method scrollViewDidEndScrollingAnimation(_:) as an opportunity to re-enable dynamics. For non-animated content offset changes, re-enabling dynamics is done right after the content offset change.

    I also added a missing super.prepare call in the prepare override of DynamicLayout and changed a bit the shouldInvalidateLayout override to:

    1. always return false when dynamics is enabled (since the animator is taking care of invalidating the layout)
    2. return the super implementation when dynamics is disabled

    To enable/disable dynamics I used the pauseDynamics property that was already present in your sample code.

    Some things I noticed while testing different options with dynamics always enabled: