HMSegmentedControl源碼閱讀

0.一些準(zhǔn)備

typedef void (^IndexChangeBlock)(NSInteger index);//block的typedef 無返回值,參數(shù)nsinterger 雕沿,新別名IndexChangeBlock
typedef NSAttributedString *(^HMTitleFormatterBlock)(HMSegmentedControl *segmentedControl, NSString *title, NSUInteger index, BOOL selected);//返回值NSAttributedString類型,是一種帶有屬性的字符串

當(dāng)改變selected index時執(zhí)行的block车猬,也可以使用addTarget:action:forControlEvents:方式來替代block的執(zhí)行:
@property (nonatomic, copy) IndexChangeBlock indexChangeBlock;
用來設(shè)置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類型時的寬度數(shù)組
@property (nonatomic, readwrite) NSArray *segmentWidthsArray;
//HMScrollView繼承自UIscrollview跺株,是實現(xiàn)HMSegmentedControl的主體控件
@property (nonatomic, strong) HMScrollView *scrollView;

另外复濒,
HMSegmentedControl繼承自UIControl類
@interface HMSegmentedControl : UIControl

1.HMScrollView

HMScrollView是實現(xiàn)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的設(shè)計:自己不處理touches事件,是把touches事件轉(zhuǎn)發(fā)給下一個響應(yīng)者(父視圖 即self)來處理乒省。因為scrollview會“屏蔽”掉下個響應(yīng)者的touches事件巧颈,即單純的[super touches...]不能把事件轉(zhuǎn)發(fā)給下個響應(yīng)者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 方法來做統(tǒng)一的初始化.

//segmentControl內(nèi)容是文字類型
- (id)initWithSectionTitles:(NSArray *)sectiontitles {
    self = [self initWithFrame:CGRectZero];//之后再通過實例對象 另外設(shè)置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是否可以滑動(當(dāng)選項過多時會有左右滑動的需要)
    self.touchEnabled = YES;//segment是否可以點擊
    self.verticalDividerEnabled = NO;//segment之間是否有豎分割線
    self.shouldAnimateUserSelection = YES;//切換segment時indicator變化是否有動畫
    
    self.contentMode = UIViewContentModeRedraw;//將在每次設(shè)置或更改frame的時候自動調(diào)用drawRect:
}
  • 如果你是直接基于 frame 來布局的袖扛,你應(yīng)該確保在初始化的時候只添加視圖砸泛,而不去設(shè)置它們的frame,把設(shè)置子視圖 frame 的過程全部放到 layoutSubviews 方法里.如果你是基于 Auto Layout 約束來進行布局蛆封,那么可以在 commonInit 調(diào)用的時候就把約束添加上去唇礁,不要重寫 layoutSubviews 方法,因為這種情況下它的默認實現(xiàn)就是根據(jù)約束來計算 frame惨篱。

  • 通過設(shè)置contentMode屬性值為UIViewContentModeRedraw盏筐。那么將在每次設(shè)置或更改frame的時候自動調(diào)用drawRect:。
    其余UIViewContentMode(部分舉例):


    UIViewContentMode

3.segment上的文本字符串相關(guān)

