Fade edges of UITableView

2019-01-14 10:05发布

I've made a little research about my problem and unfortunately there was no solution for my problem. The closest was Fade UIImageView as it approaches the edges of a UIScrollView but it's still not for me.

I want my table to apply an "invisibility gradient" on the top. If the cell is at a 50px distance from the top edge it starts to vanish. The closer it is to the upper edge, the more invisible the part is. The cells height is about 200 pixels, so the lower part of the cell need to be visible in 100%. But nevermind - I need a table view (or table view container) to do this task, because similar tables can display other cells.

If the table is a subview of a solid color view, I can achieve that by adding an image which is a horizontal gradient that I can streach to any width. The top pixel of that image starts with the exact color of the background, and going down the same color has less alpha.

But... we have a UITableView with transparent color. Below the table there is no solid color, but a pattern image/texture, that can also be different on other screens of the app.

Do you have any Idea how I can achieve this behaviour?

Regards

4条回答
Fickle 薄情
2楼-- · 2019-01-14 10:07

This is my version of the fading table view by inheriting UITableView. Tested for iOS 7 & 8.

  1. There is no need to implement scrollViewDidScroll, layoutSubviews can be used instead.
  2. By observing for bounds changes, the mask is always properly placed when the rotation changes.
  3. You can use the percent parameter to change how much fading you want at the edges, I find a small value looking better in some cases.

CEFadingTableView.m

#import "CEFadingTableView.h"

@interface CEFadingTableView()

@property (nonatomic) float percent; // 1 - 100%

@end

@implementation CEFadingTableView

- (void)awakeFromNib
{
    [super awakeFromNib];

    self.percent = 5.0f;

    [self addObserver:self forKeyPath:@"bounds" options:0 context:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if(object == self && [keyPath isEqualToString:@"bounds"])
    {
        [self initMask];
    }
}

- (void)dealloc
{
    [self removeObserver:self forKeyPath:@"bounds"];
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    [self updateMask];
}

- (void)initMask
{
    CAGradientLayer *maskLayer = [CAGradientLayer layer];

    maskLayer.locations = @[@(0.0f), @(_percent / 100), @(1 - _percent / 100), @(1.0f)];

    maskLayer.bounds = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);

    maskLayer.anchorPoint = CGPointZero;

    self.layer.mask = maskLayer;

    [self updateMask];
}

- (void)updateMask
{
    UIScrollView *scrollView = self;

    CGColorRef outer = [UIColor colorWithWhite:1.0 alpha:0.0].CGColor;
    CGColorRef inner = [UIColor colorWithWhite:1.0 alpha:1.0].CGColor;

    NSArray *colors = @[(__bridge id)outer, (__bridge id)inner, (__bridge id)inner, (__bridge id)outer];

    if(scrollView.contentOffset.y <= 0) // top
    {
        colors = @[(__bridge id)inner, (__bridge id)inner, (__bridge id)inner, (__bridge id)outer];
    }
    else if((scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height) // bottom
    {
        colors = @[(__bridge id)outer, (__bridge id)inner, (__bridge id)inner, (__bridge id)inner];
    }

    ((CAGradientLayer *)scrollView.layer.mask).colors = colors;

    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    scrollView.layer.mask.position = CGPointMake(0, scrollView.contentOffset.y);
    [CATransaction commit];
}

@end
查看更多
女痞
3楼-- · 2019-01-14 10:16

This is a translation of Aviel Gross's answer to Swift

import UIKit

class mViewController: UIViewController, UIScrollViewDelegate {

    //Emitted boilerplate code

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        if (self.tableView.layer.mask == nil) {

            //If you are using auto layout
            //self.view.layoutIfNeeded()

            let maskLayer: CAGradientLayer = CAGradientLayer()

            maskLayer.locations = [0.0,0.2,0.8,1.0]
            let width = self.tableView.frame.size.width
            let height = self.tableView.frame.size.height
            maskLayer.bounds = CGRect(x: 0.0, y: 0.0, width: width, height: height)
            maskLayer.anchorPoint = CGPoint.zero // Swift 3.0 update

            self.tableView.layer.mask = maskLayer
        }

        scrollViewDidScroll(self.tableView)
    }

    // Swift 3.0 update
    func scrollViewDidScroll(_ scrollView: UIScrollView) {

        let outerColor = UIColor(white: 1.0, alpha: 0.0).CGColor
        let innerColor = UIColor(white: 1.0, alpha: 1.0).CGColor

        var colors = [AnyObject]()

        if (scrollView.contentOffset.y+scrollView.contentInset.top <= 0) {
            colors = [innerColor, innerColor, innerColor, outerColor]
        }else if (scrollView.contentOffset.y+scrollView.frame.size.height >= scrollView.contentSize.height){
            colors = [outerColor, innerColor, innerColor, innerColor]
        }else{
            colors = [outerColor, innerColor, innerColor, outerColor]
        }

        (scrollView.layer.mask as! CAGradientLayer).colors = colors


        CATransaction.begin()
        CATransaction.setDisableActions(true)
        scrollView.layer.mask?.position = CGPoint(x: 0.0, y: scrollView.contentOffset.y)
        CATransaction.commit()
    }

    //Emitted boilerplate code
}
查看更多
Explosion°爆炸
4楼-- · 2019-01-14 10:18

