iOS专题资源__系统知识点iOS开发iOS高级开发

iOS程序员眼中的客户端免登陆(数据迁移已更新)

2017-01-15  本文已影响7053人  si1ence

一、前言,为什么要做免登陆

但是!!!

我就问你要是凯迪拉克车主你还会用高德么?!(默默掏出裤兜里的地铁卡看了一眼。。)

二、来几个常用 app 的例子

1. 今日头条:
点击更多登录方式效果
2. 每日开眼

但是!!!


如果你觉得我是因为女主人公的照片才举这个例子,呵呵,在下可不是那么肤浅的人,开眼的内容和设计以及 app 整体流畅度都很棒,但是免登陆这里有两个小瑕疵,在游客+横屏状态下

关于这两点的技术实现后面会讲

三、整体流程

  1. 用户首次进入 app 之后,判断之前是否在本机登录过,如果是用户首次登录,就调用 游客登录API,当然这个游客 guestId 是服务器根据设备号生成的,一般情况下,一个设备对应一个游客 guestId,而且这个游客 guestId 当然是不能展示给用户的(也可以在该接口返回一个上次登录信息,提示用户上次登录方式)

iPhone设备各种信息获取传送门

四、上代码之前,谈谈登录注册的一些小细节

五、代码设计:啥都别说了,都在代码里

1. 首先在全局的控制器管理类写一个弹出 view 的方法
/** 大多情况下默认的添加方式,直接添加到最顶层的控制器上
  * title:弹出登录框的提示语,如登录后方可进行评论
  * block:用户被登录框所阻拦的操作(注意循环引用)
  */
- (void)transferControlToPortalViewWithTitle:(NSString *)title block:(void(^)())block;
2. 然后在收藏等深度操作需要提示游客登录的点击事件里面判断
- (void)favoredBtnTapped:(UIButton *)sender {
    // 如果是游客账户,就提示用户进行登录操作,否则就进行正常的收藏按钮点击事件
    if ([self.systemAccountManager isGuest]) {
       [self.systemVCManager transferControlToPortalViewWithTitle:@"登录后可进行收藏操作" block:^{
           [weakSelf doFavoredAction];
       }];
    } else {
       [self doFavoredAction];
    }
}
3. 比如要实现上文中提到的 今日头条 样式的登录框,不能用 present 也不能用 modal,因为那样的话上一级的控制器视图就会被移到另外一个 Window 上,不能实现其在原界面添加半透明遮罩的效果,因此采用下列方式
[fatherVC addChildViewController:portalVC];
[fatherVC.view addSubview:portalVC.view];

此处更正一下,感谢 CZAnchor 提出的方法,这里是可以通过 present 方式实现的,代码如下:

UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController;
UIViewController *baseVC = rootVC;

while (baseVC.presentedViewController) {
   baseVC = baseVC.presentedViewController;
}

