女主宣言作为一名iOS开发,最常用到的就是分页控制器了,类似于新闻首页;最近所接触的项目中经常会遇到滚动分页的设计效果,被用来对不同数据界面的展示进行分类。因此,总结了一下iOS中关于分页界面搭建的知识点。

 

首先我们先可以来预览一下实现效果:

iOS 快速实现分页界面的搭建_iOS

实现分析

根据动图进行实现分析:这个效果的实现分为两部分顶部的QiPageMenuView和内容展示部分QiPageContentView:

QiPageMenuView是基于UIScrollView实现的,我们可以按照自己的项目需求,定制自己需要实现的效果。QiPageMenuView提供了可设置的属性有:菜单每一项是否根据文字的大小自适应宽度还是设置固定宽度、菜单的首项和最后一项距离父视图的间距、每一项之间的间距、包括了每一项QiPageItem的展示效果自定义等。也实现了菜单项超出一屏幕时自动滑动显示的效果:

iOS 快速实现分页界面的搭建_iOS_02

QiPageContentView是基于UIPageViewController实现的封装,在项目中可能有多处这样的界面效果。单纯的使用UIPageViewController写在相应的控制器中,添加相应的子控制器,通过实现UIPageViewController的数据源和代理协议也可以达到这种效果。但是UIPageViewController嵌套在主控制器中,耦合度比较高,代码量也比较大,若是多处需要使用这种效果,每次都写一遍UIPageViewController,效率和可移植性不高。QiPageMenuView和QiPageContentView之间是解耦合的,彼此都可以作为单独的控件去使用。在设计构思时,菜单视图的样式有可能是QiPageMenuView样式之外的视图,若是两者耦合度太高,就变成了QiPageContentView + QiPageMenuView组合,能展现的效果就被限制了。

 

QiPageMenuView实现与使用

1.QiPageMenuView.h中展现了QiPageMenuView可实现定制的相关属性以及初始化方法介绍。

  1. @interface QiPageMenuView : UIScrollView<QiPageControllerDelegate>

  2. /**

  3. 菜单栏点击事件

  4. */

  5. @property (nonatomic,copy)void(^pageItemClicked)(NSInteger clickedIndex,QiPageMenuView *menu);

  6. /**

  7. 常态item的字体颜色

  8. */

  9. @property (nonatomic,strong)UIColor *normalTitleColor;

  10. /**

  11. 选中item的字体颜色

  12. */

  13. @property (nonatomic,strong)UIColor *selectedTitleColor;

  14. /**

  15. 常态item的字体

  16. */

  17. @property (nonatomic,strong)UIFont *titleFont;

  18. /**

  19. 选中Item的字体

  20. */

  21. @property (nonatomic,strong)UIFont *selectedTitleFont;

  22. /**

  23. 字体距离item两边的间距,itemsAutoResizing = YES时 设置有效

  24. */

  25. @property (nonatomic,assign)CGFloat itemTitlePadding;

  26. /**

  27. item距上的间距。itemIsVerticalCentred = NO的时候设置有效

  28. */

  29. @property (nonatomic,assign)CGFloat itemTopPadding;

  30. /**

  31. items的左边缩进

  32. */

  33. @property (nonatomic,assign)CGFloat leftMargin;

  34. /**

  35. items的右边缩进

  36. */

  37. @property (nonatomic,assign)CGFloat rightMargin;

  38. /**

  39. 是否根据文字的长度自动计算item的width default YES

  40. */

  41. @property (nonatomic,assign)BOOL itemsAutoResizing;

  42. /**

  43. item是否垂直居中显示,默认yes; itemTopPadding 与 lineTopPadding 不会生效;设置NO itemHeight会自适应高

  44. */

  45. @property (nonatomic,assign)BOOL itemIsVerticalCentred;

  46. /**

  47. item之间的间距

  48. */

  49. @property (nonatomic,assign)CGFloat itemSpace;

  50. /**

  51. 每个item的高度

  52. */

  53. @property (nonatomic,assign)CGFloat itemHeight;

  54. /**

  55. 每个item的宽度。itemsAutoResizing = YES不必赋值也可。反之必须给值。

  56. */

  57. @property (nonatomic,assign)CGFloat itemWidth;

  58. /**

  59. 是否显示下划线 default YES

  60. */

  61. @property (nonatomic,assign)BOOL hasUnderLine;

  62. /**

  63. 下划线颜色

  64. */

  65. @property (nonatomic,strong)UIColor *lineColor;

  66. /**

  67. 下划线到item的间距

  68. */

  69. @property (nonatomic,assign)CGFloat lineTopPadding;

  70. /**

  71. 下划线的高度

  72. */

  73. @property (nonatomic,assign)CGFloat lineHeight;

  74. /**

  75. 下划线的宽度

  76. */

  77. @property (nonatomic,assign)CGFloat lineWitdh;

  78. /**

  79. pageController滑动完成

  80. */

  81. @property (nonatomic,assign)NSInteger pageScrolledIndex;

  82. /**

  83. 初始化方法

  84. */

  85. - (instancetype)initWithFrame:(CGRect)frame titles:(NSArray*)titles dataSource:(NSDictionary<QiPageMenuViewDataSourceKey, id> *)dataSource;

  86. - (instancetype)initWithFrame:(CGRect)frame titles:(NSArray*)titles;

  87. /**

  88. 滑动到某一项

  89. @param pageItem item

  90. */

  91. - (void)scrollToPageItem:(QiPageItem*)pageItem;

  92. /*!

  93. @brief 更新标题数组

  94. @param items selectedIndex重置选中的item

  95. */

  96. - (void)updateMenuViewWithNewItemArray:(NSArray *)items selectedIndex:(NSInteger)selectedIndex;

  97. @end

