HMSegmentedControl源码阅读

0.一些准备

typedef void (^IndexChangeBlock)(NSInteger index);//block的typedef 无返回值,参数nsinterger ,新别名IndexChangeBlock
typedef NSAttributedString *(^HMTitleFormatterBlock)(HMSegmentedControl *segmentedControl, NSString *title, NSUInteger index, BOOL selected);//返回值NSAttributedString类型,是一种带有属性的字符串

当改变selected index时执行的block,也可以使用addTarget:action:forControlEvents:方式来替代block的执行:
@property (nonatomic, copy) IndexChangeBlock indexChangeBlock;
用来设置segmentControl上文本的样式的block:
@property (nonatomic, copy) HMTitleFormatterBlock titleFormatter;
一些枚举:

HMSegmentedControlSelectionStyle:indicator类型。indicator和文本等宽(含inset)、和segment一样宽,背景大方块,箭头
HMSegmentedControlSelectionIndicatorLocation:indicator位置
HMSegmentedControlSegmentWidthStyle:segment宽度类型
HMSegmentedControlType:segmentControl类型
//indicator条纹样式图层
@property (nonatomic, strong) CALayer *selectionIndicatorStripLayer;
//indicator方块样式图层
@property (nonatomic, strong) CALayer *selectionIndicatorBoxLayer;
//indicator箭头样式图层
@property (nonatomic, strong) CALayer *selectionIndicatorArrowLayer;
//HMSegmentedControlSegmentWidthStyleFixed类型时的segment宽度,每个都等宽
@property (nonatomic, readwrite) CGFloat segmentWidth;
//HMSegmentedControlSegmentWidthStyleDynamic类型时的宽度数组
@property (nonatomic, readwrite) NSArray *segmentWidthsArray;
//HMScrollView继承自UIscrollview,是实现HMSegmentedControl的主体控件
@property (nonatomic, strong) HMScrollView *scrollView;

另外,
HMSegmentedControl继承自UIControl类
@interface HMSegmentedControl : UIControl

1.HMScrollView

HMScrollView是实现HMSegmentedControl的主体控件

@interface HMScrollView : UIScrollView
@end

@implementation HMScrollView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // If not dragging, send event to next responder
    if (!self.dragging) {
        [self.nextResponder touchesBegan:touches withEvent:event];
    } else {
        [super touchesBegan:touches withEvent:event];
    }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
    if (!self.dragging) {
        [self.nextResponder touchesMoved:touches withEvent:event];
    } else{
        [super touchesMoved:touches withEvent:event];
    }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if (!self.dragging) {
        [self.nextResponder touchesEnded:touches withEvent:event];
    } else {
        [super touchesEnded:touches withEvent:event];
    }
}
@end

这里scrollview的设计:自己不处理touches事件,是把touches事件转发给下一个响应者(父视图 即self)来处理。因为scrollview会“屏蔽”掉下个响应者的touches事件,即单纯的[super touches...]不能把事件转发给下个响应者https://stackoverflow.com/questions/7439273/uiscrollview-prevents-touchesbegan-touchesmoved-touchesended-on-view-controlle

2.初始化方法

HMSegmentedControl的初始化方法

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];    
    if (self) {
        [self commonInit];
    }
    return self;
}

要同时支持 initWithFrame 和 initWithCoder ,那么可以提供一个 commonInit 方法来做统一的初始化.

//segmentControl内容是文字类型
- (id)initWithSectionTitles:(NSArray *)sectiontitles {
    self = [self initWithFrame:CGRectZero];//之后再通过实例对象 另外设置frame    
    if (self) {
        [self commonInit];
        self.sectionTitles = sectiontitles;
        self.type = HMSegmentedControlTypeText;
    }    
    return self;
}
//图片类型
- (id)initWithSectionImages:(NSArray*)sectionImages sectionSelectedImages:(NSArray*)sectionSelectedImages {
......
}
//图文类型
- (instancetype)initWithSectionImages:(NSArray *)sectionImages sectionSelectedImages:(NSArray *)sectionSelectedImages titlesForSections:(NSArray *)section titles {
......
}

//给属性初始化一些默认的值
- (void)commonInit {
    self.scrollView = [[HMScrollView alloc] init];
    self.scrollView.scrollsToTop = NO;
    self.scrollView.showsVerticalScrollIndicator = NO;
    self.scrollView.showsHorizontalScrollIndicator = NO;
    [self addSubview:self.scrollView];//唯一一个addsubview
    
    ......
    //indicator的边沿inset
    self.selectionIndicatorEdgeInsets = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f);
    self.userDraggable = YES;//segmentControl是否可以滑动(当选项过多时会有左右滑动的需要)
    self.touchEnabled = YES;//segment是否可以点击
    self.verticalDividerEnabled = NO;//segment之间是否有竖分割线
    self.shouldAnimateUserSelection = YES;//切换segment时indicator变化是否有动画
    
    self.contentMode = UIViewContentModeRedraw;//将在每次设置或更改frame的时候自动调用drawRect:
}
  • 如果你是直接基于 frame 来布局的,你应该确保在初始化的时候只添加视图,而不去设置它们的frame,把设置子视图 frame 的过程全部放到 layoutSubviews 方法里.如果你是基于 Auto Layout 约束来进行布局,那么可以在 commonInit 调用的时候就把约束添加上去,不要重写 layoutSubviews 方法,因为这种情况下它的默认实现就是根据约束来计算 frame。

  • 通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。
    其余UIViewContentMode(部分举例):


    UIViewContentMode

3.segment上的文本字符串相关

1.计算文本字符串的size

 - (CGSize)measureTitleAtIndex:(NSUInteger)index {
    id title = self.sectionTitles[index];
    CGSize size = CGSizeZero;
    //该segment是否被选中?
    BOOL selected = (index == self.selectedSegmentIndex) ? YES : NO;//selectedSegmentIndex 初始化的时候设为0
    if ([title isKindOfClass:[NSString class]] && !self.titleFormatter) {//titleFormatter文本外观样式。为空 执行
        //是否已经选中?分别对应不同的默认文本外观
        NSDictionary *titleAttrs = selected ? [self resultingSelectedTitleTextAttributes] : [self resultingTitleTextAttributes];
        size = [(NSString *)title sizeWithAttributes:titleAttrs];
    } else if ([title isKindOfClass:[NSString class]] && self.titleFormatter) {//titleFormatter非空(已设置了样式)      
        size = [self.titleFormatter(self, title, index, selected) size];//使用设置的样式后获取size
    } else if ([title isKindOfClass:[NSAttributedString class]]) {//如果title是NSAttributedString(带属性的字符串)
        size = [(NSAttributedString *)title size];//直接获取size
    } else {
        NSAssert(title == nil, @"Unexpected type of segment title: %@", [title class]);
        size = CGSizeZero;
    }
    return CGRectIntegral((CGRect){CGPointZero, size}).size;// 将矩形值转变成整数,得到一个最小的矩形
}
//非选中状态下的文本样式
- (NSDictionary *)resultingTitleTextAttributes {
    NSDictionary *defaults = @{
        NSFontAttributeName : [UIFont systemFontOfSize:19.0f],
        NSForegroundColorAttributeName : [UIColor blackColor],
    };    
    NSMutableDictionary *resultingAttrs = [NSMutableDictionary dictionaryWithDictionary:defaults];    
    if (self.titleTextAttributes) {//由外部设置titleTextAttributes
        //addEntriesFromDictionary拼接字典,原字典已有的相同“键”,覆盖对应的键值对
        [resultingAttrs addEntriesFromDictionary:self.titleTextAttributes];
    }
    return [resultingAttrs copy];
}
//选中状态下的文本样式
- (NSDictionary *)resultingSelectedTitleTextAttributes {
    NSMutableDictionary *resultingAttrs = [NSMutableDictionary dictionaryWithDictionary:[self resultingTitleTextAttributes]];    
    if (self.selectedTitleTextAttributes) {
        [resultingAttrs addEntriesFromDictionary:self.selectedTitleTextAttributes];
    }    
    return [resultingAttrs copy];
}
  • addEntriesFromDictionary:方法,拼接字典,如果原字典和新字典有相同“键”,用新字典覆盖对应的键值对。
  • NSAttributedString叫做富文本,是一种带有属性的字符串,通过它可以轻松的在一个字符串中表现出多种字体、字号、字体大小等各不相同的风格。
    简单使用,举例:
NSAttributedString *attString = [[NSAttributedString alloc] initWithString:@"title" attributes:@{NSForegroundColorAttributeName : [UIColor blueColor]}];

可变形式NSMutableAttributedString:

NSString * aString = @"this is a string";
 NSMutableAttributedString * aAttributedString = [[NSMutableAttributedString alloc] initWithString:aString];
//文字颜色,range:作用范围前4个字符
 [aAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, 4)];
//文字字体
[aAttributedString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:25] range:NSMakeRange(0, 4)];
  • 单行文本size的获取:sizeWithAttributes:
    多行文本size的获取:boundingRectWithSize: options: attributes: context:

2.返回某个index对应的文本它的带属性的字符串

- (NSAttributedString *)attributedTitleAtIndex:(NSUInteger)index {
......略
}

4.update layout

- (void)layoutSubviews {
    [super layoutSubviews];    
    [self updateSegmentsRects];
}

- (void)setFrame:(CGRect)frame {
    [super setFrame:frame];
    [self updateSegmentsRects];
}

- (void)setSectionTitles:(NSArray *)sectionTitles {
    _sectionTitles = sectionTitles;    
    [self setNeedsLayout];//不会马上刷新layout
}

- (void)setSectionImages:(NSArray *)sectionImages {
    _sectionImages = sectionImages;    
    [self setNeedsLayout];
}
  • -setNeedsLayout:在receiver标上一个需要被重新布局的标记,在系统runloop的下一个周期自动调用layoutSubviews。

  • layoutSubviews的调用时机:

  • 其他相关方法:
    1.layoutSubviews唯一调用到的方法:
    该方法用于更新scrollview(HMScrollView)控件的一些属性contentInset、frame、scrollEnabled、content size,主要是frame和content size。以及计算每个segment的宽度。

//更新scrollview的一些属性contentInset、frame、scrollEnabled、contentsize
 - (void)updateSegmentsRects {
    self.scrollView.contentInset = UIEdgeInsetsZero;//scrollview的属性设置有部分放到了commoninit
    self.scrollView.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame));
    
    if ([self sectionCount] > 0) {//sectionCount:方法,计算segment个数
    //计算segment宽度,均分
        self.segmentWidth = self.frame.size.width / [self sectionCount];
    }
    //segmentCtrl是纯文本类型 segment宽是fix类型
    if (self.type == HMSegmentedControlTypeText && self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed) {
        [self.sectionTitles enumerateObjectsUsingBlock:^(id titleString, NSUInteger idx, BOOL *stop) {
            //计算字符串的宽度(含segment边沿左右inset)
            CGFloat stringWidth = [self measureTitleAtIndex:idx].width + self.segmentEdgeInset.left + self.segmentEdgeInset.right;//Default is segmentEdgeInset:UIEdgeInsetsMake(0, 5, 0, 5)。
            //均分宽度 和文本宽度 的最大值作为 segment宽度
            self.segmentWidth = MAX(stringWidth, self.segmentWidth);
        }];
    }
    //segmentCtrl是纯文本类型 segment宽是dynamic类型(和文本等宽,含inset)
    else if (self.type == HMSegmentedControlTypeText && self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
        //数组存放每个segment的宽   
        NSMutableArray *mutableSegmentWidths = [NSMutableArray array];     
        [self.sectionTitles enumerateObjectsUsingBlock:^(id titleString, NSUInteger idx, BOOL *stop) {
            CGFloat stringWidth = [self measureTitleAtIndex:idx].width + self.segmentEdgeInset.left + self.segmentEdgeInset.right;//HMSegmentedControlSegmentWidthStyleDynamic类型下segment宽度的计算方法
            [mutableSegmentWidths addObject:[NSNumber numberWithFloat:stringWidth]];
        }];
        self.segmentWidthsArray = [mutableSegmentWidths copy];
    }
    //segmentCtrl是图片类型
    else if (self.type == HMSegmentedControlTypeImages) {
        for (UIImage *sectionImage in self.sectionImages) {
            //计算图片宽度,含inset
            CGFloat imageWidth = sectionImage.size.width + self.segmentEdgeInset.left + self.segmentEdgeInset.right;
            self.segmentWidth = MAX(imageWidth, self.segmentWidth);
        }
    }
    //segmentCtrl是图文类型 segmentWidth是fix类型
    else if (self.type == HMSegmentedControlTypeTextImages && self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed){
        //仅用title来算segment宽,忽略image。略
    }
    //segmentCtrl是图文类型 segmentWidth是动态类型
    else if (self.type == HMSegmentedControlTypeTextImages && HMSegmentedControlSegmentWidthStyleDynamic) {
        .....略
    }

    self.scrollView.scrollEnabled = self.isUserDraggable;//default yes
    self.scrollView.contentSize = CGSizeMake([self totalSegmentedControlWidth], self.frame.size.height);
}
  1. willMoveToSuperview:当自己重写一个UIView的时候有可能用到这个方法,当本视图的父类视图改变的时候,系统会自动的执行这个方法.
 - (void)willMoveToSuperview:(UIView *)newSuperview {
    // Control is being removed
    if (newSuperview == nil)
        return;   
    if (self.sectionTitles || self.sectionImages) {
        [self updateSegmentsRects];
    }
}

5.drawing绘图

