Original article: http://ios-blog.co.uk/tutorials/how-to-make-a-magazine-app-in-ios-part-ii/
Change
The first part of the tutorial introduces many things. Sorry, I am late again. I have followed the new features of ios5 while writing this article, but due to NDA, I am not allowed to disclose any content about the new SDK. The final example also provides support for ios4 and ios5.
I will not explain newsstand too much. In the final analysis, we will create a magazine application. The Implementation Details of newsstand are irrelevant to this. I wrote two tutorials on my blog. You can read them (here and here). They already cover all aspects of newsstand. Simply put, newsstand presents magazines in a completely new way on iPad and iPhone. The original icons represent the covers of magazines (or newspapers), and all the newsstand icons are put together. For developers, newsstand includes a newsstand KIT framework, including content download, installation, and organization.
Sample program
Shows the part of the program. 9 magazines and 9 fruit-flavored covers. You can download, view the download progress through the progress bar, and then read the magazine. Another figure shows newsstand. In the nesstand group, the original application icon is replaced by the magazine cover icon. However, in the ios4 iPad, the app icon is still displayed.
The program code is put here: GitHub. Do not use this code for production unless you have undergone a large number of tests. But it can be used as a good starting point in real development. In fact, we have explained the main structure of the Code in Part 1; I suggest you read the first part before reading this chapter to better understand the main components of the program. In the next discussion, we should always separate the Journal Management (controller rather than View Controller) from the UI as much as possible. Theoretically, the "Bookstore manager" and "journal model" can also be reused in MAC, because they are very loosely coupled with the UI.
I used "Single Window template" to create this program. In the Application: didfinishlaunchingwitexceptions method, add two main components:
-(Bool) Application :( uiapplication *) applicationdidfinishlaunchingwitexceptions :( nsdictionary *) launchoptions
{
// Create a "Store" instance
_ Store = [[store alloc] init];
[_ Store startup];
Self. Shelf = [[shelfviewcontroller alloc] initwithnibname: nilbundle: Nil] autorelease];
_ Shelf. Store = _ store;
Self. Window = [[[uiwindow alloc] initwithframe: [[uiscreenmainscreen] bounds] autorelease];
Self. Window. rootviewcontroller = _ shelf;
[Self. Window makekeyandvisible];
Return yes;
}
The two components are:
- Store class, that is, the "Bookstore manager" in the block diagram. It inherits from nsobject and has nothing to do with the UI.
- The shelfviewcontroller class represents the application interface. It has a store type attribute. It does not directly access the attributes of the store class, but uses a Simple API to obtain the required information from the store. We can also use delegation, but this is basically only used between the two classes in a specific program, there is no need to define a specific set of protocols for their interaction. This controller can be divided into two parts: one for the UI, that is, the bookshelf, and the other for the background, that is, the bookstore. The bookshelves depend strictly on bookstores, but not on the contrary. The communication from the bookstore to the bookshelf uses the lazy mode, that is, notifications.
The program is integrated into newsstand through info. plist. For more information, see the apple documentation or my tutorials.
Model and Controller
There is a model in the program, that is, the "issue" model, which represents a journal located in a bookstore or a user has bought a journal. There is also a controller, that is, "Bookstore ". Although this controller is not a UI component, it is sufficient to have these two components for the "background" of the program. Theoretically, we can retrieve the bookstore status and download magazines without using any user interface. This is the basic concept of a magazine application. Many events occur in the background without user intervention: that is, the program can execute tasks without the UI being loaded.
The issue class indicates the characteristics of all magazines, the unique ID, name, release date, cover image URL, and content URL (the magazine content can be PDF files, Epub files, or zip packages ). In particular, the ID must exist throughout the life cycle of the magazine (for example, the name can be changed because of different regions, but the ID obviously does not work ). In addition, the ID is used for newsstand to identify a unique publication (by using the name field of nkissue), and is also used to associate the product with the appstore (if you want to implement in-APP purchase ).
In addition, the issue category is also important in the magazine download process. The store class is responsible for starting the download, but the issue class is responsible for monitoring the progress and installing it when the download is complete.
Finally, the issue class can indicate whether a magazine has been downloaded and whether it exists in the user's library. The issue class has an isissueavailableforread method that notifies the view whether certain operations (reading or downloading) on the journal are allowed and displays the relevant content.
The store class is the Controller class in the app. It is initialized at application startup and never released. This class starts to get the list of items (magazines) from the publisher's server as soon as it is initialized. Here we use a simple plist file to implement the magazine list, decode (deserialize) It, create issue objects, and download their cover pictures. All of these use GCD for asynchronous execution. When the magazine list is ready, message notifications are sent to all relevant objects (especially viewcontroller) for UI updates. Note that the status attribute is used to indicate the bookstore status. We have rewritten its setter method to post status update notifications in this method. For simplicity, the Status values are limited to "not initialized", "being downloaded", "ready", and "error ". You can add additional statuses as needed. Finally, when the connection is unavailable, we load the bookstore data from the local plist file (even if the user is not connected to the Internet, he can access the downloaded content ).
The downloadstoreissues method is the core code of the class, which is listed as follows:
-(Void) downloadstoreissues {
Self. Status = storestatusdownloading;
Dispatch_async (dispatch_get_global_queue (dispatch_queue_priority_high, 0), ^ {
Nsarray * _ list = [[nsarray alloc] initwithcontentsofurl: [nsurlurlwithstring: @ "http://www.viggiosoft.com/media/data/iosblog/magazine/store.plist"];
If (! _ List ){
// Let's try to retrieve it locally
_ List = [[nsarray alloc] initwithcontentsofurl: [self fileurlofcachedstorefile];
}
If (_ list ){
// Now creating all issues andstoring in the storeissues Array
[_ Listenumerateobjectsusingblock: ^ (id obj, nsuinteger idx, bool * Stop ){
Nsdictionary * issuedictionary = (nsdictionary *) OBJ;
Issue * anissue = [[Issue alloc] init];
Anissue. issueid = [issuedictionaryobjectforkey: @ "ID"];
Anissue. Title = [issuedictionary objectforkey: @ "title"];
Anissue. releasedate = [issuedictionary objectforkey: @ "Release Date"];
Anissue. coverurl = [issuedictionary objectforkey: @ "cover URL"];
Anissue. DownLoadURL = [issuedictionary objectforkey: @ "Download URL"];
Anissue. Free = [(nsnumber *) [issuedictionary objectforkey: @ "free"] boolvalue];
[Anissueaddinnewsstand];
[Storeissuesaddobject: anissue];
[Anissue release];
// Dispatch coverloading
If (! [Anissuecoverimage]) {
Dispatch_sync (dispatch_get_global_queue (dispatch_queue_priority_default, 0), ^ {
Nsdata * imgdata = [nsdata datawithcontentsofurl: [nsurlurlwithstring: anissue. coverurl];
If (imgdata ){
[Imgdata writetourl: [anissue. contenturlurlbyappendingpathcomponent: @ "cover.png"] atomically: Yes];
}
});
}
}];
// Let's save the file locally
[_ List writetourl: [selffileurlofcachedstorefile] atomically: Yes];
[_ List release];
Self. Status = storestatusready;
} Else {
Elog (@ "store downloadfailed .");
Storeissues = nil;
Self. Status = storestatuserror;
}
});
}
In this Code, download the cover image immediately after plist download. This is not good because, before refreshing the app status, if the network condition is poor, this additional network communication will cause latency. A better practice is: In a real app, because the number of journals is always limited, we can download cover pictures in the background and notify the UI to update every time an image is downloaded.
View Controller
The UI will appear soon, and it will be updated based on the notifications sent by the store class. When the notification center sends the store class "ready" signal to the UI, all the UI objects will be loaded (in this case, the coverview class, a uiview, contains only the least application logic), and displays the bookshelves to users.
This is where the app stops processing in the background and waits for user input. There are two possibilities:
- If the magazine has been downloaded, you will see the "read" button. Click this button to read the magazine. In this example, all magazines are PDF files. We can use the quick look framework provided by IOS to display PDF files.
- If the magazine has not been downloaded, you will see the "Download" button. Click this button to start the download and display the progress bar. After the download is complete, we replace the text with the button and hide the progress bar.
The View Controller depends on the store class. To obtain publication information (number of journals, details of each phase), view controllers use simple APIs instead of directly accessing the attributes of the store class.
/* "Numberofissues" is used to retrieve the number of issues in the store */
-(Nsinteger) numberofstoreissues;
/* "Issueatindex:" retrieves the issue at the given Index */
-(Issue *) issueatindex :( nsinteger) index;
/* "Issuewithid:" retrieves the issue with the given ID */
-(Issue *) issuewithid :( nsstring *) issueid;
Based on this simple API and the attributes of a journal, The View Controller creates a journal view (coverview) and places it on the screen.
Download magazine
In a textbook application, there are three important issues: Retrieving and displaying the contents of the bookshelf, downloading and reading magazines (in a textbook app, A good PDF or EPUB Reader is required, but the topic of this article is to introduce the structure and related technologies of the magazine app, and the reader is more related to the user experience ).
According to my idea, when downloading a magazine, the user must not intervene at all, and the user's actions must not affect the download result. Nowadays, many apps have the following Disadvantages: they place a runner in the center of the screen to let users wait and block interaction between users and UI objects. This approach is simple, but not a good experience. While waiting, the user can read other journals, return to the bookstore, and decide to switch to another app temporarily, or eventually close the network.Newsstand KitIt provides system-level methods to simplify developers' work and provide a good user experience.
Once the user starts downloading, the view controller sends a current request to the store class. In the following code (scheduledownloadofissue: method), a network request is generated and sent to the background. Note: The code is divided into two parts based on IOS. For ios5, we must use newsstand -- the download will be placed in the newsstand queue for management by the system; for ios4, we adopt the conventional nsoperation-Based Method: in this case, we cannot simply get the length of the downloaded content, so the progress bar in ios4 is invisible. After newsstand is downloaded, "forfree" is displayed on the progress bar ".
-(Void) scheduledownloadofissue :( issue *) issuetodownload {
Nsstring * DownLoadURL = [issuetodownload DownLoadURL];
Nsurlrequest * downloadrequest = [nsurlrequestrequestwithurl: [nsurl urlwithstring: DownLoadURL];
If (isos5 ()){
// Ios5: Use newsstand
Nkissue * nkissue = [issuetodownloadnewsstandissue];
Nkassetdownload * assetdownload = [nkissueaddassetwithrequest: downloadrequest];
[Assetdownloaddownloadwithdelegate: issuetodownload];
} Else {
// Ios4: Use nsoperation
Nsurlconnection * conn = [nsurlconnectionconnectionwithrequest: downloadrequest delegate: issuetodownload];
Nsinvocationoperation * op = [[nsinvocationoperationalloc] initwithtarget: Self selector: @ selector (startdownload :) object: conn];
If (! Downloadqueue ){
Downloadqueue = [[nsoperationqueuealloc] init];
Downloadqueue. maxconcurrentoperationcount = 1;
}
[Downloadqueue addoperation: op];
[Downloadqueue setsuincluded: No];
}
}
// Ios4 only
-(Void) startdownload :( ID) OBJ {
Nsurlconnection * conn = (nsurlconnection *) OBJ;
[Conn start];
}
In both cases, I want to emphasize the fact that store starts the download thread (operation), but it delegates subsequent work to other classes for implementation, for example, the issue object being downloaded. Therefore, the issue class is used to keep up with the download progress until the download ends.
The issue class will act as the delegate object of the download thread created by the store class. When newsstand is not used, the delegation protocol is different. To use newsstand, you must useNsurlconnectiondownloaddelegateProtocol. If newsstand is not used, useNsurlconnectiondatadelegateProtocol -- it is derived fromNsurlconnectiondelegateProtocol. The difference between the two Protocols is that the former is downloaded to the file system, and the latter is only memory data. In the second case, we store the entire downloaded content in the memory and save it to the disk only after the download is complete-Do not do this in the final product, this is because if the downloaded content reaches MB, the program will crash.
To visualize the download progress and update the UI Based on the progress in real time, we decided to separate the store controller from any UI component. Therefore, we use the KVO model and notifications. When the View Controller starts a download process, it sets itself and the magazine view (coverview) as the observer of the download object (Issue) so that when the download ends (successful or failed) you can update the UI status.
-(Void) downloadissue :( issue *) issue updatecover :( coverview *) cover {
Cover. Progress. Alpha = 1.0;
Cover. Button. Alpha = 0.0;
[Issue addobserver: Cover forkeypath: @ "downloadprogress" Options: nskeyvalueobservingoptionnew context: NULL];
[[Nsicationcenter center defacenter center] addobserver: selfselector: @ selector (issuedidenddownload :) name: issue_end_of_download_notification object: issue];
[[Nsicationcenter center defacenter center] addobserver: selfselector: @ selector (issuedidfaildownload :) name: issue_failed_download_notification object: issue];
[[Nsicationcenter center defacenter center] addobserver: coverselector: @ selector (issuedidenddownload :) name: issue_end_of_download_notification object: issue];
[[Nsicationcenter center defacenter center] addobserver: coverselector: @ selector (issuedidfaildownload :) name: issue_failed_download_notification object: issue];
[_ Store scheduledownloadofissue: issue];
}
When the download thread is terminated, both the cover view and view controller must be deregistered from the notification center. However, cover view is allowed to continue listening to the Process status. Therefore, when the download starts, cover view is registered as the observer of the issue downloadprogress attribute. That is to say, when the download progress changes, coverview will receive a notification of downloadpregress attribute changes and update the progress bar status (therefore, our uiprogressbar has an attribute that varies according to the background status, "download progress "). Coverview deregister itself at the end of the download.
When the download is complete, the issue object copies the downloaded content to the final target folder. Use the newsstand framework. The destination folder is specified by the system. In ios4, the target folder is the caches directory for compatibility with iCloud (ios4 does not support iCloud, but our app runs in both IOS versions at the same time, we must consider both cases ). When newsstand is used, we also need to update the newsstand icon as the cover image. In the following processing code after the download is complete, we simply use a code to achieve this (at the same time, the download completion notification is also posted at the end ).
-(Void) connectiondidfinishdownloading :( nsurlconnection *) connection destinationurl :( nsurl *) destinationurl {
// Copy the file to the destination directory
Nsurl * finalurl = [[self contenturl] urlbyappendingpathcomponent: @ "magazineent"];
Elog (@ "copying item from % @ to % @", destinationurl, finalurl );
[[Nsfilemanager defaultmanager] copyitematurl: destinationurltourl: finalurl error: NULL];
[[Nsfilemanager defaultmanager] removeitematurl: destinationurlerror: NULL];
// Update newsstand icon
[[Uiapplication sharedapplication] setnewsstandiconimage: [selfcoverimage];
// Post notification
[Self sendendofdownloadnotification];
}
Conclusion
This article is coming to an end. We spent a lot of determination to make the app compatible with ios4 and ios5. In the code, you will also find some interesting things, such as a "store Kit" HOOK: this is a typical immature idea of developers, their magazines may be used for sale. In this case, it makes no sense to store the journal price on the publisher's server. We can only retrieve the price information from one place, that is, the Apple store. Therefore, our app must asynchronously query the prices of all publications on the iTunes store and then display them. This part is not added to the code, but a hook is reserved for future extension. If you need it, I will write another extension tutorial. Any suggestions and participation in this project are welcome:
Githubhosted code. You are welcome to apply the sample code as a framework to any app. If you do not like this article, you should at least like the PDF files in it: some literary classics and a Django (a Web open source framework) manual.