简体   繁体   中英

How to create a top fade effect using UIScrollView?

I've got a UIScrollView in my app and I have seen in some other apps that when the user scrolls, the top section fades out on scroll rather than just dissapearing out.

I really love this effect and want to achieve it. Any ideas how it can be done?

Simple 2020 solution:

import UIKit

class FadeTail: UIIView {
    
    private lazy var gradientLayer: CAGradientLayer = {
        let l = CAGradientLayer()
        l.startPoint = CGPoint(x: 0.5, y: 0)
        l.endPoint = CGPoint(x: 0.5, y: 1)
        let baseColor = UIColor.white // for example
        l.colors = [
            baseColor.withAlphaComponent(0),
            baseColor.withAlphaComponent(1),
        ].map{$0.cgColor}
        layer.addSublayer(l)
        return l
    }()
    
    override func layoutSubviews() {
        super.layoutSubviews()
        gradientLayer.frame = bounds
    }
}

Simply

  • autolayout that view in storyboard,
  • any shape or size you wish
  • on top of the view (text, image, webview, anything) you wish to be faded.

Easy.

在此处输入图片说明

Tip - gradients, circles, etc

If you need crazy circular/banded/etc fades, use the techniques here: https://stackoverflow.com/a/61174086/294884

在此处输入图片说明

EDIT: I've put this code up on github, see here .


See my answer to a similar question.

My solution is to subclass UIScrollView , and create a mask layer in the layoutSubviews method.

#import "FadingScrollView.h"
#import <QuartzCore/QuartzCore.h>

static float const fadePercentage = 0.2;

@implementation FadingScrollView

// ...

- (void)layoutSubviews
{
    [super layoutSubviews];

    NSObject * transparent = (NSObject *) [[UIColor colorWithWhite:0 alpha:0] CGColor];
    NSObject * opaque = (NSObject *) [[UIColor colorWithWhite:0 alpha:1] CGColor];

    CALayer * maskLayer = [CALayer layer];
    maskLayer.frame = self.bounds;

    CAGradientLayer * gradientLayer = [CAGradientLayer layer];
    gradientLayer.frame = CGRectMake(self.bounds.origin.x, 0,
                                     self.bounds.size.width, self.bounds.size.height);

    gradientLayer.colors = [NSArray arrayWithObjects: transparent, opaque,
                            opaque, transparent, nil];

    // Set percentage of scrollview that fades at top & bottom
    gradientLayer.locations = [NSArray arrayWithObjects:
                               [NSNumber numberWithFloat:0],
                               [NSNumber numberWithFloat:fadePercentage],
                               [NSNumber numberWithFloat:1.0 - fadePercentage],
                               [NSNumber numberWithFloat:1], nil];

    [maskLayer addSublayer:gradientLayer];
    self.layer.mask = maskLayer;
}

@end

The code above fades the top and bottom of the UIScrollView from the background colour to transparent, but this can be easily changed to fade the top only (or fade to any colour you want).

Change this line to fade the top only:

// Fade top of scrollview only
gradientLayer.locations = [NSArray arrayWithObjects:
                           [NSNumber numberWithFloat:0],
                           [NSNumber numberWithFloat:fadePercentage],
                           [NSNumber numberWithFloat:1],
                           [NSNumber numberWithFloat:1], nil];

EDIT 2:

Or fade the top only by changing these two lines:

// Fade top of scrollview only
    gradientLayer.colors = [NSArray arrayWithObjects: transparent, opaque, nil];

    gradientLayer.locations = [NSArray arrayWithObjects: [NSNumber numberWithFloat:0],
                                                         [NSNumber numberWithFloat:fadePercentage], nil];

Or, fade the bottom only:

// Fade bottom of scrollview only
    gradientLayer.colors = [NSArray arrayWithObjects: opaque, transparent, nil];

    gradientLayer.locations = [NSArray arrayWithObjects: [NSNumber numberWithFloat:1.0 - fadePercentage],
                                                         [NSNumber numberWithFloat:1], nil];