if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
    portalVC.modalPresentationStyle = UIModalPresentationOverCurrentContext;
    baseVC.definesPresentationContext = YES;
    [baseVC presentViewController:portalVC animated:NO completion:^{}];
} else {
    baseVC.modalPresentationStyle = UIModalPresentationCurrentContext;
    [baseVC presentViewController:portalVC animated:NO completion:^{}];
}
4. 在调用登录接口的成功回调里面,需要进行两个操作
4.1 首先进行数据迁移:
/** 迁移已下载的文件 */
#warning 关于游客状态下下载的内容,需要考虑两部分:
1. 登录的正式用户之前未在本机上登录过,创建用户的下载路径后直接将游客的下载内容全部迁移过去(若只是登录过没有下载内容,就直接全部迁移过去);
2. 登录的正式有用户之前在本机登录过并有下载内容,则需要将两个路径下的下载内容合并
- (void)transferDownLoadedFile {
    // 获取下载文件根路径
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
    NSString *libraryDir = [paths objectAtIndex:0];
    NSString *rootFilePath = [NSString stringWithFormat:@"%@/%@",libraryDir,@"## 这里是项目中下载文件的路径 ##"];
    
    // 分别获取游客和正式用户的下载路径(方便起见直接使用对应ID作为路径名称)
    NSString *guestPath = [NSString stringWithFormat:@"%@/%@", rootFilePath, self.accountManager.guestId];
    NSString *userPath = [NSString stringWithFormat:@"%@/%@", rootFilePath, self.accountManager.userId];

    // 获取文件管理器
    NSFileManager *manager = [NSFileManager defaultManager];

    // 获取游客的下载文件数组 
    NSError *error = nil;
    NSArray *guestFilesArr = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:guestPath error:&error];
    if (error) {
        NSLog(@"contentsOfDirectoryAtPath guestPath:%@", error);
    }
    
    // 遍历游客的文件
    for (NSString *fileName in guestFilesArr) {
        //  拼接处 该文件在 游客状态 && 正式用户状态 的存储路径
        NSString *guestFileDir = [guestPath stringByAppendingPathComponent:fileName];
        NSString *userFileDir = [userPath stringByAppendingPathComponent:fileName];
        // 如果正式用户 下载文件中不包含该文件,就创建一下
        if (![manager fileExistsAtPath:userFileDir]) {
            [manager createDirectoryAtPath:userFileDir withIntermediateDirectories:YES attributes:nil error:&error];
        }

        BOOL isDir;
        if ([manager fileExistsAtPath:guestFileDir isDirectory:&isDir] && isDir) {
            error = nil;
            NSArray *childFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:guestFileDir error:&error];
            if (error) {
                NSLog(@"contentsOfDirectoryAtPath dir:%@", error);
            }
            // 遍历该文件夹内子文件,全部迁移到 正式用户 名下的文件
            for (NSString *childFile in childFiles) {
                NSString *filePath = [guestFileDir stringByAppendingPathComponent:childFile];
                NSString *destPath = [userFileDir stringByAppendingPathComponent:childFile];
                error = nil;
                [manager moveItemAtPath:filePath toPath:userFileDir error:&error];
                if (error) {
                    DDLogError(@"moveItemAtPath to path error:%@", error);
                    //如果正式用户下该文件存在(即用户之前在本机登录并下载过该文件)会报错,那么就将游客路径下的改文件删除
                    [manager removeItemAtPath:filePath error:&error];
                }
            }
        }
    }
}
// 1. 获取游客的 db 文件路径 guestDataBasePath
// 2. 打开游客该 db 文件
fmDataQueue = [FMDatabaseQueue databaseQueueWithPath:path];
 [fmDataQueue inDatabase:^(FMDatabase *fmDatabase) {
     if ([fmDatabase open]) {
        [fmDatabase setShouldCacheStatements:YES];
        //  创建 SQL 语句
        NSString *sqlStr = [NSString stringWithFormat:@"%@%@%@%@%@%@%@",
                    @"CREATE TABLE IF NOT EXISTS MYVIDEO  (VIDEOID TEXT  PRIMARY KEY ",
                    @",videoname      TEXT",
                    @",info           TEXT",
                    @",coverfilename  TEXT",
                    @",urlpath        TEXT")"];
         BOOL isExecute = [fmDatabase executeUpdate:createStatement];
         if (isExecute) {
             // 如有必要,可检查一下表结构是否已升级,此处不再赘述
         } else {
             NSLog(@"error occured while creating MYVIDEO table");
         }
     } else {
     NSLog(@"open datebase failed");
     }
}

// 3. 查询游客账户下已下载的 video
//  创建空数组用于存放 video 对象
NSMutableArray *videoArray = [[NSMutableArray alloc] init];
[fmDataQueue inDatabase:^(FMDatabase *fmDatabase) {
    // 书写 sql 语句
    NSString *query = [NSString stringWithFormat:@"SELECT videoid,videoname,info,coverfilename,urlpath, FROM MYVIDEO "];
    NSString *sqlQuery;
    if (wheresql != nil) {
        sqlQuery = [NSString stringWithFormat:@"%@%@", query, wheresql];
    } else {
        sqlQuery = query;
    }
    // 按时间降序排序
    sqlQuery = [sqlQuery stringByAppendingString:@" ORDER BY time DESC "];
    FMResultSet *resultSet = [fmDatabase executeQuery:sqlQuery];
    if ([fmDatabase hadError]) {
        NSLog(@"FMDB Error %d: %@", [fmDatabase lastErrorCode], [fmDatabase lastErrorMessage]);
    }
    // 取出查询的结果集
    while ([resultSet next]) {
        VideoClass *video = [[VideoClass alloc] init];
        video.videoId               = [resultSet stringForColumn:@"videoid"];
        video.videoTitle            = [resultSet stringForColumn:@"songname"];
        video.videoDescription      = [resultSet stringForColumn:@"info"];
        video.coverFileName          = [resultSet stringForColumn:@"coverfilename"];
        video.path                   = [resultSet stringForColumn:@"urlpath"];
       
        [videoArray addObject:video];
    }
    [resultSet close];
}];

// 4. 关闭游客 db
[fmDataQueue inDatabase:^(FMDatabase* fmDatabase) {
    if ([fmDatabase close]) {
        NSLog(@"close MYVIDEO succes ....");
    }
    else {
        NSLog(@"close MYVIDEO error");
    }
}];
[fmDataQueue close];
fmDataQueue = nil;

// 5. 打开 正式用户 下的 db 文件(获取游客 db 路径后,代码同上打开 游客 db)

// 6. 将 游客 下载的video 数据插入到 正式用户的 db 中
[fmDataQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
    [array enumerateObjectsUsingBlock:^(VideoClass  *video, NSUInteger idx, BOOL * _Nonnull stop) {
        [self insertOrUpdateCourse:video withDB:db];
        // 创建插入数据的 sql 语句
        NSString *insertSql = @"INSERT OR REPLACE INTO MYVIDEO (videoid,videoname,info,coverfilename,urlpath,) VALUES(?,?,?,?,?)";
        BOOL result = [fmDatabase executeUpdate:insertSql,
                    video.videoId,
                    video.videoTitle,
                    video.videoDescription,
                    video.coverFileName,
                    video.urlPath];
        if (!result) {
            NSLog(@"操蛋!插入 MYVIDEO data failed");
        } else {
            NSLog(@"牛逼!Insert MYVIDEO data success, U did it!");
        }
    }];
}];

