NewsStand是苹果公司专门对数字出版做的一个新功能。它能把所有用户订阅的报刊和杂志类的app都放在一个组图标里。实际上,News Stand相当于一个特制的文件夹专门放置报纸,杂志类应用程序。
新的api能为开发者提供以下杂志出版提供的常用功能。
1,后台下载,因为杂志的数据量比较大,通常要程序员处理什么时候下载的问题。而以前这些下载只能在杂志程序运行的时候工作。 现在,可以提交给专用的下载程序进行。而且下载的内容如果是zip和其他压缩格式,还可以自动解压。
2,badge通知。以前用户需要打开app才能知道版本更新。现在由于统一在newstand里面,下载完成能自动通知读者有新一期可以阅读了。
3,阅读界面,这方面各家需求不一样,还是要自己实现。暂时没有办法统一。 使用newstand另一个好处是新一期的封面可以用来代替原来app程序的图标
4,Newsstand 工作流程, 服务器端要先发送一个push notification给客户端的newsstand程序。客户端程序连服务器,取得要下载的url地址。 交给Newsstand后台下载打包的内容,然后进行必要的解压。最后客户端从newsstand里面拿回下载的内容,再把它们搬到程序的数据区。 剩下就交给阅读程序handle了。
详细介绍
首先介绍的就是怎样把一个应用程序改变成一个News Stand程序,这实际上有两步工作,一是让程序运行于News Stand,二是改变程序的图标。
1. 让程序运行于News Stand内
直接在Xcode中更改Info.plist(如图):
就这么简单,运行!你的程序就运行在News Stand中了。如图:
不过,出现在News Stand中的是一个系统的icon。如果我们想自定义我们自己的icon。那么,我们需要第二步。
2. 为你的News Stand程序添加图标
应用程序仍需定义标准图标,这些图标用于settings,search,Push等,(而且你的程序有可能运行于iOS 5以前的版本)。Newsstand 图标可以反应应用的内容,可以动态更新,另外还可以加一些修饰,使其看上去就像真正的杂志或者报纸。
关于BindingType和BindingEdge应该很容易理解,我就不累赘了。另外Newsstand中的图标不一定是正方形,只是不知有没有尺寸上的限制。
显示和下载杂志
现在是时候开始编写一些代码,将我们的项目加入NewsstandKit框架。
1、先导入NewsstandKit标题的分类,需要它
#import<NewsstandKit/NewsstandKit.h>
2、为简单起见,我们假设列表中可用的问题(所有免费的)是在一个plist文件,可以从服务器下载的出版商。所以为了我们定义一个类称为出版商将照顾下载当前的项目列表。一旦项目被下载他们将提交给用户store.plist是相当简单的:它是一个数组,数组的对象都是一个字典,每个字典词典代表一个问题通过提供识别名称(独特的)、标题(对用户可见),出版日期和url缩略图封面图片和内容(PDF文件)下载。
必须有一个配置表,这个配置表要从提供者下载。配置表是一个数组存储字典,字典中Name必须唯一,Title是显示的名字,Date(发布日期),封面,内容(PDF,html等)
3、制作一个列表页,展示配置表中所有的杂志,然后点击某个封面后下载当前点击的杂志。
4、系统提供NewsstandKit.framework来支持newsstand类型的程序,就是在sprint board上看到在书架中的程序。
提供有NKLibrary, NKIssue和NKAssetDownload的类。其中NKLibrary用来管理Newsstand的内容(比如,当前阅读的issue,当前所有 的issue等);
NKIssue用来表示一期刊物,您可以将刊物的URL,包装成NSURLRequest,set给NKIssue的对象。NKIssue还可以很方便的管理刊物的状态(比如None, Downloading-下载中,Available-可用)。
NKAssetDownload,可用于刊物的下载。它的 delegate符合NSURLConnectionDownloadDelegate的协议,这个协议中有三个方法:
connection:didWriteData:totalBytesWritten:expectedTotalBytes://这个方法可以用来做进度管理。 connectionDidResumeDownloading:totalBytesWritten:expectedTotalBytes://方法可以 用来做续传。 connectionDidFinishDownloading:destinationURL://方法表明下载已完成,可以更新界面的 Issue状态。
NewsstandKit是属于系统级别的,因此在app切换到后台或退出的时候,也会由系统选择继续下载。
如果使用ASIHttpRequest来下载的话也是没有问题的,但是没有后台下载,issue状态管理也需要自己来做。
5、 后台下载只能使用Newsstand Kit的framework实现。关键是自动下载。Newsstand类型的app可由push notification来触发下载流程。前提是注册push功能的时候,加上Newsstand的key。
像正常的APNS一样,app在前台,后台,或退出状态下都可以收到。前台的很简单,在didReceiveRemoteNotification的方法中,弹一个alert,问用户是否需要下载;或者直接下载都可以(我之前的做法是会将要下载的issue的信息包装到push notification的message body中)。如果app是退出状态下的话,收到Newsstand的APNS,app会直接在后台启动(这个你是看不到的),然后会走didFinishLaunchingWithOptions的方法,option会带入参数,您可以通过它获取信息。然后就可以启动下载。
首先读取,看看有没有此名字的期刊
NKIssue * nkIssue = [nkLib issueWithName:name]; if (!nkIssue) { 如果没有的话,通过下面的方法就可以加进NSLibrary了 nkIssue = [nkLib addIssueWithName:namedate:[(NSDictionary *)obj objectForKey:@"Date"]]; }
ps: 注意 NSIssue的状态
6、下载相关问题。
6-1、首先介绍程序在前端显示时怎么下载,得调用NKAssetDownload
6-1-1、每一个issue都是由一个或者多个asset组成,asset可以是pdf,images,videos,thumbnails,但是推荐打包所有资源成zip包,然后下载这一个文件
6-1-2、 最终的文件本地路径,被Newsstand framework指定,并且被指定到沙盒中的cache目录,所以也遵循icloud要求
6-1-3、本地路径可以通过 NSIssue 的contentURL属性找回
6-1-4、我们可以创建自己最终的路径,通过在contentURL后添加我们自定义的文件名字
-(NSString *)downloadPathForIssue:(NKIssue *)nkIssue { return [[nkIssue.contentURL path] stringByAppendingPathComponent:@"magazine.pdf"]; }
-(void)startDownloadIssue:(NSIndexPath *)index{ NSDictionary * publisher = [_booksAryobjectAtIndex:index.row]; NKLibrary * nkLib = [NKLibrary sharedLibrary]; NKIssue * nkIssue = [nkLib issueWithName:[publisherobjectForKey:@"Name"]]; NSLog(@"issue . name = %@",nkIssue.name); // 获取publish的severURL NSURL * downloadURL = [NSURL URLWithString:[publisherobjectForKey:@"Content"]]; if (!downloadURL) { return; } NSURLRequest * req = [NSURLRequestrequestWithURL:downloadURL]; NKAssetDownload * assetDownload = [nkIssueaddAssetWithRequest:req]; [assetDownload setUserInfo:@{@"index":[NSNumber numberWithInt:index.row]}]; [assetDownload downloadWithDelegate:self]; }
注意 NSURLConnectionDownloadDelegate代理
- (void)connection:(NSURLConnection *)connectiondidWriteData:(long long)bytesWritten totalBytesWritten:(longlong)totalBytesWritten expectedTotalBytes:(long long) expectedTotalBytes{ NKAssetDownload * dnl = [connectionnewsstandAssetDownload]; NSLog(@"第%@个 下载了 百分之%lld",[dnl.userInfoobjectForKey:@"index"],bytesWritten/totalBytesWritten); //在此更新进度条 } 但主要的还是其他两个代理方法
6-2、后台下载
6-2-1、newsstand 一个有意思的特征,就是一旦一个asset已经开始下载时,不管程序是在暂停,还是终止,Newsstand都将继续下载。
当然,在你的app暂定状态,它不会收到任何的状态更新,但是它会在后台被唤醒去处理下载的完成操作。在这个过程中,你可以copy文件到你的目标目录下,并且对zipped的文件进行解包。
如果在newsstand下载过程中被终止,情况就不一样了。实际上,下载过程中被终止,我们就不能简简单单的唤醒,连接 delegate完成下载就行了。因为当一个app被终止后,App delegate也就不存在了。所以在这种情况下,我们将会在background重新 relaunch 当前app。然后 UIApplicationDelegate application:didFinishLaunchingWithOptions: 会被调用,launchOptions 会带上 UIApplicationLaunchOptionsNewsstandDownloadsKey这样的key
如果这个key存在的话,这个key对应的value应该包含一个包含asset的数组。(这个没做实验,不做考证)
另外一个比较重要的是,当程序每次relaunch时,我们需要检查时候存在等待下载的。如果有的话,我们就必须重新附上 download delegate 完成下载,否则程序或者认为他们是被抛弃的,可能会引起崩溃。
我们要在 Appdelegate 中的 application: didFinishLaunchingWithOption: 当界面加载完后检查
// inside the app delegate'sapplication:didFinishLaunchingWithOptions: and after the view controller hasbeen initialized NKLibrary *nkLib = [NKLibrary sharedLibrary]; for(NKAssetDownload *asset in [nkLib downloadingAssets]) { [assetdownloadWithDelegate:store]; }
6-3、新内容推送
另外一个newsstand的好处是,当有新的内容推送时,会收到push notification:这就意味着这个app会自动在后台下载新的内容,并更新在newsstand group的杂志的封面。
但是 Newsstand KIt 添加了一种新的push 通知类型,叫做UIRemoteNotificationTypeNewsstandContentAvailability通知。通过此类型的通知,告诉有新的内容要更新。newsstand应用也许对其他类型的通知不感兴趣,所以把上边注册的通知类型用
[[UIApplication sharedApplication]registerForRemoteNotificationTypes:UIRemoteNotificationTypeNewsstandContentAvailability];
代替。
注册这个通知之后,会弹出一个 提示“** 想要给你发送新的杂志”,如果这个出错的话,再
- (void)application:(UIApplication *)applicationdidFailToRegisterForRemoteNotificationsWithError:(NSError *)error method.
处理。
当一个通知发送到终端时,会有两种状态,在前台跑或者不在。当程序在前台时,我们必须响应application:didReceiveRemoteNotifation:开发者可以仅仅提示一个alert,或者自动开始一个下载或者找服务器要新一期的杂志数据(title,cover,等等)。另外一种情况,收到通知后会激活application:didFinishLaunchingWithOptions:还有这个方法必须做的。然后检查 launch option中的UIApplicationLaunchOptionRemoteNotificationKey对应的值并检查负荷。 这种情况下,将要被完成是会下载最新的issue从获取issue列表的服务器。正常情况下后台任务也就跑个几秒钟,因为之前你还有检查服务器,所以我们就要设置beginBackgroundTaskWithExpirationHandler 来获取10minutes完成这个任务。
下面是一个后台完成下载的代码
NSDictionary *payload = [launchOptionsobjectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]; if(payload) { // schedule for issue downloading in background NKIssue *issue4 = [[NKLibrary sharedLibrary]issueWithName:@"Magazine-4"]; if(issue4) { NSURL *downloadURL = [NSURLURLWithString:@"http://www.viggiosoft.com/media/data/blog/newsstand/magazine-4.pdf"]; NSURLRequest *req = [NSURLRequestrequestWithURL:downloadURL]; NKAssetDownload *assetDownload = [issue4addAssetWithRequest:req]; [assetDownload downloadWithDelegate:store]; } }
6-4、最后一个就是更新newsstand的图标了。
苹果建议我们都用最新一期报刊的封面来更新app的icon,我们经常见的应该是带了一个newi的腰带的图标。这个也不难,我们通过以下代码就可以实现,不过在这一期杂志不可用的时候永远不要去更新封面图标
// update the Newsstand icon UIImage *img = [publishercoverImageForIssue:nkIssue]; if(img) { [[UIApplication sharedApplication]setNewsstandIconImage:img]; [[UIApplication sharedApplication]setApplicationIconBadgeNumber:1]; }
做完上面的所有事情,我们在bulid我们的app,然后就可以看到下面的图片:
我们点击上面的任意一个按钮下载,悲剧的是崩溃了,我们做错了什么?少做了什么?看看崩溃日志吧。
2014-06-21 18:23:34.490 NewsStandDemo[5493:60b] ***Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:'you do not have background download privileges: add 'newsstand-content' tomainBundle.infoDictionary.UIBackgroundModes' *** First throw call stack: ( 0 CoreFoundation 0x018c71e4__exceptionPreprocess + 180 1 libobjc.A.dylib 0x016468e5objc_exception_throw + 44 2 CoreFoundation 0x018c6fbb +[NSExceptionraise:format:] + 139 3 NewsstandKit 0x000d4427 -[NKIssueaddAssetWithRequest:] + 326 4 NewsStandDemo 0x00005940 -[StorescheduleDownloadOfIssue:] + 320 5 NewsStandDemo 0x00003866-[ViewController downloadIssue:updateCover:] + 630 6 NewsStandDemo 0x000034a2-[ViewController coverSelected:] + 226 7 NewsStandDemo 0x00006b34 -[CoverViewbuttonCallback:] + 68 8 libobjc.A.dylib 0x01658880 -[NSObjectperformSelector:withObject:withObject:] + 77 9 UIKit 0x003083b9-[UIApplication sendAction:to:from:forEvent:] + 108 10 UIKit 0x00308345-[UIApplication sendAction:toTarget:fromSender:forEvent:] + 61 11 UIKit 0x00409bd1-[UIControl sendAction:to:forEvent:] + 66 12 UIKit 0x00409fc6-[UIControl _sendActionsForEvents:withEvent:] + 577 13 UIKit 0x00409243-[UIControl touchesEnded:withEvent:] + 641 14 UIKit 0x00347ddd-[UIWindow _sendTouchesForEvent:] + 852 15 UIKit 0x003489d1-[UIWindow sendEvent:] + 1117 16 UIKit 0x0031a5f2-[UIApplication sendEvent:] + 242 17 UIKit 0x00304353_UIApplicationHandleEventQueue + 11455 18 CoreFoundation 0x0185077f__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 15 19 CoreFoundation 0x0185010b__CFRunLoopDoSources0 + 235 20 CoreFoundation 0x0186d1ae __CFRunLoopRun+ 910 21 CoreFoundation 0x0186c9d3CFRunLoopRunSpecific + 467 22 CoreFoundation 0x0186c7eb CFRunLoopRunInMode + 123 23 GraphicsServices 0x049f45ee GSEventRunModal+ 192 24 GraphicsServices 0x049f442b GSEventRun + 104 25 UIKit 0x00306f9bUIApplicationMain + 1225 26 NewsStandDemo 0x0000462d main + 141 27 libdyld.dylib 0x04d7b701 start + 1 ) libc++abi.dylib: terminating with uncaught exception oftype NSException根据报错的信息,我们可以看出,我们的App要更增加UIBackgroundModes,所以我们就在plist设置NewsStands UIBackgroundModes,
再次点击下载,ok 顺利下载完成。
再次点击第五个就可以完美阅读啦
参考 文档:
http://www.viggiosoft.com/blog/blog/2011/10/17/ios-newsstand-tutorial/
https://developer.apple.com/library/ios/documentation/StoreKit/Reference/NewsstandKit_Framework/_index.html
http://www.viggiosoft.com/blog/blog/2011/10/29/at-newsstand-and-subscriptions/