IOS开发 - 引导页的两种实现 - UICollectionView和UIScrollView
来源:互联网 发布:睡觉 知乎 编辑:程序博客网 时间:2024/06/04 18:02
简介:
本文中两种方法实现了IOS开发中引导页面的基本功能,在使用上差别不大。UICollecitonView比UIScrollView多了一个cell重用,以及每个Cell都是通过数据源方法设置。而UIScrollView则是一次性完成整个scrollView的初始化,以下通过两种方法的对比,回顾一下整个引导页面实现的过程。
判断是否有新版本(准备工作)
首先通过[NSUserDefaults standardUserDefaults]来存储当前的版本号。NSUserDefaults用于存储数据量小的数据,例如用户配置。
这里我给存储NSString的方法写了一个工具,分别是把版本号写入系统的文件中,从系统的文件中读取。
+ (id)objectForKey:(NSString *)defaultName { return [[NSUserDefaults standardUserDefaults] objectForKey:defaultName];}
+ (void)setObject:(id)value forKey:(NSString *)defaultName { [[NSUserDefaults standardUserDefaults] setObject:value forKey:defaultName]; [[NSUserDefaults standardUserDefaults] synchronize];}
注意:通过[NSUserDefaults standardUserDefaults]写入数据,需要用[SCUserDefaultssynchronize]做同步处理,否则会延迟写入。
+ (void)setRootViewController:(UIWindow *)window { NSDictionary *dict = [NSBundle mainBundle].infoDictionary; NSString *curVersion = dict[@"CFBundleVersion"]; NSString *lastVersion = [SCSaveTool objectForKey:@"version"]; JLTabBarController *tabBar = [[JLTabBarController alloc] init]; window.rootViewController = tabBar; if (![curVersion isEqualToString:lastVersion]) { // 有新版本,加引导页 [SCSaveTool setObject:curVersion forKey:@"version"]; // 进入引导页面 SCGuideController *gv = [[SCGuideController alloc] init]; [tabBar addChildViewController:gv]; [tabBar.view addSubview:gv.view]; }}注意:引导页面控制器必须设置为根控制器的子控制器,否则UICollectionViewCell会无法显示。
两种引导页代码的实现
这里分别通过创建UICollectionViewController和UIViewController来实现两种引导页的整个逻辑。
在UICollectionViewController中,每个引导页是一个单独的UICollectionViewCell,我用一个模型Guide来包装cell的图片名字,完成每个对应的Cell的图片更新。
在UIViewController中,每个引导页是自定义的view对象,继承至UIView,同样用模型Guide来包装每个cell的图片名字,通过一次性的实例化,把整个scrollView添加至控制器视图。
以下目录1为模型代码,2为视图代码,3为控制器代码。
1、两种方法模型代码是一样的:
.h文件:
@interface SCGuide : NSObject@property (nonatomic, copy) NSString *imageName;+ (instancetype)guideWithImageName:(NSString *)imageName;@end
.m文件:
#import "SCGuide.h"@implementation SCGuide+ (instancetype)guideWithImageName:(NSString *)imageName { SCGuide *guide = [[self alloc] init]; guide.imageName = imageName; return guide;}@end
该模型中,只有一个主图,也就是每个显示到cell的主背景图的图片名,这里虽然只给模型加了一个属性,但是为了代码的扩展性(也许会给每个cell添加一些其他的控件),这里的代码还是遵循MVC设计模式来写。
2.1、collectionViewCell代码:
.h文件:
#import <UIKit/UIKit.h>@class SCGuide;@interface SCGuideCell : UICollectionViewCell@property (nonatomic, strong) SCGuide *guide;@end
.m文件:
#import "SCGuideCell.h"#import "SCGuide.h"@interface SCGuideCell()@property (nonatomic, weak) UIImageView *imageView;@end@implementation SCGuideCell- (UIImageView *)imageView { if (!_imageView) { UIImageView *imageView = [[UIImageView alloc] init]; [self addSubview:imageView]; _imageView = imageView; } return _imageView;}- (void)setGuide:(SCGuide *)guide { self.imageView.image = [UIImage imageNamed:guide.imageName]; _guide = guide;}- (void)layoutSubviews { [super layoutSubviews]; self.imageView.frame = self.bounds;}@end
该cell类中,通过重写cell的guide属性set方法,来完成cell内部的属性初始化,包括为所有的子控件布局,这样很好地遵循了开闭原则。视图只对外暴露了一个模型属性,通过设置模型,就可以对模型对应的视图完成所有的封装完的操作。
2.2 UIView代码(用来描述scrollView中每个cell)
.h文件:
#import <UIKit/UIKit.h>@class SCGuide;@interface SCGuideCell : UIView@property (nonatomic, strong) SCGuide *guide;- (instancetype)initWithIndex:(int)index;@end
.m文件:
#import "SCGuideCell.h"#import "SCGuide.h"@interface SCGuideCell()@property(nonatomic, weak) UIImageView *imageView;@end@implementation SCGuideCell- (instancetype)initWithIndex:(int)index { if (self = [super init]) { CGRect frame = [UIScreen mainScreen].bounds; CGFloat cellW = frame.size.width; CGFloat cellH = frame.size.height; CGFloat cellX = cellW * index; CGFloat cellY = 0; self.frame = CGRectMake(cellX, cellY, cellW, cellH); } return self;}- (UIImageView *)imageView { if (!_imageView) { UIImageView *imageView = [[UIImageView alloc] init]; [self addSubview:imageView]; _imageView = imageView; } return _imageView;}- (void)setGuide:(SCGuide *)guide { self.imageView.image = [UIImage imageNamed:guide.imageName]; _guide = guide;}- (void)layoutSubviews { self.imageView.frame = self.bounds;}@end
该代码中,头文件除了提供了对应模型的接口外,还提供了一个便利初始化方法,该方法主要是为了需要得到每个cell的下标,来完成cell的frame的不同初值(因为所有cell的初始化是是一次性完成,而不是像collectionView会自动布局)
3.1UICollectionViewController的实现
#import "SCGuideController.h"#import "SCGuide.h"#import "SCGuideCell.h"#define SCGuidePageCount 4@interface SCGuideController ()@property (nonatomic, strong) NSMutableArray *guides;@end@implementation SCGuideControllerstatic NSString * const reuseIdentifier = @"GuideCell";- (instancetype)init { if (self = [super init]) { UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; layout.itemSize = SCScreenSize; layout.minimumLineSpacing = 0; layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; self = [self initWithCollectionViewLayout:layout]; self.view.frame = SCScreenBounds; self.collectionView.pagingEnabled = YES; self.collectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, SCScreenWidth); self.collectionView.showsHorizontalScrollIndicator = NO; self.collectionView.bounces = NO; self.collectionView.backgroundColor = [UIColor clearColor]; NSLog(@"%@", self.collectionView); } return self;}#pragma mark - 懒加载方法- (NSMutableArray *)guides { if (!_guides) { _guides = [NSMutableArray array]; for (int i = 0; i < SCGuidePageCount; i++) { NSString *imageName = [NSString stringWithFormat:@"guide%dBackground", i+1]; SCGuide *guide = [SCGuide guideWithImageName:imageName]; [_guides addObject:guide]; } } return _guides;}- (void)viewDidLoad { [super viewDidLoad]; [self.collectionView registerClass:[SCGuideCell class] forCellWithReuseIdentifier:reuseIdentifier]; }#pragma mark <UICollectionViewDataSource>- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return SCGuidePageCount;}- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { SCGuideCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath]; cell.guide = self.guides[indexPath.row]; return cell;}#pragma mark <UIScrollViewDelegate>- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { if (self.collectionView.contentOffset.x == self.collectionView.contentSize.width) { [self.view removeFromSuperview]; }}@end
控制器只有.m文件,因为.h文件不需要对外提供接口。该实例代码中,提供了4张主背景图,所以在顶部定义了一个cell数量的宏,方便后续修改。
既然有了前面模型和视图类的铺垫,控制器中的业务逻辑就非常清晰了。
1、拿到数据,用一个数组来保存它(guides)
2、重写控制器的初始化方法,给它指定一个layout(流水布局)
3、给控制器的collectionView设置一些属性,注意这里还需要把控制器的view的frame改成屏幕的bounds,因为默认系统的view高度为减去状态栏的20个点。
4、设置数据源,从数据数组中调到模型,赋值给每个cell,完成UI的更新。
5、设置scrollView的代理方法,实现当把最后一个cell划出屏幕时,移除引导页面,从而显示根控制器的页面。
3.2 UIViewController的实现
.m文件:
#import "SCGuideController.h"#import "SCGuide.h"#import "SCGuideCell.h"@interface SCGuideController ()<UIScrollViewDelegate>@property (nonatomic, weak) UIScrollView *scrollView;@property (nonatomic, strong) NSMutableArray *guides;@end@implementation SCGuideController#define SCGuidePageCount 4#pragma mark - 懒加载方法- (NSMutableArray *)guides { if (!_guides) { _guides = [NSMutableArray array]; for (int i = 0; i < SCGuidePageCount; i++) { NSString *imageName = [NSString stringWithFormat:@"guide%dBackground", i+1]; SCGuide *guide = [SCGuide guideWithImageName:imageName]; [_guides addObject:guide]; } } return _guides;}- (UIScrollView *)scrollView { if (!_scrollView) { CGRect frame = [UIScreen mainScreen].bounds; CGFloat imgW = frame.size.width; CGFloat imgH = frame.size.height; UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:frame]; scrollView.contentSize = CGSizeMake(imgW * SCGuidePageCount, imgH); scrollView.showsHorizontalScrollIndicator = NO; scrollView.pagingEnabled = YES; scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, imgW); scrollView.bounces = NO; scrollView.delegate = self; [self.view addSubview:scrollView]; _scrollView = scrollView; } return _scrollView;}- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. [self setGuideCell];}- (void)setGuideCell { for (int i = 0; i < SCGuidePageCount; i++) { SCGuideCell *cell = [[SCGuideCell alloc] initWithIndex:i]; cell.guide = self.guides[i]; [self.scrollView addSubview:cell]; }}- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { if (self.scrollView.contentOffset.x == self.scrollView.contentSize.width) { [self.view removeFromSuperview]; }}@end通过scrollView来实现引导页和collection有一些区别。类扩展中除了需要一个包含模型的数组属性外,还需要一个UIScrollView属性。因为控制器的view默认颜色是透明的,所以我们不需要对view的属性进行修改,而只需要把初始化好的scrollVIew添加至控制器的视图即可。
业务逻辑因为少了流水布局和数据源方法,所以和collectionView的实现也有一些差别。
1、拿到数据。
2、初始化scrollView,并设置相应的属性。
3、设置cell,每次都手动初始化一个我们前面自定义好的cell,并通过模型赋值,把所有cell添加至scrollView。
4、设置监听滚动方法,实现最后一个cell划出屏幕时,移除当前view。
总结
两种方法没有什么太大的区别,在实际开发中,可任选一种实现。
昨天发现这段代码有个bug,状态栏无法被引导页面所覆盖。因为状态栏是比较特殊的视图,所以想要让引导页视图覆盖至状态栏上,我想到的方法是单独创建一个UIWindow,然后把引导页面添加至该window上,注意UIWIndow需要将设置windowLevelAlert,让窗口的level高于状态栏即可。
在AppDelegate中添加代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. self.window = [[UIWindow alloc] initWithFrame:SCScreenBounds]; JLTabBarController *tabBar = [[JLTabBarController alloc] init]; self.window.rootViewController = tabBar; [self.window makeKeyAndVisible]; self.windowGuide = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; self.windowGuide.windowLevel = UIWindowLevelAlert; SCGuideController *gv = [[SCGuideController alloc] init]; self.windowGuide.rootViewController = gv; [self.windowGuide makeKeyAndVisible]; return YES;}
- IOS开发 - 引导页的两种实现 - UICollectionView和UIScrollView
- iOS开发之瀑布流的实现(UICollectionView与UIScrollView)
- IOS开发之瀑布流的实现(UICollectionView与UIScrollView)
- ios基于UIScrollView实现滑动引导页
- iOS使用UIScrollView实现左右滑动UITableView和UICollectionView
- iOS使用UIScrollView实现左右滑动UITableView和UICollectionView
- 瀑布流分别用UIScrollView和UICollectionView的两种写法
- 关于iOS swift3.0 UICollectionView封装引导页和轮播图
- IOS开发:引导页的实现
- 实现引导页的两种方式
- ios开发-引导页实现
- ios开发-引导页实现
- iOS开发之首次启动引导页的两种处理方法简析
- iOS开发UIScrollView的底层实现
- iOS开发 仿相册的一个小Demo 相关UICollectionView,UIScrollView,AFNetworking
- ios开发——简单引导页的实现
- 用UIScrollView实现UICollectionView显示
- UIScrollView嵌套UITableView和UICollectionView
- rrdtool安装 (centos6) 修改运维网Cacti图形旁边的字
- 进程调度-优先级算法(Java简单实现)
- 生活随想--雨滴与闪电
- Android布局整理
- 之九:磁盘页面的抽象
- IOS开发 - 引导页的两种实现 - UICollectionView和UIScrollView
- Android基础知识——组件Intent
- 第13轴项目4-数组的排序(2、字符数组的排序)
- MGMTDB: Grid Infrastructure Management Repository
- uboot的启动过程
- 容器 泛型<>
- 第十三周项目六 体验文件操作(将英语成绩、平均分、优秀\及格人数写入到文件中)
- python encode和decode函数说明
- 2014亚洲小姐候选佳丽泳装出席拜神仪式