表格视图的索引定制

2017-04-11  本文已影响28人  Kevin_wzx

1.概念

UITbableView分组展示信息时,有时在右侧会带索引,右侧的索引一般为分组的首字母,比如城市列表的展示。当点击右侧索引的字母,列表会快速跳到索引对应的分组,可以方便我们快速查找。它其实就是节索引,分好节后,只需要设置节索引数组就好了,索引名称可以和节标题不同,不过它的顺序是一一对应的。如下图:

4673_140919141445_1.jpg

2.表格视图自带的索引设置

屏幕快照 2017-04-11 下午2.42.38.png
代码如下:
屏幕快照 2017-04-11 下午2.44.20.png 屏幕快照 2017-04-11 下午2.44.34.png 屏幕快照 2017-04-11 下午2.44.51.png

上面举得是最简单的小例子,已经将要显示的数据进行了排序,实际上真正在开发的过程中,不需要手动排序,可以使用第三方的方法,将所有的城市转为拼音,按照首字母进行分组排序,然后索引也根据拼音首字母自动获得。

3.自定义索引一

效果类似于酷狗音乐的索引:
主要效果:点击右侧索引,选中的项颜色可变;滑动右边的索引,滑到哪一项,中间就显示相对应的项提示;滑动表格视图,滑动某一项,右边索引就跳到相应的项......

1429890-ebb38a935cee799a.gif
封装的索引视图UToIndexView代码如下:
.h文件
#import <UIKit/UIKit.h>

@class UToIndexView,UToIndexViewDelegate;

@protocol UToIndexViewDelegate <NSObject>

// 手指触摸的时候调用
- (int)indexView:(UToIndexView *)indexView sectionForSectionIndexTitle:(NSString *)title at:(NSInteger)index;

// 返回数据源
- (NSArray *)indexViewSectionIndexTitles:(UToIndexView *)indexView;

// 手指离开的时候调用
- (void)indexView:(UToIndexView *)indexView  cancelTouch:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;

@end

@interface UToIndexView : UIView

@property (nonatomic, weak) id <UToIndexViewDelegate> deleagte;// 代理

@property (nonatomic,assign) BOOL selectedScaleAnimation;// 默认false,选中的标题不需要放

@property (nonatomic,strong) NSMutableArray *letters;// 存放字母数组

@property (nonatomic,strong) UIColor *selectTitleColor;// 选中颜色

@property (nonatomic,strong) UIColor *normalTitleColor;// 正常颜色

// 便利构造方法
- (instancetype)initWithDelegate:(id<UToIndexViewDelegate>)delegate;

@end
.m文件
#import "UToIndexView.h"

@class UToIndexViewDelegate;

@interface UToIndexView ()

@property (nonatomic,strong) NSMutableArray *letterButtons;// 存放字母按钮的数组

@property (nonatomic,strong) UIButton *selectedButton;// 当前选中按钮

@end

@implementation UToIndexView

// 懒加载
- (NSMutableArray *)letterButtons {
    
    if (!_letterButtons) {
        
        _letterButtons = [[NSMutableArray alloc]init];
    }
    return _letterButtons;
}

-(void)setLetters:(NSMutableArray *)letters {
    
    _letters = letters;
    [self setUpUI];
}

-(void)setNormalTitleColor:(UIColor *)normalTitleColor {
    
    _normalTitleColor = normalTitleColor;
}

-(void)setSelectTitleColor:(UIColor *)selectTitleColor {
    
    _selectTitleColor = selectTitleColor;
}

- (instancetype)initWithDelegate:(id<UToIndexViewDelegate>)delegate {
    
    if (self == [super init]) {
        
        self.deleagte = delegate;
        
        if (delegate != nil && [delegate respondsToSelector:@selector(indexViewSectionIndexTitles:)]) {
            
            self.letters = [NSMutableArray arrayWithArray:[delegate indexViewSectionIndexTitles:self]];
        }
    }
    return self;
}

// 内部初始化方法
- (void)setUpUI {
    
    for (UIView *view in self.subviews) {
        
        [view removeFromSuperview];
    }
    [self.letterButtons removeAllObjects];
    
    CGFloat h = (SCREEN_WIDTH == 480) ? 15 : 18;
    CGFloat x = 0;
    [self.letters enumerateObjectsUsingBlock:^(NSString*  _Nonnull letter, NSUInteger idx, BOOL * _Nonnull stop) {
        
        CGFloat y = idx*(h);
        UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(x, y, self.frame.size.width, h)];
        [btn setTitle:letter forState:UIControlStateNormal];
        [btn setTitleColor:self.normalTitleColor forState:UIControlStateNormal];
        
        if (SCREEN_HEIGHT == 480) {
            
            btn.titleLabel.font = [UIFont systemFontOfSize:11];
        } else {
            
            btn.titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline];
        }
        btn.userInteractionEnabled = NO;
        [self addSubview:btn];
        [self.letterButtons addObject:btn];
        
        if (idx == 0) {
            
            [btn setTitleColor:self.selectTitleColor forState:UIControlStateNormal];
            self.selectedButton = btn;
        }
    }];
    [self layoutIfNeeded];
    CGRect frame = self.frame;
    self.frame = frame;
    frame.size.height = _letterButtons.count *h;
    //self.center = CGPointMake(MGScreenW-10, MGScreenH*0.5);
    self.center = CGPointMake(SCREEN_WIDTH-20, SCREEN_HEIGHT*0.4);// 设置中心位置
}

- (instancetype)initWithFrame:(CGRect)frame {
    
    if (self == [super initWithFrame:frame]) {
        
        //self.frame = CGRectMake(SCREEN_WIDTH - 20, 0, 18, SCREEN_HEIGHT);
        self.frame = CGRectMake(SCREEN_WIDTH-25, 20, 25, 400);// 设置位置
        _selectedScaleAnimation = NO;
        _normalTitleColor = [SXTool grayColor];
        _selectTitleColor = [SXTool greenColor];
        self.backgroundColor = [SXTool backGrayColor];
        self.layer.cornerRadius = 7;
        self.layer.borderWidth = 1;
        self.layer.borderColor = [SXTool backGrayColor].CGColor;
        self.alpha = 0.6;
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(scrollViewSelectButtonTitleColor:) name:@"UToWillDisplayHeaderViewNotification" object:nil];
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(scrollViewSelectButtonTitleColor:) name:@"UToDidEndDisplayingHeaderViewNotification" object:nil];
    }
    return self;
}