绘制一些相关图层,文本图层、图片图层、竖分割线图层、背景图层、indicator图层(条纹、箭头、方块类型各有图层)。这些图层添加到scrollview(实现segmentCtrl的主体控件)图层上。

 - (void)drawRect:(CGRect)rect {
    [self.backgroundColor setFill];
    UIRectFill([self bounds]);//填充颜色
    
    self.selectionIndicatorArrowLayer.backgroundColor = self.selectionIndicatorColor.CGColor;    
    self.selectionIndicatorStripLayer.backgroundColor = self.selectionIndicatorColor.CGColor;    
    self.selectionIndicatorBoxLayer.backgroundColor = self.selectionIndicatorColor.CGColor;
    self.selectionIndicatorBoxLayer.borderColor = self.selectionIndicatorColor.CGColor;

    //重绘之前把所有子图层先清除,避免重复添加图层
    self.scrollView.layer.sublayers = nil;    
    CGRect oldRect = rect;
    
    if (self.type == HMSegmentedControlTypeText) {//文本类型
        [self.sectionTitles enumerateObjectsUsingBlock:^(id titleString, NSUInteger idx, BOOL *stop) {
            CGFloat stringWidth = 0;
            CGFloat stringHeight = 0;
            CGSize size = [self measureTitleAtIndex:idx];//计算文本size
            stringWidth = size.width;
            stringHeight = size.height;
            CGRect rectDiv, fullRect;
            
            // Text inside the CATextLayer will appear blurry unless the rect values are rounded
            BOOL locationUp = (self.selectionIndicatorLocation == HMSegmentedControlSelectionIndicatorLocationUp);
            BOOL selectionStyleNotBox = (self.selectionStyle != HMSegmentedControlSelectionStyleBox);
            //文本y值在segment中的位置和 segment的indicator是否在上位、indicator是否是box类型有关
            CGFloat y = roundf((CGRectGetHeight(self.frame) - selectionStyleNotBox * self.selectionIndicatorHeight) / 2 - stringHeight / 2 + self.selectionIndicatorHeight * locationUp);
            CGRect rect;
            //宽度为fix
            if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed) {
                //文本rect
                rect = CGRectMake((self.segmentWidth * idx) + (self.segmentWidth - stringWidth) / 2, y, stringWidth, stringHeight);
                //竖分割线rect
                rectDiv = CGRectMake((self.segmentWidth * idx) - (self.verticalDividerWidth / 2), self.selectionIndicatorHeight * 2, self.verticalDividerWidth, self.frame.size.height - (self.selectionIndicatorHeight * 4));
                //背景rect
                fullRect = CGRectMake(self.segmentWidth * idx, 0, self.segmentWidth, oldRect.size.height);
            }
            //宽度为dynamic
            else if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {
                //轮询宽度数组去计算segment的x值
                CGFloat xOffset = 0;
                NSInteger i = 0;
                for (NSNumber *width in self.segmentWidthsArray) {
                    if (idx == i)
                        break;
                    xOffset = xOffset + [width floatValue];
                    i++;
                }                
                CGFloat widthForIndex = [[self.segmentWidthsArray objectAtIndex:idx] floatValue];//segment的宽,含inset
                rect = CGRectMake(xOffset, y, widthForIndex, stringHeight);
                fullRect = CGRectMake(self.segmentWidth * idx, 0, widthForIndex, oldRect.size.height);//这里似乎有点问题
                rectDiv = CGRectMake(xOffset - (self.verticalDividerWidth / 2), self.selectionIndicatorHeight * 2, self.verticalDividerWidth, self.frame.size.height - (self.selectionIndicatorHeight * 4));
            }            
            //添加图层
            // Fix rect position/size to avoid blurry labels
            rect = CGRectMake(ceilf(rect.origin.x), ceilf(rect.origin.y), ceilf(rect.size.width), ceilf(rect.size.height));            
            CATextLayer *titleLayer = [CATextLayer layer];
            titleLayer.frame = rect;
            titleLayer.alignmentMode = kCAAlignmentCenter;
            titleLayer.truncationMode = kCATruncationEnd;
            titleLayer.string = [self attributedTitleAtIndex:idx];
            titleLayer.contentsScale = [[UIScreen mainScreen] scale];            
            [self.scrollView.layer addSublayer:titleLayer];            
            // 竖分割线图层
            if (self.isVerticalDividerEnabled && idx > 0) {
                CALayer *verticalDividerLayer = [CALayer layer];
                verticalDividerLayer.frame = rectDiv;
                verticalDividerLayer.backgroundColor = self.verticalDividerColor.CGColor;                
                [self.scrollView.layer addSublayer:verticalDividerLayer];
            }        
            [self addBackgroundAndBorderLayerWithRect:fullRect];//背景和边沿图层
        }];
    } 
......
}

6.交互

  1. HMSegmentedControl重写touchesEnded: withEvent:方法。HMScrollView中重写的该方法(见1)把touch事件交给下一个响应者来处理,也即是交给HMSegmentedControl来处理。
    计算手指松开时触摸的是哪个segment,并做相应的行为处理。
