i3Factory World

Your Iphone, iPad & Android Application Factory

Browsing Posts tagged how to

Icona Newsstand Icon

The iOS5 revolution

A lot of water has flowed under the bridge since our first part of this tutorial. We’re sorry for the delay, but at the time of writing we were aware of the new featured introduced with iOS5 but we were still under NDA and not authorized to disclose anything about the SDK. Finally we’re now able to provide our example magazine app with the double iOS4/iOS5 compatibility.

 

I will not spend my time explaining all the Newsstand features, all in all we’re creating a magazine app here and the Newsstand implementation details go beyond our original purposes. I have written a two parts tutorial in my personal blog that you can read here and here, and that covers all Newsstand aspects and the required (by Apple reviewers) subscriptions. In short, Newsstand is both a new way to present magazines in the iPad and iPhones, where the original icon is replaced by a cover of the magazine or newspaper and is placed under a special group dedicated to collect all Newsstand icons installed by the user. For the developer it is also a framework, called Newsstand Kit, that introduces a new methodology to organize, download and install the app content.

The example app

The screenshot shows the final appearance of the app. A set of nine magazines with their nine fruity cover. You can download a magazine, see the progress bar change while the operation is in progress and finally read it. The other screenshot shows the appearance in the Newsstand. The typical icon has been replaced by a magazine-like representation inside the Newsstand group. The same, if run on an iPad with iOS4 installed will show the classical icon instead.



The full app code is available on GitHub. Don’t consider it as a production code, so please don’t try to reuse as it is with your clients unless you have tested it in all possible corner cases. Anyway it can be considered as a valid starting point for real apps. Essentially the principles behind the code architecture have been depicted in Part I of this tutorial; if you haven’t read it yet I recommend to jump to that article before entering in this one, at least to understand the key components of such an app. The driving aim is to keep the issues management (controller, don’t confuse it with “view controller”) separated as much as possible from the UI concerns. This means that in theory the whole code behind the Store Manager and Issue Models part can be reused even in a Mac OSX app, as they relationship with UI stuff is minimum.

I have setup this app using the basic single window Xcode template and then added the two main components in the application delegate startup method:

– (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// here we create the “Store” instance
_store = [[Store alloc] init];
[_store startup];self.shelf = [[[ShelfViewController alloc] initWithNibName:nil bundle:nil] autorelease];
_shelf.store=_store;self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
self.window.rootViewController = _shelf;
[self.window makeKeyAndVisible];
return YES;
}

The two components are:

  • the Store class, which represents the Store Manager block in the app architecture; this Class is a subclass of NSObject and is not linked to the user interface.
  • the ShelfViewController represents the UI of the application. It is linked to the store using a specific property. Note that the controller will not access the store exposed properties, but will get the needed information via a simple API. We could have used a delegate pattern instead, but as both blocks are essentially custom blocks in a custom app, there is not much need to define a protocol for their interaction. We can say that the controller part has been split in two parts, one for the UI, the shelf, and one for the back-end, the Store. Their link is such that the Shelf depends on the Store (strict link), not viceversa. Any Store to Shelf communication is done using lazy methods, based on notifications.

The app has then been integrated with the new Newsstand requirements in the info plist. Again have a look at Apple docs or my tutorial for a detailed guide.

Models and Controllers

We have one model in this app, it is the Issue model which represents a single magazine issue in the store or in the user library. There is also one controller, which is the Store. As I said this controller is not a UI component but the app back-end is self sufficient with these two components. In theory we can retrieve the store status and download magazines even without any user interface. This is a basic concept in magazine apps, where many events happen in the background are not related to direct user interaction: this means that the app should be capable to perform several tasks even if the user interface has not been loaded at all.

The role of the Issue class is to represent all relevant magazine properties, that is a unique identifier, a title, a release date, a link to the cover image, obviously the link (URL) to the content (which can be a pdf file, an epub file or a zip file for complex packages). In particular the unique identifier is a piece of data that must be maintained along the full life of the issue: it is the only way to uniquely identify a particular issue, independently of localization issues (e.g. the title can change in different countries, but not the unique ID). Besides it is the link to the Newsstand way to identify issues (the NKIssue‘s name field) and it can be also used to link the product with the App Store if we’re going to implement In App Purchases.