// 通知回调
- (void)scrollViewSelectButtonTitleColor:(NSNotification *)noti {
    
    NSNumber *number = noti.userInfo[@"section"];
    int section = number.intValue;
    
    if (section >= self.letterButtons.count) {
        
        return;
    }
    UIButton *btn = self.letterButtons[section];
    
    [UIView animateWithDuration:1.0 animations:^{
        
        [self.selectedButton setTitleColor:self.normalTitleColor forState:UIControlStateNormal];
        self.selectedButton = btn;
        [self.selectedButton setTitleColor:self.selectTitleColor forState:UIControlStateNormal];
    }];
    
    if (self.selectedScaleAnimation) {
        
        btn.layer.transform = CATransform3DMakeScale(1.5, 1.5, 1.5);
        [UIView animateWithDuration:1.0 animations:^{
            
            btn.layer.transform = CATransform3DIdentity;
        }];
    }
}

// 移除通知
- (void)dealloc {
    
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

// 开始触发回调方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    [self touchesMoved:touches withEvent:event];
}

// 触发移动回调方法
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    UITouch *touch = touches.anyObject;
    // 获取当前的触摸点
    CGPoint curP = [touch locationInView:self];
    
    for (UIButton *btn in _letterButtons) {
        
        CGPoint btnP = [self convertPoint:curP toView:btn];// 把btn的转化为坐标
        
        if ([btn pointInside:btnP withEvent:event]) {// 判断当前点是否点在按钮上
            
            self.selectedButton = btn;
            [btn setTitleColor:self.selectTitleColor forState:UIControlStateNormal];
            NSInteger i = [self.letterButtons indexOfObject:btn];
            NSString *letter = btn.currentTitle;
            
            if ([_deleagte respondsToSelector:@selector(indexView:sectionForSectionIndexTitle:at:)]) {
                
                [_deleagte indexView:self sectionForSectionIndexTitle:letter at:i];
            }
            
            if (_selectedScaleAnimation) { // 是否需要放大效果
                
                btn.layer.transform = CATransform3DMakeScale(1.5, 1.5, 1.5);
                [UIView animateWithDuration:1.0 animations:^{
                    
                    btn.layer.transform = CATransform3DIdentity;
                }];
            }
        } else {
            
            [btn setTitleColor:self.normalTitleColor forState:UIControlStateNormal];
            btn.layer.transform = CATransform3DIdentity;
        }
    }
}

// 结束触发回调方法
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    [self.selectedButton setTitleColor:self.selectTitleColor forState:UIControlStateNormal];
    
    if ([_deleagte respondsToSelector:@selector(indexView:cancelTouch:withEvent:)]) {
        
        [_deleagte indexView:self cancelTouch:touches withEvent:event];
    }
}

@end

控制器中调用:
创建视图:

屏幕快照 2017-04-11 下午4.49.31.png 屏幕快照 2017-04-11 下午4.49.06.png
屏幕快照 2017-04-11 下午3.39.31.png

数组赋值:


屏幕快照 2017-04-11 下午3.40.07.png 屏幕快照 2017-04-11 下午3.38.09.png
表格填充内容此处省略......
demo链接: https://pan.baidu.com/s/1kVBgAxD 密码: 1hmk

4.自定义索引二

效果如下:

2017-09-28 17_51_12.gif

代码如下:
h.文件

屏幕快照 2017-09-28 下午4.06.53.png 屏幕快照 2017-09-28 下午4.07.11.png

.m文件

屏幕快照 2017-09-28 下午4.07.50.png 屏幕快照 2017-09-28 下午4.08.06.png 屏幕快照 2017-09-28 下午4.08.29.png 屏幕快照 2017-09-28 下午4.08.49.png 屏幕快照 2017-09-28 下午4.09.09.png 屏幕快照 2017-09-28 下午4.09.23.png 屏幕快照 2017-09-28 下午4.09.36.png 屏幕快照 2017-09-28 下午4.09.59.png

实例应用:

1.导入头文件:#import "UToIndexView.h";添加代理:UToIndexViewDataSource
2.@property (nonatomic, strong) UToIndexView *indexView;

3.创建,代码如下:

    self.indexView = [[UToIndexView alloc] initWithFrame:CGRectMake(SCREEN_WIDTH-40, 0, 40, SCREEN_HEIGHT)];
    self.indexView.dataSources = self;
    self.indexView.bgColor = [UIColor clearColor];
    self.indexView.selectTitleColor = [UToColor greenColor];
    [self.indexView updateUI];
    [self.view addSubview:self.indexView]; 
    [self.indexView reLoadIndexItems];// 该刷新方法看需要调用

4.实现代理方法

屏幕快照 2017-09-28 下午5.28.35.png

5.滑动表格,索引相对应

屏幕快照 2017-09-28 下午5.31.36.png

5.自定义索引三

主要效果:点击右侧索引,选中某项跳到相应的项;滑动右边的索引,滑到哪一项,跳到相对应的项以及索引有如下截图的动画效果......

150DDC3B5E2D942099B093684600176D.png
封装的视图代码如下:
.h文件
#import <UIKit/UIKit.h>

@class MJNIndexView;

@protocol MJNIndexViewDataSource

// you have to implement this method to provide this UIControl with NSArray of items you want to display in your index
- (NSArray *)sectionIndexTitlesForMJNIndexView:(MJNIndexView *)indexView;

// you have to implement this method to get the selected index item 
- (void)sectionForSectionMJNIndexTitle:(NSString *)title atIndex:(NSInteger)index;

@end

@interface MJNIndexView : UIControl

@property (nonatomic, weak) id <MJNIndexViewDataSource> dataSource;

