ios 阅读器开发

来源:互联网 发布:魔术教学知乎 编辑:程序博客网 时间:2024/06/07 06:20

公司业务涉及到小说阅读这一块儿。

现在就将代码贴上来,共勉。

我下载的demo叫LSYReader,相信大家在网上也能够找到同样的demo,但是里面的业务逻辑对于刚接触阅读器开发的同学可能会有些看不太懂,所以我一边贴代码,一边详细讲解。


demo下载好之后,直接把这个文件夹导入,然后运行一下,看看是否报错,因为涉及到xml解析,所以需要添加/usr/libxml2。

下面分解步骤。

1.点击阅读的时候先把将要展现的文本下载好,最好与公司后台商量好,以字符串的形式一次性返回给你。然后把下载好的字符串保存到本地。

这里我保存到本地的文件是以当前bookid作为后缀,这样就能保证区分开阅读的书籍。

[data objectForKey:@"info"]就是我从后台拿到的整本书的字符串

 NSString *newStr = [NSStringstringWithFormat:@"%@  %@",self.bookName,[dataobjectForKey:@"info"]];



 NSString *cacheDir =NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask, YES)[0];

                

                NSString *filePath = [cacheDirstringByAppendingPathComponent:[NSStringstringWithFormat:@"/%@.txt",self.bookId]];

                

 [newStr writeToFile:filePathatomically:YESencoding:NSUTF8StringEncodingerror:nil];

这样就把当前书籍的内容保存到了本地。
2.初始化阅读器
导入头文件

#import "LSYReadViewController.h"

#import "LSYReadPageViewController.h"

#import "LSYReadUtilites.h"

LSYReadPageViewController *pageView = [[LSYReadPageViewControlleralloc] init];

                             

                pageView.bookName =self.bookName;

                

                pageView.bookId = [self.bookIdstringValue];

                

                NSURL *fileURL = [NSURLfileURLWithPath:filePath];

                

                pageView.resourceURL = fileURL;   //文件位置

                

                dispatch_async(dispatch_get_global_queue(0,0), ^{

                    

                    pageView.model = [LSYReadModelgetLocalModelWithURL:fileURLLimitChapter:_limitChapter];

                    

                    dispatch_async(dispatch_get_main_queue(), ^{

                        

                        [self.navigationControllerpushViewController:pageViewanimated:YES];

                    });

                });


这样就直接能阅读了。实现的翻页样式为仿真翻页,点击阅读页面,会弹出上下的菜单栏。

另外提醒大家注意一下,我这里实现的阅读器,每次点击阅读就会重新把资源重新下载一遍,所以需要在每次下载之前,先把之前写入本地的文件删除掉。

-(void)remoCacheFile{

    

    NSFileManager *manager = [NSFileManagerdefaultManager];

    

    NSString *cacheDir =NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask, YES)[0];

    

    NSString *filePath = [cacheDirstringByAppendingPathComponent:[NSStringstringWithFormat:@"/%@.txt",[self.bookIdstringValue]]];

    

    if ([managerfileExistsAtPath:filePath]) {

        

        [manager removeItemAtPath:filePatherror:nil];

       

        NSString *content = [NSStringstringWithContentsOfFile:filePathencoding:NSUTF8StringEncodingerror:nil];

       

        NSLog(@"read success: %@",content);

   

    }

}

按照我的做法,就需要在另外一个类里面修改一些方法。

在LSYReadModel.m里修改这个方法,也就是把解档注释掉,每次都读取最新的文本,不从本地读取。切记

+(id)getLocalModelWithURL:(NSURL *)url LimitChapter:(NSNumber *)limitChapter

