周末仿写了半糖的下拉刷新,学了两句法语

  • C’est La Vie
  • La Vie est belle

C’est La Vie 通常是用在较为消极的事情发生时,用于自我安慰或自我解嘲,鼓励自己或他人即使遇到了再大的难处,也要坦然笑对生活。鸡汤一下,开始正文。

示例

  • 半糖

半糖

  • PDPullToRefresh

PDPullToRefresh

思路

PDPullToRefresh是给UIScrollView加的分类,包括PDHeaderRefreshView和PDFooterRefreshView ,整个刷新过程可分为两部分

  • 下拉时 - C’est La Vie 动画
  • 刷新时 - La Vie est belle 动画

C’est La Vie 动画

首先得拿到C’est La Vie的字形,这里用到了CoreText,拿到字形后添加到layer.path上显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
CGMutablePathRef letters = CGPathCreateMutable();
    
    CTFontRef font = CTFontCreateWithName(CFSTR("HelveticaNeue-UltraLight"), pFontSize, NULL);
    NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:
                           (__bridge id)font, kCTFontAttributeName,
                           nil];
    NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:animationString
                                                                     attributes:attrs];
    CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)attrString);
    CFArrayRef runArray = CTLineGetGlyphRuns(line);
    
    // for each RUN
    for (CFIndex runIndex = 0; runIndex < CFArrayGetCount(runArray); runIndex++)
    {
        // Get FONT for this run
        CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runArray, runIndex);
        CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName);
        // for each GLYPH in run
        for (CFIndex runGlyphIndex = 0; runGlyphIndex < CTRunGetGlyphCount(run); runGlyphIndex++)
        {
            // get Glyph & Glyph-data
            CFRange thisGlyphRange = CFRangeMake(runGlyphIndex, 1);
            CGGlyph glyph;
            CGPoint position;
            CTRunGetGlyphs(run, thisGlyphRange, &glyph);
            CTRunGetPositions(run, thisGlyphRange, &position);
            // Get PATH of outline
            {
                CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyph, NULL);
                CGAffineTransform t = CGAffineTransformMakeTranslation(position.x, position.y);
                CGPathAddPath(letters, &t, letter);
                CGPathRelease(letter);
            }
        }
    }
    CFRelease(line);
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointZero];
    [path appendPath:[UIBezierPath bezierPathWithCGPath:letters]];
    
    CGPathRelease(letters);
    CFRelease(font);
    
    CAShapeLayer *pathLayer = [CAShapeLayer layer];
    pathLayer.frame = self.animationLayer.bounds;
    pathLayer.bounds = CGPathGetBoundingBox(path.CGPath);
    pathLayer.geometryFlipped = YES;
    pathLayer.path = path.CGPath;
    pathLayer.strokeColor = [UIColor colorWithRed:234.0/255 green:84.0/255 blue:87.0/255 alpha:1].CGColor;
    pathLayer.fillColor = nil;
    pathLayer.lineWidth = 1.0f;
    pathLayer.lineJoin = kCALineJoinBevel;

然后用KVO监听ScrollView的contentOffset属性,与pathLayer的strokeEnd关联起来,C’est La Vie就可以随着下拉做动画啦。

La Vie est belle 动画

La Vie est belle与C’est La Vie的动画不同,它是一直闪动着的,还是先拿到La Vie est belle的字形,这里用CAGradientLayer可以方便的处理颜色渐变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CAGradientLayer *gradientLayer = (CAGradientLayer *)self.gradientLayer;
    if([gradientLayer animationForKey:kAnimationKey] == nil)
    {
        // 通过不断改变渐变的起止范围,来实现光晕效果
        CABasicAnimation *startPointAnimation = [CABasicAnimation animationWithKeyPath:gradientStartPointKey];
        startPointAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(1.0, 0)];
        startPointAnimation.timingFunction = [CAMediaTimingFunction functionWithName:_animationPacing];
        
        CABasicAnimation *endPointAnimation = [CABasicAnimation animationWithKeyPath:gradientEndPointKey];
        endPointAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(1 + pHaloWidth, 0)];
        endPointAnimation.timingFunction = [CAMediaTimingFunction functionWithName:_animationPacing];
        
        CAAnimationGroup *group = [CAAnimationGroup animation];
        group.animations = @[startPointAnimation, endPointAnimation];
        group.duration = pHaloDuration;
        group.timingFunction = [CAMediaTimingFunction functionWithName:_animationPacing];
        group.repeatCount = HUGE_VALF;
        
        [gradientLayer addAnimation:group forKey:kAnimationKey];
    }

使用

安装

  • 添加 pod 'PDPullToRefresh' 到你的 Podfile ,然后pod install
  • 手动添加到你的Xcode项目中,#import "PDPullToRefresh.h"

添加下拉刷新

1
2
3
4
[tableView pd_addHeaderRefreshWithNavigationBar:YES andActionHandler:^{
// prepend data to dataSource, insert cells at top of table view
// call [tableView.pdHeaderRefreshView stopRefreshing] when done
}];

添加上拉刷新

1
2
3
4
[tableView pd_addFooterRefreshWithNavigationBar:YES andActionHandler:^{
// prepend data to dataSource, insert cells at top of table view
// call [tableView.pdFooterRefreshView stopRefreshing] when done
}];

立即刷新

1
[tableView.pdHeaderRefreshView startRefreshing];

自定义

目前仅支持下拉距离自定义,默认高度为80

1
2
@property (nonatomic, assign) CGFloat pdHeaderRefreshViewHeight;
@property (nonatomic, assign) CGFloat pdFooterRefreshViewHeight;

最后

附上Github地址,外加感谢半糖SVPullToRefresh对我的启发。