【iOS】让我们⼀次性解决导航栏的所有问题
前⼀段时间换了⼯作,公司项⽬赶得⽐较紧,没有时间更新⽂章,现在闲下来了,赶紧写⼀篇来弥补⾃⼰的羞愧。
今天我们来重点讨论导航栏返回的问题,包括各种问题的解决⽅案。
系统默认导航栏的返回按钮和返回⽅式
在默认情况下,导航栏返回按钮长这个样⼦
导航栏默认返回按钮
导航栏左上⾓的返回按钮,其⽂本默认为上⼀个ViewController的标题,如果上⼀个ViewController没有标题,则为Back(中⽂环境下为“返回”)。
在默认情况下,导航栏返回的点击交互和滑动交互如下
默认导航栏交互
这些东西不需要任何设置和操作,因此也没有其他需要说明的地⽅。
⾃定义左上⾓的返回按钮
绝⼤多数情况下,我们都需要根据产品需求⾃定义左上⾓的返回按钮,虽然这对⼤多数开发者来说不是什么难事,但依然有⼏个问题值得注意。
替换左上⾓返回按钮
替换返回按钮⾮常简单,只需要在ViewController中创建⼀个UIBarButtonItem和⼀张图⽚,并为按钮添加相应的点击事件即可,代码如下
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
UIButton * leftBtn = [UIButton buttonWithType:UIButtonTypeSystem];
leftBtn.frame = CGRectMake(0, 0, 25,25);
[leftBtn setBackgroundImage:[UIImage imageNamed:@"nav_back"] forState:UIControlStateNormal];
[leftBtn addTarget:self action:@selector(leftBarBtnClicked:) forControlEvents:UIControlEventTouchUpInside];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithCustomView:leftBtn];
}
- (void)leftBarBtnClicked:(UIButton *)btn
{
[self.navigationController popViewControllerAnimated:YES];
}
我们来看⼀眼效果
替换返回按钮
调整按钮位置
我们可以看到,上⾯的按钮是有点偏右的,那如果我们想调整按钮的位置该怎么做呢?设置Frame显然是⾏不通的,因为导航栏的NavigationItem是个⽐较特殊的View,我们⽆法通过简单的调整Frame来的调整左右按//创建返回按钮
UIButton * leftBtn = [UIButton buttonWithType:UIButtonTypeSystem];
leftBtn.frame = CGRectMake(0, 0, 25,25);
[leftBtn setBackgroundImage:[UIImage imageNamed:@"icon_back"] forState:UIControlStateNormal];
[leftBtn addTarget:self action:@selector(leftBarBtnClicked:) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem * leftBarBtn = [[UIBarButtonItem alloc]initWithCustomView:leftBtn];;
//创建UIBarButtonSystemItemFixedSpace
UIBarButtonItem * spaceItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
//将宽度设为负值
spaceItem.width = -15;
//将两个BarButtonItem都返回给NavigationItem
self.navigationItem.leftBarButtonItems = @[spaceItem,leftBarBtn];
我们来看⼀眼效果
调整返回按钮位置
可以看到,我们的返回按钮已经紧靠着屏幕边缘。
这个⽅法同样适⽤于调整导航栏右侧的按钮
让滑动返回⼿势⽣效
如果使⽤⾃定义的按钮去替换系统默认返回按钮,会出现滑动返回⼿势失效的情况。解决⽅法也很简单,只需要重新添加导航栏的interactivePopGestureRecognizer的delegate即可。
⾸先为ViewContoller添加UIGestureRecognizerDelegate协议
然后设置代理
self.navigationController.interactivePopGestureRecognizer.delegate = self;
⾄此,我们已经将返回按钮替换为我们的⾃定义按钮,并使滑动返回重新⽣效。接下来,我们继续来解决交互上的问题。
全屏滑动返回
这个⼀个很常见的需求,⽹上解决⽅案也很多,这⾥将本⼈常⽤的⽅法贴到这⾥。仅供参考
实现全屏滑动返回仅需在导航栏给导航栏添加UIGestureRecognizerDelegate协议,并在ViewDidLoad中写⼊如下代码
// 获取系统⾃带滑动⼿势的target对象
id target = self.interactivePopGestureRecognizer.delegate;
// 创建全屏滑动⼿势,调⽤系统⾃带滑动⼿势的target的action⽅法
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:@selector(handleNavigationTransition:)];
/
/ 设置⼿势代理,拦截⼿势触发
pan.delegate = self;
// 给导航控制器的view添加全屏滑动⼿势
[self.view addGestureRecognizer:pan];
// 禁⽌使⽤系统⾃带的滑动⼿势
abled = NO;
我们来看⼀眼效果(注意⿏标位置)
全屏滑动返回.gif
这种⽅法的原理其实很简单,其实就是⾃定义⼀个全屏滑动⼿势,并将滑动事件设置为系统滑动事件,然后禁⽤系统滑动⼿势即可。handleNavigationTransition就是系统滑动的⽅法,虽然系统并未提供接⼝,但是我
这种⽅法的原理其实很简单,其实就是⾃定义⼀个全屏滑动⼿势,并将滑动事件设置为系统滑动事件,
然后禁⽤系统滑动⼿势即可。handleNavigationTransition就是系统滑动的⽅法,虽然系统并未提供接⼝,但是我NavigationBar切换动画的“终极解决⽅案”
本部分⽂字代码都较多,不想看这么多废话的同学请直接翻到末尾,⽂末附有下载地址,导⼊项⽬后,继承即可⽣效。
在改变了导航栏样式,实现了全屏滑动返回之后,我们有了⼀个看起来还不错的导航栏。但是我们滑动时的切换依然是系统⾃带的动画,如果遇到前⼀个界⾯的NavigationBar为透明或前后两个Bar颜⾊不⼀样,这种渐这个问题,其实很多App,⽐如天猫、美团等都通过⼀种“整体返回”的效果来解决这个问题。效果如下:
整体滑动返回
这种解决⽅案等于将两个NavigationBar独⽴开来,因此可以相对完美的解决导航栏滑动切换中的种种Bug。
接下来,我们来看看如何实现这种效果。
基本原理
以我个⼈的认知,实现这个效果有三种基本思路:
1. 使⽤UINavigationController⾃带的setNavigationBarHidden: animated:⽅法来实现,每次push或pop时,在当前控制器的viewWillDisappear:中设置隐藏,在要跳转的控制器的viewWillAppear:中设置导航栏显⽰。
2. 在每次Push前对当前页⾯进⾏截图并保存到数组,Pop时取数组最后⼀个元素显⽰,滑动结束后调⽤系统Pop⽅法并删除最后⼀张截图。
3. 使⽤iOS 7之后开放的,UIViewControllerAnimatedTransitioning协议,来实现⾃定义导航栏转场动画及交互。
以上三种⽅法,⽅法⼀⼗分繁琐,⽽且会有很多莫名其妙的BUG,直接pass。
在iOS的交互中,push⼀般通过按钮的点击事件或View的tap事件触发,⽽pop则可能通过事件触发,也可能通过右滑⼿势触发。因此,我们将这个我们要实现的动画效果分为交互效果和⽆交互效果两种,下⾯我们将使实现交互动画效果
准备需要使⽤的数组及⼿势
#define ScreenWidth [UIScreen mainScreen].bounds.size.width
#define ScreenHeight [UIScreen mainScreen].bounds.size.height
@interface LTNavigationController ()
@property(strong,nonatomic)UIImageView * screenshotImgView;
@property(strong,nonatomic)UIView * coverView;
@property(strong,nonatomic)NSMutableArray * screenshotImgs;
@property(strong,nonatomic)UIPanGestureRecognizer *panGestureRec;
@end
@implementation LTNavigationController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// 1,创建Pan⼿势识别器,并绑定监听⽅法
_panGestureRec = [[UIScreenEdgePanGestureRecognizer alloc]initWithTarget:self action:@selector(panGestureRec:)];
_panGestureRec.edges = UIRectEdgeLeft;
// 为导航控制器的view添加Pan⼿势识别器
[self.view addGestureRecognizer:_panGestureRec];
// 2.创建截图的ImageView
_screenshotImgView = [[UIImageView alloc] init];
// app的frame是包括了状态栏⾼度的frame
_screenshotImgView.frame = CGRectMake(0, 0, ScreenWidth, ScreenHeight);
// 3.创建截图上⾯的⿊⾊半透明遮罩
_coverView = [[UIView alloc] init];
// 遮罩的frame就是截图的frame
_coverView.frame = _screenshotImgView.frame;
// 遮罩为⿊⾊
_coverView.backgroundColor = [UIColor blackColor];
// 4.存放所有的截图数组初始化
_screenshotImgs = [NSMutableArray array];
}
实现⼿势的相应事件
// 响应⼿势的⽅法
- (void)panGestureRec:(UIPanGestureRecognizer *)panGestureRec
{
// 如果当前显⽰的控制器已经是根控制器了,不需要做任何切换动画,直接返回
if(self.visibleViewController == self.viewControllers[0]) return;
ios 字符串转数组
// 判断pan⼿势的各个阶段
switch (panGestureRec.state) {
case UIGestureRecognizerStateBegan:
// 开始拖拽阶段
[self dragBegin];
break;
case UIGestureRecognizerStateEnded:
// 结束拖拽阶段
[self dragEnd];
break;
default:
// 正在拖拽阶段
[self dragging:panGestureRec];
break;
}
}
#pragma mark 开始拖动,添加图⽚和遮罩
- (void)dragBegin
{
// 重点,每次开始Pan⼿势时,都要添加截图imageview 和遮盖cover到window中
[self.view.window insertSubview:_screenshotImgView atIndex:0];
[self.view.window insertSubview:_coverView aboveSubview:_screenshotImgView];
// 并且,让imgView显⽰截图数组中的最后(最新)⼀张截图
_screenshotImgView.image = [_screenshotImgs lastObject];
//_ansform = CGAffineTransformMakeTranslation(ScreenWidth, 0);
}
// 默认的将要变透明的遮罩的初始透明度(全⿊)
#define kDefaultAlpha 0.6
// 当拖动的距离,占了屏幕的总宽⾼的3/4时, 就让imageview完全显⽰,遮盖完全消失
#define kTargetTranslateScale 0.75
#pragma mark 正在拖动,动画效果的精髓,进⾏位移和透明度变化
- (void)dragging:(UIPanGestureRecognizer *)pan
-
(void)dragging:(UIPanGestureRecognizer *)pan
{
// 得到⼿指拖动的位移
CGFloat offsetX = [pan translationInView:self.view].x;
// 让整个view都平移    // 挪动整个导航view
if (offsetX > 0) {
ansform = CGAffineTransformMakeTranslation(offsetX, 0);
}
// 计算⽬前⼿指拖动位移占屏幕总的宽⾼的⽐例,当这个⽐例达到3/4时, 就让imageview完全显⽰,遮盖完全消失
double currentTranslateScaleX = offsetX/self.view.frame.size.width;
if (offsetX < ScreenWidth) {
_ansform = CGAffineTransformMakeTranslation((offsetX - ScreenWidth) * 0.6, 0);
}
// 让遮盖透明度改变,直到减为0,让遮罩完全透明,默认的⽐例-(当前平衡⽐例/⽬标平衡⽐例)*默认的⽐例
double alpha = kDefaultAlpha - (currentTranslateScaleX/kTargetTranslateScale) * kDefaultAlpha;
_coverView.alpha = alpha;
}
#pragma mark 结束拖动,判断结束时拖动的距离作相应的处理,并将图⽚和遮罩从⽗控件上移除
- (void)dragEnd
{
// 取出挪动的距离
CGFloat translateX = ;
// 取出宽度
CGFloat width = self.view.frame.size.width;
if (translateX <= 40)="" {="" 如果⼿指移动的距离还不到屏幕的⼀半,往左边挪="" (弹回)="" [uiview="" animatewithduration:0.3="" animations:^{="" 重要~~让被右移的view弹回归位,只要清空transform即可办到="" ansform="CGAffineTransformIdentity;" 让实现截图保存功能,并在Push前截图
- (void)screenShot
{
// 将要被截图的view,即窗⼝的根控制器的view
UIViewController *beyondVC = self.ViewController;
// 背景图⽚总的⼤⼩
CGSize size = beyondVC.view.frame.size;
// 开启上下⽂,使⽤参数之后,截出来的是原图(YES  0.0 质量⾼)
UIGraphicsBeginImageContextWithOptions(size, YES, 0.0);
// 要裁剪的矩形范围
CGRect rect = CGRectMake(0, 0, ScreenWidth, ScreenHeight);
//注:iOS7以后renderInContext:由drawViewHierarchyInRect:afterScreenUpdates:替代
[beyondVC.view drawViewHierarchyInRect:rect  afterScreenUpdates:NO];
// 从上下⽂中,取出UIImage
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
// 添加截取好的图⽚到图⽚数组
if (snapshot) {
[_screenshotImgs addObject:snapshot];
}
// 千万记得,结束上下⽂(移除栈顶的基于当前位图的图形上下⽂)
UIGraphicsEndImageContext();
}
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
//有在导航控制器⾥⾯有⼦控制器的时候才需要截图
if (unt >= 1) {
// 调⽤⾃定义⽅法,使⽤上下⽂截图
[self screenShot];
}
/
/ 截图完毕之后,才调⽤⽗类的push⽅法
[super pushViewController:viewController animated:YES];
}
重写常⽤的pop⽅法
在⼀开始基本原理地⽅,我们说过pop时要删除最后⼀张截图,⽤来保证数组中的最后⼀张截图是上⼀个控制器,但是很多情况下我们可能调⽤的是导航栏的popToViewController: animated:⽅法或popToRootViewControllerAnima - (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
[_screenshotImgs removeLastObject];
return [super popViewControllerAnimated:animated];
}
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
{
for (NSInteger i = unt - 1; i > 0; i--) {
if (viewController == self.viewControllers[i]) {
break;
}
[_screenshotImgs removeLastObject];
}
return [super popToViewController:viewController animated:animated];
}
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated
{
[_screenshotImgs removeAllObjects];
return [super popToRootViewControllerAnimated:animated];
}
※在指定的控制器屏蔽⼿势
在上⾯代码中,我们使⽤的是侧滑⼿势,并将相应区域设置为屏幕左侧。
之所以不⽤全屏滑动,是因为全屏滑动⼿势在有些时候会和其他⼿势冲突,如果冲突的是我们⾃定义的⼿势,⾃然好解决,但如果是系统⼿势,如TableView的左滑菜单操作,这个事情就很蛋疼的。
但是如果必须要做全屏滑动⼿势的话,我们可以对代码稍作修改,某些控制器中屏蔽⼿势。
⾸先给导航栏添加禁⽤名单数组并配置
...
@property(nonatomic,copy)NSArray * forbiddenArray;
...
- (void)viewDidLoad {
[super viewDidLoad];
//原来代码
...
//将⼿势禁⽤,之后在Push时根据条件开启
abled = enable
//将需要禁⽤⼿势的控制器的类名加到这个数组
self.forbiddenArray = @[@"SCViewController",@"ManageAddressViewController"];
}
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
/
/在指定控制器中禁⽤⼿势解决滑动返回⼿势和某些⼿势冲突问题
BOOL enable = YES;
BOOL enable = YES;
for (NSString * string in self.forbiddenArray) {
NSString * className = NSStringFromClass([viewController class]);
if ([string isEqualToString:className]) {
enable = NO;
}
}
abled = enable;
//原有代码
.
..
}
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
NSInteger count = unt;
NSString * className = nil;
if (count >= 2) {
className = NSStringFromClass([self.viewControllers[count -2] class]);
}
BOOL enable = YES;
for (NSString * string in self.forbiddenArray) {
if ([string isEqualToString:className]) {
enable = NO;
}
}
abled = enable;
//原有代码
...
return [super popViewControllerAnimated:animated];
}
到了这⾥,我们已经完成了交互式的切换动画,效果跟开头⼀样,就不再截图。接下来我们来解决另⼀个⼤Boss-⾮交互式动画
实现⾮交互动画效果
理论基础
这⾥我们就要⽤到之前说的UIViewControllerAnimatedTransitioning来实现。限于篇幅,这⾥不再详细介绍这部分的基础知识,⼤家可以移步这两篇博客做⼀个初步的了解
向 UINavigationController 的传统动画说”再见” — ⾃定义过场动画(⼀)
iOS 7:⾃定义导航转场动画以及更多
实现原理
注:FromVC代表即将消失的视图控制器,ToVC表⽰将要展⽰的视图控制器
我们要实现的效果:
Push的时候,FromVC往左移动,ToVC从屏幕右侧出现跟随FromVC左移直⾄FromVC消失,此时ToVC刚好完整显⽰在屏幕上。
Pop的时候,FromVC向右移动,ToVC从屏幕边缘出现跟随FromVC向右移动直⾄FromVC消失,此时ToVC刚好完整显⽰在屏幕上
实现的时候,我们依然需要将Push和Pop分开讨论
先说Pop
1.和交互式动画⼀样,每次Push时对屏幕截屏并保存,Pop的再次截屏但不保存
2.把Pop时截取的图⽚作为FromVC展⽰,把Push到这个界⾯时截取的图⽚作为ToVC展⽰
3.并对两张图⽚做位移动画,动画结束后移除两张图⽚
然后是Push
1.Push时先对当前屏幕截屏。
2.将截取的图⽚保存⽅便Pop回来时使⽤,并把这张图⽚作为这次Push的FromVC保存。
3.获取当前导航栏控制器对象,调整其Transform属性中的位移参数作为ToVC展⽰
4.对截图和导航栏做位移,动画结束后直接移除截屏图⽚
为什么要对导航栏作位移?
⾸先,在Push结束之前,我们是⽆法知道ToVC具体是什么样⼦,系统的截屏⽅法对于未加载出来的View是⽆能为⼒的,⽽UIView的snapshotViewAfterScreenUpdates:⽅法⼜⽆法带着导航栏⼀起映射到⼀个新的Vie 正好在Pop的时候,为了达到想要的动画效果,⽤来展⽰的两张图⽚都需要放到导航栏的View上,因此在Push的时候我们就直接将导航栏的View做⼀个放射变换,当然,这也就意味着,当我们Push的时候,截
让我们撸⼀发代码
根据上述实现原理,我们可以知道,我们的主要⼯作重点在于打造⼀个合适的动画控制器。更准确的说,我们需要实现的细节都在UIViewControllerAnimatedTransitioning中,由于之前解释的很详细,这⾥我直接贴上相应代码-(void)animateTransition:(id)transitionContext
{
UIImageView * screentImgView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, ScreenWidth, ScreenHeight)];
UIImage * screenImg = [self screenShot];
screentImgView.image =screenImg;
//取出fromViewController,fromView和toViewController,toView
UIViewController * fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
//    UIView * fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
UIViewController * toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView * toView = [transitionContext viewForKey:UITransitionContextToViewKey];
CGRect fromViewEndFrame = [transitionContext finalFrameForViewController:fromViewController];
CGRect fromViewStartFrame = fromViewEndFrame;
CGRect toViewEndFrame = [transitionContext finalFrameForViewController:toViewController];
CGRect toViewStartFrame = toViewEndFrame;
UIView * containerView = [transitionContext containerView];
if (self.navigationOperation == UINavigationControllerOperationPush) {
[self.screenShotArray addObject:screenImg];
//igin.x += ScreenWidth;
[containerView addSubview:toView];
toView.frame = toViewStartFrame;
UIView * nextVC = [[UIView alloc]initWithFrame:CGRectMake(ScreenWidth, 0, ScreenWidth, ScreenHeight)];
//[nextVC addSubview:[toView snapshotViewAfterScreenUpdates:YES]];
[self.navigationController.tabBarController.view insertSubview:screentImgView atIndex:0];
//[self.navigationController.tabBarController.view addSubview:nextVC];
nextVC.layer.shadowColor = [UIColor blackColor].CGColor;
nextVC.layer.shadowOffset = CGSizeMake(-0.8, 0);
nextVC.layer.shadowOpacity = 0.6;
nextVC.layer.shadowOpacity = 0.6;
self.ansform = CGAffineTransformMakeTranslation(ScreenWidth, 0);
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
//toView.frame = toViewEndFrame;
self.ansform = CGAffineTransformMakeTranslation(0, 0);
< = CGPointMake(-ScreenWidth/2, ScreenHeight / 2);
// = CGPointMake(ScreenWidth/2, ScreenHeight / 2);
} completion:^(BOOL finished) {
[nextVC removeFromSuperview];
[screentImgView removeFromSuperview];
[transitionContext completeTransition:YES];
}];
}
if (self.navigationOperation == UINavigationControllerOperationPop) {
[containerView addSubview:toView];
UIImageView * lastVcImgView = [[UIImageView alloc]initWithFrame:CGRectMake(-ScreenWidth, 0, ScreenWidth, ScreenHeight)];
lastVcImgView.image = [self.screenShotArray lastObject];
screentImgView.layer.shadowColor = [UIColor blackColor].CGColor;
screentImgView.layer.shadowOffset = CGSizeMake(-0.8, 0);
screentImgView.layer.shadowOpacity = 0.6;
[self.navigationController.tabBarController.view addSubview:lastVcImgView];
[self.navigationController.tabBarController.view addSubview:screentImgView];
// fromView.frame = fromViewStartFrame;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
< = CGPointMake(ScreenWidth * 3 / 2 , ScreenHeight / 2);
< = CGPointMake(ScreenWidth/2, ScreenHeight/2);
//fromView.frame = fromViewEndFrame;
} completion:^(BOOL finished) {
//[self.navigationController setNavigationBarHidden:NO];
[lastVcImgView removeFromSuperview];
[screentImgView removeFromSuperview];
[self.screenShotArray removeLastObject];
[transitionContext completeTransition:YES];
}];
}
}
- (void)removeLastScreenShot
{
[self.screenShotArray removeLastObject];
}
- (UIImage *)screenShot
{
// 将要被截图的view,即窗⼝的根控制器的view(必须不含状态栏,默认ios7中控制器是包含了状态栏的)
UIViewController *beyondVC = self.navigationController.ViewController;
// 背景图⽚总的⼤⼩
CGSize size = beyondVC.view.frame.size;
// 开启上下⽂,使⽤参数之后,截出来的是原图(YES  0.0 质量⾼)
UIGraphicsBeginImageContextWithOptions(size, YES, 0.0);
// 要裁剪的矩形范围
CGRect rect = CGRectMake(0, 0, ScreenWidth, ScreenHeight);
//注:iOS7以后renderInContext:由drawViewHierarchyInRect:afterScreenUpdates:替代
[beyondVC.view drawViewHierarchyInRect:rect  afterScreenUpdates:NO];
/
/ 从上下⽂中,取出UIImage
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
// 千万记得,结束上下⽂(移除栈顶的基于当前位图的图形上下⽂)
UIGraphicsEndImageContext();
// 返回截取好的图⽚
return snapshot;
}
注:removeLastScreenShot需要在使⽤滑动⼿势Pop后调⽤,⽤来清除动画控制器中保存的截图,否则当交互式和⾮交互式动画交替使⽤时,会出现截图混乱的问题。看看效果
我们将动画持续时间调制两秒,观察⼀下效果
完成效果.gif
后记
这篇⽂章开始于四个⽉之前,中间由于个⼈以及⼯作原因拖了⼜拖,终于在最近补完,逻辑混乱之处请见谅。
制作完成的导航栏和动画控制器的下载地址
导航栏和动画控制器下载地址
使⽤⽅法:
1.将这四个⽂件导⼊⼯程
2.将需要动画的导航栏继承KLTNavigationController即可
如果我的⽂章对您有帮助,请点赞或评论,谢谢!

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。