1.計算文本字符串的size

 - (CGSize)measureTitleAtIndex:(NSUInteger)index {
    id title = self.sectionTitles[index];
    CGSize size = CGSizeZero;
    //該segment是否被選中砸讳?
    BOOL selected = (index == self.selectedSegmentIndex) ? YES : NO;//selectedSegmentIndex 初始化的時候設(shè)為0
    if ([title isKindOfClass:[NSString class]] && !self.titleFormatter) {//titleFormatter文本外觀樣式琢融。為空 執(zhí)行
        //是否已經(jīng)選中?分別對應(yīng)不同的默認文本外觀
        NSDictionary *titleAttrs = selected ? [self resultingSelectedTitleTextAttributes] : [self resultingTitleTextAttributes];
        size = [(NSString *)title sizeWithAttributes:titleAttrs];
    } else if ([title isKindOfClass:[NSString class]] && self.titleFormatter) {//titleFormatter非空(已設(shè)置了樣式)      
        size = [self.titleFormatter(self, title, index, selected) size];//使用設(shè)置的樣式后獲取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;// 將矩形值轉(zhuǎn)變成整數(shù)簿寂,得到一個最小的矩形
}
//非選中狀態(tài)下的文本樣式
- (NSDictionary *)resultingTitleTextAttributes {
    NSDictionary *defaults = @{
        NSFontAttributeName : [UIFont systemFontOfSize:19.0f],
        NSForegroundColorAttributeName : [UIColor blackColor],
    };    
    NSMutableDictionary *resultingAttrs = [NSMutableDictionary dictionaryWithDictionary:defaults];    
    if (self.titleTextAttributes) {//由外部設(shè)置titleTextAttributes
        //addEntriesFromDictionary拼接字典漾抬,原字典已有的相同“鍵”,覆蓋對應(yīng)的鍵值對
        [resultingAttrs addEntriesFromDictionary:self.titleTextAttributes];
    }
    return [resultingAttrs copy];
}
//選中狀態(tài)下的文本樣式
- (NSDictionary *)resultingSelectedTitleTextAttributes {
    NSMutableDictionary *resultingAttrs = [NSMutableDictionary dictionaryWithDictionary:[self resultingTitleTextAttributes]];    
    if (self.selectedTitleTextAttributes) {
        [resultingAttrs addEntriesFromDictionary:self.selectedTitleTextAttributes];
    }    
    return [resultingAttrs copy];
}
  • addEntriesFromDictionary:方法常遂,拼接字典纳令,如果原字典和新字典有相同“鍵”,用新字典覆蓋對應(yīng)的鍵值對。
  • NSAttributedString叫做富文本平绩,是一種帶有屬性的字符串坤按,通過它可以輕松的在一個字符串中表現(xiàn)出多種字體、字號馒过、字體大小等各不相同的風(fēng)格臭脓。
    簡單使用,舉例:
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對應(yīng)的文本它的帶屬性的字符串

- (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標(biāo)上一個需要被重新布局的標(biāo)記窘奏,在系統(tǒng)runloop的下一個周期自動調(diào)用layoutSubviews嘹锁。

  • layoutSubviews的調(diào)用時機:

  • 其他相關(guān)方法:
    1.layoutSubviews唯一調(diào)用到的方法:
    該方法用于更新scrollview(HMScrollView)控件的一些屬性contentInset、frame着裹、scrollEnabled领猾、content size,主要是frame和content size骇扇。以及計算每個segment的寬度摔竿。

//更新scrollview的一些屬性contentInset、frame少孝、scrollEnabled继低、contentsize
 - (void)updateSegmentsRects {
    self.scrollView.contentInset = UIEdgeInsetsZero;//scrollview的屬性設(shè)置有部分放到了commoninit
    self.scrollView.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame));
    
    if ([self sectionCount] > 0) {//sectionCount:方法,計算segment個數(shù)
    //計算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) {
        //數(shù)組存放每個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是動態(tài)類型
    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:當(dāng)自己重寫一個UIView的時候有可能用到這個方法,當(dāng)本視圖的父類視圖改變的時候,系統(tǒng)會自動的執(zhí)行這個方法.
 - (void)willMoveToSuperview:(UIView *)newSuperview {
    // Control is being removed
    if (newSuperview == nil)
        return;   
    if (self.sectionTitles || self.sectionImages) {
        [self updateSegmentsRects];
    }
}

5.drawing繪圖

繪制一些相關(guān)圖層狐树,文本圖層焙压、圖片圖層、豎分割線圖層褪迟、背景圖層冗恨、indicator圖層(條紋答憔、箭頭味赃、方塊類型各有圖層)。這些圖層添加到scrollview(實現(xiàn)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;

    //重繪之前把所有子圖層先清除心俗,避免重復(fù)添加圖層
    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類型有關(guān)
            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) {
                //輪詢寬度數(shù)組去計算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事件交給下一個響應(yīng)者來處理揪利,也即是交給HMSegmentedControl來處理。
    計算手指松開時觸摸的是哪個segment狠持,并做相應(yīng)的行為處理疟位。
//參數(shù)touches表示觸摸產(chǎn)生的所有UITouch對象,而event表示特定的事件
 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [touch locationInView:self];//表示觸摸在參數(shù)view這個視圖上的位置,這里返回的位置是針對參數(shù)view的坐標(biāo)系的喘垂。
    //手指松開時觸摸的是哪個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) {
                //輪詢做減法直到不能減為止甜刻,就得到當(dāng)前觸摸到得那個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之前已經(jīng)選中了,不做處理正勒。
        if (segment != self.selectedSegmentIndex && segment < sectionsCount) {
            if (self.isTouchEnabled)
                [self setSelectedSegmentIndex:segment animated:self.shouldAnimateUserSelection notify:YES];
        }
    }
}

2.index change
以源代碼中的例子為例:


第一種情況:手指點擊上面的segment改變index得院,scrollview隨之相應(yīng)滑動切換。調(diào)用HMSegmentedControl重寫的touchesEnded:withEvent: ->調(diào)用- (void)setSelectedSegmentIndex:(NSUInteger)index animated:(BOOL)animated notify:(BOOL)notify章贞,notify參數(shù)直接傳入yes祥绞,-> 調(diào)用notifyForSegmentChangeToIndex:

//這個方法主要實現(xiàn)改變indicator位移鸭限。參數(shù)notify:是否通知scrollView做相應(yīng)的處理(以源碼中的例子為例)
- (void)setSelectedSegmentIndex:(NSUInteger)index animated:(BOOL)animated notify:(BOOL)notify {
    _selectedSegmentIndex = index;//設(shè)置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];
            //執(zhí)行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改變而發(fā)送通知(notify)通知vc要執(zhí)行某些動作喧枷,比如要人為手動地去改變scrollview的可視區(qū)域(setContentOffset:animated: 或者scrollRectToVisible:animated:)
- (void)notifyForSegmentChangeToIndex:(NSInteger)index {
    if (self.superview)
        [self sendActionsForControlEvents:UIControlEventValueChanged];
       //如果設(shè)計了一個自定義控件類(UIControl)虹统,可以使用sendActionsForControlEvent方法,為基本的UIControl事件或自己的自定義事件發(fā)送通知隧甚。發(fā)送與指定類型相關(guān)的所有行為消息车荔。我們可以在任意位置(包括控件內(nèi)部和外部)調(diào)用控件的這個方法來發(fā)送參數(shù)controlEvents指定的消息。(見viewdidload中與UIControlEventValueChange相關(guān)的addtarget事件)
    
    if (self.indexChangeBlock)
        self.indexChangeBlock(index);
}

用到的兩種消息傳遞方法:

  • [self sendActionsForControlEvents:UIControlEventValueChanged];
    - (void)sendActionsForControlEvents:(UIControlEvents)controlEvents如果設(shè)計了一個自定義控件類(UIControl戚扳,HMSegmentedControl就是繼承自UIControl)忧便,可以使用sendActionsForControlEvent方法,為基本的UIControl事件或自己的自定義事件發(fā)送通知帽借。發(fā)送與指定類型相關(guān)的所有行為消息珠增。我們可以在任意位置(包括控件內(nèi)部和外部)調(diào)用控件的這個方法來發(fā)送參數(shù)controlEvents指定的消息。
    在源代碼例子里面砍艾,有兩個與UIControlEventValueChange事件:當(dāng)在執(zhí)行[self sendActionsForControlEvents:UIControlEventValueChanged];時蒂教,給vc中的UIControlEventValueChange 事件發(fā)送通知,segmentedControlChangedValue:會被調(diào)用
[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];
    }];

