Skip to content

Commit

Permalink
Merge pull request #8 from misterwindupbird/ondemand
Browse files Browse the repository at this point in the history
Icons are now only downloaded on demand (when the PhotoAppLink interface is first presented). Previously all icons were downloaded on app start, even if users never used the PhotoAppLink feature.
The library now requires iOS 4 or later.
  • Loading branch information
pocketpixels committed Jul 27, 2012
2 parents a3fd6d9 + 5684817 commit 0bbf352
Show file tree
Hide file tree
Showing 13 changed files with 236 additions and 114 deletions.
3 changes: 0 additions & 3 deletions PhotoAppLink/PALAppInfo.h
Expand Up @@ -33,7 +33,6 @@
NSString* appleID;
NSString* platform;
BOOL freeApp;
UIImage* thumbnail;
NSURL* thumbnailURL;
}

Expand Down Expand Up @@ -63,8 +62,6 @@
@property (nonatomic, copy) NSString *platform;
// whether the app is free or paid
@property (nonatomic) BOOL freeApp;
// The image thumbnail (with appropriate scale for the device)
@property (nonatomic, readonly) UIImage* thumbnail;
// URL to thumbnail image
@property (nonatomic, readonly) NSURL* thumbnailURL;

Expand Down
11 changes: 1 addition & 10 deletions PhotoAppLink/PALAppInfo.m
Expand Up @@ -11,7 +11,7 @@ @implementation PALAppInfo
@synthesize name, scheme, appDescription, bundleID, appleID;
@synthesize platform, freeApp;
@synthesize thumbnailURL, installed, liveOnAppStore, canSend, canReceive;
@synthesize thumbnail;


- (id)initWithPropertyDict:(NSDictionary*)properties {
self = [super init];
Expand Down Expand Up @@ -45,7 +45,6 @@ - (void) dealloc
[bundleID release];
[appleID release];
[thumbnailURL release];
[thumbnail release];
[platform release];
[super dealloc];
}
Expand All @@ -59,13 +58,5 @@ - (NSURL*)appStoreLink
return [NSURL URLWithString:affiliateLink];
}

- (UIImage*)thumbnail
{
if (thumbnail) return thumbnail;
thumbnail = [[[PALManager sharedPALManager] cachedIconForApp:self] retain];
if (thumbnail) return thumbnail;
else return [UIImage imageNamed:GENERIC_APP_ICON];
}

@end

12 changes: 6 additions & 6 deletions PhotoAppLink/PALConfig.h
Expand Up @@ -9,12 +9,6 @@
#define DEBUG_PLIST_URL @"http://www.photoapplink.com/photoapplink_debug.plist"


// If you are not using app icons to display the list of supported apps in your UI
// (for example because you only use the action sheet displaying the app names)
// you can set this to NO to disable downloading of these app icons
#define USING_APP_ICONS YES


// Substitute your own Linkshare Site ID here if you like.
// This Linkshare ID is used to create affiliate links to
// supported apps on the App Store
Expand All @@ -24,3 +18,9 @@
// The image to use for apps with missing icons
// The file has to be included in the app bundle
#define GENERIC_APP_ICON @"PAL_unknown_app_icon.png"


// The placeholder image to show while the image
// is loading. It will be replaced with the actual
// app icon or the generic icon.
#define PLACEHOLDER_APP_ICON @"PAL_default_app_icon.png"
11 changes: 6 additions & 5 deletions PhotoAppLink/PALManager.h
Expand Up @@ -22,12 +22,11 @@

#import <Foundation/Foundation.h>

@class PALAppInfo;
typedef void(^PALImageRequestHandler)(UIImage* image, NSError* error);

@interface PALManager : NSObject <UIActionSheetDelegate> {
NSArray* supportedApps;
UIImage* imageToSend;
}
@class PALAppInfo;

@interface PALManager : NSObject <UIActionSheetDelegate, NSURLConnectionDelegate>

// the PALAppInfo objects of the photo editing apps that are installed on the user's device
// and support receiving images via the photo app link protocol
Expand Down Expand Up @@ -61,4 +60,6 @@

- (UIImage*)cachedIconForApp:(PALAppInfo*)app;

- (void)asyncIconForApp:(PALAppInfo*)app withCompletion:(PALImageRequestHandler)completion;