{

    NSString *key = [url.pathlastPathComponent];

//    NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:key];

//    if (!data) {

        if ([[keypathExtension] isEqualToString:@"txt"]) {

            LSYReadModel *model = [[LSYReadModelalloc] initWithContent:[LSYReadUtilitesencodeWithURL:url]];

            model.resource = url;

            model.limitChater = limitChapter;

            [LSYReadModelupdateLocalModel:model url:url];

            return model;

        }

        elseif ([[key pathExtension] isEqualToString:@"epub"]){

            NSLog(@"this is epub");

            LSYReadModel *model = [[LSYReadModelalloc] initWithePub:url.path];

            model.resource = url;

            [LSYReadModelupdateLocalModel:model url:url];

            return model;

        }

        else{

            @throw [NSExceptionexceptionWithName:@"FileException"reason:@"文件格式错误"userInfo:nil];

        }

        

//    }

//    NSKeyedUnarchiver *unarchive = [[NSKeyedUnarchiver alloc]initForReadingWithData:data];

//    LSYReadModel *model = [unarchive decodeObjectForKey:key];

//    

//    if ([model.font floatValue] != [LSYReadConfig shareInstance].fontSize) {

//        if ([[key pathExtension] isEqualToString:@"txt"]) {

//            LSYReadModel *model = [[LSYReadModel alloc] initWithContent:[LSYReadUtilites encodeWithURL:url]];

//            model.resource = url;

//            [LSYReadModel updateLocalModel:model url:url];

//            return model;

//        }

//    }

    

    returnnil;

}

3.自定义菜单

找到View这个文件夹。里面有bottomView和topView 分别代表在阅读的时候点击屏幕弹出来的上下两个菜单,如果当前功能不能满足需求,就在这里面修改。另外这两个类都遵循LSYMenuView的代理,所以新加控件如果想执行的话,需要找对写代理的地方以及执行的地方。

4.看到第一章或者最后一章给提醒或者修改章节标题的话需要在这儿做处理。

在这个类里修改LSYReadPageViewController.m

- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray<UIViewController *> *)pendingViewControllers

{

   

    _chapter =_chapterChange;

    

    _page =_pageChange;

    

    LSYRecordModel *recordModel =_model.record;

   

    LSYChapterModel *chapterModel = recordModel.chapterModel;

    

    if (_chapter ==0 && _page ==0 ) {

        

        [LjxUntilshowHint:@"已经是第一章了"yOffset:-200];

    }

    

    NSUInteger chapter = recordModel.chapterCount-1;

    

    if (_chapter == chapter &&_page == chapterModel.pageCount-1 ) {

        

        [LjxUntilshowHint:@"作品更新中,敬请期待"yOffset:-200];

        

    }

    if (_page ==0) {

        

        [_readViewrefreshTitleWithTitle:[NSStringstringWithFormat:@"XX%@",self.bookName]];

        

    }else{

        

        [_readViewrefreshTitleWithTitle:chapterModel.title];

        

    }

}


5.如果要有vip收费限制,或者看到某一章节需要登录的话,就要做限制,那么限制需要做在这里。

还是在第四步的类里面修改,self.limitChaptor 代表的是多少章之前是免费读或者不需要登录就能看,剩下的就需要付费或者登录了