當(dāng)self.indexChangeBlock(index);時凝垛,“通知”vc改變scrollview的scrollRectToVisible:可視區(qū)域懊悯。

  • 這兩種消息傳遞方法可以相互替代。不過也可以用代理來實現(xiàn)梦皮。

Alternativly, you could useaddTarget:action:forControlEvents:

第二種情況:手指滑動HMSegmentedControl下方的scrollVIew炭分。HMSegmentedControl要根據(jù)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代理方法中會調(diào)用到這個方法剑肯。
- (void)setSelectedSegmentIndex:(NSUInteger)index animated:(BOOL)animated {
    //notify參數(shù)直接傳值為no捧毛,不會調(diào)用到notifyForSegmentChangeToIndex:方法,即只會改變indicator位置
    [self setSelectedSegmentIndex:index animated:animated notify:NO];
}

7.其他

1.- (void)scrollToSelectedSegmentIndex:(BOOL)animatedsegment欄目太多让网,一屏顯示不全時需要滾動岖妄。一些坐標(biāo)的計算,沒啥好說的寂祥。
2.scrollRectToVisible: animated:
[self.scrollView scrollRectToVisible:rectToScrollTo animated:animated];將scrollView坐標(biāo)系內(nèi)的一塊指定區(qū)域移到scrollView的窗口中(centerX)荐虐,如果這部分已經(jīng)存在于窗口中,則什么也不做丸凭。
3.vc中 有一行代碼self.edgesForExtendedLayout = UIRectEdgeNone;
http://www.reibang.com/p/c0b8c5f131a0

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末福扬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子惜犀,更是在濱河造成了極大的恐慌铛碑,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虽界,死亡現(xiàn)場離奇詭異汽烦,居然都是意外死亡,警方通過查閱死者的電腦和手機莉御,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門撇吞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人礁叔,你說我怎么就攤上這事牍颈。” “怎么了琅关?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵煮岁,是天一觀的道長。 經(jīng)常有香客問我涣易,道長画机,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任新症,我火速辦了婚禮步氏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘账劲。我一直安慰自己戳护,他們只是感情好金抡,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布瀑焦。 她就那樣靜靜地躺著腌且,像睡著了一般。 火紅的嫁衣襯著肌膚如雪榛瓮。 梳的紋絲不亂的頭發(fā)上铺董,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音禀晓,去河邊找鬼精续。 笑死,一個胖子當(dāng)著我的面吹牛粹懒,可吹牛的內(nèi)容都是我干的重付。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼凫乖,長吁一口氣:“原來是場噩夢啊……” “哼确垫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起帽芽,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤删掀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后导街,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體披泪,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年搬瑰,在試婚紗的時候發(fā)現(xiàn)自己被綠了铅搓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡暇仲,死狀恐怖叫倍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情佩厚,我是刑警寧澤姆钉,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站抄瓦,受9級特大地震影響潮瓶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜钙姊,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一毯辅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧煞额,春花似錦思恐、人聲如沸沾谜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽基跑。三九已至,卻和暖如春描焰,著一層夾襖步出監(jiān)牢的瞬間媳否,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工荆秦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留篱竭,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓步绸,卻偏偏與公主長得像掺逼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瓤介,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容