@end
166 changes: 117 additions & 49 deletions PhotoAppLink/PALManager.m
Expand Up @@ -30,6 +30,11 @@
static NSString *const LAUNCH_DATE_KEY = @"launchDate";
static NSString *const SUPPORTED_APPS_PLIST_KEY = @"supportedApps";
static NSString *const PASTEBOARD_NAME = @"com.photoapplink.pasteboard";
static NSString *const RECEIVED_DATA_KEY = @"ReceivedDataKey";
static NSString *const COMPLETION_BLOCK_KEY = @"CompletionBlockKey";
static NSString *const APPINFO_KEY = @"AppInfoKey";

static const int kImageDownloadErrorCode = 2001;

#ifdef DEBUG
const int MINIMUM_SECS_BETWEEN_UPDATES = 0;
Expand All @@ -38,17 +43,39 @@
const int MINIMUM_SECS_BETWEEN_UPDATES = 4 * 60 * 60;
#endif

@interface PALManager()
@interface PALManager()
{
CFMutableDictionaryRef connectionToData;
NSMutableDictionary* loadedIcons;
}
@property (nonatomic,copy) NSArray *supportedApps;
@property (nonatomic,retain) UIImage *imageToSend;
- (void)downloadAndCacheIconsForAllApps;
@end

@implementation PALManager

@synthesize supportedApps;
@synthesize imageToSend;

// Since the PALManager class is a singleton, the init method will only ever be called once
- (id)init
{
self = [super init];
if (self) {
loadedIcons = [NSMutableDictionary new];

// This will map NSURLConnections to downloaded data using the connection as the key. We can't use NSMutableDictionary
// because NSURLConnection does not support copy.
connectionToData = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(receivedMemoryWarning)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
[self createAppIconCacheDirectory];
}
return self;
}

// trigger background update of the list of supported apps
// This update is only performed once every few days
Expand All @@ -64,10 +91,6 @@ - (void)updateSupportedAppsInBackground
if (!lastUpdateDate || secondsSinceLastUpdate > MINIMUM_SECS_BETWEEN_UPDATES) {
[self performSelectorInBackground:@selector(requestSupportedAppURLSchemesUpdate) withObject:nil];
}
else if (USING_APP_ICONS){
// still check for any missing app icons and download them
[self performSelectorInBackground:@selector(downloadAndCacheIconsForAllApps) withObject:nil];
}
}


Expand Down Expand Up @@ -125,10 +148,6 @@ - (void)requestSupportedAppURLSchemesUpdate
[userPrefs synchronize];
if (plist) CFRelease(plist);
}
if (USING_APP_ICONS) {
// download app icons for all apps in the list of supported apps
[self downloadAndCacheIconsForAllApps];
}
}
@catch (NSException * e) {
NSLog(@"Caught exception in -[PALManager requestSupportedAppURLSchemesUpdate]: %@", e);
Expand Down Expand Up @@ -237,12 +256,6 @@ - (void)createAppIconCacheDirectory
}
}

- (void)clearAppIconCache
{
[[NSFileManager defaultManager] removeItemAtPath:[self appIconCacheDirectory] error:nil];
[self createAppIconCacheDirectory];
}

- (NSString*)cachedIconPathForApp:(PALAppInfo*)app
{
NSString* lastPathComponent = [[[app.thumbnailURL path] componentsSeparatedByString:@"/"] lastObject];
Expand All @@ -253,55 +266,110 @@ - (NSString*)cachedIconPathForApp:(PALAppInfo*)app

- (UIImage*)cachedIconForApp:(PALAppInfo*)app
{
UIImage* icon = [UIImage imageWithContentsOfFile:[self cachedIconPathForApp:app]];
UIImage* icon = [loadedIcons objectForKey:app.thumbnailURL];

if (icon == nil) {
icon = [UIImage imageWithContentsOfFile:[self cachedIconPathForApp:app]];

// if we pulled an icons from the cache, keep it in memory
if (icon != nil) {
[loadedIcons setObject:icon forKey:app.thumbnailURL];
}
}
if (icon == nil) return nil;
return [self screenScaleImageForImage:icon];
}

- (UIImage*)screenScaleImageForImage:(UIImage*)image
{
BOOL isRetina = [[UIScreen mainScreen] respondsToSelector:@selector(scale)] && [[UIScreen mainScreen] scale] == 2.0f;
if (!isRetina || [icon scale] > 1.0) return icon;
if (!isRetina || [image scale] > 1.0) return image;
else {
// need to create image with appropriate scale
float scale = [[UIScreen mainScreen] scale];
UIImageOrientation orientation = [icon imageOrientation];
UIImage* retinaIcon = [UIImage imageWithCGImage:icon.CGImage scale:scale orientation:orientation];
return retinaIcon;
UIImageOrientation orientation = [image imageOrientation];
UIImage* retinaImage = [UIImage imageWithCGImage:image.CGImage scale:scale orientation:orientation];
return retinaImage;
}
}

- (void)downloadAndCacheIconsForAllApps

- (void)asyncIconForApp:(PALAppInfo *)appInfo
withCompletion:(PALImageRequestHandler)completion
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
@try {
// ensure that the cache directory exists
[self createAppIconCacheDirectory];

NSUserDefaults* userPrefs = [NSUserDefaults standardUserDefaults];
NSDictionary* plistDict = [userPrefs dictionaryForKey:PLIST_DICT_USERPREF_KEY];
NSArray* plistApps = [plistDict objectForKey:SUPPORTED_APPS_PLIST_KEY];
if (plistApps == nil) return;
NSURLRequest* request = [[NSURLRequest alloc] initWithURL:appInfo.thumbnailURL
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:60.0];
NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
[request release];

NSMutableDictionary* connectionInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:[NSMutableData data], RECEIVED_DATA_KEY,
[[completion copy] autorelease], COMPLETION_BLOCK_KEY,
appInfo, APPINFO_KEY, nil];
CFDictionaryAddValue(connectionToData, connection, connectionInfo);
}