2.QiPageMenuView滑动显示的核心代码

  1. - (void)scrollToPageItem:(QiPageItem*)pageItem {

  2.  

  3. [self refreshUnderLineViewPosition:pageItem];

  4.  

  5. if (self.contentSize.width <= self.width) {

  6. return;

  7. }

  8.  

  9. CGRect originalRect = pageItem.frame;

  10. CGRect convertRect = [self convertRect:originalRect toView:self.superview];

  11. CGFloat targetX;

  12. CGFloat realMidX = CGRectGetMinX(originalRect)+CGRectGetWidth(originalRect)/2;

  13. if (CGRectGetMidX(convertRect) < CGRectGetMidX(self.frame)) {

  14. //是否需要右滑

  15. if (realMidX> CGRectGetMidX(self.frame)) {

  16. targetX = realMidX-CGRectGetMidX(self.frame);

  17. }else {

  18. targetX = 0;

  19. }

  20. [self setContentOffset:CGPointMake(targetX, 0) animated:YES];

  21.  

  22. } else if (CGRectGetMidX(convertRect) > CGRectGetMidX(self.frame)) {

  23. if (realMidX+CGRectGetMidX(self.frame)<self.contentSize.width) {

  24. targetX = realMidX-CGRectGetMidX(self.frame);

  25.  

  26. } else {

  27. targetX = self.contentSize.width - CGRectGetMaxX(self.frame);

  28. }

  29. [self setContentOffset:CGPointMake(targetX, 0) animated:YES];

  30. }

  31. }

  1. QiPageMenuView使用的两种方式:

    方式一:

  1. //定制样式

  2. NSDictionary *dataSource = @{

  3. QiPageMenuViewNormalTitleColor : [UIColor blackColor],

  4. QiPageMenuViewSelectedTitleColor : [UIColor redColor],

  5. QiPageMenuViewTitleFont : [UIFont systemFontOfSize:14],

  6. QiPageMenuViewSelectedTitleFont : [UIFont systemFontOfSize:14],

  7. QiPageMenuViewItemIsVerticalCentred : @(YES),

  8. QiPageMenuViewItemTitlePadding : @(10.0),

  9. QiPageMenuViewItemTopPadding : @(20.0),

  10. QiPageMenuViewItemPadding : @(10.0),

  11. QiPageMenuViewLeftMargin : @(20.0),

  12. QiPageMenuViewRightMargin : @(20.0),

  13. QiPageMenuViewItemsAutoResizing : @(YES),

  14. QiPageMenuViewItemWidth : @(90.0),

  15. QiPageMenuViewItemHeight : @(40.0),

  16. QiPageMenuViewHasUnderLine :@(YES),

  17. QiPageMenuViewLineColor : [UIColor greenColor],

  18. QiPageMenuViewLineWidth : @(30.0),

  19. QiPageMenuViewLineHeight : @(4.0),

  20. QiPageMenuViewLineTopPadding : @(10.0)

  21. };

  22.  

  23. QiPageMenuView *menuView = [[QiPageMenuView alloc]initWithFrame:CGRectMake(0, 0, self.view.width, 50) titles:@[@"消息",@"节日消息",@"广播通知",@"QISHARE",@"奇舞团"] dataSource:dataSource];

  24. menuView.backgroundColor = [UIColor orangeColor];

  25. [self.view addSubview:menuView];

 