I took this tutorial and made some changes and additions:

  • It now works on all tableviews - even if they are part of bigger screen.
  • It works regardless of the background or whatever is behind the tableview.
  • The mask changes depends on the position of the table view - when scrolled to top only the bottom faded, in when scrolled to bottom only top is faded...

1. Start by importing QuartzCore and setting a mask layer in your controller:

EDIT: No need for reference to CAGradientLayer in class.

#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>

@interface mViewController : UIViewController
.
.
@end

2. Add this to viewWillAppear viewDidLayoutSubviews: (See @Darren's comment on this one)

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    if (!self.tableView.layer.mask)
    {
        CAGradientLayer *maskLayer = [CAGradientLayer layer];

        maskLayer.locations = @[[NSNumber numberWithFloat:0.0], 
                                [NSNumber numberWithFloat:0.2], 
                                [NSNumber numberWithFloat:0.8], 
                                [NSNumber numberWithFloat:1.0]];

        maskLayer.bounds = CGRectMake(0, 0,
                            self.tableView.frame.size.width,
                            self.tableView.frame.size.height);
        maskLayer.anchorPoint = CGPointZero;

       self.tableView.layer.mask = maskLayer;
    }
    [self scrollViewDidScroll:self.tableView];
}

3. Make sure you are a delegate of UIScrollViewDelegate by adding it in the .h of your controller:

@interface mViewController : UIViewController <UIScrollViewDelegate>

4. To finish, implement scrollViewDidScroll in your controller .m:

#pragma mark - Scroll View Delegate Methods

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    CGColorRef outerColor = [UIColor colorWithWhite:1.0 alpha:0.0].CGColor;
    CGColorRef innerColor = [UIColor colorWithWhite:1.0 alpha:1.0].CGColor;
    NSArray *colors;

    if (scrollView.contentOffset.y + scrollView.contentInset.top <= 0) {
        //Top of scrollView
        colors = @[(__bridge id)innerColor, (__bridge id)innerColor,
                   (__bridge id)innerColor, (__bridge id)outerColor];
    } else if (scrollView.contentOffset.y + scrollView.frame.size.height
               >= scrollView.contentSize.height) {
        //Bottom of tableView
        colors = @[(__bridge id)outerColor, (__bridge id)innerColor,
                   (__bridge id)innerColor, (__bridge id)innerColor];
    } else {
        //Middle
        colors = @[(__bridge id)outerColor, (__bridge id)innerColor,
                   (__bridge id)innerColor, (__bridge id)outerColor];
    }
    ((CAGradientLayer *)scrollView.layer.mask).colors = colors;

    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    scrollView.layer.mask.position = CGPointMake(0, scrollView.contentOffset.y);
    [CATransaction commit];
}

Again: most of the solution is from this tutorial in cocoanetics.

查看更多
兄弟一词,经得起流年.
5楼-- · 2019-01-14 10:32

Swift 3 Version of @victorfigol's excellent solution:

class FadingTableView : UITableView {
  var percent = Float(0.05)

  fileprivate let outerColor = UIColor(white: 1.0, alpha: 0.0).cgColor
  fileprivate let innerColor = UIColor(white: 1.0, alpha: 1.0).cgColor

  override func awakeFromNib() {
    super.awakeFromNib()
    addObserver(self, forKeyPath: "bounds", options: NSKeyValueObservingOptions(rawValue: 0), context: nil)
  }

  override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if object is FadingTableView && keyPath == "bounds" {
        initMask()
    }
  }

  deinit {
    removeObserver(self, forKeyPath:"bounds")
  }

  override func layoutSubviews() {
    super.layoutSubviews()
    updateMask()
  }

  func initMask() {
    let maskLayer = CAGradientLayer()
    maskLayer.locations = [0.0, NSNumber(value: percent), NSNumber(value:1 - percent), 1.0]
    maskLayer.bounds = CGRect(x:0, y:0, width:frame.size.width, height:frame.size.height)
    maskLayer.anchorPoint = CGPoint.zero
    self.layer.mask = maskLayer

    updateMask()
  }

  func updateMask() {
    let scrollView : UIScrollView = self

    var colors = [AnyObject]()

    if scrollView.contentOffset.y <= -scrollView.contentInset.top { // top
        colors = [innerColor, innerColor, innerColor, outerColor];
    }
    else if (scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height { // bottom
        colors = [outerColor, innerColor, innerColor, innerColor]
    }
    else {
        colors = [outerColor, innerColor, innerColor, outerColor]
    }

    if let mask = scrollView.layer.mask as? CAGradientLayer {
        mask.colors = colors

        CATransaction.begin()
        CATransaction.setDisableActions(true)
        mask.position = CGPoint(x: 0.0, y: scrollView.contentOffset.y)
        CATransaction.commit()
     }
  }
}
查看更多
登录 后发表回答