- (nullableUIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController

{


    

    _pageChange =_page;

   

    _chapterChange =_chapter;

    

    if ([self.limitChaptorintegerValue] == 0) {

        

        if (_pageChange ==_model.chapters.lastObject.pageCount-1 && _chapterChange == _model.chapters.count-1) {

            returnnil;

        }

        if (_pageChange ==_model.chapters[_chapterChange].pageCount-1) {

            _chapterChange++;

            _pageChange =0;

        }

        else{

            _pageChange++;

        }

        

    }else{

        

        if (_chapter >=[self.limitChaptorintegerValue] &&_page == _model.chapters[_chapter].pageCount-1) {

            

            if ([[NSUserDefaultsstandardUserDefaults]objectForKey:@"token"]) {

                

                if (_pageChange ==_model.chapters.lastObject.pageCount-1 && _chapterChange == _model.chapters.count-1) {

                    returnnil;

                }

                if (_pageChange ==_model.chapters[_chapterChange].pageCount-1) {

                    _chapterChange++;

                    _pageChange =0;

                }

                else{

                    

                    _pageChange++;

                    

                }

                

            }else{

                

                

                UIAlertController *alert = [UIAlertControlleralertControllerWithTitle:@"温馨提示"message:@"后续章节需要登录才能阅读,是否登录?"preferredStyle:UIAlertControllerStyleAlert];

                

                UIAlertAction *sureAction = [UIAlertActionactionWithTitle:@"确定"style:UIAlertActionStyleDefaulthandler:^(UIAlertAction *_Nonnull action) {

                    

                    FYLoginViewController *login = [[FYLoginViewControlleralloc]init];

                    

                    login.fromOtherVC =YES;

                    

                    login.fromRead =YES;

                    

                    login.loginBLOCK  = ^(CGFloat y,BOOL isLogin){

                        

                        if (isLogin) {

                            

                            [[UIApplicationsharedApplication] setStatusBarHidden:YES];

                        }

                    };

                    [login setHidesBottomBarWhenPushed:YES];

                    

                    [self.navigationControllerpushViewController:loginanimated:YES];

                    

                }];

                

                UIAlertAction *cancleAction = [UIAlertActionactionWithTitle:@"取消"style:UIAlertActionStyleCancelhandler:^(UIAlertAction *_Nonnull action) {

                    

                    

                }];

                

                [alert addAction:sureAction];

                

                [alert addAction:cancleAction];

                

                [selfpresentViewController:alertanimated:YEScompletion:nil];

                

            }

            

        }else{

            

            if (_pageChange ==_model.chapters.lastObject.pageCount-1 && _chapterChange == _model.chapters.count-1) {

                returnnil;

            }

            if (_pageChange ==_model.chapters[_chapterChange].pageCount-1) {

                _chapterChange++;

                _pageChange =0;

            }

            else{

                _pageChange++;

            }

        }

    }

    

    

    

    return [selfreadViewWithChapter:_chapterChangepage:_pageChange];

}

6.当然也会涉及到阅读章节的管理,比如阅读历史,下次再打开同一本书,会有阅读历史。这里我采用的是本地数据库存储。直接存储章节和页数,因为每一次翻页读会走这个方法,每次翻页我会先把这本书之前存的章节和页数先删除掉,这样会加快数据库运转效率。

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed

{

    if (!completed) {

        LSYReadViewController *readView = previousViewControllers.firstObject;

        _readView = readView;

        _page = readView.recordModel.page;

        _chapter = readView.recordModel.chapter;

    }

    else{

       

        [[FYBookManagershareInstance]deleteBookWithBookId:self.bookId];

        

        [[FYBookManagershareInstance]insertBookWithBookId:self.bookIdChapter:[NSStringstringWithFormat:@"%lu",(unsignedlong)_chapter]Page:[NSStringstringWithFormat:@"%lu",(unsignedlong)_page]];

        

        [selfupdateReadModelWithChapter:_chapterpage:_page];


    }

}

然后我把我关于本地数据库这块的代码贴上来。

#import <Foundation/Foundation.h>

#import "FMDatabase.h"


@interface FYBookManager : NSObject{

    

    FMDatabase *_dataBase;

}

+(instancetype)shareInstance;


-(void)insertBookWithBookId:(NSString *)bookid Chapter:(NSString *)chapter Page:(NSString *)page;


-(void)deleteBookWithBookId:(NSString *)bookid;


-(BOOL)selectBookWithBookId:(NSString *)bookid;


-(void)updateBookWithBookId:(NSString *)bookid Chapter:(NSString *)chapter Page:(NSString *)page;


-(NSDictionary *)selectedBookWithBookId:(NSString *)bookid;


@end


/**********/

#import "FYBookManager.h"


@implementation FYBookManager


+(instancetype)shareInstance{

    

    staticFYBookManager *manager;

    

    staticdispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        

        manager = [[FYBookManageralloc]init];

    });

    

    return manager;

}

-(instancetype)init{

    

    if (self = [superinit]) {

        

        NSString *cacheDir =NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask, YES)[0];

       

        NSString *filePath = [cacheDirstringByAppendingPathComponent:@"fybook.db"];

        

        _dataBase = [[FMDatabasealloc]initWithPath:filePath];

        

        if ([_dataBaseopen]) {

            

            NSLog(@"数据库打开");

        }

        

        NSString *createTableSql =@"create table if not exists bookBox (id integer primary key autoincrement,bookid varchar (256),chapter varchar (256), page varhar (256))";

        

        if([_dataBaseexecuteUpdate:createTableSql]){

            

            NSLog(@"表创建成功");

        }

    }

    

    returnself;

}