// FOR ALL COLORS USE RGB MODEL - DON'T USE whiteColor, blackColor, grayColor or colorWithWhite, colorWithHue

// set this to NO if you want to get selected items during the pan (default is YES)
@property (nonatomic, assign) BOOL getSelectedItemsAfterPanGestureIsFinished;

/* set the font and size of index items (if font size you choose is too large it will be automatically adjusted to the largest possible)
(default is HelveticaNeue 15.0 points)*/
@property (nonatomic, strong) UIFont *font;

/* set the font of the selected index item (usually you should choose the same font with a bold style and much larger)
(default is the same font as previous one with size 40.0 points) */
@property (nonatomic, strong) UIFont *selectedItemFont;

// set the color for index items 
@property (nonatomic, strong) UIColor *fontColor;

// set if items in index are going to darken during a pan (default is YES)
@property (nonatomic, assign) BOOL darkening;

// set if items in index are going ti fade during a pan (default is YES)
@property (nonatomic, assign) BOOL fading;

// set the color for the selected index item
@property (nonatomic, strong) UIColor *selectedItemFontColor;

// set index items aligment (NSTextAligmentLeft, NSTextAligmentCenter or NSTextAligmentRight - default is NSTextAligmentCenter)
@property (nonatomic, assign) NSTextAlignment itemsAligment;

// set the right margin of index items (default is 10.0 points)
@property (nonatomic, assign) CGFloat rightMargin;

/* set the upper margin of index items (default is 20.0 points)
please remember that margins are set for the largest size of selected item font*/
@property (nonatomic, assign) CGFloat upperMargin;

// set the lower margin of index items (default is 20.0 points)
// please remember that margins are set for the largest size of selected item font
@property (nonatomic, assign) CGFloat lowerMargin;

// set the maximum amount for item deflection (default is 75.0 points)
@property (nonatomic,assign) CGFloat maxItemDeflection;

// set the number of items deflected below and above the selected one (default is 3 items)
@property (nonatomic, assign) int rangeOfDeflection;

// set the curtain color if you want a curtain to appear (default is none)
@property (nonatomic, strong) UIColor *curtainColor;

// set the amount of fading for the curtain between 0 to 1 (default is 0.2)
@property (nonatomic, assign) CGFloat curtainFade;

// set if you need a curtain not to hide completely (default is NO)
@property (nonatomic, assign) BOOL curtainStays;

// set if you want a curtain to move while panning (default is NO)
@property (nonatomic, assign) BOOL curtainMoves;

// set if you need a curtain to have the same upper and lower margins (default is NO)
@property (nonatomic, assign) BOOL curtainMargins;

// set the minimum gap between item (default is 5.0 points)
@property (nonatomic, assign) CGFloat minimumGapBetweenItems;

// set this property to YES and it will automatically set margins so that gaps between items are set to the minimumItemGap value (default is YES)
@property BOOL ergonomicHeight;

// set the maximum height for index egronomicHeight - it might be useful for iPad (default is 400.0 ponts)
@property (nonatomic, assign) CGFloat maxValueForErgonomicHeight;

// use this method if you want to change index items or change some properties for layout
- (void)refreshIndexItems;

@end
.m文件
#import "MJNIndexView.h"
#import <QuartzCore/QuartzCore.h>


@interface MJNIndexView ()

// item properties
@property (nonatomic, strong) NSArray *indexItems;
@property (nonatomic, strong) NSArray *itemsAtrributes;

@property (nonatomic, strong) NSNumber *section;

// sizes for items
@property (nonatomic) CGFloat itemsOffset;
@property (nonatomic) CGPoint firstItemOrigin;
@property (nonatomic) CGSize indexSize;
@property (nonatomic, assign) CGFloat maxWidth;
@property (nonatomic) BOOL animate;

// curtain properties
@property (nonatomic, strong) CAGradientLayer *gradientLayer;
@property (nonatomic) BOOL curtain;
@property (nonatomic, assign) CGFloat curtainFadeFactor;

// easter eggs properties
@property (nonatomic, assign) BOOL dot;
@property (nonatomic, assign) int times;

@end

@implementation MJNIndexView
// we need to synthetise fontColor because we need our own setter and getter methods
@synthesize fontColor = _fontColor;

#pragma mark getters

- (UIColor *)fontColor
{
    if (!_fontColor) {
        self.fontColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1.0];
    }
    return _fontColor;
}

- (UIColor *)selectedItemFontColor
{
    if (!_selectedItemFontColor) {
        _selectedItemFontColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:1.0];
        
    }
    return _selectedItemFontColor;
}

#pragma mark setters
- (void)setCurtainColor:(UIColor *)curtainColor
{
    _curtainColor = curtainColor;
    
}

- (void)setFontColor:(UIColor *)fontColor
{
    // we need to convert grayColor, whiteColor and blackColor to RGB;
    if ([fontColor isEqual:[UIColor grayColor]]) {
        _fontColor = [UIColor colorWithRed:0.5
                                     green:0.5
                                      blue:0.5
                                     alpha:1.0];
    } else if ([fontColor isEqual:[UIColor blackColor]]) {
        _fontColor = [UIColor colorWithRed:0.0
                                     green:0.0
                                      blue:0.0
                                     alpha:1.0];
    } else if ([fontColor isEqual:[UIColor whiteColor]]) {
        _fontColor = [UIColor colorWithRed:1.0
                                     green:1.0
                                      blue:1.0
                                     alpha:1.0];
    } else _fontColor = fontColor;
}

- (void)setCurtainFade:(CGFloat)curtainFade
{
    if (self.gradientLayer) {
        [self.gradientLayer removeFromSuperlayer];
        self.gradientLayer = nil;
    }
    _curtainFade = curtainFade;
}

- (void)setDataSource:(id<MJNIndexViewDataSource>)dataSource
{
    _dataSource = dataSource;
    self.indexItems = [dataSource sectionIndexTitlesForMJNIndexView:self];
}

