网络编程

当前位置:永利402游戏网站-永利402com官方网站 > 网络编程 > 导航栏的平整呈现和隐敝 - 个人页的本身修养(

导航栏的平整呈现和隐敝 - 个人页的本身修养(

来源:http://www.xtcsyb.com 作者:永利402游戏网站-永利402com官方网站 时间:2019-09-11 14:45

本文是《个人页的自我修养》系列文章的一篇,全部:

如我在传送门:iOS导航栏切换界面时隐藏和显示中所说,现在很多App的个人中心模块都是不保留导航栏的,会直接使导航栏透明,比如做的很好的QQ个人信息界面:

  • 导航栏的平滑显示和隐藏 - 个人页的自我修养
  • 多个UITableView共用一个tableHeader的效果实现 - 个人页的自我修养
  • 处理Pan手势和ScrollView滚动冲突 - 个人页的自我修养

图片 1image.png

关于“个人页”

带有社交属性的APP一般都会有一个“个人页”,用以展示某个用户的个人信息和发布的内容。以下是几个例子:

图片 2个人页例子.png

以上页面的共同特征是:1、透明的导航栏以更好的展示背景图2、可按标签切换到不同的内容页(这个特性看需求,不一定有)3、滚动时会停靠在页面顶部的SegmentView3、各个可滚动的内容页共用一个header

最近刚好写到Rabo微博客户端的个人页的部分,发现踩到几个有意思的坑,解决下来决定写个系列文章把相关解决方法和代码分享一下。先看一下要实现的整体效果:

图片 3overView.gif

这篇文章先处理导航栏的平滑隐藏和显示。

为什么说QQ做的很好呢?既然有透明的导航栏也有不透明的导航栏,那一定会在界面切换之间存在一个过渡的过程,而这个过程,QQ做的特别好,在从透明导航栏界面返回到不透明导航栏界面时,导航栏的透明度是一个渐进的过渡效果,甚至会有一种毛玻璃的效果,感兴趣的可以打开手机QQ到个人界面看一看,效果很赞。

导航栏的平滑显示和隐藏

而很多App的做法其实比较粗糙,类似于我在传送门:iOS导航栏切换界面时隐藏和显示中的做法,需要导航栏透明时,直接将导航栏隐藏起来。直接隐藏起来的意思是,整个导航栏就用不了了,也就是说,标题、返回按钮等都需要自己去做,这是一个比较麻烦的地方,此外,在有无导航栏的界面间切换时,过程是比较生硬的,导航栏不是渐变出现的。如果说这些都可以接受,那最大的一个问题,也是我在那篇文章里提到的,如果正好处于用UITabbarConatroller切换界面,那么导航栏会有一个往上缩回的快速动画,这其实就很不美观了,当然我们可以通过将隐藏导航栏的动画去掉来达到对Tabbar切换友好的效果:

1、现有解决方案

先看一下手机QQ,是我目前能找到的处理得算比较好的导航栏返回效果。导航栏有跟随返回手势透明度渐变的动画。

图片 4QQ返回.gif

但导航栏的返回交互动画是自定义的,没有系统自带的视差效果和毛玻璃效果,而且中断返回操作的话导航栏会闪一下,影响观感。

图片 5QQ取消返回.gif

再看一下其他3家的处理方式,他们的处理方法基本一致,都是在进入个人页时隐藏了系统导航栏,然后添加一个自定义的导航栏,所以过度会比较生硬,与整体的返回效果有断层。

图片 6微博.gif图片 7百度贴吧.gif图片 8Twitter.gif

好,看完以上的例子,轮到我们来实现啦。我们今天的目标是不自定义导航栏,在系统自带导航栏的基础上进行非侵入的实现。先看效果:

图片 9navDemo.gif你可以在这里下载本篇文章的代码:

[self.navigationController setNavigationBarHidden:NO animated:NO];
2、记录某个VC的导航栏透明度

对于同一个NavigationController上的ViewController,NavigationBar是全局的,并不能单独设置某个ViewController的导航栏样式和属性。所以我们先给ViewController用扩展添加一个记录导航栏透明度的属性:

//ET_NavBarTransparent.swiftextension UIViewController { fileprivate struct AssociatedKeys { static var navBarBgAlpha: CGFloat = 1.0 } var navBarBgAlpha: CGFloat { get { let alpha = objc_getAssociatedObject(self, &AssociatedKeys.navBarBgAlpha) as? CGFloat if alpha == nil { //默认透明度为1 return 1.0 }else{ return alpha! } } set { var alpha = newValue if alpha > 1 { alpha = 1 } if alpha < 0 { alpha = 0 } objc_setAssociatedObject(self, &AssociatedKeys.navBarBgAlpha, alpha, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) //设置导航栏透明度 navigationController?.setNeedsNavigationBackground(alpha: alpha) } }}

好的,现在可以根据需要随时记录下某个VC的导航栏透明度了,而不会因为push到下个页面而丢失了这个信息。

但是这样一来你在UINavigationController体系下切换界面时由于没有了动画,这边的效果又会变得很差。这两个矛盾没有想到可以调和的手段,除非在业务上就不显示Tabbar了,但始终不是长久之计。

3、设置导航栏背景的透明度

要实现上面demo的效果,我们不能修改整个导航栏的透明度,因为导航栏上的NavigationBarItem是需要保留下来的,如果设置整个导航栏的透明度,左右的Item和标题栏都会跟着一起透明了。

图片 10navItem.png

然而,系统API并没有访问背景View的接口,只好动用下黑魔法了。先看一下导航栏的层级:

图片 11navlevel.png

首先想到调整第一层_barBackgroundView(_UIBarBackground)的透明度,但试了一下,调整这一层级会丢失毛玻璃效果,效果很突兀:

图片 12bgAlphaErr.gif

经过测试,调整_backgroundEffectView(-UIVisualEffectView)不会丢失毛玻璃效果:

图片 13bgAlphaRight.gif

下面是调整导航栏背景透明度的相关代码:

//ET_NavBarTransparent.swiftextension UINavigationController { //Some other code fileprivate func setNeedsNavigationBackground(alpha:CGFloat) { let barBackgroundView = navigationBar.value(forKey: "_barBackgroundView") as AnyObject let backgroundImageView = barBackgroundView.value(forKey: "_backgroundImageView") as? UIImageView if navigationBar.isTranslucent { if backgroundImageView != nil && backgroundImageView!.image != nil { (barBackgroundView as! UIView).alpha = alpha }else{ if let backgroundEffectView = barBackgroundView.value(forKey: "_backgroundEffectView") as? UIView { backgroundEffectView.alpha = alpha } } }else{ (barBackgroundView as! UIView).alpha = alpha } if let shadowView = barBackgroundView.value(forKey: "_shadowView") as? UIView { shadowView.alpha = alpha } }}

到这里,我们只要给viewController的扩展属性navBarBgAlpha赋值,就可以随意设置导航栏的透明度了。

同时,我们虽然说QQ做的很好,但也依然有一些不足,多把玩一下导航栏过渡的过程就会发现,如果准备从透明导航栏返回时又决定不反回了,还是停留在导航栏透明的界面,这时候导航栏虽然会回到透明,但会有一个导航栏闪现一下的小瑕疵。

4、监控返回手势的进度

在手势返回的交互中,如果前后两个VC的导航栏透明度不一样,需要根据手势的进度实时调节透明度。这里method swizzling一下,用UINavigationController的"_updateInteractiveTransition:"方法监控返回交互动画的进度。

//ET_NavBarTransparent.swiftextension UINavigationController { //Some other code open override class func initialize(){ if self == UINavigationController.self { let originalSelectorArr = ["_updateInteractiveTransition:"] //method swizzling for ori in originalSelectorArr { let originalSelector = NSSelectorFromString let swizzledSelector = NSSelectorFromString("et_ let originalMethod = class_getInstanceMethod(self.classForCoder(), originalSelector) let swizzledMethod = class_getInstanceMethod(self.classForCoder(), swizzledSelector) method_exchangeImplementations(originalMethod, swizzledMethod) } } } func et__updateInteractiveTransition(_ percentComplete: CGFloat) { et__updateInteractiveTransition(percentComplete) let topVC = self.topViewController if topVC != nil { //transitionCoordinator带有两个VC的转场上下文 let coor = topVC?.transitionCoordinator if coor != nil { //fromVC 的导航栏透明度 let fromAlpha = coor?.viewController(forKey: .from)?.navBarBgAlpha //toVC 的导航栏透明度 let toAlpha = coor?.viewController(forKey: .to)?.navBarBgAlpha //计算当前的导航栏透明度 let nowAlpha = fromAlpha! + (toAlpha!-fromAlpha!)*percentComplete //设置导航栏透明度 self.setNeedsNavigationBackground(alpha: nowAlpha) } } }}

看一下到这一步的效果:

图片 14releaseFinger.gif

在手势交互的过程中,透明度的变化跟预期一样跟随手势变化。但一旦松手,系统会自动完成或取消返回操作,在这一过程中,以上的方法并没有调用,而导致透明度停留在最后的那个状态。我们需要在UINavigationControllerDelegate中添加边缘返回手势松手时的监控,还有要处理一下直接点击返回按钮和正常Push到新界面时的情况:

//ET_NavBarTransparent.swiftextension UINavigationController:UINavigationControllerDelegate,UINavigationBarDelegate { public func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { let topVC = navigationController.topViewController if topVC != nil { let coor = topVC?.transitionCoordinator if coor != nil { //添加对返回交互的监控 if #available(iOS 10.0, *) { coor?.notifyWhenInteractionChanges({  in self.dealInteractionChanges }) } else { coor?.notifyWhenInteractionEnds({  in self.dealInteractionChanges }) } } } } //处理返回手势中断对情况 private func dealInteractionChanges(_ context:UIViewControllerTransitionCoordinatorContext) { if context.isCancelled { //自动取消了返回手势 let cancellDuration:TimeInterval = context.transitionDuration * Double( context.percentComplete) UIView.animate(withDuration: cancellDuration, animations: { let nowAlpha = (context.viewController(forKey: .from)?.navBarBgAlpha)! self.setNeedsNavigationBackground(alpha: nowAlpha) self.navigationBar.tintColor = context.viewController(forKey: .from)?.navBarTintColor }) }else{ //自动完成了返回手势 let finishDuration:TimeInterval = context.transitionDuration * Double(1 - context.percentComplete) UIView.animate(withDuration: finishDuration, animations: { let nowAlpha = (context.viewController(forKey: .to)?.navBarBgAlpha)! self.setNeedsNavigationBackground(alpha: nowAlpha) self.navigationBar.tintColor = context.viewController(forKey: .to)?.navBarTintColor }) } } public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool { if viewControllers.count >= (navigationBar.items?.count)! { //点击返回按钮 let popToVC = viewControllers[viewControllers.count-2] setNeedsNavigationBackground(alpha: (popToVC.navBarBgAlpha)) navigationBar.tintColor = popToVC.navBarTintColor _ = self.popViewController(animated: true) } return true } public func navigationBar(_ navigationBar: UINavigationBar, shouldPush item: UINavigationItem) -> Bool { //push到一个新界面 setNeedsNavigationBackground(alpha: (topViewController?.navBarBgAlpha)!) navigationBar.tintColor = topViewController?.navBarTintColor return true } }

好的,到这里,对返回和push操作的处理已经完成。

图片 15releaseFingerRight.gif

现在问题已经讲完了,基于这些问题,我们自己来尝试实现一种更好的平滑过渡效果,不自定义导航栏,直接利用系统原生的导航栏,使用Category和Runtime的技术,达到这个效果:

5、使用

只需要在隐藏导航栏背景的viewController上把navBarBgAlpha设为0就可以了:

 override func viewDidLoad() { super.viewDidLoad() self.navBarBgAlpha = 0 //other code }

然后在比如tableView滚动到某个位置,需要显示导航栏时,把navBarBgAlpha设为1。

图片 1620170322193055722.gif

6、其他

要达到平滑的转场效果,还需要对navigationBar的tintColor进行类似的操作,这部分就留给大家自己看一下源码的相关部分啦。还有一些细节,比如状态栏颜色变化的时机,“preferredStatusBarStyle:”的调用链等,也交给大家去发现和思考了。

代码可以在示例工程下载(觉得有帮助的小伙伴请不吝加Star~):

其实我们的目的总结起来有三个:

1、不去自定义导航栏,就用系统原生的,标题、返回按钮啥的都方便加,这也就是说不隐藏导航栏,而是要单独让导航栏背景透明;2、在导航栏透明与否的界面间切换时透明度有渐变效果;3、在UINavigationController体系和UITabarController体系下切换界面都很完美。

对于第三个目的,我们之前在UITabarController下切换时会有导航栏隐藏的小动画,但如果我们满足了第一个目的,那就不存在隐藏导航栏了,所以第三个问题也就不会存在了。

我们先来看第一个目的。

导航栏上应该是有很多view的,我们要做的是只让背景透明,而保留标题、返回按钮。iOS没有直接给我们提供对于导航栏背景view的访问途径,那么我们只能自己来找了。

首先我们遍历打印出UINavigationBar的所有子视图,是所有,包括子视图的一层层子视图,来看看到底导航栏都包含了哪些东西:

图片 17image.png

上面这张图就是导航栏UINavigationBar所包含的所有子view了,序号和缩进表示了其层级归属关系,打印的方法可以看这篇文章:传送门:iOS遍历打印所有子视图

从这些子view的类名能够大概猜出他们都是导航栏上的什么,让我们大胆猜测一下,_UIBarBackground 是背景视图,下属的 UIImageView 是背景图片,_UINavigationBarBackIndicatorView 是返回箭头,UINavigationItemView 是添加的一些导航栏按钮,包括返回按钮,因为我没有给导航栏添加任何其他按钮,所以这里一定是返回按钮,下属的 UILabel 就是 “返回” 两个字了。

根据上面得到的信息,我们就尝试将_UIBarBackground、UIImageView、UIVisualEffectView的 alpha 值设为 1 或者 0 来改变导航栏背景的透明度。

我们可以给 UINavigationController 创建一个类别,来给这个类添加一个方法,用于设置导航栏的透明度:

// UIViewController+Cloudox.m- setNeedsNavigationBackground:alpha { // 导航栏背景透明度设置 UIView *barBackgroundView = [[self.navigationBar subviews] objectAtIndex:0];// _UIBarBackground UIImageView *backgroundImageView = [[barBackgroundView subviews] objectAtIndex:0];// UIImageView if (self.navigationBar.isTranslucent) { if (backgroundImageView != nil && backgroundImageView.image != nil) { barBackgroundView.alpha = alpha; } else { UIView *backgroundEffectView = [[barBackgroundView subviews] objectAtIndex:1];// UIVisualEffectView if (backgroundEffectView != nil) { backgroundEffectView.alpha = alpha; } } } else { barBackgroundView.alpha = alpha; }}

到目前为止,我们会得到什么效果呢?看一下:

图片 18image.png

我们成功的将导航栏背景设为透明了!但是那条细线是什么情况?!有它在岂不是前功尽弃了,再用上面的方法已经不管用了,这条线不在我们找出来的子view之中,通过查资料,要隐藏这跟细线的方法很多,但是要跟我们对导航栏背景的设置不冲突,又要能到只在将导航栏背景设为透明时才隐藏,下面这种方法是比较好的方法:

// 对导航栏下面那条线做处理self.navigationBar.clipsToBounds = alpha == 0.0;

当我们对导航栏的透明度设为 0 时,就会隐藏细线,否则不隐藏,这样当切换到其他界面时,细线就又会出来了。

现在导航栏的透明就比较完美了:

图片 19image.png

对于这种将导航栏背景直接设为透明的情况,在 Tabbar 切换界面时,也不会出现导航栏收起的小动画:

图片 2020170322221410849.gif

为了方便,我们创建一个 UIViewController 的Category,为其增加一个属性——导航栏透明度(navBarBgAlpha),Category一般是不可以添加属性的,但我们可以通过Runtime的关联对象来做到,具体做法参看我的这篇文章:传送门:iOS中OC给Category添加属性,由于只能关联对象,所以我们无法直接添加 CGFloat 类型的属性,我们就直接添加 NSString 类型的属性就好了,用的时候再用 [NSString floatValue] 方法。这样每个 ViewController 都可以管理自己的导航栏透明度,在这个新增属性的setter方法中,我们调用前面在在 UINavigationController 的Category 中添加的设置导航栏透明度的方法,这样就打通了。

UIViewController的设置方法如下:

// UIViewController+Cloudox.h@interface UIViewController @property (copy, nonatomic) NSString *navBarBgAlpha;@end// UIViewController+Cloudox.m#import "UIViewController+Cloudox.h"// 导入runtime才可以使用关联对象#import <objc/runtime.h>// 导入我们的Category才可以调用我们添加的方法#import "UINavigationController+Cloudox.h"@implementation UIViewController //定义常量 必须是C语言字符串static char *CloudoxKey = "CloudoxKey";-setNavBarBgAlpha:(NSString *)navBarBgAlpha{ /* OBJC_ASSOCIATION_ASSIGN; //assign策略 OBJC_ASSOCIATION_COPY_NONATOMIC; //copy策略 OBJC_ASSOCIATION_RETAIN_NONATOMIC; // retain策略 OBJC_ASSOCIATION_RETAIN; OBJC_ASSOCIATION_COPY; */ /* * id object 给哪个对象的属性赋值 const void *key 属性对应的key id value 设置属性值为value objc_AssociationPolicy policy 使用的策略,是一个枚举值,和copy,retain,assign是一样的,手机开发一般都选择NONATOMIC objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy); */ objc_setAssociatedObject(self, CloudoxKey, navBarBgAlpha, OBJC_ASSOCIATION_COPY_NONATOMIC); // 设置导航栏透明度(利用Category自己添加的方法) [self.navigationController setNeedsNavigationBackground:[navBarBgAlpha floatValue]];}-(NSString *)navBarBgAlpha{ return objc_getAssociatedObject(self, CloudoxKey);}@end

使用时我们只需要:

// 让导航栏透明self.navBarBgAlpha = @"0.0";// 让导航栏不透明self.navBarBgAlpha = @"1.0";

现在实现了比较好的透明导航栏效果,但在透明的导航栏与不透明的导航栏界面直接切换时,导航栏的透明度是直接跳变的:

图片 2120170322221442553.gif

而我们想要的是像QQ一样从完全透明到不透明之间有一个随着滑动手势变化的透明度渐变效果,这样是最好的转场效果了。

我们需要的随着手势滑动返回界面的进度,来实时变化导航栏的透明度,比如滑动到了界面一半的时候,导航栏透明度应该是 0.5。对于这个需求,首先想到的是,我们要监控这个滑动事件的滑动进度。

正好,UINavigationController 有一个方法 _updateInteractiveTransition: 就是监控这个手势及其进度的,那么我们就可以使用 Runtime 黑魔法——方法交换来实现我们的需求。

怎么交换呢?通过要交换的方法和我们定义的方法的名称,获取到对应的方法实现,然后用 method_exchangeImplementations 方法交换两个方法的实现:

+ initialize { if (self == [UINavigationController self]) { // 交换方法 SEL originalSelector = NSSelectorFromString(@"_updateInteractiveTransition:"); SEL swizzledSelector = NSSelectorFromString(@"et__updateInteractiveTransition:"); Method originalMethod = class_getInstanceMethod([self class], originalSelector); Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector); method_exchangeImplementations(originalMethod, swizzledMethod); }}

这一步我们在 initialize 方法中去做,这样一调用时就会生效了,关于 initialize 可以查看这篇文章:传送门:OC中load方法和initialize方法的异同。

我们自己创建一个用于交换的方法,这个方法中,除了调用原方法外(注意由于方法名称对应的实现已经交换了,这里我们目的是调用原实现,但是使用的名称确实本方法自己的名称),还添加一个处理,_updateInteractiveTransition: 有一个参数就是界面滑动过程的百分比,那么我们获取上一个界面的导航栏透明度、下一个界面的导航栏透明度、以及滑动的进度,通过很简单的数学计算就可以得出当前进度应该对应的透明度是多少了,这里也可以看出我们给 ViewController 添加一个导航栏透明度属性是多么有意义,这里就可以直接调用了,当然,要记得导入我们的Category:

// 交换的方法,监控滑动手势- et__updateInteractiveTransition:percentComplete { [self et__updateInteractiveTransition:(percentComplete)]; UIViewController *topVC = self.topViewController; if (topVC != nil) { id<UIViewControllerTransitionCoordinator> coor = topVC.transitionCoordinator; if (coor != nil) { // 随着滑动的过程设置导航栏透明度渐变 CGFloat fromAlpha = [[coor viewControllerForKey:UITransitionContextFromViewControllerKey].navBarBgAlpha floatValue]; CGFloat toAlpha = [[coor viewControllerForKey:UITransitionContextToViewControllerKey].navBarBgAlpha floatValue]; CGFloat nowAlpha = fromAlpha + (toAlpha - fromAlpha) * percentComplete; NSLog(@"from:%f, to:%f, now:%f",fromAlpha, toAlpha, nowAlpha); [self setNeedsNavigationBackground:nowAlpha]; } }}

我们打印了透明度渐变的过程,可以看一下:

图片 22image

是按照预想地在随着滑动界面的进度渐变透明度的,实际的效果也是这样的:

图片 2320170322221544345.gif

就目前的效果,其实还是不错的,不过也有一些小瑕疵,比如滑动到一半松手时会有一个小跳变,对于这一点,我们可以在 UINavigationController 的 Delegate 中添加一个处理,监控松手后时自动完成返回还是取消返回操作,同时使用 UIView 动画(关于 UIView 动画可以看我的这篇文章:传送门:iOS基础动画教程),在自动操作的那个时间内将透明度变为对应界面的导航栏透明度,让其变化的不那么跳跃:

#pragma mark - UINavigationController Delegate- navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:animated { UIViewController *topVC = self.topViewController; if (topVC != nil) { id<UIViewControllerTransitionCoordinator> coor = topVC.transitionCoordinator; if (coor != nil) { [coor notifyWhenInteractionChangesUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context){ [self dealInteractionChanges:context]; }]; } }}- dealInteractionChanges:(id<UIViewControllerTransitionCoordinatorContext>)context { if ([context isCancelled]) {// 自动取消了返回手势 NSTimeInterval cancelDuration = [context transitionDuration] * [context percentComplete]; [UIView animateWithDuration:cancelDuration animations:^{ CGFloat nowAlpha = [[context viewControllerForKey:UITransitionContextFromViewControllerKey].navBarBgAlpha floatValue]; NSLog(@"自动取消返回到alpha:%f", nowAlpha); [self setNeedsNavigationBackground:nowAlpha]; }]; } else {// 自动完成了返回手势 NSTimeInterval finishDuration = [context transitionDuration] * (1 - [context percentComplete]); [UIView animateWithDuration:finishDuration animations:^{ CGFloat nowAlpha = [[context viewControllerForKey: UITransitionContextToViewControllerKey].navBarBgAlpha floatValue]; NSLog(@"自动完成返回到alpha:%f", nowAlpha); [self setNeedsNavigationBackground:nowAlpha]; }]; }}

对于直接点击返回按钮以及 push 到下一个界面的操作,也可以增加一次处理:

#pragma mark - UINavigationBar Delegate- navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item { if (self.viewControllers.count >= navigationBar.items.count) {// 点击返回按钮 UIViewController *popToVC = self.viewControllers[self.viewControllers.count - 1]; [self setNeedsNavigationBackground:[popToVC.navBarBgAlpha floatValue]]; }}- navigationBar:(UINavigationBar *)navigationBar didPushItem:(UINavigationItem *)item { // push到一个新界面 [self setNeedsNavigationBackground:[self.topViewController.navBarBgAlpha floatValue]];}

不过意义不是特别大。

以上这些处理基本都在 Category 里写代码,一次搞定,真正在自己的 ViewController 需要做的只是一句:

- viewWillAppear:animated { [super viewWillAppear:animated]; self.navBarBgAlpha = @"0.0";}

很简单吧~更多效果有兴趣的可以自己继续修修补补,这个过程也是很有意思的。

再次宣传,代码可以在示例工程下载(觉得有帮助的小伙伴请不吝加Star~):

查看作者首页

参考:

本文由永利402游戏网站-永利402com官方网站发布于网络编程,转载请注明出处:导航栏的平整呈现和隐敝 - 个人页的本身修养(

关键词:

上一篇:自定义 Xcode ViewController 类模板

下一篇:没有了