- (void)receivedMemoryWarning
{
if (loadedIcons != nil) [loadedIcons removeAllObjects];
}



#pragma mark -
#pragma mark NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSMutableDictionary* connectionInfo = CFDictionaryGetValue(connectionToData, connection);
[[connectionInfo objectForKey:RECEIVED_DATA_KEY] appendData:data];
}


- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSMutableDictionary* connectionInfo = CFDictionaryGetValue(connectionToData, connection);
PALImageRequestHandler completion = [connectionInfo objectForKey:COMPLETION_BLOCK_KEY];
NSData* imageData = [connectionInfo objectForKey:RECEIVED_DATA_KEY];
PALAppInfo* appInfo = [connectionInfo objectForKey:APPINFO_KEY];

UIImage* image = [UIImage imageWithData:imageData];

if (image == nil) {
NSError* downloadError = [NSError errorWithDomain:@"com.photoapplink" code:kImageDownloadErrorCode userInfo:nil];
completion(nil, downloadError);
}
else {
image = [self screenScaleImageForImage:image];
NSString* cachedIconPath = [self cachedIconPathForApp:appInfo];
[imageData writeToFile:cachedIconPath atomically:YES];

for (NSDictionary* plistAppInfo in plistApps) {
PALAppInfo* appInfo = [[PALAppInfo alloc] initWithPropertyDict:plistAppInfo];
NSString* cachedIconPath = [self cachedIconPathForApp:appInfo];
if (![[NSFileManager defaultManager] isReadableFileAtPath:cachedIconPath]) {
NSData* imageData = [[NSData alloc] initWithContentsOfURL:appInfo.thumbnailURL];
// verify that the data is actually an image
UIImage* image = [[UIImage alloc] initWithData:imageData];
if (image != nil) {
[imageData writeToFile:cachedIconPath atomically:YES];
}
[imageData release];
[image release];
}
[appInfo release];
if (loadedIcons != nil) {
[loadedIcons setObject:image forKey:appInfo.thumbnailURL];
}
}
@catch (NSException * e) {
NSLog(@"Caught exception in -[PALManager requestSupportedAppURLSchemesUpdate]: %@", e);
completion(image, nil);
}

[pool release];
CFDictionaryRemoveValue(connectionToData, connection);
[connection release];
}


- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSMutableDictionary* connectionInfo;
connectionInfo = CFDictionaryGetValue(connectionToData, connection);
PALImageRequestHandler completion = [connectionInfo objectForKey:COMPLETION_BLOCK_KEY];

completion(nil, error);

CFDictionaryRemoveValue(connectionToData, connection);
[connection release];
}

#pragma mark -
#pragma mark Action Sheet

Expand Down
1 change: 1 addition & 0 deletions PhotoAppLink/PALMoreAppsTableCellView.h
Expand Up @@ -24,6 +24,7 @@

@interface PALMoreAppsTableCellView : UIView {
PALAppInfo* appInfo;
UIImage* icon;
}

@property (nonatomic, retain) PALAppInfo *appInfo;
Expand Down

0 comments on commit 0bbf352

Please sign in to comment.