Recently, I have been developing the iOS version of a single paper magazine. Actually, this is an attempt to uncover this very topic.
I'll start with the entry. What is the Newsstand? Where did this entity come from and what did it become? Reflecting, I came to the following: this version of the magazine, wrapped in an iOS program, differs from pdf by a simply insane size. One of the reasons is a huge pile of pictures. However, this bunch creates the gloss of the iOS magazine. The reason for the emergence of the idea of ​​Newsstand, as I understand it, was Apple’s position on the ownership of certain content. Those. the task was to honestly (often charge) distribute periodicals, so much so that it was difficult to copy-paste. These guys coped with the task - I suppose there is no electronic publishing house with a large cash flow than Newsstand (if I may say so).
After reading, at the user level, with several magazines, it is time to look for a technical solution. First, the Newsstand Kit surfaced.
')
The Newsstand Kit consists of the following things: a special library management mechanism (NKLibrary) of individual issues of the magazine (NKIssue) and their loading (NKAssetDownload), including background loading. Well cool! However, at that time it was completely unclear to me how to turn these things into a magazine.
Yet. It is obvious that the program-magazine consists of two parts: the management of journal issues and the display of a separate journal. Those. Newsstand Kit is focused on the first component - management. The second component - the display of a single issue of the magazine - Apple did not burden any patterns. I'll start with her, the second.
Theoretically, we have all the necessary information downloaded from our server and must display it. Those. it was about the “Application Brochure”. The downloaded information can, conditionally, be divided into raw materials (images, movies, texts, html-iny, pdf-s) and a config, by which we can put all this together. I suppose that not everyone will be interested in reading about the Brochure, so I’m sending these further to the management part.
As I mentioned above, Apple doesn’t limit developers a little to the display of a particular magazine. However, it is probably taken in a good form, flipping through the magazine with a swipe. For this, I used the UIScrollView and the idea of ​​an infinite scrolling (dev apple -> WWDC -> ScrollView Techniques -> Infinite scrolling). If briefly, then on all main View it is necessary to put UIScrollView. At UIScrollView, set the contentSize to three screens in width, and put on it, successively three UIViews (each one the size of one screen). There are a couple of settings, and we have a beautiful swipe on. At the finish, it remains to implement the delegate method of UIScrollView - scrollViewDidEndDecelerating: - and, according to it, update the internal UIView. The complication of the endless scrolling - if you invest in the visible UIVeiw another, similar, UIScrollView - leads to horizontal and vertical scrolling.
OK. The question remains: where do we get the UIView that is currently needed?
Let's start with "this moment". We can say that its characteristic is the number of the open page of the magazine. It turns out that we, in the application, need a thing that will store the index of the current page. Such a thing is needed one for the entire application, and it should be easy access - we need a singleton. Having an index, the question boils down to something new: where to get UIView adequate index? Probably the config knows it. Well and further: why not ask our singleton to read the config and, knowing the index, to issue the desired view? I will give the singleton interface and go to config
@interface DataManager : NSObject @property (nonatomic) int articleIndex; @property (nonatomic) int pageIndex; @property (nonatomic, strong) NSArray *articles; + (id) sharedManager; - (UIView *) currentView; - (UIView *) prevView; - (UIView *) nextView; - (UIView *) upperView; - (UIView *) lowerView; - (int) lastArticleIndex; - (int) lastPageIndex; @end
Config in Objective-C is accepted or convenient to see the .plist file. Actually, you need to collect a plist, which will display the pages of the magazine, some settings of these pages and links to raw materials. I guess the examples will be clearer
<plist version="1.0"> <array> <array> <dict> <key>class</key> <string>SimplePage</string> <key>properties</key> <dict> <key>baseImage</key> <string>1-0.png</string> </dict> </dict> </array> <array> <array> <array>
<dict> <dict> <key>class</key> <string>PhotoPage</string> <key>properties</key> <dict> <key>baseImage</key> <string>9-0-4.png</string> <key>photos</key> <array> <dict> <key>photo</key> <string>9-1-f1.png</string> <key>thumbnail</key> <string>9-1-f1m.png</string> </dict> <dict> <dict> <dict>
I suppose that you noticed the first property of each page - class. Yeah, how convenient! I wrote the class name in the config, and created the necessary page for it. Fragment of the above singleton
- (UIView *) currentView { NSArray *article = [self.articles objectAtIndex:articleIndex]; NSDictionary *page = [article objectAtIndex:pageIndex]; return [self viewWithDictionary:page]; } - (UIView *) viewWithDictionary: (NSDictionary *) dictionary { Class class = NSClassFromString([dictionary valueForKey:@"class"]); NSDictionary *pageProperties = [dictionary valueForKey:@"properties"]; UIView *uiView = [[class alloc] initWithDictionary:pageProperties]; return uiView; }
What is left to mention? I will show the interface SimplePage. The rest of the page classes somehow inherit it.
@interface SimplePage : UIView @property (nonatomic, strong) NSString *imageDirectory; - (id) initWithDictionary: (NSDictionary *) pageProperties; @end
Those. we create a set of classes that inherit UIView, and according to these classes we write the configuration of one log.
Here comes another variable imageDirectory. The thing is that later we will need to collect the brochure pictures, put plist to them and send it to live on the server. Therefore, instead of adding pictures to the project, I collected them in the simulator daddy - DocumentsDirectory. But its initialization
#if IS_LOCAL // doc/img dir imageDirectory = [DocumentsDirectory stringByAppendingPathComponent:@"img"]; #else // issue dir NKIssue *nkCurrentlyReadingIssue = [[NKLibrary sharedLibrary] currentlyReadingIssue]; imageDirectory = [nkCurrentlyReadingIssue.contentURL path]; #endif #if DEBUG NSLog(@"imageDirectory %@", imageDirectory); #endif
That's all that I wanted to tell you about the implementation of the display of one magazine. Let's go further?
NK Management
The word itself suggests that you need something manager. A collection of something downloaded from the server. So we come to another structure. To understand it, you need to superficially consider NKLibrary and NKIssue.
Simply put, NKLibrary is a collection for NKIssue-s. NKLibrary has a number of utilities, i.e. This collection, sharpened by Newsstand. This is also a singleton and, perhaps, at this stage, everything.
NKIssue stores information about a single log. Minimally, issue is required to have a name and date. If I'm not mistaken, the name will be the key, and the date will be sorted.
So, something, having looked somewhere, we have issues.plist
<plist version="1.0"> <array> <dict> <key>Name</key> <string>f-2</string> <key>Title</key> <string>/ 2013</string> <key>Date</key> <date>2013-11-28T08:00:00Z</date> <key>Cover</key> <string>http://fo-nt.net/f/f2.png</string> <key>Content</key> <string>http://fo-nt.net/f/f2.zip</string> </dict> <dict> <dict>
We download the plist from the server, iterate through it, create NKIssue and add them to the NKLibrary
NKLibrary *nkLib = [NKLibrary sharedLibrary]; issuesDictionary = [NSArray arrayWithContentsOfFile:issuesPlistFilePath]; for (NSDictionary *issueDictionary in issuesDictionary) { NSString *name = [issueDictionary valueForKey:@"Name"]; NKIssue *nkIssue = [nkLib issueWithName:name]; if(!nkIssue) { [nkLib addIssueWithName:name date:[issueDictionary objectForKey:@"Date"]]; NSString *coverPath = [issueDictionary valueForKey:@"Cover"]; if (IS_RETINA) coverPath = [self retinaURLStringForString:coverPath]; NSString *coverName = [coverPath lastPathComponent];
I will explain. Naturally, here we have already imported NewsstandKit. Otherwise, how could we know about NKLibrary and NKIssue? With ease in the line, we get an instance of NKLibrary - nkLib. Going through the array, we ask nkLib to give us a specific log by its name. If the library says “FIG you” - it will become clear that there is a journal in the config, but not in the library -> you need to add.
In the listing, there is still the line 'if (IS_RETINA)'. Briefly, the thing is that all the pictures of the magazine are on the server. Here we already know what our display is. Well, why download pictures for someone else's display. Looking ahead, I’ll say that the download kit, for each magazine, is recommended by Apple as an archive. At the finish, it is logical to make two archives for one magazine: simple and @ 2x.
We have the current NKLibrary. Modestly, but you can already display the UI of existing journals.
Visualization of the library display for each magazine is different. However, there is something settled. There is NKIssue with a set of properties - we display them. Among them, especially interesting is the '' status '' property, which can be: none, downloading and available. This I lead to the fact that the displayed log can be downloaded, wait for the download to finish and read, respectively.
You need to download the kit, which was prepared at the stage of creating a brochure. NKAssetDownload is the third NK thing - a specialized loader for the Newsstand (probably so). Procedure: using NKIssue and NSURLRequest (obtained from NSURL, which, in turn, is obtained from the URL line for the selected log), create an instance of NKAssetDownload. It needs to call the downloadWithDelegate method: (id <NSURLConnectionDownloadDelegate>) delegate
NSURLRequest *req = [NSURLRequest requestWithURL:downloadURL]; NKAssetDownload *assetDownload = [nkIssue addAssetWithRequest:req]; [assetDownload downloadWithDelegate:self]; [assetDownload setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:index], @"Index", nil]];
The NKAssetDownload feature is background downloading. It is required for Newsstand. This means that the download will continue in the background. There still have goodies. However, the bg download interests us because it imposes the obligation, on return, to call the downloadWithDelegate method on all NKAssetDownload instances:
for (NKAssetDownload *assetDownload in [nkIssue downloadingAssets]) { [assetDownload downloadWithDelegate:self]; }
Here, it seems, everything. We reanimate the download with some progress bar and wait for it to end. When the download is complete, correctly, replace the application icon: [[UIApplication sharedApplication] setNewsstandIconImage: [publisher coverImageForIssue: nkIssue]]. If we used the archive, unzip it. Ah, yes, the application, after downloading the log, is obliged to write its files along the path of the NKIssue contentURL property (I am not sure if it is obligated. Perhaps there is something optimized).
Further, NKIssue accepts the status "available" - it is possible to show the "brochure".
The next question: updating the library - comes down to reloading the issues.plist and processing it with the existing method. Remember, there we checked whether the magazine was added to the library or not. True, there is a substantive problematics. For a number of magazines, the user can forget about him, by the time a new one is released. And here, as inappropriate, at hand APNS. This is another topic. I admit, I didn't master it the first time. And indeed, I did not make these notifications in my journal. The key stumbling block was the certificate, or rather the need to convert it.
However, APNS: firstly, this is the service of sending notifications. The program can get the id of the device on which it is running and transfer it to your server. First you need to subscribe to these notifications.
@implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
After that, UIApplicationDelegate Protocol has an application: didRegisterForRemoteNotificationsWithDeviceToken: method, in which we have DeviceToken (id) and from which we can merge information to our server. Next is the application: didReceiveRemoteNotification: method, in which you can do something after receiving the notification.
On the server side, you need to accept requests (HTTP) that carry DeviceTokens and store them in the database. Further, according to the purchased tickets, or rather, according to what is deployed on the server, you need to search for sending messages to APNS. According to its API, attaching a certificate and password, we connect and send messages that contain both the payload and the DeviceTokens. There are still nuances, but this is a separate topic.
Among other things, two more restrictions are imposed on iOS magazine. Perovoy - Privacy Policy URL. On the technical side, it is extremely simple. The second limitation is the need to distribute content using iTunesConnect in-app purchases. As I understand it, this means that the user is obliged to buy my free magazine for 0 money.
Total, iTunesConnect In-App Purchases means using the Store Kit. In UserDefaults, I set up the isFreeSubscribed property. I check it on tapu, and in case of NO I show alert. By agreement, I sign
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:@"FreeSubscription"]]; productsRequest.delegate=self; [productsRequest start];
Plus, you need to implement the delegate methods and set isFreeSubscribed to YES to forget about In-App Purchases. ProductProd for SKProductsRequest must be obtained from iTunesConnect, in the Manage In-App Purchases section.
At the finish of the story, I believe that there is not even half of the journal needs, however, I want to believe that I managed to outline the most necessary technical issues for the simplest magazine.