You can use a CAGradientLayer by

  1. Adding the QuartzCore.framework to your project (see Linking to Library or Framework ).

  2. Add #import of the QuartzCore headers:

     #import <QuartzCore/QuartzCore.h>
  3. And then use CAGradientLayer :

     - (void)addGradientMaskToView:(UIView *)view { CAGradientLayer *gradient = [CAGradientLayer layer]; gradient.frame = view.bounds; gradient.colors = @[(id)[[UIColor clearColor] CGColor], (id)[[UIColor whiteColor] CGColor]]; gradient.startPoint = CGPointMake(0.5, 0.0); // this is the default value, so this line is not needed gradient.endPoint = CGPointMake(0.5, 0.20); [view.layer setMask:gradient]; }

Note, this CAGradientLayer is a gradient from a color with alpha of 0.0 (eg clearColor ) to a color to a color with alpha of 1.0 (eg whiteColor ), not just from black to white. You can adjust the startPoint (the default value is probably fine) and the endPoint to adjust where you want the gradient to be applied.

And generally, when doing this with a UIScrollView , unless you want the gradient to scroll with you, you make the UIScrollView a subview of some other UIView and apply this gradient to that container view, not the scroll view itself.

Thanks to Fattie's answer I created the following UIView extension, in Swift, that takes care of the gradient fading and provides more styles (bottom, top, left right, vertical and horizontal) as well as fade percentage.

For any comments/recommendations, please let me know at the gist I've created , I try to keep both that and this answer up to date with any changes I add.

The Extension:

extension UIView {

    enum UIViewFadeStyle {
        case bottom
        case top
        case left
        case right

        case vertical
        case horizontal
    }

    func fadeView(style: UIViewFadeStyle = .bottom, percentage: Double = 0.07) {
        let gradient = CAGradientLayer()
        gradient.frame = bounds
        gradient.colors = [UIColor.white.cgColor, UIColor.clear.cgColor]

        let startLocation = percentage
        let endLocation = 1 - percentage

        switch style {
        case .bottom:
            gradient.startPoint = CGPoint(x: 0.5, y: endLocation)
            gradient.endPoint = CGPoint(x: 0.5, y: 1)
        case .top:
            gradient.startPoint = CGPoint(x: 0.5, y: startLocation)
            gradient.endPoint = CGPoint(x: 0.5, y: 0.0)
        case .vertical:
            gradient.startPoint = CGPoint(x: 0.5, y: 0.0)
            gradient.endPoint = CGPoint(x: 0.5, y: 1.0)
            gradient.colors = [UIColor.clear.cgColor, UIColor.white.cgColor, UIColor.white.cgColor, UIColor.clear.cgColor]
            gradient.locations = [0.0, startLocation, endLocation, 1.0] as [NSNumber]

        case .left:
            gradient.startPoint = CGPoint(x: startLocation, y: 0.5)
            gradient.endPoint = CGPoint(x: 0.0, y: 0.5)
        case .right:
            gradient.startPoint = CGPoint(x: endLocation, y: 0.5)
            gradient.endPoint = CGPoint(x: 1, y: 0.5)
        case .horizontal:
            gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
            gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
            gradient.colors = [UIColor.clear.cgColor, UIColor.white.cgColor, UIColor.white.cgColor, UIColor.clear.cgColor]
            gradient.locations = [0.0, startLocation, endLocation, 1.0] as [NSNumber]
        }

        layer.mask = gradient
    }

}

You add an alpha mask layer to a view containing your scroll view like this:

CALayer *mask = [CALayer layer];
CGImageRef maskRef = [UIImage imageNamed:@"scrollMask"].CGImage;
CGImageRef maskImage = CGImageMaskCreate(CGImageGetWidth(maskRef),
                                             CGImageGetHeight(maskRef),
                                             CGImageGetBitsPerComponent(maskRef),
                                             CGImageGetBitsPerPixel(maskRef),
                                             CGImageGetBytesPerRow(maskRef),
                                             CGImageGetDataProvider(maskRef), NULL, false);
mask.contents = (__bridge id)maskImage;
mask.frame = CGRectMake(0, 0, view.bounds.size.width, view.bounds.size.height);
view.layer.mask = mask;
view.layer.masksToBounds = YES;
CGImageRelease(maskImage);

where "scrollMask" is a grayscale image defining mask region: white == fully masked, black == not masked at all and gray == partially masked.

To create the effect you're looking for, the mask image would be black with a white gradient at the top like this:

在此处输入图片说明

For more details, take a look at the documentation for CGImageMaskCreate .

We built onto Steph Sharp's code to only fade when necessary. Our code is setup as a static utility method instead of a subclass so that we could reuse the method in our subclasses of UIScrollView and UITableView. Here is our static utility method:

#define kScrollViewFadeColorLight [UIColor colorWithRed:0.56 green:0.56 blue:0.56 alpha:0.0]
#define kScrollViewFadeColorDark [UIColor colorWithRed:0.56 green:0.56 blue:0.56 alpha:1.0]

#define kScrollViewScrollBarWidth 7.0
#define kScrollViewFadingEdgeLength 40.0

+ (void) applyFadeToScrollView:(UIScrollView*) scrollView {
  CGFloat topOffset = -scrollView.contentInset.top;
  CGFloat bottomOffset = scrollView.contentSize.height + scrollView.contentInset.bottom - scrollView.bounds.size.height;
  CGFloat distanceFromTop = scrollView.contentOffset.y - topOffset;
  CGFloat distanceFromBottom = bottomOffset - scrollView.contentOffset.y;
  BOOL isAtTop = distanceFromTop < 1.0;
  BOOL isAtBottom = distanceFromBottom < 1.0;
  if (isAtTop && isAtBottom) {
    // There is no scrolling to be done here, so don't fade anything!
    scrollView.layer.mask = nil;
    return;
  }
  NSObject* transparent = (NSObject*)[kScrollViewFadeColorLight CGColor];
  NSObject* opaque = (NSObject*)[kScrollViewFadeColorDark CGColor];

  CALayer* maskLayer = [CALayer layer];
  maskLayer.frame = scrollView.bounds;

  CALayer* scrollGutterLayer = [CALayer layer];
  scrollGutterLayer.frame = CGRectMake(scrollView.bounds.size.width - kScrollViewScrollBarWidth, 0.0,
                                       kScrollViewScrollBarWidth, scrollView.bounds.size.height);
  scrollGutterLayer.backgroundColor = (__bridge CGColorRef)(opaque);
  [maskLayer addSublayer:scrollGutterLayer];

  CAGradientLayer* gradientLayer = [CAGradientLayer layer];
  gradientLayer.frame = CGRectMake(0.0, 0.0, scrollView.bounds.size.width, scrollView.bounds.size.height);
  CGFloat fadePercentage = kScrollViewFadingEdgeLength / scrollView.bounds.size.height;
  NSMutableArray* colors = [[NSMutableArray alloc] init];
  NSMutableArray* locations = [[NSMutableArray alloc] init];
  if (!isAtTop) {
    [colors addObjectsFromArray:@[transparent, opaque]];
    [locations addObjectsFromArray:@[@0.0, [NSNumber numberWithFloat:fadePercentage]]];
  }
  if (!isAtBottom) {
    [colors addObjectsFromArray:@[opaque, transparent]];
    [locations addObjectsFromArray:@[[NSNumber numberWithFloat:1.0 - fadePercentage], @1.0]];
  }
  gradientLayer.colors = colors;
  gradientLayer.locations = locations;
  [maskLayer addSublayer:gradientLayer];
  scrollView.layer.mask = maskLayer;
}

Here is our usage of that method.

FadingScrollView.h

@interface FadingScrollView : UIScrollView
@end

FadingScrollView.m

@implementation FadingScrollView
- (void) layoutSubviews {
  [super layoutSubviews];
  [Utils applyFadeToScrollView:self];
}
@end

FadingTableView.h

@interface FadingTableView : UITableView    
@end

FadingTableView.m

@implementation FadingTableView
- (void) layoutSubviews {
  [super layoutSubviews];
  [Utils applyFadeToScrollView:self];
}
@end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM