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]做同步处理,否则会延迟写入。


有了读写版本数据的工具代码,接下来就可以分析是否需要进入引导页面。从CFBundleVersion字典中取出版本号,然后判断,如果和当前版本不相等,就执行进入引导页面代码。

+ (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;}


0 0
原创粉丝点击