#pragma mark view lifecycle methods

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // initialising all default values
        self.backgroundColor = [UIColor clearColor];
        self.darkening = YES;
        self.fading = YES;
        self.itemsAligment = NSTextAlignmentCenter;
        self.upperMargin = 20.0;
        self.lowerMargin = 20.0;
        self.rightMargin = 10.0;
        self.maxItemDeflection = 100.0;
        self.rangeOfDeflection = 3;
        self.font = [UIFont fontWithName:@"HelveticaNeue" size:15.0];
        self.selectedItemFont = [UIFont fontWithName:@"HelveticaNeue" size:50.0];
        self.ergonomicHeight = YES;
        self.maxValueForErgonomicHeight = 400.0;
        self.minimumGapBetweenItems = 5.0;
        self.getSelectedItemsAfterPanGestureIsFinished = YES;
    }
    return self;
}

- (id)init
{
    self = [self initWithFrame:CGRectZero];
    return self;
}

- (void)didMoveToSuperview
{
    [self getAllItemsSize];
    [self initialiseAllAttributes];
    [self resetPosition];
}

// refreshing our index items
- (void)refreshIndexItems
{
    // if items existed we have to remove all sublayers from main layer
    if (self.itemsAtrributes) {
        for (NSDictionary *item in self.itemsAtrributes) {
            CALayer *layer = item[@"layer"];
            [layer removeFromSuperlayer];
        }
        self.itemsAtrributes = nil;
    }
    
    if (self.gradientLayer) [self.gradientLayer removeFromSuperlayer];
    
    
    self.indexItems = [self.dataSource sectionIndexTitlesForMJNIndexView:self];
    [self getAllItemsSize];
    [self initialiseAllAttributes];
    [self resetPosition];
}

#pragma mark calculating initial values and sizes then setting them
// calculating all necessary sizes and values to draw index items
- (void)getAllItemsSize
{
    CGSize indexSize = CGSizeZero;
    
    // determining font sizes
    CGFloat lineHeight = self.font.lineHeight;
    CGFloat maxlineHeight = self.selectedItemFont.capHeight;
    CGFloat capitalLetterHeight = self.font.capHeight;
    CGFloat ascender = self.font.ascender;
    CGFloat descender = - self.font.descender;
    CGFloat entireHeight = ascender;
    
    // checking for upper and lower case letters and setting entireHeight value accordingly
    if ([self checkForLowerCase] && [self checkForUpperCase]) {
        entireHeight = lineHeight;
        maxlineHeight = self.selectedItemFont.lineHeight;
    } else if ([self checkForLowerCase] && ![self checkForUpperCase]) {
        entireHeight = capitalLetterHeight + descender;
        maxlineHeight = self.selectedItemFont.lineHeight;
    }
    
    // calculating size of all index items
    for (NSString *item in self.indexItems) {
        CGSize currentItemSize = [item sizeWithAttributes:@{NSFontAttributeName:self.font}];
        indexSize.height += entireHeight;
        if (currentItemSize.width > indexSize.width) {
            indexSize.width = currentItemSize.width;
        }
    }
    
    // calculating if deflectionRange is not too small based on the width of the longest index item using the font for selected item
    for (NSString *item in self.indexItems) {
        CGSize currentItemSize = [item sizeWithAttributes:@{NSFontAttributeName:self.selectedItemFont}];
        if (currentItemSize.width > self.maxWidth) {
            self.maxWidth = currentItemSize.width;
        }
        if (currentItemSize.width > self.maxItemDeflection) {
            self.maxItemDeflection = currentItemSize.width;
        }
    }
    
    // ajdusting margins to ensure that minimum offset is 5.0 points
    CGFloat optimalIndexHeight = indexSize.height;
    if (optimalIndexHeight > self.maxValueForErgonomicHeight) optimalIndexHeight = self.maxValueForErgonomicHeight;
    CGFloat offsetRatio = self.minimumGapBetweenItems * (float)([self.indexItems count]-1) + optimalIndexHeight + maxlineHeight / 1.5;
    if (self.ergonomicHeight && (self.bounds.size.height - offsetRatio > 0.0)) {
        self.upperMargin = (self.bounds.size.height - offsetRatio)/2.0;
        self.lowerMargin = self.upperMargin;
    }
    
    // checking if self.font size is not to large to draw entire index - if it's calculating the largest possible using recurency
    if (indexSize.height  > self.bounds.size.height - (self.upperMargin + self.lowerMargin) - (self.minimumGapBetweenItems * [self.indexItems count])) {
        self.font = [self.font fontWithSize:(self.font.pointSize - 0.1)];
        [self getAllItemsSize];
        
    } else {
        // calculating an offset between index items
        self.itemsOffset = ((self.bounds.size.height - (self.upperMargin + self.lowerMargin + maxlineHeight / 1.5)) - indexSize.height) / (float)([self.indexItems count]-1);

        // calculating the first item origin based on the offset, an items aligment and marging
        if (self.itemsAligment == NSTextAlignmentRight) {
            self.firstItemOrigin = CGPointMake(self.bounds.size.width - self.rightMargin,
                                               self.upperMargin + maxlineHeight / 2.5 + entireHeight / 2.0);
            
        } else if (self.itemsAligment == NSTextAlignmentCenter) {
            self.firstItemOrigin = CGPointMake(self.bounds.size.width - self.rightMargin - indexSize.width/2,
                                               self.upperMargin + maxlineHeight / 2.5 + entireHeight / 2.0);
            
        } else self.firstItemOrigin = CGPointMake(self.bounds.size.width - self.rightMargin - indexSize.width,
                                                  self.upperMargin + maxlineHeight / 2.5 + entireHeight / 2.0);
        // 
        self.itemsOffset += entireHeight;
        self.indexSize = indexSize;
    }
    
    // checking if range of items to deflect is not too big
    if (self.rangeOfDeflection > [self.indexItems count]/2 - 1) self.rangeOfDeflection = (int)[self.indexItems count]/2 - 1;
}