//参数touches表示触摸产生的所有UITouch对象,而event表示特定的事件
 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [touch locationInView:self];//表示触摸在参数view这个视图上的位置,这里返回的位置是针对参数view的坐标系的。
    //手指松开时触摸的是哪个segment
    if (CGRectContainsPoint(self.bounds, touchLocation)) {
        NSInteger segment = 0;
        if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleFixed) {
            segment = (touchLocation.x + self.scrollView.contentOffset.x) / self.segmentWidth;
        } else if (self.segmentWidthStyle == HMSegmentedControlSegmentWidthStyleDynamic) {            
            CGFloat widthLeft = (touchLocation.x + self.scrollView.contentOffset.x);
            for (NSNumber *width in self.segmentWidthsArray) {
                //轮询做减法直到不能减为止,就得到当前触摸到得那个segment
                widthLeft = widthLeft - [width floatValue];
                if (widthLeft <= 0)
                    break;               
                segment++;
            }
        }
        
        NSUInteger sectionsCount = 0;        
        if (self.type == HMSegmentedControlTypeImages) {
            sectionsCount = [self.sectionImages count];
        } else if (self.type == HMSegmentedControlTypeTextImages || self.type == HMSegmentedControlTypeText) {
            sectionsCount = [self.sectionTitles count];
        }
        //如果这个segment之前已经选中了,不做处理。
        if (segment != self.selectedSegmentIndex && segment < sectionsCount) {
            if (self.isTouchEnabled)
                [self setSelectedSegmentIndex:segment animated:self.shouldAnimateUserSelection notify:YES];
        }
    }
}

2.index change
以源代码中的例子为例:


第一种情况:手指点击上面的segment改变index,scrollview随之相应滑动切换。调用HMSegmentedControl重写的touchesEnded:withEvent: ->调用- (void)setSelectedSegmentIndex:(NSUInteger)index animated:(BOOL)animated notify:(BOOL)notify,notify参数直接传入yes,-> 调用notifyForSegmentChangeToIndex:

//这个方法主要实现改变indicator位移。参数notify:是否通知scrollView做相应的处理(以源码中的例子为例)
- (void)setSelectedSegmentIndex:(NSUInteger)index animated:(BOOL)animated notify:(BOOL)notify {
    _selectedSegmentIndex = index;//设置index
    [self setNeedsDisplay];//通知重绘
    
    if (index == HMSegmentedControlNoSegment) {
        [self.selectionIndicatorArrowLayer removeFromSuperlayer];
        [self.selectionIndicatorStripLayer removeFromSuperlayer];
        [self.selectionIndicatorBoxLayer removeFromSuperlayer];
    } else {
        [self scrollToSelectedSegmentIndex:animated];
        
        if (animated) {
            //如果indicator图层没有添加到父图层上,意味着没有index选中。把indicator图层添加上,不带动画
            if(self.selectionStyle == HMSegmentedControlSelectionStyleArrow) {
                if ([self.selectionIndicatorArrowLayer superlayer] == nil) {
                    [self.scrollView.layer addSublayer:self.selectionIndicatorArrowLayer];                    
                    [self setSelectedSegmentIndex:index animated:NO notify:YES];
                    return;
                }
            }else {   
                    ......             
            }            
            if (notify)
                [self notifyForSegmentChangeToIndex:index];
            //执行indicator位移动画
            // Restore CALayer animations
            self.selectionIndicatorArrowLayer.actions = nil;
            ......            

            [CATransaction begin];
            [CATransaction setAnimationDuration:0.15f];
            [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
            [self setArrowFrame];
            self.selectionIndicatorBoxLayer.frame = [self frameForSelectionIndicator];//arrow类型才对?
            ......
            [CATransaction commit];
        }
        //animated = NO,没有动画
        else {
            //直接setframe改变位移
            NSMutableDictionary *newActions = [[NSMutableDictionary alloc] initWithObjectsAndKeys:[NSNull null], @"position", [NSNull null], @"bounds", nil];
            self.selectionIndicatorArrowLayer.actions = newActions;
            [self setArrowFrame];
            ...... 
            if (notify)
                [self notifyForSegmentChangeToIndex:index];
        }
    }
}
//因手指点击segment(而不是滑动segmentcontrol下方的scrollview),index改变而发送通知(notify)通知vc要执行某些动作,比如要人为手动地去改变scrollview的可视区域(setContentOffset:animated: 或者scrollRectToVisible:animated:)
- (void)notifyForSegmentChangeToIndex:(NSInteger)index {
    if (self.superview)
        [self sendActionsForControlEvents:UIControlEventValueChanged];
       //如果设计了一个自定义控件类(UIControl),可以使用sendActionsForControlEvent方法,为基本的UIControl事件或自己的自定义事件发送通知。发送与指定类型相关的所有行为消息。我们可以在任意位置(包括控件内部和外部)调用控件的这个方法来发送参数controlEvents指定的消息。(见viewdidload中与UIControlEventValueChange相关的addtarget事件)
    
    if (self.indexChangeBlock)
        self.indexChangeBlock(index);
}

用到的两种消息传递方法:

  • [self sendActionsForControlEvents:UIControlEventValueChanged];
    - (void)sendActionsForControlEvents:(UIControlEvents)controlEvents如果设计了一个自定义控件类(UIControl,HMSegmentedControl就是继承自UIControl),可以使用sendActionsForControlEvent方法,为基本的UIControl事件或自己的自定义事件发送通知。发送与指定类型相关的所有行为消息。我们可以在任意位置(包括控件内部和外部)调用控件的这个方法来发送参数controlEvents指定的消息。
    在源代码例子里面,有两个与UIControlEventValueChange事件:当在执行[self sendActionsForControlEvents:UIControlEventValueChanged];时,给vc中的UIControlEventValueChange 事件发送通知,segmentedControlChangedValue:会被调用
[segmentedControl1 addTarget:self action:@selector(segmentedControlChangedValue:) forControlEvents:UIControlEventValueChanged];
[segmentedControl2 addTarget:self action:@selector(segmentedControlChangedValue:) forControlEvents:UIControlEventValueChanged];

 - (void)segmentedControlChangedValue:(HMSegmentedControl *)segmentedControl {
    NSLog(@"Selected index %ld (via UIControlEventValueChanged)", (long)segmentedControl.selectedSegmentIndex);
}
  • indexChangeBlock
    ViewController中对indexChangeBlock赋值。
    __weak typeof(self) weakSelf = self;
    [self.segmentedControl4 setIndexChangeBlock:^(NSInteger index) {
        [weakSelf.scrollView scrollRectToVisible:CGRectMake(viewWidth * index, 0, viewWidth, 200) animated:YES];
    }];

self.indexChangeBlock(index);时,“通知”vc改变scrollview的scrollRectToVisible:可视区域。

  • 这两种消息传递方法可以相互替代。不过也可以用代理来实现。

Alternativly, you could useaddTarget:action:forControlEvents:

第二种情况:手指滑动HMSegmentedControl下方的scrollVIew。HMSegmentedControl要根据scrollview滑动的情况来改变index。

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    CGFloat pageWidth = scrollView.frame.size.width;
    NSInteger page = scrollView.contentOffset.x / pageWidth;
    
    [self.segmentedControl4 setSelectedSegmentIndex:page animated:YES];
}
//在scrollview代理方法中会调用到这个方法。
- (void)setSelectedSegmentIndex:(NSUInteger)index animated:(BOOL)animated {
    //notify参数直接传值为no,不会调用到notifyForSegmentChangeToIndex:方法,即只会改变indicator位置
    [self setSelectedSegmentIndex:index animated:animated notify:NO];
}

7.其他

1.- (void)scrollToSelectedSegmentIndex:(BOOL)animatedsegment栏目太多,一屏显示不全时需要滚动。一些坐标的计算,没啥好说的。
2.scrollRectToVisible: animated:
[self.scrollView scrollRectToVisible:rectToScrollTo animated:animated];将scrollView坐标系内的一块指定区域移到scrollView的窗口中(centerX),如果这部分已经存在于窗口中,则什么也不做。
3.vc中 有一行代码self.edgesForExtendedLayout = UIRectEdgeNone;
http://www.jianshu.com/p/c0b8c5f131a0

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容