方式二:

  1. QiPageMenuView *menuView = [[QiPageMenuView alloc]initWithFrame:CGRectMake(0, 0, self.view.width, 50) titles:@[@"系统消息",@"节日消息",@"广播通知"]];

  2. menuView.backgroundColor = [UIColor orangeColor];

  3. //定制样式

  4. menuView.normalTitleColor = [UIColor blackColor];

  5. menuView.selectedTitleColor = [UIColor redColor];

  6. menuView.titleFont = [UIFont systemFontOfSize:14];

  7. menuView.selectedTitleFont = [UIFont systemFontOfSize:14];

  8. menuView.itemIsVerticalCentred = YES;

  9. menuView.itemTitlePadding = 10.0;

  10. menuView.itemTopPadding = 20.0;

  11. menuView.itemSpace = 10.0;

  12. menuView.leftMargin = 20.0;

  13. menuView.rightMargin = 20.0;

  14. menuView.itemsAutoResizing = YES;

  15. menuView.itemWidth = 90;

  16. menuView.itemHeight = 40;

  17. menuView.hasUnderLine = YES;

  18. menuView.lineColor = [UIColor greenColor];

  19. menuView.lineWitdh = 30;

  20. menuView.lineHeight = 4.0;

  21. menuView.lineTopPadding = 10;

  22. [self.view addSubview:menuView];

 

QiPageContentView实现与使用

  1. QiPageContentView.h

  1. @interface QiPageContentView : UIView<UIPageViewControllerDelegate, UIPageViewControllerDataSource,UIScrollViewDelegate>

  2.  

  3. @property (nonatomic, strong) UIPageViewController *pageViewController;

  4. @property (nonatomic, strong) NSArray *controllerArray; //!< 控制器数组

  5. /**

  6. 滑动结束:block回调

  7. */

  8. @property (nonatomic,copy)void(^pageContentViewDidScroll)(NSInteger currentIndex,NSInteger beforeIndex,QiPageContentView *pageView);

  9.  

  10. /**

  11. 滑动结束:代理回调 若实现block代理不会走

  12. */

  13. @property (nonatomic, weak) id<QiPageContentViewDelegate> contentViewDelgate;

  14.  

  15. /**

  16. 设置滑动至某一个控制器

  17.  

  18. @param index index

  19. @param beforeIndex 控制方向

  20. */

  21. - (void)setPageContentShouldScrollToIndex:(NSInteger)index beforIndex:(NSInteger)beforeIndex;

  22.  

  23. /**

  24. 初始化方法

  25.  

  26. @param frame frame

  27. @param childViewControllers childViewControllers

  28. @return 实例

  29. */

  30. - (instancetype)initWithFrame:(CGRect)frame childViewController:(NSArray*)childViewControllers;

  31.  

  32. @end

    2. QiPageContentView使用

  1. QiPageContentView *contenView = [[QiPageContentView alloc]initWithFrame:CGRectMake(0, 10, self.view.width, self.view.height - 88-10) childViewController:@[ctrl,ctrl1,ctrl2,ctrl3]];

  2. [self.view addSubview:contenView];