// checking if there are any items with lower case
- (BOOL)checkForLowerCase
{
    NSCharacterSet *lowerCaseSet = [NSCharacterSet lowercaseLetterCharacterSet];
    for (NSString *item in self.indexItems) {
        if ([item rangeOfCharacterFromSet:lowerCaseSet].location != NSNotFound) return YES;
    }
    return NO;
}

// checking ig there are any items with upper case
- (BOOL)checkForUpperCase
{
    NSCharacterSet *upperCaseSet = [NSCharacterSet uppercaseLetterCharacterSet];
    for (NSString *item in self.indexItems) {
        if ([item rangeOfCharacterFromSet:upperCaseSet].location != NSNotFound) return YES;
    }
    return NO;
}

- (void) initialiseAllAttributes
{
    CGFloat verticalPos = self.firstItemOrigin.y;
    NSMutableArray *newItemsAttributes = [NSMutableArray new];
    
    int count = 0;
    
    for (NSString *item in self.indexItems) {
        
        // calculating items origins based on items aligment and firstItemOrigin
        CGPoint point;
        
        if (self.itemsAligment == NSTextAlignmentCenter){
            
            CGSize itemSize = [item sizeWithAttributes:@{NSFontAttributeName:self.font}];
            point.x = self.firstItemOrigin.x - itemSize.width/2;
        } else if (self.itemsAligment == NSTextAlignmentRight) {
            
            CGSize itemSize = [item sizeWithAttributes:@{NSFontAttributeName:self.font}];
            point.x = self.firstItemOrigin.x - itemSize.width;
        } else point.x = self.firstItemOrigin.x;
        
        point.y = verticalPos;
        NSValue *newValueForPoint = [NSValue valueWithCGPoint:point];
        
        
        if (!self.itemsAtrributes) {
            CATextLayer * singleItemTextLayer = [CATextLayer layer];
            
            NSNumber *alpha = @(CGColorGetAlpha([self.fontColor CGColor]));
            
            // setting zPosition a little above because we might need to put something below
            NSNumber *zPosition = @(5.0);
            
            NSCache *itemAttributes = [@{@"item":item,
                                                   @"origin":newValueForPoint,
                                                   @"position":newValueForPoint,
                                                   @"font":self.font,
                                                   @"color":self.fontColor,
                                                   @"alpha":alpha,
                                                   @"zPosition":zPosition,
                                                   @"layer":singleItemTextLayer}mutableCopy];
            
            [newItemsAttributes addObject:itemAttributes];
        } else {
            self.itemsAtrributes[count][@"origin"] = newValueForPoint;
            self.itemsAtrributes[count][@"position"] = newValueForPoint;
        }
        
        verticalPos += self.itemsOffset;
        count ++;
        
    }
    if (self.curtainColor) [self addCurtain];
    if (!self.itemsAtrributes) self.itemsAtrributes = newItemsAttributes;
}

// reseting positions of index items
- (void) resetPosition
{
    for (NSCache *itemAttributes in self.itemsAtrributes){
        CGPoint origin = [[itemAttributes objectForKey:@"origin"] CGPointValue];
        [itemAttributes setObject:[NSValue valueWithCGPoint:origin] forKey:@"position"];
        [itemAttributes setObject:self.font forKey:@"font"];
        [itemAttributes setObject:@(1.0) forKey:@"alpha"];
        [itemAttributes setObject:self.fontColor forKey:@"color"];
        [itemAttributes setObject:@(5.0) forKey:@"zPosition"];

    }
    
    [self drawIndex];
    [self setNeedsDisplay];
    
    self.animate = YES;
}

