在做ios界面时,发现有个效果很多APP都有,而项目中也会遇到,针对需要的效果,做了个简单的封装,分享出来,希望能帮到需要的人。
实现效果
1.能动态添加和删除菜单(如新闻APP里的关注模块,小说APP里的关注类型)
2.支持二级菜单(如最新下面可以选择具体的类型)
3.支持view与bar联动。
下方的视图与上方的一级菜单有联动性。
demo效果图
实现代码
上述的功能都已实现,具体代码如下
ViewToolTopBarView.h
//
// ViewToolTopBarView.h
// BaseViewManager
//
// Created by admin on 2021/2/3.
// Copyright © 2021 admin. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface TopBarItemInfo : NSObject
@property(nonatomic,readwrite)NSString * itemKey;//菜单唯一标识
@property(nonatomic,readwrite)NSString * itemShowName;//显示名称
@property(nonatomic,readwrite)UIView * itemShowView;//点击选项 显示的界面(只有第一层菜单有界面)
@property(nonatomic,readwrite)CGFloat itemDefaultFontSize;//字体默认大小(默认17)
@property(nonatomic,readwrite)CGFloat itemSelectFontSize;//字体选中大小(默认19)
///菜单子选项信息,只有第一层菜单有子选项,,,点击子选项,会展示下拉列表
@property(nonatomic,readwrite)BOOL itemHasChild;//是否有子选项
@property(nonatomic,readwrite)UIImage * itemTagDefaultImage;//默认状态图片
@property(nonatomic,readwrite)UIImage * itemTagSelectImage;//选择状态图片
@property(nonatomic,readwrite)NSArray<TopBarItemInfo*>* childItems;//子选项
-(instancetype)initWithKey:(NSString *)itemKey Name:(NSString *)name;
-(void)loadChildInfo:(NSArray<TopBarItemInfo*>*) childItems defaultImage:(UIImage *)defaultImage selectImage:(UIImage *)selectImage;
@end
@interface ViewToolTopBarView : UIView
/**
初始化bar,传入信息有
frame:空间大小位置
maxshow:界面最多展示几项(方便计算没项宽度)
selectColor:选择后的颜色
defaultColor:默认颜色
completion:用户点击选择item后,回调selectItemKey菜单唯一标识
*/
-(instancetype)initWithFrame:(CGRect)frame maxShowItem:(int)maxShow selectColor:(UIColor*)selectColor defaultColor:(UIColor*)defaultColor didSelect:(void(^)(NSString * selectItemKey))completion;
/**
itemKey:item的菜单唯一标识 自动选择到某项
调用后会回调 init方法传入的completion回调
*/
-(void)selectToItemForKey:(NSString *)itemKey;
-(void)selectToIndex:(NSInteger)index;
/**
动态添加多个选项到末尾
*/
-(void)addItems:(NSArray<TopBarItemInfo*>*)items;
/**
插入一个选项到某一位置
*注意:index要小于所有选项数量,需要调用者自己管理位置信息
*/
-(void)insertItem:(TopBarItemInfo *)item toIndex:(NSInteger)index;
/**
删除多个item。
itemKeys:item的菜单唯一标识
*/
-(void)deleteItemForKeys:(NSArray<NSString *>*)itemKeys;
@end
NS_ASSUME_NONNULL_END
ViewToolTopBarView.m
//
// ViewToolTopBarView.m
// BaseViewManager
//
// Created by admin on 2021/2/3.
// Copyright © 2021 admin. All rights reserved.
//
#import "ViewToolTopBarView.h"
#define BarHeight 44//bar按钮高度
#define BarTipViewHeight 5//bar下方选中横条高度
@interface ViewToolTopBarView()<UIScrollViewDelegate>
@property(nonatomic,strong)NSString * selectItemKey;//选择的item
@property(nonatomic,assign)NSInteger selectItemIndex;//选择的item
@property(nonatomic,strong)NSString * selectChileItemKey;//选择的item
@property(nonatomic,strong)NSMutableDictionary<NSString*,NSNumber*> * allTopbarBtnIndexInfo;//items名字展示顺序信息
@property(nonatomic,strong)UIScrollView * topbarScrollView;//items展示视图
@property(nonatomic,strong)NSMutableArray<UIButton*> * allTopbarBtns;//items名字展示lable
@property(nonatomic,strong)UIScrollView * contentScrollView;//items内容视图
@property(nonatomic,strong)UIView * topBarSelectView;//item选择视图
@property(nonatomic,strong)UIScrollView * childScrollView;//child内容选择视图
@property(nonatomic,strong)NSArray<TopBarItemInfo*> * nowShowChildItems;//所有子菜单
@property (copy, nonatomic) void (^didChooseBlock)(NSString * selectItemKet);//选择完的回调
@property(nonatomic,strong)NSMutableArray<TopBarItemInfo*> * allItems;//所有菜单
@property(nonatomic,assign)CGFloat itemWidth;//每个item的宽度
@property(nonatomic,assign)int maxShowNum;//最多展示的item个数
@property(nonatomic,strong)UIColor * defaultColor;//默认颜色
@property(nonatomic,strong)UIColor * selectColor;//选择后的颜色
@end
@implementation ViewToolTopBarView
/**
初始化bar,传入信息有
frame:空间大小位置
maxshow:界面最多展示几项(方便计算没项宽度)
selectColor:选择后的颜色
defaultColor:默认颜色
completion:用户点击选择item后,回调
*/
-(instancetype)initWithFrame:(CGRect)frame maxShowItem:(int)maxShow selectColor:(UIColor*)selectColor defaultColor:(UIColor*)defaultColor didSelect:(void(^)(NSString * selectItemKey))completion{
self = [super initWithFrame:frame];
if(self){
self.selectItemKey = @"";
self.allItems = [NSMutableArray array];
self.allTopbarBtns = [NSMutableArray array];
self.allTopbarBtnIndexInfo = [NSMutableDictionary dictionary];
self.topbarScrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(0, 0, self.bounds.size.width, BarHeight)];
self.topbarScrollView.delegate = self;
self.topbarScrollView.tag = 99;
self.topbarScrollView.backgroundColor = [UIColor whiteColor];
[self addSubview:self.topbarScrollView];
self.contentScrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(0, BarHeight, self.bounds.size.width,self.bounds.size.height - BarHeight)];
self.contentScrollView.backgroundColor = [UIColor whiteColor];
self.contentScrollView.delegate = self;
self.contentScrollView.tag = 100;
self.contentScrollView.pagingEnabled = YES;
[self addSubview:self.contentScrollView];
self.childScrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(0, BarHeight, self.bounds.size.width,BarHeight-BarTipViewHeight)];
self.childScrollView.backgroundColor = [UIColor whiteColor];
self.childScrollView.hidden = YES;
[self addSubview:self.childScrollView];
self.maxShowNum = maxShow;
self.selectColor = selectColor;
self.defaultColor = defaultColor;
self.didChooseBlock = completion;
}
return self;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
// 停止类型1、停止类型2
BOOL scrollToScrollStop = !scrollView.tracking && !scrollView.dragging && !scrollView.decelerating;
if (scrollToScrollStop && scrollView.tag == 100) {
[self scrollViewDidEndScroll];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (!decelerate) {
// 停止类型3
BOOL dragToDragStop = scrollView.tracking && !scrollView.dragging && !scrollView.decelerating;
if (dragToDragStop && scrollView.tag == 100) {
[self scrollViewDidEndScroll];
}
}
}
#pragma mark - scrollView 滚动停止
- (void)scrollViewDidEndScroll {
NSInteger index = self.contentScrollView.contentOffset.x / self.contentScrollView.bounds.size.width;
[self selectToIndex:index isAuto:YES];
}
//手指离开立即
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
if(scrollView.tag == 99){
[self changeTopBarToSelect];
}
}
/**
itemKey:item的菜单唯一标识 自动选择到某项
调用后会回调 init方法传入的completion回调
*/
-(void)selectToItemForKey:(NSString *)itemKey{
int index = 0;
if(itemKey && [itemKey length]>0){
index = [[self.allTopbarBtnIndexInfo objectForKey:itemKey] intValue];
}
[self selectToIndex:index];
}
/**
动态添加多个选项到末尾
*/
-(void)addItems:(NSArray<TopBarItemInfo*>*)items{
[self.allItems addObjectsFromArray:items];
[self loadView];
}
/**
插入一个选项到某一位置
*注意:index要小于所有选项数量,需要调用者自己管理位置信息
*/
-(void)insertItem:(TopBarItemInfo *)item toIndex:(NSInteger)index{
[self.allItems insertObject:item atIndex:index];
[self loadView];
}
/**
删除多个item。
itemKeys:item的菜单唯一标识
*/
-(void)deleteItemForKeys:(NSArray<NSString *>*)itemKeys{
NSMutableArray * deleArray = [NSMutableArray array];
BOOL hasDeleSelect = NO;
for (TopBarItemInfo * item in self.allItems) {
if([itemKeys containsObject:item.itemKey]){
[deleArray addObject:item];
if([item.itemKey isEqualToString:self.selectItemKey]){
hasDeleSelect = YES;
}
}
}
[self.allItems removeObjectsInArray:deleArray];
if(hasDeleSelect){
self.selectItemKey = self.allItems.firstObject.itemKey;
[self loadView];
}
}
//根据菜单,加载bar和内容
-(void)loadView{
if(self.allItems.count > self.maxShowNum){
self.itemWidth = self.bounds.size.width / self.maxShowNum;
}else{
self.itemWidth = self.bounds.size.width / self.allItems.count;
}
for (UIButton * btn in self.allTopbarBtns) {
[btn removeFromSuperview];
}
for (int i=0; i<self.allItems.count; i++) {
TopBarItemInfo * item = self.allItems[i];
UIButton * barBtn = [[UIButton alloc]initWithFrame:CGRectMake(i*self.itemWidth, 0, self.itemWidth, BarHeight-BarTipViewHeight)];
[barBtn setTitle:item.itemShowName forState:UIControlStateNormal];
[barBtn setTitleColor:self.selectColor forState:UIControlStateSelected];
[barBtn setTitleColor:self.defaultColor forState:UIControlStateNormal];
barBtn.titleLabel.font = [UIFont systemFontOfSize:item.itemDefaultFontSize];
barBtn.tag = i;
[self.allTopbarBtnIndexInfo setValue:@(i) forKey:item.itemKey];
if(item.itemHasChild){
[barBtn setImage:item.itemTagDefaultImage forState:UIControlStateNormal];
[barBtn setImage:item.itemTagSelectImage forState:UIControlStateSelected];
[ViewToolTopBarView setBtn:barBtn IconInRightWithSpacing:2];
}
[barBtn addTarget:self action:@selector(barBtnClick:) forControlEvents:UIControlEventTouchUpInside];
[self.topbarScrollView addSubview:barBtn];
item.itemShowView.frame = CGRectMake(i*self.bounds.size.width, 0, self.contentScrollView.bounds.size.width, self.contentScrollView.bounds.size.height);
[self.contentScrollView addSubview:item.itemShowView];
[self.allTopbarBtns addObject:barBtn];
}
self.topBarSelectView = [[UIView alloc]initWithFrame:CGRectMake(0,BarHeight-BarTipViewHeight, self.itemWidth/3, BarTipViewHeight)];
self.topBarSelectView.backgroundColor = self.selectColor;
[self.topbarScrollView addSubview:self.topBarSelectView];
self.topbarScrollView.contentSize = CGSizeMake(self.allItems.count*self.itemWidth, 0);
self.contentScrollView.contentSize = CGSizeMake(self.allItems.count*self.bounds.size.width, 0);
[self selectToItemForKey:self.selectItemKey];
}
-(void)barBtnClick:(UIButton *)btn{
[self selectToIndex:btn.tag];
}
-(void)selectToIndex:(NSInteger)index{
[self selectToIndex:index isAuto:NO];
}
-(void)selectToIndex:(NSInteger)index isAuto:(BOOL)isAuto{
for (UIButton * btn in self.allTopbarBtns) {
TopBarItemInfo * nowSelectInfo = self.allItems[index];
if(btn.tag == index){
[btn setSelected:YES];
btn.titleLabel.font = [UIFont systemFontOfSize:nowSelectInfo.itemSelectFontSize];
[ViewToolTopBarView setBtn:btn IconInRightWithSpacing:2];
[UIView animateWithDuration:0.2f animations:^{
self.topBarSelectView.center = CGPointMake(btn.center.x, self.topBarSelectView.center.y);
}];
if([self.selectItemKey isEqualToString:nowSelectInfo.itemKey] && isAuto){
//之前选择的就是这项,并且是滑动内容自动选择
return;
}
if(nowSelectInfo.itemHasChild){
//有子选项,并且不是自动切换(滑动内容视图切换)
if([self.selectItemKey isEqualToString:nowSelectInfo.itemKey]){
//之前选择的就是这项,
if(!isAuto){
if(self.childScrollView.isHidden){
[UIView animateWithDuration:0.2f animations:^{
btn.imageView.transform = CGAffineTransformMakeRotation(M_PI);
}];
[self showChildChooseView:nowSelectInfo.childItems];
}else{
[UIView animateWithDuration:0.2f animations:^{
btn.imageView.transform = CGAffineTransformMakeRotation(0);
}];
self.childScrollView.hidden = YES;
}
}
return;
}else{
[UIView animateWithDuration:0.2f animations:^{
btn.imageView.transform = CGAffineTransformMakeRotation(0);
}];
self.childScrollView.hidden = YES;
self.selectItemKey = nowSelectInfo.itemKey;
TopBarItemInfo * firstChild = nowSelectInfo.childItems.firstObject;
self.selectChileItemKey = firstChild.itemKey;
if(self.didChooseBlock){
self.didChooseBlock(self.selectChileItemKey);
}
}
}else{
[UIView animateWithDuration:0.2f animations:^{
btn.imageView.transform = CGAffineTransformMakeRotation(0);
}];
self.childScrollView.hidden = YES;
self.selectItemKey = nowSelectInfo.itemKey;
if(self.didChooseBlock){
self.didChooseBlock(self.selectItemKey);
}
}
}else{
[UIView animateWithDuration:0.2f animations:^{
btn.imageView.transform = CGAffineTransformMakeRotation(0);
}];
[btn setSelected:NO];
btn.titleLabel.font = [UIFont systemFontOfSize:nowSelectInfo.itemDefaultFontSize];
[ViewToolTopBarView setBtn:btn IconInRightWithSpacing:2];
}
}
self.selectItemIndex = index;
[self changeTopBarToSelect];
[self.contentScrollView setContentOffset:CGPointMake(index*self.bounds.size.width, 0) animated:YES];
}
-(void)changeTopBarToSelect{
CGFloat contentOffset = self.selectItemIndex *self.itemWidth - self.bounds.size.width/2 + self.itemWidth/2;//居中位置
CGFloat contentSize = self.allItems.count * self.itemWidth;
if(contentSize-contentOffset<self.bounds.size.width){
contentOffset = contentSize-self.bounds.size.width;
}
if(contentOffset<0){
contentOffset = 0;
}
[self.topbarScrollView setContentOffset:CGPointMake(contentOffset, 0) animated:YES];
}
-(void)showChildChooseView:(NSArray<TopBarItemInfo*>*) childItems{
self.nowShowChildItems = childItems;
for (UIView * childView in self.childScrollView.subviews) {
[childView removeFromSuperview];
}
self.childScrollView.hidden = NO;
for (int i=0; i<self.nowShowChildItems.count; i++) {
TopBarItemInfo * item = self.nowShowChildItems[i];
UIButton * barBtn = [[UIButton alloc]initWithFrame:CGRectMake(i*self.itemWidth , 0, self.itemWidth, BarHeight-BarTipViewHeight)];
[barBtn setTitle:item.itemShowName forState:UIControlStateNormal];
[barBtn setTitleColor:self.selectColor forState:UIControlStateSelected];
[barBtn setTitleColor:self.defaultColor forState:UIControlStateNormal];
barBtn.titleLabel.font = [UIFont systemFontOfSize:item.itemDefaultFontSize];
barBtn.tag = i;
[barBtn addTarget:self action:@selector(childSelectClick:) forControlEvents:UIControlEventTouchUpInside];
if([self.selectChileItemKey isEqualToString:item.itemKey]){
[barBtn setSelected:YES];
}
[self.childScrollView addSubview:barBtn];
}
self.childScrollView.contentSize = CGSizeMake(self.nowShowChildItems.count*self.itemWidth, 0);
}
-(void)childSelectClick:(UIButton *)btn{
[UIView animateWithDuration:0.2f animations:^{
UIButton * btnTemp = self.allTopbarBtns[self.selectItemIndex];
btnTemp.imageView.transform = CGAffineTransformMakeRotation(0);
}];
self.childScrollView.hidden = YES;
TopBarItemInfo * nowSelectInfo = self.nowShowChildItems[btn.tag];
self.selectChileItemKey = nowSelectInfo.itemKey;//记录上次的子目录
if(self.didChooseBlock){
self.didChooseBlock(nowSelectInfo.itemKey);
}
}
/**
*设置按钮图片居右
*Spacing:间隔
*/
+(void)setBtn:(UIButton *)btn IconInRightWithSpacing:(CGFloat)Spacing
{
CGFloat img_W = btn.imageView.frame.size.width;
CGFloat tit_W = btn.titleLabel.frame.size.width;
btn.titleEdgeInsets = (UIEdgeInsets){
.top = 0,
.left = - (img_W + Spacing / 2),
.bottom = 0,
.right = (img_W + Spacing / 2),
};
btn.imageEdgeInsets = (UIEdgeInsets){
.top = 0,
.left = (tit_W + Spacing / 2),
.bottom = 0,
.right = - (tit_W + Spacing / 2),
};
}
@end
@implementation TopBarItemInfo
-(instancetype)init{
self = [super init];
if(self){
self.itemDefaultFontSize = 18;
self.itemSelectFontSize = 18;
}
return self;
}
-(instancetype)initWithKey:(NSString *)itemKey Name:(NSString *)name{
self = [super init];
if(self){
self.itemDefaultFontSize = 17;
self.itemSelectFontSize = 19;
self.itemKey = itemKey;
self.itemShowName = name;
}
return self;
}
-(void)loadChildInfo:(NSArray<TopBarItemInfo*>*) childItems defaultImage:(UIImage *)defaultImage selectImage:(UIImage *)selectImage{
self.itemHasChild = YES;
self.childItems = childItems;
self.itemTagDefaultImage = defaultImage;
self.itemTagSelectImage = selectImage;
}
@end
使用demo
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic,strong)ViewToolTopBarView * topbar;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
__weak typeof(self) weakSelf = self;
self.topbar = [[ViewToolTopBarView alloc]initWithFrame:CGRectMake(0, 40, self.view.bounds.size.width, self.view.bounds.size.height-40) maxShowItem:5 selectColor:[UIColor blueColor] defaultColor:[UIColor grayColor] didSelect:^(NSString * _Nonnull selectItemKey) {
NSLog(@"选择了选项:%@",selectItemKey);
if([selectItemKey isEqualToString:@"barsixChild2"]){
[weakSelf.topbar selectToIndex:3];
}
}];
UIView * viewTemp = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 0, 0)];
viewTemp.backgroundColor = [UIColor redColor];
UIImage * childDeImage = [[UIImage imageNamed:@"Service_Red_Point"] imageWithTintColor:[UIColor grayColor]];
UIImage * childSeImage = [[UIImage imageNamed:@"Service_Red_Point"] imageWithTintColor:[UIColor blueColor]];
TopBarItemInfo * barone = [[TopBarItemInfo alloc]initWithKey:@"barone" Name:@"最新"];
barone.itemShowView = viewTemp;
[barone loadChildInfo:@[[[TopBarItemInfo alloc]initWithKey:@"baroneChild1" Name:@"最新1"],
[[TopBarItemInfo alloc]initWithKey:@"baroneChild2" Name:@"最新2"]]
defaultImage:childDeImage
selectImage:childSeImage];
UIView * viewTemp2 = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 0, 0)];
viewTemp2.backgroundColor = [UIColor greenColor];
TopBarItemInfo * bartwo = [[TopBarItemInfo alloc]initWithKey:@"bartwo" Name:@"精华"];
bartwo.itemShowView = viewTemp2;
UIView * viewTemp3 = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 0, 0)];
viewTemp3.backgroundColor = [UIColor blueColor];
TopBarItemInfo * barthree = [[TopBarItemInfo alloc]initWithKey:@"barthree" Name:@"专题A1"];
barthree.itemShowView = viewTemp3;
[barthree loadChildInfo:@[[[TopBarItemInfo alloc]initWithKey:@"barthreeChild1" Name:@"专题1"],
[[TopBarItemInfo alloc]initWithKey:@"barthreeChild2" Name:@"专题2"],
[[TopBarItemInfo alloc]initWithKey:@"barthreeChild3" Name:@"专题3"],
[[TopBarItemInfo alloc]initWithKey:@"barthreeChild4" Name:@"专题4"],
[[TopBarItemInfo alloc]initWithKey:@"barthreeChild5" Name:@"专题5"]]
defaultImage:childDeImage
selectImage:childSeImage];
UIView * viewTemp4 = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 0, 0)];
viewTemp4.backgroundColor = [UIColor yellowColor];
TopBarItemInfo * barthfour = [[TopBarItemInfo alloc]initWithKey:@"barthfour" Name:@"专题B"];
barthfour.itemShowView = viewTemp4;
UIView * viewTemp5 = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 0, 0)];
viewTemp5.backgroundColor = [UIColor grayColor];
TopBarItemInfo * barfive= [[TopBarItemInfo alloc]initWithKey:@"barfive" Name:@"专题C"];
barfive.itemShowView = viewTemp5;
UIView * viewTemp6 = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 0, 0)];
viewTemp6.backgroundColor = [UIColor linkColor];
TopBarItemInfo * barsix= [[TopBarItemInfo alloc]initWithKey:@"barsix" Name:@"专题D"];
barsix.itemShowView = viewTemp6;
[barsix loadChildInfo:@[[[TopBarItemInfo alloc]initWithKey:@"barsixChild1" Name:@"专题D1"],
[[TopBarItemInfo alloc]initWithKey:@"barsixChild2" Name:@"专题D2"]]
defaultImage:childDeImage
selectImage:childSeImage];
[self.topbar addItems:@[barone,bartwo,barthree,barthfour,barfive,barsix]];
[self.view addSubview:self.topbar];
}
@end
有些地方有待优化,但基本功能都已实现,可借鉴。
联系作者
期待你的点赞和关注!如有疑问,联系作者。