HMSegmentedControl源码阅读

作者: WeiHing | 来源:发表于2016-08-15 22:46 被阅读1658次

    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

    相关文章

      网友评论

      • Breezes:问一下,可以有办法知道某个文字在上面的位置吗

      本文标题:HMSegmentedControl源码阅读

      本文链接:https://www.haomeiwen.com/subject/grbysttx.html