#pragma mark calculating item position during the pan gesture
- (void) positionForIndexItemsWhilePanLocation:(CGPoint)location
{    
    CGFloat verticalPos = self.firstItemOrigin.y;
  
    int section = 0;
    for (NSCache *itemAttributes in self.itemsAtrributes) {
        
        CGFloat alpha = [[itemAttributes objectForKey: @"alpha"] floatValue];
        CGPoint point = [[itemAttributes objectForKey:@"position"] CGPointValue];
        CGPoint origin = [[itemAttributes objectForKey:@"origin"] CGPointValue];
        CGFloat fontSize = [[itemAttributes objectForKey:@"fontSize"] floatValue];
        UIColor *fontColor;
        
        BOOL inRange = NO;
        
        // we have to map amplitude of deflection
        
        float mappedAmplitude = self.maxItemDeflection / self.itemsOffset / ((float)self.rangeOfDeflection);
        
        // now we are checking if touch is within the range of items we would like to deflect
        BOOL min = location.y > point.y - ((float)self.rangeOfDeflection * self.itemsOffset);
        BOOL max = location.y < point.y + ((float)self.rangeOfDeflection * self.itemsOffset);
        
        if (min && max) {
            
            // these calculations are necessary to make our deflection not linear
            float differenceMappedToAngle = 90.0 / (self.itemsOffset * (float)self.rangeOfDeflection);
            float angle = (fabs(point.y - location.y)* differenceMappedToAngle);
            float angleInRadians = angle * (M_PI/180);
            float arcusTan = fabs(atan(angleInRadians));
            
            // now we have to calculate the deflected position of an item
            point.x = origin.x - (self.maxItemDeflection) + (fabs(point.y - location.y) * mappedAmplitude) * (arcusTan);
//            point.x = origin.x - 20 + (fabs(point.y - location.y) * mappedAmplitude) * (arcusTan);
            point.x = MIN(point.x, origin.x);
            
            // we have to map difference to range in order to determine right zPosition
            float differenceMappedToRange = self.rangeOfDeflection / (self.rangeOfDeflection * self.itemsOffset);
    
            CGFloat zPosition = self.rangeOfDeflection - fabs(point.y - location.y) * differenceMappedToRange;
            
            [itemAttributes setObject:@(5.0 + zPosition) forKey:@"zPosition"];
            
            // calculating a fontIncrease factor of the deflected item
            CGFloat fontIncrease = (self.maxItemDeflection - (fabs(point.y - location.y)) *
                                    mappedAmplitude) / (self.maxItemDeflection / (self.selectedItemFont.pointSize - self.font.pointSize));
            
            fontIncrease = MAX(fontIncrease, 0.0);
            
            fontSize = self.font.pointSize + fontIncrease;
            
            // calculating a color darkening factor
            float differenceMappedToColorChange = 1.0 / (self.rangeOfDeflection * self.itemsOffset);
            CGFloat colorChange = fabs(point.y - location.y) * differenceMappedToColorChange;
            
            if (self.darkening) {
                fontColor = [self darkerColor:self.fontColor by: colorChange];
            } else fontColor = self.fontColor;
            
            // we're using the same factor for alpha (fading)
            if (self.fading) {
                alpha = colorChange;
            } else alpha = 1.0;
           
            [itemAttributes setObject:[UIFont fontWithName:self.font.fontName size:fontSize] forKey:@"font"];
            
            [itemAttributes setObject:fontColor forKey:@"color"];
            
            // checking if the item is the most deflected one -> it means it is the selected one 
            BOOL selectedInRange  = location.y > point.y - self.itemsOffset / 2.0 && location.y < point.y + self.itemsOffset / 2.0;
            // we need also to check if the selected item is the first or the last one in the index
            BOOL firstItemInRange = (section == 0 && (location.y < self.upperMargin + self.selectedItemFont.pointSize / 2.0));
            BOOL lastItemInRange = (section == [self.itemsAtrributes  count] - 1 &&
                                    location.y > self.bounds.size.height - (self.lowerMargin + self.selectedItemFont.pointSize / 2.0));
            
            // if our location is pointing to the selected item we have to change this item's font, color and make it's zPosition the largest to be sure it's on the top
            if (selectedInRange || firstItemInRange || lastItemInRange) {
                alpha = 1.0;
                
                [itemAttributes setObject:[UIFont fontWithName:self.selectedItemFont.fontName size:fontSize] forKey:@"font"];
                [itemAttributes setObject:self.selectedItemFontColor forKey:@"color"];
                [itemAttributes setObject:@(10.0) forKey:@"zPosition"];
                if (!self.getSelectedItemsAfterPanGestureIsFinished && [self.section integerValue] != section) {
                    [self.dataSource sectionForSectionMJNIndexTitle:self.indexItems[section] atIndex:section];
                }
                self.section = @(section);

            }
            
            // we're marking these items as inRange items
            inRange = YES;

        }
        
        // if item is not in range we have to reset it's x position, alpha value, font name, size and color, zPosition
        if (!inRange) {
            
            point.x = origin.x;
            alpha = 1.0;
            [itemAttributes setObject:self.font forKey:@"font"];
            fontColor = self.fontColor;
            [itemAttributes setObject:self.fontColor forKey:@"color"];
            [itemAttributes setObject:@(5.0) forKey:@"zPosition"];
        }
        
        // we have to store some values in itemAtrributes array
        point.y = verticalPos;
        NSValue *newValueForPoint = [NSValue valueWithCGPoint:point];
        [itemAttributes setObject:newValueForPoint forKey:@"position"];
        [itemAttributes setObject:@(alpha) forKey:@"alpha"];
        verticalPos += self.itemsOffset;
        section ++;
    }
    
    // when are calculations are over we can redraw all items
    [self drawIndex];
    // we set this to NO because we want the animation duration to be as short as possible
    self.animate = NO;
}

// calculating darker color to the given one
- (UIColor *)darkerColor:(UIColor *)color by:(float)value
{
    double h, s, b, a;
    if ([color getHue:&h saturation:&s brightness:&b alpha:&a])
        return [UIColor colorWithHue:h
                          saturation:s
                          brightness:b * value
                               alpha:a];
    return nil;
}

#pragma mark drawing CATextLayers with indexitems
- (void) drawIndex
{
    for (NSCache *itemAttributes in self.itemsAtrributes) {
        // getting attributes necessary to check if we need to animate
        
        UIFont *currentFont = [itemAttributes objectForKey:@"font"];
        CATextLayer * singleItemTextLayer = [itemAttributes objectForKey:@"layer"];
        
        // checking if all CATexts exists
        if ([self.itemsAtrributes count] != [self.layer.sublayers count] - 1) {
            [self.layer addSublayer:singleItemTextLayer];
        }
        
        // checking if font size is different if it's different we have to animate CALayer
        if (singleItemTextLayer.fontSize != currentFont.pointSize) {
            // we have to animate several CALayers at once
            [CATransaction begin];
            
            // if we need to animate faster we're changing the duration to be as short as possible
            
            if (!self.animate) [CATransaction setAnimationDuration:0.005];
            else [CATransaction setAnimationDuration:0.2];
            
            // getting other attributes and updading CALayer
            CGPoint point = [[itemAttributes objectForKey:@"position"] CGPointValue];
            NSString *currentItem = [itemAttributes objectForKey:@"item"];
            CGSize textSize = [currentItem sizeWithAttributes:@{NSFontAttributeName:currentFont}];
            UIColor *fontColor = [itemAttributes objectForKey:@"color"];
            
            singleItemTextLayer.zPosition = [[itemAttributes objectForKey:@"zPosition"] floatValue];
            singleItemTextLayer.font = (__bridge CFTypeRef)(currentFont.fontName);
            singleItemTextLayer.fontSize = currentFont.pointSize;
            singleItemTextLayer.opacity = [[itemAttributes objectForKey:@"alpha"] floatValue];
            singleItemTextLayer.string = currentItem;
            singleItemTextLayer.backgroundColor = [UIColor clearColor].CGColor;
            singleItemTextLayer.foregroundColor = fontColor.CGColor;
            singleItemTextLayer.bounds = CGRectMake(0.0,
                                                    0.0,
                                                    textSize.width,
                                                    textSize.height);
            singleItemTextLayer.position = CGPointMake(point.x + textSize.width/2.0,
                                                       point.y);
            singleItemTextLayer.contentsScale = [[UIScreen mainScreen]scale];
            
            [CATransaction commit];
        
        }
    }
}

#pragma mark managing touch events

- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
    int section = 0;
    
    // checking if item any item is touched
    for (NSCache *itemAttributes in self.itemsAtrributes) {
        CGPoint point = [[itemAttributes objectForKey:@"position"] CGPointValue];
        CGPoint location = [touch locationInView:self];
        if (location.y > point.y - self.itemsOffset / 2.0  &&
            location.y < point.y + self.itemsOffset / 2.0) {
            self.section = @(section);
        }
        section ++;
    }
    self.dot = NO;
    [self endEditing:YES];
    return YES;
    
}


- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
    CGFloat currentY = [touch locationInView:self].y;
    CGFloat prevY = [touch previousLocationInView:self].y;
    
    [self showCurtain];
        
    // if pan is longer than three pixel we need to accelerate animation by setting self.animate to NO
    if (fabs(currentY - prevY) > 3.0) {
        self.animate = NO;
    }
    // drawing deflection
    [self positionForIndexItemsWhilePanLocation:[touch locationInView:self]];
    
    return YES;
}

- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
    // sending selected items to dataSource
    [self.dataSource sectionForSectionMJNIndexTitle:self.indexItems[[self.section integerValue]] atIndex:[self.section integerValue]];

    // some easter eggs ;)
    if ([self.section integerValue] == 3 * self.times) {
        self.times ++;
        if (self.times == 5) {
            self.dot = YES;
            [self setNeedsDisplay];
        }
    } else self.times = 0;
    
    // if pan stopped we can deacellerate animation, reset position and hide curtain
    self.animate = YES;
    [self resetPosition];
    [self hideCurtain];
}

- (void)cancelTrackingWithEvent:(UIEvent *)event
{
    // if touch was canceled we reset everything
    self.animate = YES;
    [self resetPosition];
    [self hideCurtain];
    }

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    // UIView will be "transparent" for touch events if we return NO
    // we are going to return YES only if items or area right to them is being touched
    if ((point.x > self.bounds.size.width - (self.indexSize.width + self.rightMargin + 10.0)) &&
        point.y > 0.0 && point.y < self.bounds.size.height) return YES;
    //if (point.y > self.bounds.size.height) return NO;
    return NO;
}

#pragma drawing curtain with CAGradientLayer or CALayer
- (void)addCurtain
{
    // if we want a curtain to fade we have to use CAGradientLayer
    if (self.curtainFade != 0.0) {
        if (self.curtainFade > 1) self.curtainFade = 1;
        if (!self.gradientLayer) {
            self.gradientLayer = [CAGradientLayer layer];
            [self.layer insertSublayer:self.gradientLayer atIndex:0];
        }
        
        // we have to read color components
        const CGFloat * colorComponents = CGColorGetComponents(self.curtainColor.CGColor);
        self.gradientLayer.colors = @[(id)[[UIColor colorWithRed:colorComponents[0]
                                                           green:colorComponents[1]
                                                            blue:colorComponents[2]
                                                           alpha:0.0] CGColor],(id)[self.curtainColor CGColor]];
        
        // calculating endPoint for gradient based on maxItemDeflection and maxWidth of longest selected item
        self.curtainFadeFactor = (self.bounds.size.width - self.rightMargin - self.maxItemDeflection - 0.25 * self.maxWidth - 15.0) / self.bounds.size.width;
        self.gradientLayer.startPoint = CGPointMake(self.curtainFadeFactor - (self.curtainFade * self.curtainFadeFactor),
                                                    0.0);
        self.gradientLayer.endPoint = CGPointMake(MAX(self.curtainFadeFactor,0.02),
                                                  0.0);
    } else {
        
        // if we do not need the fading curtain we can use simple CALayer
        if (!self.gradientLayer) {
            self.gradientLayer = [CAGradientLayer layer];
            [self.layer insertSublayer:self.gradientLayer atIndex:0];
        }
        self.gradientLayer.backgroundColor = self.curtainColor.CGColor;
    }
   
    // curtain is added now we have to hide it first
    self.curtain = YES;
    [self hideCurtain];
}

// hiding the curtain
- (void)hideCurtain
{
    // first we have to check if the curtain is shown and a color for it is set
    if (self.curtain && self.curtainColor) {
        CGRect curtainBoundsRect;
        CGFloat curtainVerticalCenter;
        CGFloat curtainHorizontalCenter;
        CGFloat multiplier = 2.0;
        
        if (!self.curtainMargins) {
            curtainBoundsRect = CGRectMake(0.0,
                                           0.0,
                                           self.indexSize.width * multiplier + self.rightMargin,
                                           self.bounds.size.height);
            curtainVerticalCenter = self.bounds.size.height / 2.0;
        }
        else {
            // if we need cutain to have the same margins as index items we have to change its height and its vertical center
            curtainBoundsRect = CGRectMake(0.0,
                                           0.0,
                                           self.indexSize.width * multiplier + self.rightMargin,
                                           self.bounds.size.height - (self.upperMargin + self.lowerMargin));
            curtainVerticalCenter = self.upperMargin + curtainBoundsRect.size.height / 2.0;
        }

        if (!self.curtainStays) {
            curtainHorizontalCenter = self.bounds.size.width + self.bounds.size.width / 2.0;
         
        } else {
            // if we don't want the curtain to hide completely we have again to check if we need margins or not and change its height respectively
            if (self.curtainMargins) curtainBoundsRect = CGRectMake(0.0,
                                                                    0.0,
                                                                    self.bounds.size.width,
                                                                    self.bounds.size.height - (self.upperMargin + self.lowerMargin));
            else curtainBoundsRect = self.bounds;
            
            
            // now we need to calculate an offset needed to position curtain not entirely outside the screen
            // to do this we must check items aligment and calculate horizontal center for its position
            CGFloat offset;
            if (self.itemsAligment == NSTextAlignmentRight) offset = self.bounds.size.width - (self.firstItemOrigin.x  - self.indexSize.width/2.0);
            else if (self.itemsAligment == NSTextAlignmentCenter) offset =  (self.bounds.size.width - self.firstItemOrigin.x);
            else offset = (self.bounds.size.width - (self.firstItemOrigin.x +  self.indexSize.width/2.0));
            
            curtainHorizontalCenter = (self.bounds.size.width + self.bounds.size.width / 2.0) -  2 * offset;
            
            // if we are using CAGradientLayer we have to change horizonl center value and recalculate the start and endpoint for gradient
            if ([self.gradientLayer.class isSubclassOfClass:[CAGradientLayer class]]) {
                
                curtainHorizontalCenter = (self.bounds.size.width + self.bounds.size.width / 2.0) -  (2.0  * offset + self.curtainFade * offset);
                
                self.gradientLayer.startPoint = CGPointMake(0.001,
                                                            0.0);
                
                self.gradientLayer.endPoint = CGPointMake(MAX(self.curtainFade,
                                                              300.0 * self.gradientLayer.startPoint.x) * 00.1, 0.0);
            }
        }
        
        // now we can set the courtain bounds and position andset the BOOL self.curtain to NO which meanse the curtain is hidden
        self.gradientLayer.bounds = curtainBoundsRect;
        self.gradientLayer.position = CGPointMake(curtainHorizontalCenter, curtainVerticalCenter);
        self.curtain = NO;
    }
}