Other than this role, the Issue class will have a central role in the downloading of a magazine. Essentially the Store class will schedule a download but it will be monitored (progress) and then finished (effective magazine installation) by the Issue class.

Finally this class has limited capabilities to represent a magazine already downloaded and available in the user library. For this we provide the simple isIssueAvailableForRead that will be used by the user interface to know which action is possible with an issue (read or download) and eventually display the contents.

The Store class is the app controller. It is initialized at the very beginning of the app and its instance is never released. Besides immediately after initialization this class will fetch the store contents from the publisher server. In the example we decided to implement the store contents as a simple property list, and we instruct the store to retrieve this property list, decode it and create the Issue objects, and finally download their cover images. All this is done asynchronously using the Grand Central Dispatch and at the end a notification will be sent to inform all interested objects (in particular the view controller) that the store contents are ready and the UI can be safely displayed. Note that the store status is represented by a property status. We have overridden its setter method to post a notification that informs any interested observer of the new state. For simplicity we decided to limit the possible statuses to “not initialized”, “downloading”, “ready” and “error”. A more complex app can introduce extra states if needed. Finally as a fallback in case of missing connection (the user must be able to access his/her content even if not connected to the Internet) we reload a locally saved copy of the store.

The central piece of the code is in the downloadStoreIssues method of the class, that we report below:

-(void)downloadStoreIssues {
self.status=StoreStatusDownloading;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0), ^{
NSArray *_list = [[NSArray alloc] initWithContentsOfURL:[NSURL URLWithString:@”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 and storing in the storeIssues array
[_list enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSDictionary *issueDictionary = (NSDictionary *)obj;
Issue *anIssue = [[Issue alloc] init];
anIssue.issueID=[issueDictionary objectForKey:@”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];
[anIssue addInNewsstand];
[storeIssues addObject:anIssue];
[anIssue release];
// dispatch cover loading
if(![anIssue coverImage]) {
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:anIssue.coverURL]];
if(imgData) {
[imgData writeToURL:[anIssue.contentURL URLByAppendingPathComponent:@”cover.png”] atomically:YES];
}
});
}
}];
// let’s save the file locally
[_list writeToURL:[self fileURLOfCachedStoreFile] atomically:YES];
[_list release];
self.status=StoreStatusReady;
} else {
ELog(@”Store download failed.”);
storeIssues = nil;
self.status=StoreStatusError;
}
});
}

Note that in the code above for simplicity I have queued the cover downloading immediately after the property list download. Probably this is not the best way to do this, as we are adding extra networking steps before presenting the app status, with possible delays if network conditions are not good. We can do this in an example where we know that the number of issues is limited, probably in a real app it makes more sense to do this in a background job and then notify the UI to update its status every time a new cover is ready to be displayed.

The view controller

The user interface will wake up immediately, and will change its appearance based on the notifications coming from the store. When the notification center will deliver the store “I’m ready” message to the UI, this one will load all UI issue representations (the CoverView class in the code, a basic view with really minimum application logic) and show the shelf to the user.

At this point the app terminated its back-end processing and is waiting for user commands. Here we have two possibilities:

  • A magazine has been downloaded, the user will see a “READ” button, clicking on it he will be able to read it. In our example all our content is made of pdf files, we decided to use the Quick Look framework in iOS, it is enough for our purposes.
  • A magazine has not been downloaded yet, the user will see a “DOWNLOAD” button, clicking on it the download will start and we’ll show a progress bar. At the end we need to replace the button label and hide the progress. We’ll see this part in more detail.

The view controller is dependent on the store. In order to get the issues information (number of issues, detail of each issue) it will not access directly to the store properties but will use a very simple API, made of three methods:

