//
|
// VTMenuBar.m
|
// VTMagicView
|
//
|
// Created by tianzhuo on 15/1/6.
|
// Copyright (c) 2015年 tianzhuo. All rights reserved.
|
//
|
|
#import "VTMenuBar.h"
|
#import "UIScrollView+VTMagic.h"
|
#import "VTMagicMacros.h"
|
|
static NSInteger const kVTMenuBarTag = 1000;
|
|
@interface VTMenuBar()
|
|
@property (nonatomic, strong) NSMutableArray *frameList; // frame数组
|
@property (nonatomic, strong) NSMutableArray *sliderList; // slider frames
|
@property (nonatomic, strong) NSMutableDictionary *visibleDict; // 屏幕上可见的items
|
@property (nonatomic, strong) NSMutableSet *cacheSet; // 缓存池
|
@property (nonatomic, strong) NSString *identifier; // 重用标识符
|
@property (nonatomic, strong) NSMutableArray *indexList; // 索引集合
|
@property (nonatomic, strong) UIFont *itemFont;
|
|
@end
|
|
@implementation VTMenuBar
|
@dynamic delegate;
|
|
- (instancetype)initWithFrame:(CGRect)frame {
|
self = [super initWithFrame:frame];
|
if (self) {
|
_itemScale = 1.0;
|
_itemSpacing = 25.f;
|
_sliderHeight = 2.0f;
|
_sliderExtension = CGFLOAT_MAX;
|
_bubbleInset = UIEdgeInsetsMake(2, 5, 2, 5);
|
_indexList = [[NSMutableArray alloc] init];
|
_frameList = [[NSMutableArray alloc] init];
|
_sliderList = [[NSMutableArray alloc] init];
|
_visibleDict = [[NSMutableDictionary alloc] init];
|
_cacheSet = [[NSMutableSet alloc] init];
|
}
|
return self;
|
}
|
|
- (void)layoutSubviews {
|
[super layoutSubviews];
|
|
if (_needSkipLayout && !self.isDecelerating) {
|
return;
|
}
|
|
UIButton *menuItem = nil;
|
CGRect frame = CGRectZero;
|
NSArray *indexList = [_visibleDict allKeys];
|
for (NSNumber *index in indexList) {
|
menuItem = _visibleDict[index];
|
frame = [_frameList[[index integerValue]] CGRectValue];
|
if (![self vtm_isItemNeedDisplayWithFrame:frame]) {
|
[menuItem setSelected:NO];
|
[menuItem removeFromSuperview];
|
[_visibleDict removeObjectForKey:index];
|
[_cacheSet addObject:menuItem];
|
} else {
|
menuItem.selected = NO;
|
menuItem.frame = frame;
|
}
|
}
|
|
NSMutableArray *leftIndexList = [_indexList mutableCopy];
|
[leftIndexList removeObjectsInArray:indexList];
|
for (NSNumber *index in leftIndexList) {
|
frame = [_frameList[[index integerValue]] CGRectValue];
|
if ([self vtm_isItemNeedDisplayWithFrame:frame]) {
|
[self loadItemAtIndex:[index integerValue]];
|
}
|
}
|
|
BOOL transformScale = !self.isDecelerating || !_needSkipLayout;
|
[self updateSelectedItem:transformScale];
|
}
|
|
#pragma mark - update menuItem state
|
- (void)updateSelectedItem:(BOOL)transformScale {
|
_selectedItem.selected = NO;
|
UIButton *originalItem = _selectedItem;
|
_selectedItem = _visibleDict[@(_currentIndex)];
|
_selectedItem.selected = _deselected ? NO : YES;
|
|
if (!transformScale || 1.0 == _itemScale) {
|
return;
|
}
|
|
_selectedItem.titleLabel.layer.transform = CATransform3DMakeScale(_itemScale, _itemScale, _itemScale);
|
if (![originalItem isEqual:_selectedItem]) {
|
originalItem.titleLabel.layer.transform = CATransform3DIdentity;
|
}
|
}
|
|
- (void)deselectMenuItem {
|
self.deselected = YES;
|
_selectedItem.selected = NO;
|
}
|
|
- (void)reselectMenuItem {
|
self.deselected = NO;
|
_selectedItem.selected = YES;
|
}
|
|
#pragma mark - functional methods
|
- (void)reloadData {
|
[self resetCacheData];
|
[self resetItemFrames];
|
[self setNeedsLayout];
|
[self layoutIfNeeded];
|
}
|
|
-(void)resetCacheData {
|
[_indexList removeAllObjects];
|
NSInteger pageCount = _menuTitles.count;
|
for (NSInteger index = 0; index < pageCount; index++) {
|
[_indexList addObject:@(index)];
|
}
|
|
NSArray *visibleItems = [_visibleDict allValues];
|
for (UIButton *menuItem in visibleItems) {
|
[menuItem setSelected:NO];
|
[menuItem removeFromSuperview];
|
[_cacheSet addObject:menuItem];
|
}
|
[_visibleDict removeAllObjects];
|
}
|
|
#pragma mark reset item frames
|
- (void)resetItemFrames {
|
[_frameList removeAllObjects];
|
|
if (!_menuTitles.count) {
|
return;
|
}
|
|
UIButton *menuItem = nil;
|
if (!_itemFont) {
|
menuItem = [self createItemAtIndex:_currentIndex];
|
_itemFont = menuItem.titleLabel.font;
|
NSAssert(_itemFont != nil, @"item shouldn't be nil, you must conform VTMagicViewDataSource");
|
}
|
|
switch (_layoutStyle) {
|
case VTLayoutStyleDivide:
|
[self resetFramesForDivide];
|
break;
|
case VTLayoutStyleCenter:
|
[self resetFramesForCenter];
|
break;
|
default:
|
[self resetFramesForDefault];
|
break;
|
}
|
|
[self resetSliderFrames];
|
CGRect lastFrame = [[_frameList lastObject] CGRectValue];
|
CGFloat contentWidth = CGRectGetMaxX(lastFrame);
|
contentWidth += _menuInset.right;
|
self.contentSize = CGSizeMake(contentWidth, 0);
|
if (menuItem && _currentIndex < _frameList.count) {
|
menuItem.frame = [_frameList[_currentIndex] CGRectValue];
|
}
|
}
|
|
- (void)resetFramesForDefault {
|
CGFloat itemWidth = 0;
|
NSString *title = nil;
|
CGRect frame = CGRectZero;
|
CGFloat itemX = _menuInset.left;
|
CGFloat height = self.frame.size.height;
|
height -= _menuInset.top + _menuInset.bottom;
|
for (int index = 0; index < _menuTitles.count; index++) {
|
itemWidth = [self itemWidthAtIndex:index];
|
if (!itemWidth) {
|
title = [_menuTitles objectAtIndex:index];
|
itemWidth = _itemWidth ?: ([self sizeWithTitle:title].width + _itemSpacing);
|
}
|
frame = CGRectMake(itemX, _menuInset.top, itemWidth, height);
|
[_frameList addObject:[NSValue valueWithCGRect:frame]];
|
itemX += frame.size.width;
|
}
|
}
|
|
- (void)resetFramesForDivide {
|
CGRect frame = CGRectZero;
|
NSInteger count = _menuTitles.count;
|
CGFloat height = self.frame.size.height;
|
height -= _menuInset.top + _menuInset.bottom;
|
CGFloat totalSpace = _menuInset.left + _menuInset.right;
|
CGFloat itemWidth = (CGRectGetWidth(self.frame) - totalSpace)/count;
|
frame.origin = CGPointMake(_menuInset.left, _menuInset.top);
|
frame.size = CGSizeMake(itemWidth, height);
|
for (int index = 0; index < count; index++) {
|
[_frameList addObject:[NSValue valueWithCGRect:frame]];
|
frame.origin.x += itemWidth;
|
}
|
}
|
|
- (void)resetFramesForCenter {
|
[self resetFramesForDefault];
|
CGFloat menuWidth = CGRectGetWidth(self.frame);
|
CGRect lastFame = [[_frameList lastObject] CGRectValue];
|
CGFloat contentWidth = menuWidth - _menuInset.right;
|
CGFloat itemOffset = (contentWidth - CGRectGetMaxX(lastFame))/2;
|
if (itemOffset <= 0) {
|
return;
|
}
|
|
CGRect frame = CGRectZero;
|
NSArray *frames = [NSArray arrayWithArray:_frameList];
|
[_frameList removeAllObjects];
|
for (NSValue *value in frames) {
|
frame = [value CGRectValue];
|
frame.origin.x += itemOffset;
|
[_frameList addObject:[NSValue valueWithCGRect:frame]];
|
}
|
}
|
|
- (CGFloat)itemWidthAtIndex:(NSUInteger)itemIndex {
|
return [self.delegate menuBar:self itemWidthAtIndex:itemIndex];
|
}
|
|
#pragma mark reset slider frames
|
- (void)resetSliderFrames {
|
[_sliderList removeAllObjects];
|
|
if (!_menuTitles.count) {
|
return;
|
}
|
|
switch (_sliderStyle) {
|
case VTSliderStyleBubble:
|
[self resetSliderForBubble];
|
break;
|
default:
|
[self resetSliderForDefault];
|
break;
|
}
|
}
|
|
- (void)resetSliderForDefault {
|
NSInteger index = 0;
|
CGRect itemFrame = CGRectZero;
|
CGFloat sliderY = CGRectGetHeight(self.frame) - _sliderHeight + _sliderOffset;
|
CGRect frame = CGRectMake(0, sliderY, 0, _sliderHeight);
|
for (NSString *title in _menuTitles) {
|
itemFrame = [self itemFrameAtIndex:index];
|
frame.size.width = [self sliderWidthAtIndex:index];
|
if (!CGRectGetWidth(frame)) {
|
if (_sliderWidth) {
|
frame.size.width = _sliderWidth;
|
} else if (CGFLOAT_MAX != _sliderExtension) {
|
frame.size.width = [self sizeWithTitle:title].width;
|
frame.size.width += _sliderExtension * 2;
|
} else {
|
frame.size.width = itemFrame.size.width;
|
}
|
}
|
frame.origin.x = CGRectGetMidX(itemFrame) - frame.size.width/2;
|
[_sliderList addObject:[NSValue valueWithCGRect:frame]];
|
index++;
|
}
|
}
|
|
- (void)resetSliderForBubble {
|
NSInteger index = 0;
|
CGSize size = CGSizeZero;
|
CGRect frame = CGRectZero;
|
CGRect itemFrame = CGRectZero;
|
CGPoint bubblePoint = CGPointZero;
|
for (NSString *title in _menuTitles) {
|
size = [self sizeWithTitle:title];
|
itemFrame = [self itemFrameAtIndex:index];
|
size.width += _bubbleInset.left + _bubbleInset.right;
|
size.height += _bubbleInset.top + _bubbleInset.bottom;
|
bubblePoint.x = CGRectGetMidX(itemFrame) - size.width/2;
|
bubblePoint.y = CGRectGetMidY(itemFrame) - size.height/2;
|
frame = (CGRect){bubblePoint, size};
|
[_sliderList addObject:[NSValue valueWithCGRect:frame]];
|
index++;
|
}
|
}
|
|
- (CGFloat)sliderWidthAtIndex:(NSUInteger)itemIndex {
|
return [self.delegate menuBar:self sliderWidthAtIndex:itemIndex];
|
}
|
|
- (CGSize)sizeWithTitle:(NSString *)title {
|
CGSize size = CGSizeZero;
|
if ([title respondsToSelector:@selector(sizeWithAttributes:)]) {
|
size = [title sizeWithAttributes:@{NSFontAttributeName : _itemFont}];
|
} else {
|
#pragma clang diagnostic push
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
size = [title sizeWithFont:_itemFont];
|
#pragma clang diagnostic pop
|
}
|
return size;
|
}
|
|
#pragma mark - 查询
|
- (CGRect)itemFrameAtIndex:(NSUInteger)index {
|
if (_frameList.count <= index) {
|
return CGRectZero;
|
}
|
return [_frameList[index] CGRectValue];
|
}
|
|
- (UIButton *)itemAtIndex:(NSUInteger)index {
|
return [self itemAtIndex:index autoCreate:NO];
|
}
|
|
- (UIButton *)createItemAtIndex:(NSUInteger)index {
|
return [self itemAtIndex:index autoCreate:YES];
|
}
|
|
- (UIButton *)itemAtIndex:(NSUInteger)index autoCreate:(BOOL)autoCreate {
|
if (_menuTitles.count <= index) {
|
return nil;
|
}
|
|
UIButton *menuItem = _visibleDict[@(index)];
|
if (autoCreate && !menuItem) {
|
menuItem = [self loadItemAtIndex:index];
|
}
|
return menuItem;
|
}
|
|
- (UIButton *)loadItemAtIndex:(NSInteger)index {
|
UIButton *menuItem = [_datasource menuBar:self menuItemAtIndex:index];
|
NSAssert([menuItem isKindOfClass:[UIButton class]], @"The class of menu item:%@ must be UIButton", menuItem);
|
if (menuItem) {
|
[menuItem addTarget:self action:@selector(menuItemClick:) forControlEvents:UIControlEventTouchUpInside];
|
menuItem.titleLabel.layer.transform = CATransform3DIdentity;
|
menuItem.tag = index + kVTMenuBarTag;
|
if (index < _frameList.count) {
|
menuItem.frame = [_frameList[index] CGRectValue];
|
}
|
[menuItem setSelected:NO];
|
[self addSubview:menuItem];
|
[_visibleDict setObject:menuItem forKey:@(index)];
|
}
|
return menuItem;
|
}
|
|
- (CGRect)sliderFrameAtIndex:(NSUInteger)index {
|
if (_sliderList.count <= index) {
|
return CGRectZero;
|
}
|
return [_sliderList[index] CGRectValue];
|
}
|
|
- (UIButton *)dequeueReusableItemWithIdentifier:(NSString *)identifier {
|
_identifier = identifier;
|
UIButton *menuItem = [_cacheSet anyObject];
|
if (menuItem) {
|
[_cacheSet removeObject:menuItem];
|
}
|
return menuItem;
|
}
|
|
#pragma mark - item 点击事件
|
- (void)menuItemClick:(UIButton *)sender {
|
NSInteger itemIndex = sender.tag - kVTMenuBarTag;
|
if ([self.delegate respondsToSelector:@selector(menuBar:didSelectItemAtIndex:)]) {
|
[self.delegate menuBar:self didSelectItemAtIndex:itemIndex];
|
}
|
}
|
|
@end
|