QiPageContentView与QiPageMenuView解耦

QiPageContentView与QiPageMenuView使用各自头文件中定义的协议或者block属性实现两者之间事件交互,从而达到分页联动效果;两者的解耦,使得它们的使用更加灵活。

  1. QiPageMenuView交互事件的传递

  1. @protocol QiPageMenuViewDelegate <NSObject>

  2. /**

  3. 菜单点击了某个item

  4.  

  5. @param index 点击了index

  6. */

  7. - (void)pageMenuViewDidClickedIndex:(NSInteger)index beforeIndex:(NSInteger)beforeIndex;

  8.  

  9. @end

  10.  

  11. @interface QiPageMenuView : UIScrollView

  12. /**

  13. 菜单栏点击事件:block回调

  14. */

  15. @property (nonatomic,copy)void(^pageItemClicked)(NSInteger clickedIndex,NSInteger beforeIndex,QiPageMenuView *menu);

  16.  

  17. /**

  18. 菜单栏点击事件:代理回调 若实现block代理不会走

  19. */

  20. @property (nonatomic, weak) id<QiPageMenuViewDelegate> menuViewDelgate;

2.QiPageContentView交互事件的传递

  1. @protocol QiPageContentViewDelegate <NSObject>

  2.  

  3. /**

  4. 滑动完成回调

  5. @param index 滑动至index

  6. */

  7. - (void)pageContentViewDidScrollToIndex:(NSInteger)index beforeIndex:(NSInteger)beforeIndex;

  8. @end

  9.  

  10. @interface QiPageContentView : UIView<UIPageViewControllerDelegate, UIPageViewControllerDataSource,UIScrollViewDelegate>

  11. @property (nonatomic, strong) UIPageViewController *pageViewController;

  12. @property (nonatomic, strong) NSArray *controllerArray; //!< 控制器数组

  13. /**

  14. 滑动结束:block回调

  15. */

  16. @property (nonatomic,copy)void(^pageContentViewDidScroll)(NSInteger currentIndex,NSInteger beforeIndex,QiPageContentView *pageView);

  17. /**

  18. 滑动结束:代理回调 若实现block代理不会走

  19. */

  20. @property (nonatomic, weak) id<QiPageContentViewDelegate> contentViewDelgate;

3.QiPageContentView和QiPageMenuView组合实现分页界面

  1. QiPageMenuView *menuView = [[QiPageMenuView alloc]initWithFrame:CGRectMake(0, 0, self.view.width, 50) titles:@[@"系统消息",@"节日消息",@"广播通知",@"最新",@"最热"] dataSource:dataSource];

  2. menuView.backgroundColor = [UIColor orangeColor];

  3. [self.view addSubview:menuView];

  4.  

  5. QiPageContentView *contenView = [[QiPageContentView alloc]initWithFrame:CGRectMake(0, menuView.bottom+10, self.view.width, self.view.height - menuView.bottom - 10 - 88-10) childViewController:@[ctrl,ctrl1,ctrl2,ctrl3,ctrl4]];

  6. [self.view addSubview:contenView];

  7.  

  8. menuView.pageItemClicked = ^(NSInteger clickedIndex, NSInteger beforeIndex, QiPageMenuView *menu) {

  9. NSLog(@"点击了:之前:%ld 现在:%ld",beforeIndex,clickedIndex);

  10. [contenView setPageContentShouldScrollToIndex:clickedIndex beforIndex:beforeIndex];

  11. };

  12.  

  13. contenView.pageContentViewDidScroll = ^(NSInteger currentIndex, NSInteger beforeIndex, QiPageContentView * _Nonnull pageView) {

  14. menuView.pageScrolledIndex = currentIndex;

  15. NSLog(@"滚动了:之前:%ld 现在:%ld",beforeIndex,currentIndex);

  16. };

iOS 快速实现分页界面的搭建_iOS_03