// showing the curtain
- (void)showCurtain
{
    // first we have to check if the curtain is shown and a color for it is set
    if (!self.curtain && self.curtainColor && self.curtainMoves) {
        CGFloat curtainVerticalCenter;
        CGRect curtainBoundsRect;
        
        // again like in case for hideCurtain we must calculate position and size for all possible configurations
        if (!self.curtainMargins) {
            curtainBoundsRect = self.bounds;
            curtainVerticalCenter = self.bounds.size.height / 2.0;
        } else {
            curtainBoundsRect = CGRectMake(0.0, self.upperMargin, self.bounds.size.width, self.bounds.size.height - (self.upperMargin + self.lowerMargin));
            curtainVerticalCenter = self.upperMargin + curtainBoundsRect.size.height / 2.0;
        }
        
        if ([self.gradientLayer.class isSubclassOfClass:[CAGradientLayer class]]) {
            // we need to use CATransaction because we need the animation to bee faster
            [CATransaction begin];
            [CATransaction setAnimationDuration:0.075];
            
            self.gradientLayer.bounds = curtainBoundsRect;
            self.gradientLayer.startPoint = CGPointMake(MAX(self.curtainFadeFactor - (self.curtainFade * self.curtainFadeFactor),0.001), 0.0);
            self.gradientLayer.endPoint = CGPointMake(MAX(self.curtainFadeFactor,0.3), 0.0);
                        self.gradientLayer.position = CGPointMake(self.bounds.size.width / 2.0, curtainVerticalCenter);
            [CATransaction commit];
        } else {
            [CATransaction begin];
            [CATransaction setAnimationDuration:0.075];
            
            self.gradientLayer.bounds = curtainBoundsRect;
            self.gradientLayer.position = CGPointMake((self.bounds.size.width - self.rightMargin - self.maxItemDeflection - 0.25 * self.maxWidth - 15.0) + self.bounds.size.width / 2.0, curtainVerticalCenter);
            [CATransaction commit];
        };
        
        self.curtain = YES;
    }

    
}

// drawing text labels for test purposes only
- (void)drawLabel:(NSString *)label withFont:(UIFont *)font forSize:(CGSize)size
          atPoint:(CGPoint)point withAlignment:(NSTextAlignment)alignment lineBreakMode:(NSLineBreakMode)lineBreak color:(UIColor *)color
{
    // obtain current context
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // save context state first
    CGContextSaveGState(context);
    
    // obtain size of drawn label
    CGSize newSize = [label sizeWithAttributes:@{NSFontAttributeName:font}];
    // determine correct rect for this label
    CGRect rect = CGRectMake(point.x, point.y,
                             newSize.width, newSize.height);
    // set text color in context
    CGContextSetFillColorWithColor(context, color.CGColor);
    // draw text
    [label drawInRect:rect
             withFont:font
        lineBreakMode:lineBreak
            alignment:alignment];
    
    // restore context state
    CGContextRestoreGState(context);
}
// drawing rectangles - for test purposes only
- (void)drawTestRectangleAtPoint:(CGPoint)p withSize:(CGSize)size red:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSaveGState(context);
    CGContextSetRGBFillColor(context, red, green, blue, alpha);
    CGContextBeginPath(context);
    CGRect rect = CGRectMake(p.x, p.y, size.width, size.height);
    CGContextAddRect(context, rect);
    CGContextFillPath(context);
    CGContextRestoreGState(context);
}

// our drawRect - for test purposee only
- (void)drawRect:(CGRect)rect
{
    if (self.dot) {
        [self drawTestRectangleAtPoint:CGPointMake(self.bounds.size.width / 2.0 - 100.0, self.bounds.size.height / 2.0 - 100.0)
                              withSize:CGSizeMake(200.0, 200.0)
                                   red:1.0
                                 green:1.0
                                  blue:1.0
                                 alpha:1.0];
        
        [self drawLabel:@"Index for tableView designed by mateusz@ nuckowski.com"
               withFont:[UIFont fontWithName:@"HelveticaNeue-UltraLight" size:25.0]
                forSize:CGSizeMake(175.0, 150.0)
                atPoint:CGPointMake(self.bounds.size.width / 2.0 - 78.0, self.bounds.size.height / 2.0 - 80.0)
          withAlignment:NSTextAlignmentCenter
          lineBreakMode:NSLineBreakByWordWrapping
                  color:[UIColor colorWithRed:0.0 green:105.0/255.0 blue:240.0/255.0 alpha:1.0]];
    }
}

@end

控制器中调用:

屏幕快照 2017-04-11 下午4.47.41.png 屏幕快照 2017-04-11 下午4.48.36.png

这个是索引数据刷新,注意在合适地方刷新

屏幕快照 2017-04-11 下午4.47.28.png

表格填充部分省略......数组关联......

6.Swift之仿写酷狗音乐索引

http://www.jianshu.com/p/6b09e03947a0

上一篇 下一篇

猜你喜欢

热点阅读