-(void)insertBookWithBookId:(NSString *)bookid Chapter:(NSString *)chapter Page:(NSString *)page{

    

    BOOL insert = [_dataBaseexecuteUpdate:@"insert into bookBox (bookid,chapter,page) values (?,?,?)",bookid,chapter,page];

    

    if (insert) {

        

        NSLog(@"插入数据成功");

        

    }else{

        

        NSLog(@"插入数据失败");

    }

}

-(void)deleteBookWithBookId:(NSString *)bookid{

    

    NSString *deleteSql =@"delete from bookBox where id = ?";

    

    if([_dataBaseexecuteUpdate:deleteSql,bookid])

    {

        

        NSLog(@"数据删除");

    }


}

-(BOOL)selectBookWithBookId:(NSString *)bookid{

    

    NSString *selectSql =@"select *from bookBox where bookid=?";

    

    FMResultSet *set = [_dataBaseexecuteQuery:selectSql,bookid];


    

    while ([setnext]) {

        

        returnYES;

        

    }

    

    returnNO;

}

-(void)updateBookWithBookId:(NSString *)bookid Chapter:(NSString *)chapter Page:(NSString *)page{

    

    NSString *updateSql =@"update bookBox set chapter = ?,page = ? where id = ?";

    

    if([_dataBaseexecuteUpdate:updateSql,chapter,page,bookid]){

        

        NSLog(@"修改成功");

    }


}

-(NSDictionary *)selectedBookWithBookId:(NSString *)bookid{

    

    NSString *selectSql =@"select *from bookBox where bookid=?";

    

    FMResultSet *set = [_dataBaseexecuteQuery:selectSql,bookid];

    

    NSMutableDictionary *dic = [[NSMutableDictionaryalloc]init];

   

    while ([setnext]) {


      [dic setObject:[setstringForColumn:@"chapter"]forKey:@"chapter"];

        

        [dic setObject:[setstringForColumn:@"page"]forKey:@"page"];


    }

    

    return dic;

}

@end

7.点击菜单左下角会弹出目录。

目录的类是这个:LSYChapterVC.m

我在这个类里修改了一些东西,能够直接滚动到当前阅读的章节

-(UITableView *)tabView

{

    if (!_tabView) {

        _tabView = [[UITableViewalloc] init];

        _tabView.delegate =self;

        _tabView.dataSource =self;

        _tabView.separatorStyle =UITableViewCellSeparatorStyleNone;

        

        NSIndexPath *indexPath = [NSIndexPathindexPathForRow:_readModel.record.chapterinSection:0];

        

        [_tabViewscrollToRowAtIndexPath:indexPathatScrollPosition:UITableViewScrollPositionTopanimated:NO];

    }

    

    return_tabView;

}

还可以在这里类里面设置关于比如之前说过的章节阅读限制,比如前20章免费,那么前20章的目录就是可点击状态的颜色,其他的为不可点击的颜色。

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

    

    UITableViewCell *cell = [tableViewdequeueReusableCellWithIdentifier:chapterCell];

    

    if (!cell) {

        cell = [[UITableViewCellalloc] initWithStyle:UITableViewCellStyleDefaultreuseIdentifier:chapterCell];

    }

   

    cell.textLabel.highlightedTextColor = [UIColorredColor];

    

    if ([_readModel.limitChaterintegerValue] ==0) {

        

        cell.textLabel.textColor = [UIColorblackColor];

        

    }else{

        

        if (indexPath.row < [_readModel.limitChaterintegerValue] ) {

            

            cell.textLabel.textColor = [UIColorblackColor];

            

        }else{

            

            cell.textLabel.textColor = [UIColorcolorWithHexString:@"#808080"];

            

        }

    }

    cell.textLabel.text =_readModel.chapters[indexPath.row].title;

   

    if (indexPath.row ==_readModel.record.chapter) {

        

        [tableView selectRowAtIndexPath:indexPathanimated:NOscrollPosition:UITableViewScrollPositionNone];

    }

    return cell;

}


目前能想到的就这些,如果有人有新的需求,就提出来,我能解决的一定知无不言,大家共同进步。如果解决不了的 也会把自己的思路贴出来,然后一起探讨。



原创粉丝点击