15

When you are in a conversation within the Messages app in iOS 7, if you scroll up or down you will notice that the bubbles and more so the text saying when the messages were sent, will bounce into place.

I am trying to replicate this in my own table view, but am not finding a way to do it.

I assume it is using UIDynamics, but I am not sure how to tie that in with the scrolling and the content bouncing.

Any help would be appreciated

kdbdallas
  • 4,513
  • 10
  • 38
  • 53
  • I have the answer, google BPXLFlowLayout by Brandon Alexander, Black Pixel. His class BPXLFlowLayout is very close to the exact feel of the physics in Messages. – Fattie Apr 22 '14 at 08:57
  • BTW don't forget this critical related tip when you're working with these things: http://stackoverflow.com/a/23926712/294884 – Fattie Jun 08 '14 at 17:54
  • Just to be perfectly clear, the essential answer is **"you use UICollectionViewFlowLayout"**. As I mention above, BPXLFlowLayout is an amazing version of BPXLFlowLayout, which perfectly does what you want. – Fattie Jun 24 '14 at 08:05

3 Answers3

12

If you want to replicate that effect yourself, you need UIKit Dynamics. I was interested in that effect myself and it was explained at WWDC this year.

Take a look at WWDC Session 217: Exploring Scroll Views on iOS 7

There are also read to use components on the internet, such as THSpringyCollectionView

Thomas Keuleers
  • 6,075
  • 2
  • 32
  • 35
12

I was interested in that effect also and during my research on the web I found this tutorial - http://www.objc.io/issue-5/collection-views-and-uidynamics.html It is implementing the same idea.

Artem Goryaev
  • 209
  • 3
  • 3
  • 1
    I found that this tutorial (as well as the demo at WWDC session) does not replicate the behavior of the Messages app. In the Messages app, there is no oscillation, everything is smooth. In the demo app, there is a residual oscillation, even if your damping ratio is ≥ 1 (which physically doesn't make sense). – KPM Dec 17 '13 at 14:37
  • 1
    @KPM Try giving the spring attachment behavior some length (like 1.0f). – stephencelis Jan 24 '14 at 03:04
  • Yup BPXLFlowLayout is based on that tutorial, and Brandon has done all the hard work duplicating the physics of the Messages app! Cheers – Fattie Apr 22 '14 at 08:58
  • @JoeBlow BPXLFlowLayout doesn't reproduce Message's physics. There's way too much oscillation and bounding. – awolf Jul 11 '14 at 22:54
  • Hi @awolf! Hmm, the main thing we do is physics programming (for games and stuff). For me, I've absolutely copied the physics of "Messages.app", just using BPXLFlowLayout. I mean -- obviously, you'll have to tweak the values in there, depending on the sizes of your "boxes" or whatever you're doing. Is there a chance you just need to tweak it to match? (I'm sorry if I implied it was "ready" - of course, it would only work anyway for objects of some certain size/weight.) If I'm not mistaken the Apple app is indeed precisely a UICollectionViewFlowLayout - just like BPXLFlowLayout. Hope it helps – Fattie Jul 16 '14 at 14:02
  • @JoeBlow I figured it out eventually, but BPXL definitely doesn't do it. In order to prevent oscillation it's necessary to dynamically increase the damping factor on a quadratic scale as the attached views get closer and closer to their attachment points. – awolf Jul 17 '14 at 04:09
  • 1
    Hi awolf! That sounds great, you should post your code. Let's see it. I did get it working perfectly just using BPXL and tuning the values, but we may have been working on different sizes or who knows what? Cheers – Fattie Jul 17 '14 at 10:41
  • @awolf -- Are there any more code details you would mind sharing on the layout to prevent oscillation? I am having the same problem – Chase S Jul 23 '15 at 20:18
  • 1
    @ChaseS this was for a client project hence can't simply post the code. Speaking in general terms though the main adjustment I needed was adding an action block to the UIAttachmentBehavior responsible for the spring action. This action block gets called each time the UIAttachmentBehavior calculates position inside the UIDynamicAnimator. – awolf Jul 24 '15 at 18:34
  • 3
    @ChaseS Inside that block I'd look at the delta between the spring anchor point and the item point (call that variable "d"). If d was less than 1.0, I'd simply apply an extremely large damping factor like 2000.0 (so that it would settle down instantly). Otherwise, I'd determine the damping factor using a quadratic function (e.g. [Constant] * d^2 + [Another constant]). I chose [Constant] and [Another constant] experimentally. Finally, I also played with the behavior's frequency, choosing a slightly higher frequency for smaller values of d. Good luck! – awolf Jul 24 '15 at 18:37
  • @awolf Thank you for taking the time to write back and give a great explanation. – Chase S Jul 25 '15 at 04:56