// 7. 合并数据库成功后,根据游客 db 路径,删除 游客 db 文件
NSFileManager *fm = [NSFileManager defaultManager];
BOOL success = [fm removeItemAtPath:fullPath error:&error];
if (error) {
      NSLog(@"怎么会删除失败了,难道我姿势不对?delete file at path error:%@", error);
}

4.2 然后进行隐藏登录界面,并调用一下之前传进来的 block,继续用户之前的操作
- (void)hidePortalView {
    if (self.loginSucessBlock) {
        self.loginSucessBlock();
    }
    UIView animateWithDuration:0.2 animations:^{
        self.portalVC.view.alpha = 0;
    } completion:^(BOOL finished) {
        [self.portalVC.view removeFromSuperview];
        [self.portalVC removeFromParentViewController];
    }
}
5. 进行横竖屏适配
- (void)signInWithAccountBtnTapped:(UIButton *)sender {
    SignInController *signInVC = [[SignInController alloc] initWithType:InputViewLogin];
    
    // 设置控制器的 modal 方式为遵循当前控制器的环境,实现当前是横(竖)屏就以横(竖)屏方式modal
    signInVC.modalPresentationStyle = UIModalPresentationCurrentContext;
    
    [self presentViewController:signInVC animated:YES completion:nil];
}
// 根据状态栏方向得到当前页面横竖屏信息
UIDeviceOrientation deviceOrientation = (UIDeviceOrientation)[UIApplication sharedApplication].statusBarOrientation;
// 根据横竖屏状态,做出相应的 UI 层级调整,并做出相应标记
if (deviceOrientation == UIDeviceOrientationPortrait ||deviceOrientation ==
    UIDeviceOrientationPortraitUpsideDown) {
    [self doPortraitUIAdjustment];
    self.isLandScape = NO;
} else {
    [self doLandScapeUIAdjustment];
    self.isLandScape = YES;
}
// 在横屏状态下,应该可以随设备重力感应进行 LandscapeRight 和 LandscapeLeft 两个方向的自动翻转
- (BOOL)shouldAutorotate {
    if (self.isLandScape) {
        return YES;
    } else {
        return NO;
    }
}

// 如果是横屏状态,应该支持 LandscapeRight 和 LandscapeLeft 两个方向,竖屏状态下只支持 Portrait
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    if (self.isLandScape) {
        return UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight;
    } else {
        return UIInterfaceOrientationMaskPortrait;
    }
}

// 默认的方向
-(UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    if (self.isLandScape) {
        return UIInterfaceOrientationLandscapeRight;;
    } else {
        return UIInterfaceOrientationPortrait;
    }
}

#warning 至此,横竖屏适配算是大功告成了

大概的思路就是这些,由于跟项目相关性比较大,而且代码实现方式也比较简单,因此木有 demo,如果有其他问题欢迎在留言区进行交流

上一篇下一篇

猜你喜欢

热点阅读