/* “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 information, and on the Issue properties, it will create the Issue view representation (CoverView) and place it on the screen.

Downloading a magazine

In a magazine app there are three critical sections: retrieve and display of the store contents, download the contents and finally read them (a good PDF or epub reader is mandatory in an app like that; it’s not the purpose of this tutorial, aimed to explain the architecture and the techniques needed to make such an app, but it is a relevant part in the user experience).

My approach in the magazine download is that the user must be completely free to do any action and such actions shouldn’t interfere with the result of the download. Now many apps take a shortcut: they put a spinner in the center of the screen, then block any user interaction with the UI objects behind the spinner and ask the user to wait. It’s easy to do this, but it is not the better user experience. While waiting the user can read another issue, can navigate inside the store or decide to switch temporarily to another application or finally can temporarily lose the network. Newsstand Kit provides a system level methodology that simplifies (in some cases) the developer life but at the same time removes any excuse on not doing a good job in term of user experience.

As soon as the user triggers a new download, the view controller will send its download request to the store class. In the code below you can see the scheduleDownloadOfIssue: code. This method will prepare the network request and will send it in the background. Note how we split the app behavior based on the operating system version we’re on. If it is iOS5 then we must use the Newsstand approach – where the downloading will be managed by the system in a Newsstand queue – , if we are in iOS4 we’ll follow a classic methodology based on NSOperation: in this case I missed for simplicity to get the content length of the content to be downloaded, so in the iOS4 case the progress bar will not be displayed. This information instead if provided “for free” by Newsstand.

-(void)scheduleDownloadOfIssue:(Issue *)issueToDownload {
NSString *downloadURL = [issueToDownload downloadURL];
NSURLRequest *downloadRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:downloadURL]];
if(isOS5()) {
// iOS5 : use Newsstand
NKIssue *nkIssue = [issueToDownload newsstandIssue];
NKAssetDownload *assetDownload = [nkIssue addAssetWithRequest:downloadRequest];
[assetDownload downloadWithDelegate:issueToDownload];
} else {
// iOS4 : use NSOperation
NSURLConnection *conn = [NSURLConnection connectionWithRequest:downloadRequest delegate:issueToDownload];
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(startDownload:) object:conn];
if(!downloadQueue) {
downloadQueue = [[NSOperationQueue alloc] init];
downloadQueue.maxConcurrentOperationCount=1;
}
[downloadQueue addOperation:op];
[downloadQueue setSuspended:NO];
}
}// iOS4 only
-(void)startDownload:(id)obj {
NSURLConnection *conn = (NSURLConnection *)obj;
[conn start];
}

In the two cases I’d like to emphasize the fact that the Store initiates the download operation but then it will delegate any further processing to the interested part, the is the Issue to be downloaded. So it will be the Issue class that will monitor the download and then will finalize it.

The Issue class will behave as delegate of the download operation triggered by the Store class. The delegate protocol changes if you’re using Newsstand or not. In the former case you will need to be compliant with the NSURLConnectionDownloadDelegate protocol while in the latter the protocol to be respected will be the un-documented (or: documented in the header files only!) NSURLConnectionDataDelegate which is a “spin-off” of the classic NSURLConnectionDelegate. The difference between the two protocols in that one will write in the file system, the other in memory. Again don’t use the “data” approach for production purposes, here we’re loading the whole content in memory and then saving on disk when the content has been completely downloaded: really not the best approach if your content is hundred of megabytes, your app will certainly crash.

In order to visually track the progress and then update the user interface, we decided, being coherent of our principles, to keep the application store controller independent on any UI choice. To do this we make use of KVO and Notifications. Essentially when the view controller starts a download, it will set itself and the magazine view as notification observers of the download result, in order to update its status as soon the download terminates (with success or with error):

-(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];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(issueDidEndDownload:) name:ISSUE_END_OF_DOWNLOAD_NOTIFICATION object:issue];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(issueDidFailDownload:) name:ISSUE_FAILED_DOWNLOAD_NOTIFICATION object:issue];
[[NSNotificationCenter defaultCenter] addObserver:cover selector:@selector(issueDidEndDownload:) name:ISSUE_END_OF_DOWNLOAD_NOTIFICATION object:issue];
[[NSNotificationCenter defaultCenter] addObserver:cover selector:@selector(issueDidFailDownload:) name:ISSUE_FAILED_DOWNLOAD_NOTIFICATION object:issue];
[_store scheduleDownloadOfIssue:issue];
}

Both the cover view and the view controller will need to unregister from the notification center as soon as the download operation terminates. Besides to allow the cover view to keep track of the progress status, as the download starts it will be registered as a KVO observer of the downloadProgress property of the Issue. This means that for each change of this property, that will occur during the downloading phase, the cover view will be informed of the change to update the progress bar (so we have here a separation again of the back-end property, the download progress, from the UI, the UIProgressBar). The cover view will unregister itself as soon as download terminates.

When the download terminates, the Issue instance will copy the downloaded content to the final destination. In the Newsstand framework this destination is specified by the system, in iOS4 will copy it in the Caches directory to be compliant with iCloud requirements (yes, iOS4 doesn’t work with iCloud but our app is the same for both generations of the OS, so we must be good citizens in any case).

In the Newsstand case we’ll also need to update the cover image for the Newsstand icon. We’ll do it with a simple command inside the code that manages the download termination (at the end we also send the end of download notification we mentioned previously):

-(void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *)destinationURL {
// copy the file to the destination directory
NSURL *finalURL = [[self contentURL] URLByAppendingPathComponent:@”magazine.pdf”];
ELog(@”Copying item from %@ to %@”,destinationURL,finalURL);
[[NSFileManager defaultManager] copyItemAtURL:destinationURL toURL:finalURL error:NULL];
[[NSFileManager defaultManager] removeItemAtURL:destinationURL error:NULL];
// update Newsstand icon
[[UIApplication sharedApplication] setNewsstandIconImage:[self coverImage]];
// post notification
[self sendEndOfDownloadNotification];
}

Conclusions

This is the conclusion of this tutorial. We decided after a long interval to make an effort to propose a complete app valid for both iOS4 and iOS5 environments. In the code you will find more interesting stuff, e.g. a “hook” to the Store Kit: this is a typical issue not considered by developers when they make applications that sell magazines. In such case storing the issue price in the publisher server is not of help as we must retrieve the pricing information from the only relevant source, that is the App Store. To do this our app must – asynchronously – query the iTunes store for the prices of all the publications and then display them once retrieved. I’m not adding this extra step in the code, just the hook as placeholder for future extensions, but of course if required by our readers I can extend this tutorial further. I would appreciate any suggestion and any contribution in the GitHub hosted code, I would like to see the example code provided with this tutorial as a good skeleton for new apps. If you didn’t enjoy this article, at least you can enjoy the literature classics (and a Django manual…) that I used to create my PDF files.

Carlo Vigiani

To automatically support orientations in your iPhone and iPad apps we have to do 3 things:
1) Provide launch images
2) Update your Info.plist settings
3) Implement the -shouldAutorotateToInterfaceOrientation: method.
IMPORTANT: It is strongly recommended that your application support all orientations. This includes portrait, portrait upside-down, landscape left and landscape right. iPad apps that require an orientation must support both variants of that orientation.

1) Provide Launch Images

IMPORTANT: All file names are case sensitive.
iPhone-only applications:
iPhone-only applications may only have one launch image. It should be in PNG format and measure 320 x 480 pixels. Name your launch image file Default.png.
For iPhone 4 high resolution, you can include an additional launch image. It should be in PNG formate and measure 640 x 960 pixels. Name it Default@2x.png. This image will get picked up by the iOS if your app is running on an iPhone 4.
Note: If your app is not running on an iPhone 4, and you provided both Default.png and Default@2x.png, iOS will automatically pick up Default.png as the launch image.
iPad-only applications:
Create a launch image for each supported orientation in the PNG format. Each launch image must be 1004 x 768 pixels (for landscape) or 748 x 1024 pixels (for portrait).
Default launch image files:
  • Default-PortraitUpsideDown.png – upside-down portrait version.
  • Default-LandscapeLeft.png – left-oriented landscape version.
  • Default-LandscapeRight.png – right-oriented landscape version.
  • Default-Portrait.png – generic portrait version.
  • Default-Landscape.png – generic landscape version.
  • Default.png – default portrait launch. Its usage is strongly discouraged, use more specific launch images instead.
Note: For iPad-only applications, it is common application to provide only Default-Portrait.png and Default-Landscape.png. More specific launch images will take precedence over the generic versions, for example Default-PortraitUpsideDown.png takes precedence over the Default-Portrait.png image file for this specific orientation.
See the ios reference guide site “Providing Launch Images for Different Orientations” section on the iPad Programming Guide for more information.
Universal applications:
Include launch images for both iPhone and iPad. iPhone launch image will still be named Default.png and Default@2x.png. Name your iPad portrait launch image Default-Portrait.png (do not use Default.png as the iPad portrait launch image).

Update Your Info.plist Settings

In your Info.plist file:
  • Specify values for the UISupportedInterfaceOrientations key for the supported orientations.
  • Specify values for the UIInterfaceOrientation key for the initial launch orientation.
3) Method
This method returns which orientations are allowed (Portrait, PortraitUpsideDown, LandscapeLeft and LandscapeRight). Calling this method would not do anything as your class shouldn’t change the orientation dynamically.

That method doesn’t cause the interface to rotate, it just decides if the device is ALLOWED to.

So if you want to change the factors you use to determine if rotating is allowed, you’ll have to create an instance variable.

make your header have something like this:

@interface MyClassName : NSObject {     BOOL canLandscape; } 

In your “other” method, set that flag (canLandscape = YES;).

In your shouldAutorotateToInterfaceOrientation:, you can check this to help you decide

if (canLandscape) {     ...dosomethinghere... }  

L’utilizzo dei gradienti e’ molto diffuso nei componenti standard di UIKit (il framework Cocoa Touch di riferimento dell’interfaccia utente in iOS) oltre che in un gran numero di applicazioni. Sarebbe bello che le API di UIKit fornissero un modo comodo per impostare i gradienti, ma attualmente non e’ cosi’, sebbene le librerie grafiche di base “Quartz” contengano supporto nativo a tale funzionalita’. In aggiunta mentre MacOSX dispone della classe NSGradient che consente di configurare dei gradienti, anche tramite curve di Bezier, questa classe non e’ disponibile all’interno di UIKit. Piuttosto su CocoaTouch, per tramite delle librerie grafiche contenute nel framework Quartz, abbiamo accesso al layer CAGradientLayer che consente di costuire dei gradienti di tipo assiale, ossia lineari lungo un asse passante per due punti (a differenza di NSGradient che consente la creazione anche di gradienti radiali o su curve di Bezier).
La soluzione in UIKit e’ piuttosto semplice e richiede la creazione di una sottoclasse di UIView e l’utilizzo del layer CAGradientLayer. Vediamo perche’.
Innanzitutto che cos’e’ un layer: il layer e’ l’elemento grafico di base usato dal sistema per il rendering grafico della view sullo schermo oltre che essere l’elemento di base per tutte le animazioni che si vedono nei dispositivi basati su iOS. Nel momento in cui una view viene creata ad essa e’ associato un layer di riferimento che verra’ utilizzato dalla view, senza possibilita’ di essere modificato. Per poter conoscere che tipo di layer si intende utilizzare, il sistema durante la creazione della view chiama il metodo di classe layerClass il cui unico compito e’ quello di indicare il nome della classe (derivata da CALayer) che dovra’ essere utilizzata per quella view. Ora dato che layerClass e’ un metodo di classe, ossia un metodo che opera su oggetti classe e non su oggetti istanza, noi non possiamo associare il layer alla nostra istanza di view semplicemente cambiando una property, ma saremo costretti a costruire una sottoclasse per la nostra vista, che chiameremo per esempio GradientView che conterra’ la seguente porzione di codice:


+(Class)layerClass {
return([CAGradientLayer class]);
}

Da questo momento in poi tutte le view di tipo GradientView verranno costruite a partire da un layer gradiente. Quel che resta da fare e’ impostare i colori e gli “stop” che costituiranno il gradiente. Supponendo allora di instanziare la view tramite Interface Builder, costruiremo il codice di inizializzazione della view nel metodo initWithCoder: nella seguente maniera, in cui abbiamo costruito un semplice gradiente tra i colori rosso e giallo:


- (id)initWithCoder:(NSCoder *)decoder {

self = [super initWithCoder:decoder];
if (self) {
// Initialization code.
[(CAGradientLayer *)self.layer setColors:[NSArray arrayWithObjects:(id)[UIColor redColor].CGColor,(id)[UIColor yellowColor].CGColor,nil]];
}
return self;
}

Si noti come si sia dovuto effettuare il casting di self.layer per evitare un warning in compilazione.
E’ ovvio che possiamo facilmente estendere il nostro codice permettendo l’impostazione programmatica dei colori, cosi’ da poterli definire in fase di instanziazione, lasciando eventualmente dei default nei metodi di inizializzazione.

Gradiente assiale

By continuing to use the site, you agree to the use of cookies. more information

The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.

Close