4

enter image description here

You can add a springy bounce to content in your scrollview like this:

  1. Set up a UIScrollview and add to your view.

    mainScroller = [UIScrollView new];
    mainScroller.frame = CGRectMake(0, 0, w, h);
    mainScroller.bounces = true;
    mainScroller.pagingEnabled = false;
    mainScroller.delegate = self;
    [self.view addSubview:mainScroller];
    
  2. Layout a UIView and add it within your scrollview.

    contentView = [UIView new];
    contentView.frame = CGRectZero;
    [mainScroller addSubview:contentView];
    
  3. Add subviews your your 'contentView'.

    UIView * unitView = [UIView new];
    unitView.frame = CGRectMake(0, yOff, w, 280);
    [contentView addSubview:unitView]; 
    
  4. Resize both contentView and scrollview to fit the content. Below w is view width and yOff the total cumulative height of the content.

    contentView.frame = CGRectMake(0, 0, w, yOff);
    mainScroller.contentSize = CGSizeMake(w, yOff);
    
  5. In your scrollViewDidScroll method, add the following code:

     float y = mainScroller.contentOffset.y;
     contentView.transform = CGAffineTransformMakeTranslation(0, y);
    
     for (UIView * sub in contentView.subviews){
         int index = (int)[contentView.subviews indexOfObject:sub];
         float delay = index * 0.03;
         float duration = 1.0f - delay;
    
         [UIView animateWithDuration:duration
                               delay:delay
              usingSpringWithDamping:0.8
               initialSpringVelocity:0.7
                             options:UIViewAnimationOptionCurveEaseInOut| UIViewAnimationOptionAllowUserInteraction
                          animations:^{
                              sub.transform = CGAffineTransformMakeTranslation(0, -y);
                         }
                     completion:^(BOOL finished){
                     }]; 
     }
    

The UIViewAnimationOptionAllowUserInteraction animation option allows for you to interact with the content immediately. Tweak the delay, duration, spring dampening and spring velocity variables to suit your needs.

The code could be further adapted to allow for touch detection; as it stands, the springy bounce originates at the top of the scrollView and descends down through the views, which for shorter scrollViews is barely noticeable.

EDIT: Touch detection

If you want to allow for touch detection, replace with these lines in your scrollViewDidScroll method:

int index = (int)[contentView.subviews indexOfObject:sub];
int deviation = abs(touchIndex - index);
float delay = deviation * 0.03;
float duration = 1.0f - delay;

Where the new variable touchIndex is defined like so:

-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {

    //grab the location of the touch event
    CGPoint location = [scrollView.panGestureRecognizer locationInView:scrollView];
    int yTouch = location.y; //grab y coordinate
    touchIndex = (yTouch - 100) / 100; //calculate the index of the cell, where 100 is the height of the cell

}

If you have dynamic cell heights, store them in an array e.g. 'selectedHeights'. Then update your scrollViewWillBeginDragging method for the below, where the 'tally' float is set to 70 to allow for a header.

-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {

    //grab the location of the touch event
    CGPoint location = [scrollView.panGestureRecognizer locationInView:scrollView];
    int yTouch = location.y; //grab y coordinate
    float tally = 70.0f;
    for (int n = 0; n < selectedHeights.count; n++){
        float cellHeight = [selectedHeights[n] floatValue];
        tally += cellHeight;
        if (tally > yTouch){
            touchIndex = n;
            break;
        }
    }
}
Johnny Rockex
  • 4,136
  • 3
  • 35
  • 55
  • can you please explain how do i do it with a tableview..i just need the bounce animation. – sujith1406 Aug 15 '16 at 23:58
  • I imagine that there is a way with a tableview, or certainly a uicollectionview with a custom flow layout, where you track the location of the cell re the superview, but I don't know it, someone else might? If you're worried about dequeing on a large number of cells, I can post some code for that. The whole thing doesn't add up to many more lines than Apple's required tableview methods, and when you start to add dynamic backgrounds (like the messages app blue gradient) it makes sense. – Johnny Rockex Aug 16 '16 at 06:24
  • hi @JohnnyRockex, could you please put your code in a demo? Just like your gif shows? It'll be more valuable. Thanks – Paradise Jul 11 '17 at 03:27
  • @JohnnyRockex could you explain for the table view or please provide sample code – Sai kumar Reddy Sep 04 '20 at 13:05
  • @SaikumarReddy Hi Sai, it's been a while since I did this code, but it looks like it's not using a UITableView. My guess now would be to use either a tableView or a UICollectionView, and hook into the function in the scrollViewDidScroll method and then manipulate from there. Good luck. – Johnny Rockex Sep 04 '20 at 15:46