Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit.

  • Loading branch information...
commit 04ea16182594eb8c60849cb8211fe0fcbd6d7210 0 parents
@iHasApp iHasApp authored
Showing with 2,574 additions and 0 deletions.
  1. +4 −0 .gitignore
  2. +40 −0 README.md
  3. +1 −0  iHasApp.framework/Headers
  4. +1 −0  iHasApp.framework/Resources
  5. +120 −0 iHasApp.framework/Versions/A/Headers/iHasApp.h
  6. BIN  iHasApp.framework/Versions/A/Resources/Info.plist
  7. BIN  iHasApp.framework/Versions/A/Resources/InfoPlist.strings
  8. BIN  iHasApp.framework/Versions/A/Resources/iHasApp-Info.plist
  9. BIN  iHasApp.framework/Versions/A/iHasApp
  10. +1 −0  iHasApp.framework/Versions/Current
  11. +1 −0  iHasApp.framework/iHasApp
  12. +356 −0 iHasAppExample/iHasAppExample.xcodeproj/project.pbxproj
  13. +7 −0 iHasAppExample/iHasAppExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  14. BIN  ...AppExample.xcodeproj/project.xcworkspace/xcuserdata/danielamitay.xcuserdatad/UserInterfaceState.xcuserstate
  15. +85 −0 iHasAppExample/iHasAppExample.xcodeproj/xcuserdata/danielamitay.xcuserdatad/xcschemes/iHasAppExample.xcscheme
  16. +22 −0 iHasAppExample/iHasAppExample.xcodeproj/xcuserdata/danielamitay.xcuserdatad/xcschemes/xcschememanagement.plist
  17. +19 −0 iHasAppExample/iHasAppExample/AppDelegate.h
  18. +50 −0 iHasAppExample/iHasAppExample/AppDelegate.m
  19. +15 −0 iHasAppExample/iHasAppExample/DetailViewController.h
  20. +146 −0 iHasAppExample/iHasAppExample/DetailViewController.m
  21. +17 −0 iHasAppExample/iHasAppExample/MasterViewController.h
  22. +196 −0 iHasAppExample/iHasAppExample/MasterViewController.m
  23. +32 −0 iHasAppExample/iHasAppExample/SDWebImage/SDImageCache.h
  24. +340 −0 iHasAppExample/iHasAppExample/SDWebImage/SDImageCache.m
  25. +19 −0 iHasAppExample/iHasAppExample/SDWebImage/SDImageCacheDelegate.h
  26. +21 −0 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageCompat.h
  27. +36 −0 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageDecoder.h
  28. +124 −0 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageDecoder.m
  29. +42 −0 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageDownloader.h
  30. +174 −0 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageDownloader.m
  31. +21 −0 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageDownloaderDelegate.h
  32. +39 −0 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageManager.h
  33. +290 −0 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageManager.m
  34. +19 −0 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageManagerDelegate.h
  35. +44 −0 iHasAppExample/iHasAppExample/SDWebImage/SDWebImagePrefetcher.h
  36. +112 −0 iHasAppExample/iHasAppExample/SDWebImage/SDWebImagePrefetcher.m
  37. +52 −0 iHasAppExample/iHasAppExample/SDWebImage/UIImageView+WebCache.h
  38. +48 −0 iHasAppExample/iHasAppExample/SDWebImage/UIImageView+WebCache.m
  39. BIN  iHasAppExample/iHasAppExample/SDWebImage/placeholder-icon.png
  40. +2 −0  iHasAppExample/iHasAppExample/en.lproj/InfoPlist.strings
  41. +45 −0 iHasAppExample/iHasAppExample/iHasAppExample-Info.plist
  42. +14 −0 iHasAppExample/iHasAppExample/iHasAppExample-Prefix.pch
  43. +19 −0 iHasAppExample/iHasAppExample/main.m
4 .gitignore
@@ -0,0 +1,4 @@
+
+iHasApp.framework/.DS_Store
+
+.DS_Store
40 README.md
@@ -0,0 +1,40 @@
+iHasApp Framework
+=========================
+
+The iHasApp iOS Framework allows you to detect installed apps on a user's device.
+
+Basic Setup
+-----------
+
+1. Sign up for a free account on [iHasApp.com](https://www.ihasapp.com) to receive your api key.
+
+2. Add iHasApp.framework to your project and ensure that it is linked to the project target.
+
+3. Add `#import <iHasApp/iHasApp.h>` to the files in which you wish to access iHasApp.
+
+4. Initialize and configure the iHasApp object.
+
+App Store Safe
+--------------
+
+The iHasApp framework utilizes only public, documented, and non-deprecated APIs. It is completely App Store safe.
+There are already a number of approved apps on the App Store that have integrated iHasApp.
+
+Like all things, it is always a prudent idea to either ask your users' permission or to include a clause in your EULA.
+
+Example Application
+--------------
+
+This framework comes with a demo application that demonstrates initialization, country configuration (automatically grabbing the device's current locale), delegate methods, and information display. You will need to insert your own API key in the '-viewDidLoad' section of 'MasterViewController.m'.
+
+The iHasAppExample project uses Olivier Poitrey's [SDWebImage](https://github.com/rs/SDWebImage) project to asynchronously display the app icons.
+
+Documentation
+--------------
+
+The 'iHasApp.h' header file is structurally commented. If you would like to see the Appledoc representation, visit the [iHasApp Documentation](https://www.ihasapp.com/documentation).
+
+Troubleshooting
+--------------
+
+Feel free to contact me at daniel@ihasapp.com
1  iHasApp.framework/Headers
1  iHasApp.framework/Resources
120 iHasApp.framework/Versions/A/Headers/iHasApp.h
@@ -0,0 +1,120 @@
+//
+// iHasApp.h
+// iHasApp
+//
+// Created by Daniel Amitay on 4/30/12.
+// Copyright (c) 2012 Objective-See, LLC. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+typedef enum {
+ iHasAppErrorConnection = 0,
+ iHasAppErrorInvalidKey = 1,
+ iHasAppErrorReachedLimit = 2,
+ iHasAppErrorUnknown = 3
+} iHasAppError;
+
+
+/** The iHasAppDelegate protocol describes the interface iHasApp delegates should adopt to respond to app detection events.
+ */
+@protocol iHasAppDelegate <NSObject>
+
+@optional
+
+/** Sent to the delegate when the app detection has successfully completed.
+
+ @param allApps An array containing NSDictionaries of the iHasApp object's most recently detected apps. Equivalent to calling -detectedApps on the iHasApp object.
+ @see appDetectionDidSucceed:
+ */
+- (void)appDetectionDidSucceed:(NSArray *)allApps;
+
+/** Sent to the delegate when the app detection has unsuccessfully terminated.
+
+ @param detectionError Contains an error enum describing the problem.
+ @see appDetectionDidSucceed:
+ */
+- (void)appDetectionDidFail:(iHasAppError)detectionError;
+
+@end
+
+
+/** The `iHasApp` class is used to perform on-device app detection.
+
+ You will need to register for a free account at https://www.ihasapp.com to obtain a valid API key.
+ */
+@interface iHasApp : NSObject
+
+/**---------------------------------------------------------------------------------------
+ * @name Properties
+ * ---------------------------------------------------------------------------------------
+ */
+
+/** The API key from your https://www.ihasapp.com account dashboard. An incorrect API key will result in a failed app detection. Default is nil.
+ */
+@property (nonatomic, strong) NSString *APIKey;
+
+/** The delegate object to receive detection events.
+ */
+@property (nonatomic, assign) NSObject<iHasAppDelegate> *delegate;
+
+/** The two-letter country code for the store you want to search. The search uses the default store front for the specified country. Default is US.
+
+ See http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 for a list of ISO Country Codes.
+
+ //To determine device-specific country codes, use:
+ NSString *countryCode = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
+
+ */
+@property (nonatomic, strong) NSString *country;
+
+/**---------------------------------------------------------------------------------------
+ * @name Starting the Detection
+ * ---------------------------------------------------------------------------------------
+ */
+
+/** Initializes and returns the iHasApp object with the specified delegate and APIKey.
+
+ @param delegate The object to receive the delegate callbacks.
+ @param key The API key from your https://www.ihasapp.com account dashboard.
+ @return An initialized iHasApp object.
+ */
+- (iHasApp *)initWithDelegate:(id<iHasAppDelegate>)delegate andKey:(NSString *)key;
+
+
+/** Starts the app detection process.
+ */
+- (void)beginDetection;
+
+/**---------------------------------------------------------------------------------------
+ * @name Informational
+ * ---------------------------------------------------------------------------------------
+ */
+
+/** Returns an array containing NSDictionaries of the most recently detected apps.
+
+ @return An array containing NSDictionaries of the iHasApp object's most recently detected apps, or nil if the detection is incomplete or unsuccessful. The order of the dictionaries in the array is not defined.
+
+ The dictionaries are the same as the results returned from an iTunes Search API request. See the "results" of the following api response for an example: http://itunes.apple.com/lookup?id=284882215
+
+ @see appIds
+ */
+- (NSArray *)detectedApps;
+
+
+/** Returns an array containing NSStrings of the most recently detected app ids (referred to as 'trackId' in iTunes dictionaries).
+
+ @return An array containing NSStrings of the iHasApp object's most recently detected app ids, or nil if the detection is incomplete or unsuccessful. The order of the strings in the array is not defined.
+ @see detectedApps
+ */
+- (NSArray *)appIds;
+
+
+/** Returns the iHasApp framework version string.
+
+ @return An NSString representation of the iHasApp framework version.
+ */
+- (NSString *)version;
+
+
+@end
BIN  iHasApp.framework/Versions/A/Resources/Info.plist
Binary file not shown
BIN  iHasApp.framework/Versions/A/Resources/InfoPlist.strings
Binary file not shown
BIN  iHasApp.framework/Versions/A/Resources/iHasApp-Info.plist
Binary file not shown
BIN  iHasApp.framework/Versions/A/iHasApp
Binary file not shown
1  iHasApp.framework/Versions/Current
1  iHasApp.framework/iHasApp
356 iHasAppExample/iHasAppExample.xcodeproj/project.pbxproj
@@ -0,0 +1,356 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 634F3C301593C41900D6361C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 634F3C2F1593C41900D6361C /* UIKit.framework */; };
+ 634F3C321593C41900D6361C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 634F3C311593C41900D6361C /* Foundation.framework */; };
+ 634F3C341593C41900D6361C /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 634F3C331593C41900D6361C /* CoreGraphics.framework */; };
+ 634F3C3A1593C41900D6361C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 634F3C381593C41900D6361C /* InfoPlist.strings */; };
+ 634F3C3C1593C41900D6361C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 634F3C3B1593C41900D6361C /* main.m */; };
+ 634F3C401593C41900D6361C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 634F3C3F1593C41900D6361C /* AppDelegate.m */; };
+ 634F3C431593C41900D6361C /* MasterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 634F3C421593C41900D6361C /* MasterViewController.m */; };
+ 634F3C461593C41900D6361C /* DetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 634F3C451593C41900D6361C /* DetailViewController.m */; };
+ 634F3C6D1593CC5C00D6361C /* SDImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 634F3C5E1593CC5C00D6361C /* SDImageCache.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+ 634F3C6E1593CC5C00D6361C /* SDWebImageDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 634F3C621593CC5C00D6361C /* SDWebImageDecoder.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+ 634F3C6F1593CC5C00D6361C /* SDWebImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 634F3C641593CC5C00D6361C /* SDWebImageDownloader.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+ 634F3C701593CC5C00D6361C /* SDWebImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 634F3C671593CC5C00D6361C /* SDWebImageManager.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+ 634F3C711593CC5C00D6361C /* SDWebImagePrefetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 634F3C6A1593CC5C00D6361C /* SDWebImagePrefetcher.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+ 634F3C721593CC5C00D6361C /* UIImageView+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 634F3C6C1593CC5C00D6361C /* UIImageView+WebCache.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+ 634F3C761593CCEF00D6361C /* placeholder-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 634F3C751593CCEF00D6361C /* placeholder-icon.png */; };
+ 634F3C7C1593D3E000D6361C /* iHasApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 634F3C7B1593D3E000D6361C /* iHasApp.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 634F3C2B1593C41900D6361C /* iHasAppExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iHasAppExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 634F3C2F1593C41900D6361C /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
+ 634F3C311593C41900D6361C /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+ 634F3C331593C41900D6361C /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
+ 634F3C371593C41900D6361C /* iHasAppExample-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "iHasAppExample-Info.plist"; sourceTree = "<group>"; };
+ 634F3C391593C41900D6361C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+ 634F3C3B1593C41900D6361C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ 634F3C3D1593C41900D6361C /* iHasAppExample-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "iHasAppExample-Prefix.pch"; sourceTree = "<group>"; };
+ 634F3C3E1593C41900D6361C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+ 634F3C3F1593C41900D6361C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+ 634F3C411593C41900D6361C /* MasterViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MasterViewController.h; sourceTree = "<group>"; };
+ 634F3C421593C41900D6361C /* MasterViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MasterViewController.m; sourceTree = "<group>"; };
+ 634F3C441593C41900D6361C /* DetailViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DetailViewController.h; sourceTree = "<group>"; };
+ 634F3C451593C41900D6361C /* DetailViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DetailViewController.m; sourceTree = "<group>"; };
+ 634F3C5D1593CC5C00D6361C /* SDImageCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDImageCache.h; sourceTree = "<group>"; };
+ 634F3C5E1593CC5C00D6361C /* SDImageCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDImageCache.m; sourceTree = "<group>"; };
+ 634F3C5F1593CC5C00D6361C /* SDImageCacheDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDImageCacheDelegate.h; sourceTree = "<group>"; };
+ 634F3C601593CC5C00D6361C /* SDWebImageCompat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageCompat.h; sourceTree = "<group>"; };
+ 634F3C611593CC5C00D6361C /* SDWebImageDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageDecoder.h; sourceTree = "<group>"; };
+ 634F3C621593CC5C00D6361C /* SDWebImageDecoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageDecoder.m; sourceTree = "<group>"; };
+ 634F3C631593CC5C00D6361C /* SDWebImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageDownloader.h; sourceTree = "<group>"; };
+ 634F3C641593CC5C00D6361C /* SDWebImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageDownloader.m; sourceTree = "<group>"; };
+ 634F3C651593CC5C00D6361C /* SDWebImageDownloaderDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageDownloaderDelegate.h; sourceTree = "<group>"; };
+ 634F3C661593CC5C00D6361C /* SDWebImageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageManager.h; sourceTree = "<group>"; };
+ 634F3C671593CC5C00D6361C /* SDWebImageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageManager.m; sourceTree = "<group>"; };
+ 634F3C681593CC5C00D6361C /* SDWebImageManagerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageManagerDelegate.h; sourceTree = "<group>"; };
+ 634F3C691593CC5C00D6361C /* SDWebImagePrefetcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImagePrefetcher.h; sourceTree = "<group>"; };
+ 634F3C6A1593CC5C00D6361C /* SDWebImagePrefetcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImagePrefetcher.m; sourceTree = "<group>"; };
+ 634F3C6B1593CC5C00D6361C /* UIImageView+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImageView+WebCache.h"; sourceTree = "<group>"; };
+ 634F3C6C1593CC5C00D6361C /* UIImageView+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImageView+WebCache.m"; sourceTree = "<group>"; };
+ 634F3C751593CCEF00D6361C /* placeholder-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "placeholder-icon.png"; sourceTree = "<group>"; };
+ 634F3C7B1593D3E000D6361C /* iHasApp.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = iHasApp.framework; path = ../../iHasApp.framework; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 634F3C281593C41900D6361C /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 634F3C301593C41900D6361C /* UIKit.framework in Frameworks */,
+ 634F3C321593C41900D6361C /* Foundation.framework in Frameworks */,
+ 634F3C341593C41900D6361C /* CoreGraphics.framework in Frameworks */,
+ 634F3C7C1593D3E000D6361C /* iHasApp.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 634F3C201593C41900D6361C = {
+ isa = PBXGroup;
+ children = (
+ 634F3C351593C41900D6361C /* iHasAppExample */,
+ 634F3C2E1593C41900D6361C /* Frameworks */,
+ 634F3C2C1593C41900D6361C /* Products */,
+ );
+ sourceTree = "<group>";
+ };
+ 634F3C2C1593C41900D6361C /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 634F3C2B1593C41900D6361C /* iHasAppExample.app */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ 634F3C2E1593C41900D6361C /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 634F3C2F1593C41900D6361C /* UIKit.framework */,
+ 634F3C311593C41900D6361C /* Foundation.framework */,
+ 634F3C331593C41900D6361C /* CoreGraphics.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+ 634F3C351593C41900D6361C /* iHasAppExample */ = {
+ isa = PBXGroup;
+ children = (
+ 634F3C731593CC8500D6361C /* iHasApp */,
+ 634F3C5C1593CC5C00D6361C /* SDWebImage */,
+ 634F3C3E1593C41900D6361C /* AppDelegate.h */,
+ 634F3C3F1593C41900D6361C /* AppDelegate.m */,
+ 634F3C411593C41900D6361C /* MasterViewController.h */,
+ 634F3C421593C41900D6361C /* MasterViewController.m */,
+ 634F3C441593C41900D6361C /* DetailViewController.h */,
+ 634F3C451593C41900D6361C /* DetailViewController.m */,
+ 634F3C361593C41900D6361C /* Supporting Files */,
+ );
+ path = iHasAppExample;
+ sourceTree = "<group>";
+ };
+ 634F3C361593C41900D6361C /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ 634F3C371593C41900D6361C /* iHasAppExample-Info.plist */,
+ 634F3C381593C41900D6361C /* InfoPlist.strings */,
+ 634F3C3B1593C41900D6361C /* main.m */,
+ 634F3C3D1593C41900D6361C /* iHasAppExample-Prefix.pch */,
+ );
+ name = "Supporting Files";
+ sourceTree = "<group>";
+ };
+ 634F3C5C1593CC5C00D6361C /* SDWebImage */ = {
+ isa = PBXGroup;
+ children = (
+ 634F3C751593CCEF00D6361C /* placeholder-icon.png */,
+ 634F3C5D1593CC5C00D6361C /* SDImageCache.h */,
+ 634F3C5E1593CC5C00D6361C /* SDImageCache.m */,
+ 634F3C5F1593CC5C00D6361C /* SDImageCacheDelegate.h */,
+ 634F3C601593CC5C00D6361C /* SDWebImageCompat.h */,
+ 634F3C611593CC5C00D6361C /* SDWebImageDecoder.h */,
+ 634F3C621593CC5C00D6361C /* SDWebImageDecoder.m */,
+ 634F3C631593CC5C00D6361C /* SDWebImageDownloader.h */,
+ 634F3C641593CC5C00D6361C /* SDWebImageDownloader.m */,
+ 634F3C651593CC5C00D6361C /* SDWebImageDownloaderDelegate.h */,
+ 634F3C661593CC5C00D6361C /* SDWebImageManager.h */,
+ 634F3C671593CC5C00D6361C /* SDWebImageManager.m */,
+ 634F3C681593CC5C00D6361C /* SDWebImageManagerDelegate.h */,
+ 634F3C691593CC5C00D6361C /* SDWebImagePrefetcher.h */,
+ 634F3C6A1593CC5C00D6361C /* SDWebImagePrefetcher.m */,
+ 634F3C6B1593CC5C00D6361C /* UIImageView+WebCache.h */,
+ 634F3C6C1593CC5C00D6361C /* UIImageView+WebCache.m */,
+ );
+ path = SDWebImage;
+ sourceTree = "<group>";
+ };
+ 634F3C731593CC8500D6361C /* iHasApp */ = {
+ isa = PBXGroup;
+ children = (
+ 634F3C7B1593D3E000D6361C /* iHasApp.framework */,
+ );
+ name = iHasApp;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 634F3C2A1593C41900D6361C /* iHasAppExample */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 634F3C551593C41900D6361C /* Build configuration list for PBXNativeTarget "iHasAppExample" */;
+ buildPhases = (
+ 634F3C271593C41900D6361C /* Sources */,
+ 634F3C281593C41900D6361C /* Frameworks */,
+ 634F3C291593C41900D6361C /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = iHasAppExample;
+ productName = iHasAppExample;
+ productReference = 634F3C2B1593C41900D6361C /* iHasAppExample.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 634F3C221593C41900D6361C /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0430;
+ ORGANIZATIONNAME = "Shout Messenger";
+ };
+ buildConfigurationList = 634F3C251593C41900D6361C /* Build configuration list for PBXProject "iHasAppExample" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ );
+ mainGroup = 634F3C201593C41900D6361C;
+ productRefGroup = 634F3C2C1593C41900D6361C /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 634F3C2A1593C41900D6361C /* iHasAppExample */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 634F3C291593C41900D6361C /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 634F3C3A1593C41900D6361C /* InfoPlist.strings in Resources */,
+ 634F3C761593CCEF00D6361C /* placeholder-icon.png in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 634F3C271593C41900D6361C /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 634F3C3C1593C41900D6361C /* main.m in Sources */,
+ 634F3C401593C41900D6361C /* AppDelegate.m in Sources */,
+ 634F3C431593C41900D6361C /* MasterViewController.m in Sources */,
+ 634F3C461593C41900D6361C /* DetailViewController.m in Sources */,
+ 634F3C6D1593CC5C00D6361C /* SDImageCache.m in Sources */,
+ 634F3C6E1593CC5C00D6361C /* SDWebImageDecoder.m in Sources */,
+ 634F3C6F1593CC5C00D6361C /* SDWebImageDownloader.m in Sources */,
+ 634F3C701593CC5C00D6361C /* SDWebImageManager.m in Sources */,
+ 634F3C711593CC5C00D6361C /* SDWebImagePrefetcher.m in Sources */,
+ 634F3C721593CC5C00D6361C /* UIImageView+WebCache.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ 634F3C381593C41900D6361C /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 634F3C391593C41900D6361C /* en */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "<group>";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 634F3C531593C41900D6361C /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+ CLANG_ENABLE_OBJC_ARC = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 4.0;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 634F3C541593C41900D6361C /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+ CLANG_ENABLE_OBJC_ARC = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 4.0;
+ OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1";
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 634F3C561593C41900D6361C /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(SRCROOT)/iHasAppExample\"",
+ "\"$(SRCROOT)/..\"",
+ );
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "iHasAppExample/iHasAppExample-Prefix.pch";
+ INFOPLIST_FILE = "iHasAppExample/iHasAppExample-Info.plist";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ WRAPPER_EXTENSION = app;
+ };
+ name = Debug;
+ };
+ 634F3C571593C41900D6361C /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(SRCROOT)/iHasAppExample\"",
+ "\"$(SRCROOT)/..\"",
+ );
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "iHasAppExample/iHasAppExample-Prefix.pch";
+ INFOPLIST_FILE = "iHasAppExample/iHasAppExample-Info.plist";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ WRAPPER_EXTENSION = app;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 634F3C251593C41900D6361C /* Build configuration list for PBXProject "iHasAppExample" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 634F3C531593C41900D6361C /* Debug */,
+ 634F3C541593C41900D6361C /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 634F3C551593C41900D6361C /* Build configuration list for PBXNativeTarget "iHasAppExample" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 634F3C561593C41900D6361C /* Debug */,
+ 634F3C571593C41900D6361C /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 634F3C221593C41900D6361C /* Project object */;
+}
7 iHasAppExample/iHasAppExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+ version = "1.0">
+ <FileRef
+ location = "self:iHasAppExample.xcodeproj">
+ </FileRef>
+</Workspace>
BIN  ...oj/project.xcworkspace/xcuserdata/danielamitay.xcuserdatad/UserInterfaceState.xcuserstate
Binary file not shown
85 ...ppExample.xcodeproj/xcuserdata/danielamitay.xcuserdatad/xcschemes/iHasAppExample.xcscheme
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "634F3C2A1593C41900D6361C"
+ BuildableName = "iHasAppExample.app"
+ BlueprintName = "iHasAppExample"
+ ReferencedContainer = "container:iHasAppExample.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ buildConfiguration = "Debug">
+ <Testables>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "634F3C2A1593C41900D6361C"
+ BuildableName = "iHasAppExample.app"
+ BlueprintName = "iHasAppExample"
+ ReferencedContainer = "container:iHasAppExample.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ </TestAction>
+ <LaunchAction
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ buildConfiguration = "Debug"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "634F3C2A1593C41900D6361C"
+ BuildableName = "iHasAppExample.app"
+ BlueprintName = "iHasAppExample"
+ ReferencedContainer = "container:iHasAppExample.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ buildConfiguration = "Release"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "634F3C2A1593C41900D6361C"
+ BuildableName = "iHasAppExample.app"
+ BlueprintName = "iHasAppExample"
+ ReferencedContainer = "container:iHasAppExample.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
22 ...pExample.xcodeproj/xcuserdata/danielamitay.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>SchemeUserState</key>
+ <dict>
+ <key>iHasAppExample.xcscheme</key>
+ <dict>
+ <key>orderHint</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <key>SuppressBuildableAutocreation</key>
+ <dict>
+ <key>634F3C2A1593C41900D6361C</key>
+ <dict>
+ <key>primary</key>
+ <true/>
+ </dict>
+ </dict>
+</dict>
+</plist>
19 iHasAppExample/iHasAppExample/AppDelegate.h
@@ -0,0 +1,19 @@
+//
+// AppDelegate.h
+// iHasAppExample
+//
+// Created by Daniel Amitay on 6/21/12.
+// Copyright (c) 2012 Objective-See, LLC. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface AppDelegate : UIResponder <UIApplicationDelegate>
+
+@property (strong, nonatomic) UIWindow *window;
+
+@property (strong, nonatomic) UINavigationController *navigationController;
+
+@property (strong, nonatomic) UISplitViewController *splitViewController;
+
+@end
50 iHasAppExample/iHasAppExample/AppDelegate.m
@@ -0,0 +1,50 @@
+//
+// AppDelegate.m
+// iHasAppExample
+//
+// Created by Daniel Amitay on 6/21/12.
+// Copyright (c) 2012 Objective-See, LLC. All rights reserved.
+//
+
+#import "AppDelegate.h"
+
+#import "MasterViewController.h"
+
+#import "DetailViewController.h"
+
+@implementation AppDelegate
+
+@synthesize window = _window;
+@synthesize navigationController = _navigationController;
+@synthesize splitViewController = _splitViewController;
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
+{
+ self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
+ if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
+ {
+ MasterViewController *masterViewController = [[MasterViewController alloc] init];
+ self.navigationController = [[UINavigationController alloc] initWithRootViewController:masterViewController];
+ self.window.rootViewController = self.navigationController;
+ }
+ else
+ {
+ MasterViewController *masterViewController = [[MasterViewController alloc] init];
+ UINavigationController *masterNavigationController = [[UINavigationController alloc] initWithRootViewController:masterViewController];
+
+ DetailViewController *detailViewController = [[DetailViewController alloc] init];
+ UINavigationController *detailNavigationController = [[UINavigationController alloc] initWithRootViewController:detailViewController];
+
+ masterViewController.detailViewController = detailViewController;
+
+ self.splitViewController = [[UISplitViewController alloc] init];
+ self.splitViewController.delegate = detailViewController;
+ self.splitViewController.viewControllers = [NSArray arrayWithObjects:masterNavigationController, detailNavigationController, nil];
+
+ self.window.rootViewController = self.splitViewController;
+ }
+ [self.window makeKeyAndVisible];
+ return YES;
+}
+
+@end
15 iHasAppExample/iHasAppExample/DetailViewController.h
@@ -0,0 +1,15 @@
+//
+// DetailViewController.h
+// iHasAppExample
+//
+// Created by Daniel Amitay on 6/21/12.
+// Copyright (c) 2012 Objective-See, LLC. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface DetailViewController : UITableViewController <UISplitViewControllerDelegate>
+
+@property (strong, nonatomic) NSDictionary *appDictionary;
+
+@end
146 iHasAppExample/iHasAppExample/DetailViewController.m
@@ -0,0 +1,146 @@
+//
+// DetailViewController.m
+// iHasAppExample
+//
+// Created by Daniel Amitay on 6/21/12.
+// Copyright (c) 2012 Objective-See, LLC. All rights reserved.
+//
+
+#import "DetailViewController.h"
+
+@interface DetailViewController ()
+@property (strong, nonatomic) UIPopoverController *masterPopoverController;
+- (void)configureView;
+@end
+
+@implementation DetailViewController
+
+@synthesize appDictionary = _appDictionary;
+@synthesize masterPopoverController = _masterPopoverController;
+
+#pragma mark - Managing the detail item
+
+- (void)setAppDictionary:(NSDictionary *)appDictionary
+{
+ if (_appDictionary != appDictionary)
+ {
+ _appDictionary = appDictionary;
+
+ [self configureView];
+ }
+
+ if (self.masterPopoverController != nil)
+ {
+ [self.masterPopoverController dismissPopoverAnimated:YES];
+ }
+}
+
+- (void)configureView
+{
+ [self.tableView reloadData];
+ if (self.appDictionary)
+ {
+ self.title = [self.appDictionary objectForKey:@"trackName"];
+ NSIndexPath *topIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
+ [self.tableView scrollToRowAtIndexPath:topIndexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
+ }
+ else
+ {
+ self.title = nil;
+ }
+
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+ self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
+ [self configureView];
+}
+
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
+{
+ if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
+ {
+ return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
+ }
+ else
+ {
+ return YES;
+ }
+}
+
+#pragma mark - Split view
+
+- (void)splitViewController:(UISplitViewController *)splitController willHideViewController:(UIViewController *)viewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController:(UIPopoverController *)popoverController
+{
+ barButtonItem.title = @"Apps";
+ [self.navigationItem setLeftBarButtonItem:barButtonItem animated:YES];
+ self.masterPopoverController = popoverController;
+}
+
+- (void)splitViewController:(UISplitViewController *)splitController willShowViewController:(UIViewController *)viewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
+{
+ [self.navigationItem setLeftBarButtonItem:nil animated:YES];
+ self.masterPopoverController = nil;
+}
+
+#pragma mark - Table View
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+ return 1;
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
+{
+ if(self.appDictionary)
+ {
+ return @"Key-value pairs";
+ }
+ else
+ {
+ return nil;
+ }
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ return self.appDictionary.allKeys.count;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ static NSString *CellIdentifier = @"Cell";
+
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+ if (cell == nil)
+ {
+ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
+ cell.detailTextLabel.numberOfLines = 0;
+ cell.selectionStyle = UITableViewCellSelectionStyleNone;
+ }
+
+ NSString *key = [self.appDictionary.allKeys objectAtIndex:indexPath.row];
+ NSString *value = [[self.appDictionary objectForKey:key] description];
+
+ cell.textLabel.text = key;
+ cell.detailTextLabel.text = value;
+
+ return cell;
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ NSString *key = [self.appDictionary.allKeys objectAtIndex:indexPath.row];
+ NSString *value = [[self.appDictionary objectForKey:key] description];
+
+ CGSize maxSize = CGSizeMake(self.view.bounds.size.width - 20.0f, 999.0f);
+ CGSize labelSize = [value sizeWithFont:[UIFont systemFontOfSize:14.0f]
+ constrainedToSize:maxSize
+ lineBreakMode:UILineBreakModeTailTruncation];
+
+ return (labelSize.height + 30.0f);
+}
+
+@end
17 iHasAppExample/iHasAppExample/MasterViewController.h
@@ -0,0 +1,17 @@
+//
+// MasterViewController.h
+// iHasAppExample
+//
+// Created by Daniel Amitay on 6/21/12.
+// Copyright (c) 2012 Objective-See, LLC. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@class DetailViewController;
+
+@interface MasterViewController : UITableViewController
+
+@property (strong, nonatomic) DetailViewController *detailViewController;
+
+@end
196 iHasAppExample/iHasAppExample/MasterViewController.m
@@ -0,0 +1,196 @@
+//
+// MasterViewController.m
+// iHasAppExample
+//
+// Created by Daniel Amitay on 6/21/12.
+// Copyright (c) 2012 Objective-See, LLC. All rights reserved.
+//
+
+#import "MasterViewController.h"
+
+#import "DetailViewController.h"
+
+#import "UIImageView+WebCache.h"
+
+#import <iHasApp/iHasApp.h>
+
+@interface MasterViewController () <iHasAppDelegate>
+{
+ iHasApp *appEngine;
+ NSArray *detectedApps;
+}
+@end
+
+@implementation MasterViewController
+
+@synthesize detailViewController = _detailViewController;
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ self.title = @"Apps";
+ self.tableView.rowHeight = 57.0f;
+
+ if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
+ {
+ self.clearsSelectionOnViewWillAppear = NO;
+ self.contentSizeForViewInPopover = CGSizeMake(320.0f, 600.0f);
+ }
+
+ UIBarButtonItem *rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh
+ target:self
+ action:@selector(detectApps)];
+ self.navigationItem.rightBarButtonItem = rightBarButtonItem;
+
+ #warning Replace YOUR-API-KEY accordingly
+ appEngine = [[iHasApp alloc] initWithDelegate:self andKey:@"YOUR-API-KEY"];
+ appEngine.country = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
+ [self detectApps];
+}
+
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
+{
+ if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
+ {
+ return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
+ }
+ else
+ {
+ return YES;
+ }
+}
+
+#pragma mark - iHasApp methods
+
+- (void)detectApps
+{
+ if (self.detailViewController)
+ {
+ self.detailViewController.appDictionary = nil;
+ }
+ [appEngine beginDetection];
+ NSLog(@"appDetectionDidBegin");
+ detectedApps = nil;
+ [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
+ [self.tableView reloadData];
+}
+
+- (void)appDetectionDidSucceed:(NSArray *)appsDetected
+{
+ NSLog(@"appDetectionDidSucceed:");
+ detectedApps = appsDetected;
+ [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
+ [self.tableView reloadData];
+}
+
+- (void)appDetectionDidFail:(iHasAppError)detectionError
+{
+ NSLog(@"appDetectionDidFail:");
+ detectedApps = [NSArray array];
+ [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
+
+ NSString *error;
+ switch (detectionError)
+ {
+ case iHasAppErrorUnknown:
+ error = @"iHasAppError: Unknown";
+ break;
+ case iHasAppErrorConnection:
+ error = @"iHasAppError: Connection";
+ break;
+ case iHasAppErrorInvalidKey:
+ error = @"iHasAppError: InvalidKey";
+ break;
+ case iHasAppErrorReachedLimit:
+ error = @"iHasAppError: ReachedLimit";
+ break;
+
+ default:
+ break;
+ }
+ UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:error
+ message:nil
+ delegate:nil
+ cancelButtonTitle:@"OK"
+ otherButtonTitles:nil];
+ [alertView show];
+}
+
+#pragma mark - Table View
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+ return 1;
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
+{
+ if(detectedApps)
+ {
+ return [NSString stringWithFormat:@"%i Apps Detected", [[appEngine detectedApps] count]];
+ }
+ else
+ {
+ return @"Detection in progress...";
+ }
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ return detectedApps.count;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ static NSString *CellIdentifier = @"Cell";
+
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+ if (cell == nil)
+ {
+ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
+ if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
+ {
+ cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
+ cell.selectionStyle = UITableViewCellSelectionStyleBlue;
+ }
+ else
+ {
+ cell.selectionStyle = UITableViewCellSelectionStyleGray;
+ }
+ }
+
+ NSDictionary *appDictionary = [detectedApps objectAtIndex:indexPath.row];
+
+ NSString *trackName = [appDictionary objectForKey:@"trackName"];
+ NSString *trackId = [[appDictionary objectForKey:@"trackId"] description];
+ NSString *artworkUrl60 = [appDictionary objectForKey:@"artworkUrl60"];
+
+ cell.textLabel.text = trackName;
+ cell.detailTextLabel.text = trackId;
+
+ [cell.imageView setImageWithURL:[NSURL URLWithString:artworkUrl60]
+ placeholderImage:[UIImage imageNamed:@"placeholder-icon"]];
+
+ return cell;
+}
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ NSDictionary *appDictionary = [detectedApps objectAtIndex:indexPath.row];
+ if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
+ {
+ if (!self.detailViewController)
+ {
+ self.detailViewController = [[DetailViewController alloc] init];
+ }
+ self.detailViewController.appDictionary = appDictionary;
+ [self.navigationController pushViewController:self.detailViewController animated:YES];
+ }
+ else
+ {
+ self.detailViewController.appDictionary = appDictionary;
+ }
+}
+
+@end
32 iHasAppExample/iHasAppExample/SDWebImage/SDImageCache.h
@@ -0,0 +1,32 @@
+/*
+ * This file is part of the SDWebImage package.
+ * (c) Olivier Poitrey <rs@dailymotion.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#import <Foundation/Foundation.h>
+#import "SDImageCacheDelegate.h"
+
+@interface SDImageCache : NSObject
+{
+ NSMutableDictionary *memCache;
+ NSString *diskCachePath;
+ NSOperationQueue *cacheInQueue, *cacheOutQueue;
+}
+
++ (SDImageCache *)sharedImageCache;
+- (void)storeImage:(UIImage *)image forKey:(NSString *)key;
+- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk;
+- (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk;
+- (UIImage *)imageFromKey:(NSString *)key;
+- (UIImage *)imageFromKey:(NSString *)key fromDisk:(BOOL)fromDisk;
+- (void)queryDiskCacheForKey:(NSString *)key delegate:(id <SDImageCacheDelegate>)delegate userInfo:(NSDictionary *)info;
+
+- (void)removeImageForKey:(NSString *)key;
+- (void)clearMemory;
+- (void)clearDisk;
+- (void)cleanDisk;
+
+@end
340 iHasAppExample/iHasAppExample/SDWebImage/SDImageCache.m
@@ -0,0 +1,340 @@
+/*
+ * This file is part of the SDWebImage package.
+ * (c) Olivier Poitrey <rs@dailymotion.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#import "SDImageCache.h"
+#import "SDWebImageDecoder.h"
+#import <CommonCrypto/CommonDigest.h>
+
+#ifdef ENABLE_SDWEBIMAGE_DECODER
+#import "SDWebImageDecoder.h"
+#endif
+
+static NSInteger cacheMaxCacheAge = 60*60*24*7; // 1 week
+
+static SDImageCache *instance;
+
+@implementation SDImageCache
+
+#pragma mark NSObject
+
+- (id)init
+{
+ if ((self = [super init]))
+ {
+ // Init the memory cache
+ memCache = [[NSMutableDictionary alloc] init];
+
+ // Init the disk cache
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
+ diskCachePath = [[[paths objectAtIndex:0] stringByAppendingPathComponent:@"ImageCache"] retain];
+
+ if (![[NSFileManager defaultManager] fileExistsAtPath:diskCachePath])
+ {
+ [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:NULL];
+ }
+
+ // Init the operation queue
+ cacheInQueue = [[NSOperationQueue alloc] init];
+ cacheInQueue.maxConcurrentOperationCount = 1;
+ cacheOutQueue = [[NSOperationQueue alloc] init];
+ cacheOutQueue.maxConcurrentOperationCount = 1;
+
+#if TARGET_OS_IPHONE
+ // Subscribe to app events
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(clearMemory)
+ name:UIApplicationDidReceiveMemoryWarningNotification
+ object:nil];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(cleanDisk)
+ name:UIApplicationWillTerminateNotification
+ object:nil];
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0
+ UIDevice *device = [UIDevice currentDevice];
+ if ([device respondsToSelector:@selector(isMultitaskingSupported)] && device.multitaskingSupported)
+ {
+ // When in background, clean memory in order to have less chance to be killed
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(clearMemory)
+ name:UIApplicationDidEnterBackgroundNotification
+ object:nil];
+ }
+#endif
+#endif
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [memCache release], memCache = nil;
+ [diskCachePath release], diskCachePath = nil;
+ [cacheInQueue release], cacheInQueue = nil;
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [super dealloc];
+}
+
+#pragma mark SDImageCache (class methods)
+
++ (SDImageCache *)sharedImageCache
+{
+ if (instance == nil)
+ {
+ instance = [[SDImageCache alloc] init];
+ }
+
+ return instance;
+}
+
+#pragma mark SDImageCache (private)
+
+- (NSString *)cachePathForKey:(NSString *)key
+{
+ const char *str = [key UTF8String];
+ unsigned char r[CC_MD5_DIGEST_LENGTH];
+ CC_MD5(str, (CC_LONG)strlen(str), r);
+ NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
+ r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15]];
+
+ return [diskCachePath stringByAppendingPathComponent:filename];
+}
+
+- (void)storeKeyWithDataToDisk:(NSArray *)keyAndData
+{
+ // Can't use defaultManager another thread
+ NSFileManager *fileManager = [[NSFileManager alloc] init];
+
+ NSString *key = [keyAndData objectAtIndex:0];
+ NSData *data = [keyAndData count] > 1 ? [keyAndData objectAtIndex:1] : nil;
+
+ if (data)
+ {
+ [fileManager createFileAtPath:[self cachePathForKey:key] contents:data attributes:nil];
+ }
+ else
+ {
+ // If no data representation given, convert the UIImage in JPEG and store it
+ // This trick is more CPU/memory intensive and doesn't preserve alpha channel
+ UIImage *image = [[self imageFromKey:key fromDisk:YES] retain]; // be thread safe with no lock
+ if (image)
+ {
+#if TARGET_OS_IPHONE
+ [fileManager createFileAtPath:[self cachePathForKey:key] contents:UIImageJPEGRepresentation(image, (CGFloat)1.0) attributes:nil];
+#else
+ NSArray* representations = [image representations];
+ NSData* jpegData = [NSBitmapImageRep representationOfImageRepsInArray: representations usingType: NSJPEGFileType properties:nil];
+ [fileManager createFileAtPath:[self cachePathForKey:key] contents:jpegData attributes:nil];
+#endif
+ [image release];
+ }
+ }
+
+ [fileManager release];
+}
+
+- (void)notifyDelegate:(NSDictionary *)arguments
+{
+ NSString *key = [arguments objectForKey:@"key"];
+ id <SDImageCacheDelegate> delegate = [arguments objectForKey:@"delegate"];
+ NSDictionary *info = [arguments objectForKey:@"userInfo"];
+ UIImage *image = [arguments objectForKey:@"image"];
+
+ if (image)
+ {
+ [memCache setObject:image forKey:key];
+
+ if ([delegate respondsToSelector:@selector(imageCache:didFindImage:forKey:userInfo:)])
+ {
+ [delegate imageCache:self didFindImage:image forKey:key userInfo:info];
+ }
+ }
+ else
+ {
+ if ([delegate respondsToSelector:@selector(imageCache:didNotFindImageForKey:userInfo:)])
+ {
+ [delegate imageCache:self didNotFindImageForKey:key userInfo:info];
+ }
+ }
+}
+
+- (void)queryDiskCacheOperation:(NSDictionary *)arguments
+{
+ NSString *key = [arguments objectForKey:@"key"];
+ NSMutableDictionary *mutableArguments = [[arguments mutableCopy] autorelease];
+
+ UIImage *image = [[[UIImage alloc] initWithContentsOfFile:[self cachePathForKey:key]] autorelease];
+ if (image)
+ {
+#ifdef ENABLE_SDWEBIMAGE_DECODER
+ UIImage *decodedImage = [UIImage decodedImageWithImage:image];
+ if (decodedImage)
+ {
+ image = decodedImage;
+ }
+#endif
+ [mutableArguments setObject:image forKey:@"image"];
+ }
+
+ [self performSelectorOnMainThread:@selector(notifyDelegate:) withObject:mutableArguments waitUntilDone:NO];
+}
+
+#pragma mark ImageCache
+
+- (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk
+{
+ if (!image || !key)
+ {
+ return;
+ }
+
+ [memCache setObject:image forKey:key];
+
+ if (toDisk)
+ {
+ if (!data) return;
+ NSArray *keyWithData;
+ if (data)
+ {
+ keyWithData = [NSArray arrayWithObjects:key, data, nil];
+ }
+ else
+ {
+ keyWithData = [NSArray arrayWithObjects:key, nil];
+ }
+ [cacheInQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self
+ selector:@selector(storeKeyWithDataToDisk:)
+ object:keyWithData] autorelease]];
+ }
+}
+
+- (void)storeImage:(UIImage *)image forKey:(NSString *)key
+{
+ [self storeImage:image imageData:nil forKey:key toDisk:YES];
+}
+
+- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk
+{
+ [self storeImage:image imageData:nil forKey:key toDisk:toDisk];
+}
+
+
+- (UIImage *)imageFromKey:(NSString *)key
+{
+ return [self imageFromKey:key fromDisk:YES];
+}
+
+- (UIImage *)imageFromKey:(NSString *)key fromDisk:(BOOL)fromDisk
+{
+ if (key == nil)
+ {
+ return nil;
+ }
+
+ UIImage *image = [memCache objectForKey:key];
+
+ if (!image && fromDisk)
+ {
+ image = [[[UIImage alloc] initWithContentsOfFile:[self cachePathForKey:key]] autorelease];
+ if (image)
+ {
+ [memCache setObject:image forKey:key];
+ }
+ }
+
+ return image;
+}
+
+- (void)queryDiskCacheForKey:(NSString *)key delegate:(id <SDImageCacheDelegate>)delegate userInfo:(NSDictionary *)info
+{
+ if (!delegate)
+ {
+ return;
+ }
+
+ if (!key)
+ {
+ if ([delegate respondsToSelector:@selector(imageCache:didNotFindImageForKey:userInfo:)])
+ {
+ [delegate imageCache:self didNotFindImageForKey:key userInfo:info];
+ }
+ return;
+ }
+
+ // First check the in-memory cache...
+ UIImage *image = [memCache objectForKey:key];
+ if (image)
+ {
+ // ...notify delegate immediately, no need to go async
+ if ([delegate respondsToSelector:@selector(imageCache:didFindImage:forKey:userInfo:)])
+ {
+ [delegate imageCache:self didFindImage:image forKey:key userInfo:info];
+ }
+ return;
+ }
+
+ NSMutableDictionary *arguments = [NSMutableDictionary dictionaryWithCapacity:3];
+ [arguments setObject:key forKey:@"key"];
+ [arguments setObject:delegate forKey:@"delegate"];
+ if (info)
+ {
+ [arguments setObject:info forKey:@"userInfo"];
+ }
+ [cacheOutQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(queryDiskCacheOperation:) object:arguments] autorelease]];
+}
+
+- (void)removeImageForKey:(NSString *)key
+{
+ if (key == nil)
+ {
+ return;
+ }
+
+ [memCache removeObjectForKey:key];
+ [[NSFileManager defaultManager] removeItemAtPath:[self cachePathForKey:key] error:nil];
+}
+
+- (void)clearMemory
+{
+ [cacheInQueue cancelAllOperations]; // won't be able to complete
+ [memCache removeAllObjects];
+}
+
+- (void)clearDisk
+{
+ [cacheInQueue cancelAllOperations];
+ [[NSFileManager defaultManager] removeItemAtPath:diskCachePath error:nil];
+ [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:NULL];
+}
+
+- (void)cleanDisk
+{
+ NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-cacheMaxCacheAge];
+ NSDirectoryEnumerator *fileEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:diskCachePath];
+ for (NSString *fileName in fileEnumerator)
+ {
+ NSString *filePath = [diskCachePath stringByAppendingPathComponent:fileName];
+ NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
+ if ([[[attrs fileModificationDate] laterDate:expirationDate] isEqualToDate:expirationDate])
+ {
+ [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
+ }
+ }
+}
+
+@end
19 iHasAppExample/iHasAppExample/SDWebImage/SDImageCacheDelegate.h
@@ -0,0 +1,19 @@
+//
+// SDImageCacheDelegate.h
+// Dailymotion
+//
+// Created by Olivier Poitrey on 16/09/10.
+// Copyright 2010 Dailymotion. All rights reserved.
+//
+
+#import "SDWebImageCompat.h"
+
+@class SDImageCache;
+
+@protocol SDImageCacheDelegate <NSObject>
+
+@optional
+- (void)imageCache:(SDImageCache *)imageCache didFindImage:(UIImage *)image forKey:(NSString *)key userInfo:(NSDictionary *)info;
+- (void)imageCache:(SDImageCache *)imageCache didNotFindImageForKey:(NSString *)key userInfo:(NSDictionary *)info;
+
+@end
21 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageCompat.h
@@ -0,0 +1,21 @@
+//
+// SDWebImageCompat.h
+// SDWebImageCompat
+//
+// Created by Jamie Pinkham on 3/15/11.
+// Copyright 2011 __MyCompanyName__. All rights reserved.
+//
+
+#import <TargetConditionals.h>
+
+#if !TARGET_OS_IPHONE
+#import <AppKit/AppKit.h>
+#ifndef UIImage
+#define UIImage NSImage
+#endif
+#ifndef UIImageView
+#define UIImageView NSImageView
+#endif
+#else
+#import <UIKit/UIKit.h>
+#endif
36 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageDecoder.h
@@ -0,0 +1,36 @@
+/*
+ * This file is part of the SDWebImage package.
+ * (c) Olivier Poitrey <rs@dailymotion.com>
+ *
+ * Created by james <https://github.com/mystcolor> on 9/28/11.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#import <Foundation/Foundation.h>
+#import "SDWebImageCompat.h"
+
+@protocol SDWebImageDecoderDelegate;
+
+@interface SDWebImageDecoder : NSObject
+{
+ NSOperationQueue *imageDecodingQueue;
+}
+
++ (SDWebImageDecoder *)sharedImageDecoder;
+- (void)decodeImage:(UIImage *)image withDelegate:(id <SDWebImageDecoderDelegate>)delegate userInfo:(NSDictionary *)info;
+
+@end
+
+@protocol SDWebImageDecoderDelegate <NSObject>
+
+- (void)imageDecoder:(SDWebImageDecoder *)decoder didFinishDecodingImage:(UIImage *)image userInfo:(NSDictionary *)userInfo;
+
+@end
+
+@interface UIImage (ForceDecode)
+
++ (UIImage *)decodedImageWithImage:(UIImage *)image;
+
+@end
124 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageDecoder.m
@@ -0,0 +1,124 @@
+/*
+ * This file is part of the SDWebImage package.
+ * (c) Olivier Poitrey <rs@dailymotion.com>
+ *
+ * Created by james <https://github.com/mystcolor> on 9/28/11.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#import "SDWebImageDecoder.h"
+
+#define DECOMPRESSED_IMAGE_KEY @"decompressedImage"
+#define DECODE_INFO_KEY @"decodeInfo"
+
+#define IMAGE_KEY @"image"
+#define DELEGATE_KEY @"delegate"
+#define USER_INFO_KEY @"userInfo"
+
+@implementation SDWebImageDecoder
+static SDWebImageDecoder *sharedInstance;
+
+- (void)notifyDelegateOnMainThreadWithInfo:(NSDictionary *)dict
+{
+ [dict retain];
+ NSDictionary *decodeInfo = [dict objectForKey:DECODE_INFO_KEY];
+ UIImage *decodedImage = [dict objectForKey:DECOMPRESSED_IMAGE_KEY];
+
+ id <SDWebImageDecoderDelegate> delegate = [decodeInfo objectForKey:DELEGATE_KEY];
+ NSDictionary *userInfo = [decodeInfo objectForKey:USER_INFO_KEY];
+
+ [delegate imageDecoder:self didFinishDecodingImage:decodedImage userInfo:userInfo];
+ [dict release];
+}
+
+- (void)decodeImageWithInfo:(NSDictionary *)decodeInfo
+{
+ UIImage *image = [decodeInfo objectForKey:IMAGE_KEY];
+
+ UIImage *decompressedImage = [UIImage decodedImageWithImage:image];
+ if (!decompressedImage)
+ {
+ // If really have any error occurs, we use the original image at this moment
+ decompressedImage = image;
+ }
+
+ NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
+ decompressedImage, DECOMPRESSED_IMAGE_KEY,
+ decodeInfo, DECODE_INFO_KEY, nil];
+
+ [self performSelectorOnMainThread:@selector(notifyDelegateOnMainThreadWithInfo:) withObject:dict waitUntilDone:NO];
+}
+
+- (id)init
+{
+ if ((self = [super init]))
+ {
+ // Initialization code here.
+ imageDecodingQueue = [[NSOperationQueue alloc] init];
+ }
+
+ return self;
+}
+
+- (void)decodeImage:(UIImage *)image withDelegate:(id<SDWebImageDecoderDelegate>)delegate userInfo:(NSDictionary *)info
+{
+ NSDictionary *decodeInfo = [NSDictionary dictionaryWithObjectsAndKeys:
+ image, IMAGE_KEY,
+ delegate, DELEGATE_KEY,
+ info, USER_INFO_KEY, nil];
+
+ NSOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(decodeImageWithInfo:) object:decodeInfo];
+ [imageDecodingQueue addOperation:operation];
+ [operation release];
+}
+
+- (void)dealloc
+{
+ [imageDecodingQueue release], imageDecodingQueue = nil;
+ [super dealloc];
+}
+
++ (SDWebImageDecoder *)sharedImageDecoder
+{
+ if (!sharedInstance)
+ {
+ sharedInstance = [[SDWebImageDecoder alloc] init];
+ }
+ return sharedInstance;
+}
+
+@end
+
+
+@implementation UIImage (ForceDecode)
+
++ (UIImage *)decodedImageWithImage:(UIImage *)image
+{
+ CGImageRef imageRef = image.CGImage;
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+ CGContextRef context = CGBitmapContextCreate(NULL,
+ CGImageGetWidth(imageRef),
+ CGImageGetHeight(imageRef),
+ 8,
+ // Just always return width * 4 will be enough
+ CGImageGetWidth(imageRef) * 4,
+ // System only supports RGB, set explicitly
+ colorSpace,
+ // Makes system don't need to do extra conversion when displayed.
+ kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
+ CGColorSpaceRelease(colorSpace);
+ if (!context) return nil;
+
+ CGRect rect = (CGRect){CGPointZero, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)};
+ CGContextDrawImage(context, rect, imageRef);
+ CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context);
+ CGContextRelease(context);
+
+ UIImage *decompressedImage = [[UIImage alloc] initWithCGImage:decompressedImageRef];
+ CGImageRelease(decompressedImageRef);
+ return [decompressedImage autorelease];
+}
+
+@end
42 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageDownloader.h
@@ -0,0 +1,42 @@
+/*
+ * This file is part of the SDWebImage package.
+ * (c) Olivier Poitrey <rs@dailymotion.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#import <Foundation/Foundation.h>
+#import "SDWebImageDownloaderDelegate.h"
+#import "SDWebImageCompat.h"
+
+extern NSString *const SDWebImageDownloadStartNotification;
+extern NSString *const SDWebImageDownloadStopNotification;
+
+@interface SDWebImageDownloader : NSObject
+{
+ @private
+ NSURL *url;
+ id<SDWebImageDownloaderDelegate> delegate;
+ NSURLConnection *connection;
+ NSMutableData *imageData;
+ id userInfo;
+ BOOL lowPriority;
+}
+
+@property (nonatomic, retain) NSURL *url;
+@property (nonatomic, assign) id<SDWebImageDownloaderDelegate> delegate;
+@property (nonatomic, retain) NSMutableData *imageData;
+@property (nonatomic, retain) id userInfo;
+@property (nonatomic, readwrite) BOOL lowPriority;
+
++ (id)downloaderWithURL:(NSURL *)url delegate:(id<SDWebImageDownloaderDelegate>)delegate userInfo:(id)userInfo lowPriority:(BOOL)lowPriority;
++ (id)downloaderWithURL:(NSURL *)url delegate:(id<SDWebImageDownloaderDelegate>)delegate userInfo:(id)userInfo;
++ (id)downloaderWithURL:(NSURL *)url delegate:(id<SDWebImageDownloaderDelegate>)delegate;
+- (void)start;
+- (void)cancel;
+
+// This method is now no-op and is deprecated
++ (void)setMaxConcurrentDownloads:(NSUInteger)max __attribute__((deprecated));
+
+@end
174 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageDownloader.m
@@ -0,0 +1,174 @@
+/*
+ * This file is part of the SDWebImage package.
+ * (c) Olivier Poitrey <rs@dailymotion.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#import "SDWebImageDownloader.h"
+
+#ifdef ENABLE_SDWEBIMAGE_DECODER
+#import "SDWebImageDecoder.h"
+@interface SDWebImageDownloader (ImageDecoder) <SDWebImageDecoderDelegate>
+@end
+#endif
+
+NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
+NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
+
+@interface SDWebImageDownloader ()
+@property (nonatomic, retain) NSURLConnection *connection;
+@end
+
+@implementation SDWebImageDownloader
+@synthesize url, delegate, connection, imageData, userInfo, lowPriority;
+
+#pragma mark Public Methods
+
++ (id)downloaderWithURL:(NSURL *)url delegate:(id<SDWebImageDownloaderDelegate>)delegate
+{
+ return [self downloaderWithURL:url delegate:delegate userInfo:nil];
+}
+
++ (id)downloaderWithURL:(NSURL *)url delegate:(id<SDWebImageDownloaderDelegate>)delegate userInfo:(id)userInfo
+{
+
+ return [self downloaderWithURL:url delegate:delegate userInfo:userInfo lowPriority:NO];
+}
+
++ (id)downloaderWithURL:(NSURL *)url delegate:(id<SDWebImageDownloaderDelegate>)delegate userInfo:(id)userInfo lowPriority:(BOOL)lowPriority
+{
+ // Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator )
+ // To use it, just add #import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import
+ if (NSClassFromString(@"SDNetworkActivityIndicator"))
+ {
+ id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
+ [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
+ selector:NSSelectorFromString(@"startActivity")
+ name:SDWebImageDownloadStartNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
+ selector:NSSelectorFromString(@"stopActivity")
+ name:SDWebImageDownloadStopNotification object:nil];
+ }
+
+ SDWebImageDownloader *downloader = [[[SDWebImageDownloader alloc] init] autorelease];
+ downloader.url = url;
+ downloader.delegate = delegate;
+ downloader.userInfo = userInfo;
+ downloader.lowPriority = lowPriority;
+ [downloader performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:YES];
+ return downloader;
+}
+
++ (void)setMaxConcurrentDownloads:(NSUInteger)max
+{
+ // NOOP
+}
+
+- (void)start
+{
+ // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests
+ NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15];
+ self.connection = [[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO] autorelease];
+
+ // If not in low priority mode, ensure we aren't blocked by UI manipulations (default runloop mode for NSURLConnection is NSEventTrackingRunLoopMode)
+ if (!lowPriority)
+ {
+ [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
+ }
+ [connection start];
+ [request release];
+
+ if (connection)
+ {
+ self.imageData = [NSMutableData data];
+ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:nil];
+ }
+ else
+ {
+ if ([delegate respondsToSelector:@selector(imageDownloader:didFailWithError:)])
+ {
+ [delegate performSelector:@selector(imageDownloader:didFailWithError:) withObject:self withObject:nil];
+ }
+ }
+}
+
+- (void)cancel
+{
+ if (connection)
+ {
+ [connection cancel];
+ self.connection = nil;
+ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:nil];
+ }
+}
+
+#pragma mark NSURLConnection (delegate)
+
+- (void)connection:(NSURLConnection *)aConnection didReceiveData:(NSData *)data
+{
+ [imageData appendData:data];
+}
+
+#pragma GCC diagnostic ignored "-Wundeclared-selector"
+- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection
+{
+ self.connection = nil;
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:nil];
+
+ if ([delegate respondsToSelector:@selector(imageDownloaderDidFinish:)])
+ {
+ [delegate performSelector:@selector(imageDownloaderDidFinish:) withObject:self];
+ }
+
+ if ([delegate respondsToSelector:@selector(imageDownloader:didFinishWithImage:)])
+ {
+ UIImage *image = [[UIImage alloc] initWithData:imageData];
+
+#ifdef ENABLE_SDWEBIMAGE_DECODER
+ [[SDWebImageDecoder sharedImageDecoder] decodeImage:image withDelegate:self userInfo:nil];
+#else
+ [delegate performSelector:@selector(imageDownloader:didFinishWithImage:) withObject:self withObject:image];
+#endif
+ [image release];
+ }
+}
+
+- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
+{
+ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:nil];
+
+ if ([delegate respondsToSelector:@selector(imageDownloader:didFailWithError:)])
+ {
+ [delegate performSelector:@selector(imageDownloader:didFailWithError:) withObject:self withObject:error];
+ }
+
+ self.connection = nil;
+ self.imageData = nil;
+}
+
+#pragma mark SDWebImageDecoderDelegate
+
+#ifdef ENABLE_SDWEBIMAGE_DECODER
+- (void)imageDecoder:(SDWebImageDecoder *)decoder didFinishDecodingImage:(UIImage *)image userInfo:(NSDictionary *)userInfo
+{
+ [delegate performSelector:@selector(imageDownloader:didFinishWithImage:) withObject:self withObject:image];
+}
+#endif
+
+#pragma mark NSObject
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [url release], url = nil;
+ [connection release], connection = nil;
+ [imageData release], imageData = nil;
+ [userInfo release], userInfo = nil;
+ [super dealloc];
+}
+
+
+@end
21 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageDownloaderDelegate.h
@@ -0,0 +1,21 @@
+/*
+ * This file is part of the SDWebImage package.
+ * (c) Olivier Poitrey <rs@dailymotion.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#import "SDWebImageCompat.h"
+
+@class SDWebImageDownloader;
+
+@protocol SDWebImageDownloaderDelegate <NSObject>
+
+@optional
+
+- (void)imageDownloaderDidFinish:(SDWebImageDownloader *)downloader;
+- (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image;
+- (void)imageDownloader:(SDWebImageDownloader *)downloader didFailWithError:(NSError *)error;
+
+@end
39 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageManager.h
@@ -0,0 +1,39 @@
+/*
+ * This file is part of the SDWebImage package.
+ * (c) Olivier Poitrey <rs@dailymotion.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#import "SDWebImageCompat.h"
+#import "SDWebImageDownloaderDelegate.h"
+#import "SDWebImageManagerDelegate.h"
+#import "SDImageCacheDelegate.h"
+
+typedef enum
+{
+ SDWebImageRetryFailed = 1 << 0,
+ SDWebImageLowPriority = 1 << 1,
+ SDWebImageCacheMemoryOnly = 1 << 2
+} SDWebImageOptions;
+
+@interface SDWebImageManager : NSObject <SDWebImageDownloaderDelegate, SDImageCacheDelegate>
+{
+ NSMutableArray *downloadDelegates;
+ NSMutableArray *downloaders;
+ NSMutableArray *cacheDelegates;
+ NSMutableArray *cacheURLs;
+ NSMutableDictionary *downloaderForURL;
+ NSMutableArray *failedURLs;
+}
+
++ (id)sharedManager;
+- (UIImage *)imageWithURL:(NSURL *)url;
+- (void)downloadWithURL:(NSURL *)url delegate:(id<SDWebImageManagerDelegate>)delegate;
+- (void)downloadWithURL:(NSURL *)url delegate:(id<SDWebImageManagerDelegate>)delegate options:(SDWebImageOptions)options;
+- (void)downloadWithURL:(NSURL *)url delegate:(id<SDWebImageManagerDelegate>)delegate retryFailed:(BOOL)retryFailed __attribute__ ((deprecated)); // use options:SDWebImageRetryFailed instead
+- (void)downloadWithURL:(NSURL *)url delegate:(id<SDWebImageManagerDelegate>)delegate retryFailed:(BOOL)retryFailed lowPriority:(BOOL)lowPriority __attribute__ ((deprecated)); // use options:SDWebImageRetryFailed|SDWebImageLowPriority instead
+- (void)cancelForDelegate:(id<SDWebImageManagerDelegate>)delegate;
+
+@end
290 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageManager.m
@@ -0,0 +1,290 @@
+/*
+ * This file is part of the SDWebImage package.
+ * (c) Olivier Poitrey <rs@dailymotion.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#import "SDWebImageManager.h"
+#import "SDImageCache.h"
+#import "SDWebImageDownloader.h"
+
+static SDWebImageManager *instance;
+
+@implementation SDWebImageManager
+
+- (id)init
+{
+ if ((self = [super init]))
+ {
+ downloadDelegates = [[NSMutableArray alloc] init];
+ downloaders = [[NSMutableArray alloc] init];
+ cacheDelegates = [[NSMutableArray alloc] init];
+ cacheURLs = [[NSMutableArray alloc] init];
+ downloaderForURL = [[NSMutableDictionary alloc] init];
+ failedURLs = [[NSMutableArray alloc] init];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [downloadDelegates release], downloadDelegates = nil;
+ [downloaders release], downloaders = nil;
+ [cacheDelegates release], cacheDelegates = nil;
+ [cacheURLs release], cacheURLs = nil;
+ [downloaderForURL release], downloaderForURL = nil;
+ [failedURLs release], failedURLs = nil;
+ [super dealloc];
+}
+
+
++ (id)sharedManager
+{
+ if (instance == nil)
+ {
+ instance = [[SDWebImageManager alloc] init];
+ }
+
+ return instance;
+}
+
+/**
+ * @deprecated
+ */
+- (UIImage *)imageWithURL:(NSURL *)url
+{
+ return [[SDImageCache sharedImageCache] imageFromKey:[url absoluteString]];
+}
+
+/**
+ * @deprecated
+ */
+- (void)downloadWithURL:(NSURL *)url delegate:(id<SDWebImageManagerDelegate>)delegate retryFailed:(BOOL)retryFailed
+{
+ [self downloadWithURL:url delegate:delegate options:(retryFailed ? SDWebImageRetryFailed : 0)];
+}
+
+/**
+ * @deprecated
+ */
+- (void)downloadWithURL:(NSURL *)url delegate:(id<SDWebImageManagerDelegate>)delegate retryFailed:(BOOL)retryFailed lowPriority:(BOOL)lowPriority
+{
+ SDWebImageOptions options = 0;
+ if (retryFailed) options |= SDWebImageRetryFailed;
+ if (lowPriority) options |= SDWebImageLowPriority;
+ [self downloadWithURL:url delegate:delegate options:options];
+}
+
+- (void)downloadWithURL:(NSURL *)url delegate:(id<SDWebImageManagerDelegate>)delegate
+{
+ [self downloadWithURL:url delegate:delegate options:0];
+}
+
+- (void)downloadWithURL:(NSURL *)url delegate:(id<SDWebImageManagerDelegate>)delegate options:(SDWebImageOptions)options
+{
+ // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, XCode won't
+ // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
+ if ([url isKindOfClass:NSString.class])
+ {
+ url = [NSURL URLWithString:(NSString *)url];
+ }
+
+ if (!url || !delegate || (!(options & SDWebImageRetryFailed) && [failedURLs containsObject:url]))
+ {
+ return;
+ }
+
+ // Check the on-disk cache async so we don't block the main thread
+ [cacheDelegates addObject:delegate];
+ [cacheURLs addObject:url];
+ NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:delegate, @"delegate", url, @"url", [NSNumber numberWithInt:options], @"options", nil];
+ [[SDImageCache sharedImageCache] queryDiskCacheForKey:[url absoluteString] delegate:self userInfo:info];
+}
+
+- (void)cancelForDelegate:(id<SDWebImageManagerDelegate>)delegate
+{
+ NSUInteger idx;
+ while ((idx = [cacheDelegates indexOfObjectIdenticalTo:delegate]) != NSNotFound)
+ {
+ [cacheDelegates removeObjectAtIndex:idx];
+ [cacheURLs removeObjectAtIndex:idx];
+ }
+
+ while ((idx = [downloadDelegates indexOfObjectIdenticalTo:delegate]) != NSNotFound)
+ {
+ SDWebImageDownloader *downloader = [[downloaders objectAtIndex:idx] retain];
+
+ [downloadDelegates removeObjectAtIndex:idx];
+ [downloaders removeObjectAtIndex:idx];
+
+ if (![downloaders containsObject:downloader])
+ {
+ // No more delegate are waiting for this download, cancel it
+ [downloader cancel];
+ [downloaderForURL removeObjectForKey:downloader.url];
+ }
+
+ [downloader release];
+ }
+}
+
+#pragma mark SDImageCacheDelegate
+
+- (NSUInteger)indexOfDelegate:(id<SDWebImageManagerDelegate>)delegate waitingForURL:(NSURL *)url
+{
+ // Do a linear search, simple (even if inefficient)
+ NSUInteger idx;
+ for (idx = 0; idx < [cacheDelegates count]; idx++)
+ {
+ if ([cacheDelegates objectAtIndex:idx] == delegate && [[cacheURLs objectAtIndex:idx] isEqual:url])
+ {
+ return idx;
+ }
+ }
+ return NSNotFound;
+}
+
+- (void)imageCache:(SDImageCache *)imageCache didFindImage:(UIImage *)image forKey:(NSString *)key userInfo:(NSDictionary *)info
+{
+ NSURL *url = [info objectForKey:@"url"];
+ id<SDWebImageManagerDelegate> delegate = [info objectForKey:@"delegate"];
+
+ NSUInteger idx = [self indexOfDelegate:delegate waitingForURL:url];
+ if (idx == NSNotFound)
+ {
+ // Request has since been canceled
+ return;
+ }
+
+ if ([delegate respondsToSelector:@selector(webImageManager:didFinishWithImage:)])
+ {
+ [delegate performSelector:@selector(webImageManager:didFinishWithImage:) withObject:self withObject:image];
+ }
+
+ [cacheDelegates removeObjectAtIndex:idx];
+ [cacheURLs removeObjectAtIndex:idx];
+}
+
+- (void)imageCache:(SDImageCache *)imageCache didNotFindImageForKey:(NSString *)key userInfo:(NSDictionary *)info
+{
+ NSURL *url = [info objectForKey:@"url"];
+ id<SDWebImageManagerDelegate> delegate = [info objectForKey:@"delegate"];
+ SDWebImageOptions options = [[info objectForKey:@"options"] intValue];
+
+ NSUInteger idx = [self indexOfDelegate:delegate waitingForURL:url];
+ if (idx == NSNotFound)
+ {
+ // Request has since been canceled
+ return;
+ }
+
+ [cacheDelegates removeObjectAtIndex:idx];
+ [cacheURLs removeObjectAtIndex:idx];
+
+ // Share the same downloader for identical URLs so we don't download the same URL several times
+ SDWebImageDownloader *downloader = [downloaderForURL objectForKey:url];
+
+ if (!downloader)
+ {
+ downloader = [SDWebImageDownloader downloaderWithURL:url delegate:self userInfo:info lowPriority:(options & SDWebImageLowPriority)];
+ [downloaderForURL setObject:downloader forKey:url];
+ }
+ else
+ {
+ // Reuse shared downloader
+ downloader.userInfo = info;
+ downloader.lowPriority = (options & SDWebImageLowPriority);
+ }
+
+ [downloadDelegates addObject:delegate];
+ [downloaders addObject:downloader];
+}
+
+#pragma mark SDWebImageDownloaderDelegate
+
+- (void)imageDownloader:(SDWebImageDownloader *)downloader didFinishWithImage:(UIImage *)image
+{
+ [downloader retain];
+ SDWebImageOptions options = [[downloader.userInfo objectForKey:@"options"] intValue];
+
+ // Notify all the downloadDelegates with this downloader
+ for (NSInteger idx = (NSInteger)[downloaders count] - 1; idx >= 0; idx--)
+ {
+ NSUInteger uidx = (NSUInteger)idx;
+ SDWebImageDownloader *aDownloader = [downloaders objectAtIndex:uidx];
+ if (aDownloader == downloader)
+ {
+ id<SDWebImageManagerDelegate> delegate = [[[downloadDelegates objectAtIndex:uidx] retain] autorelease];
+
+ if (image)
+ {
+ if ([delegate respondsToSelector:@selector(webImageManager:didFinishWithImage:)])
+ {
+ [delegate performSelector:@selector(webImageManager:didFinishWithImage:) withObject:self withObject:image];
+ }
+ }
+ else
+ {
+ if ([delegate respondsToSelector:@selector(webImageManager:didFailWithError:)])
+ {
+ [delegate performSelector:@selector(webImageManager:didFailWithError:) withObject:self withObject:nil];
+ }
+ }
+
+ [downloaders removeObjectAtIndex:uidx];
+ [downloadDelegates removeObjectAtIndex:uidx];
+ }
+ }
+
+ if (image)
+ {
+ // Store the image in the cache
+ [[SDImageCache sharedImageCache] storeImage:image
+ imageData:downloader.imageData
+ forKey:[downloader.url absoluteString]
+ toDisk:!(options & SDWebImageCacheMemoryOnly)];
+ }
+ else if (!(options & SDWebImageRetryFailed))
+ {
+ // The image can't be downloaded from this URL, mark the URL as failed so we won't try and fail again and again
+ // (do this only if SDWebImageRetryFailed isn't activated)
+ [failedURLs addObject:downloader.url];
+ }
+
+
+ // Release the downloader
+ [downloaderForURL removeObjectForKey:downloader.url];
+ [downloader release];
+}
+
+- (void)imageDownloader:(SDWebImageDownloader *)downloader didFailWithError:(NSError *)error;
+{
+ [downloader retain];
+
+ // Notify all the downloadDelegates with this downloader
+ for (NSInteger idx = (NSInteger)[downloaders count] - 1; idx >= 0; idx--)
+ {
+ NSUInteger uidx = (NSUInteger)idx;
+ SDWebImageDownloader *aDownloader = [downloaders objectAtIndex:uidx];
+ if (aDownloader == downloader)
+ {
+ id<SDWebImageManagerDelegate> delegate = [[[downloadDelegates objectAtIndex:uidx] retain] autorelease];
+
+ if ([delegate respondsToSelector:@selector(webImageManager:didFailWithError:)])
+ {
+ [delegate performSelector:@selector(webImageManager:didFailWithError:) withObject:self withObject:error];
+ }
+
+ [downloaders removeObjectAtIndex:uidx];
+ [downloadDelegates removeObjectAtIndex:uidx];
+ }
+ }
+
+ // Release the downloader
+ [downloaderForURL removeObjectForKey:downloader.url];
+ [downloader release];
+}
+
+@end
19 iHasAppExample/iHasAppExample/SDWebImage/SDWebImageManagerDelegate.h
@@ -0,0 +1,19 @@
+/*
+ * This file is part of the SDWebImage package.
+ * (c) Olivier Poitrey <rs@dailymotion.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+@class SDWebImageManager;
+@class UIImage;
+
+@protocol SDWebImageManagerDelegate <NSObject>
+
+@optional
+
+- (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage *)image;
+- (void)webImageManager:(SDWebImageManager *)imageManager didFailWithError:(NSError *)error;
+
+@end
44 iHasAppExample/iHasAppExample/SDWebImage/SDWebImagePrefetcher.h
@@ -0,0 +1,44 @@
+/*
+ * This file is part of the SDWebImage package.
+ * (c) Olivier Poitrey <rs@dailymotion.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#import <Foundation/Foundation.h>
+#import "SDWebImageManagerDelegate.h"
+
+@interface SDWebImagePrefetcher : NSObject <SDWebImageManagerDelegate>
+{
+ NSArray *_prefetchURLs;
+ NSUInteger _skippedCount;
+ NSUInteger _finishedCount;
+ NSUInteger _requestedCount;
+ NSTimeInterval _startedTime;
+}
+
+/**
+ * Maximum number of URLs to prefetch at the same time. Defaults to 3.
+ */
+@property (nonatomic, assign) NSUInteger maxConcurrentDownloads;
+
++ (SDWebImagePrefetcher *)sharedImagePrefetcher;
+
+/**
+ * Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching,
+ * currently one image is downloaded at a time,
+ * and skips images for failed downloads and proceed to the next image in the list
+ *
+ * @param NSArray list of URLs to prefetch
+ */
+- (void)prefetchURLs:(NSArray *)urls;
+
+
+/**
+ * Remove and cancel queued list
+ */
+- (void)cancelPrefetching;
+
+
+@end
112 iHasAppExample/iHasAppExample/SDWebImage/SDWebImagePrefetcher.m
@@ -0,0 +1,112 @@
+/*
+ * This file is part of the SDWebImage package.
+ * (c) Olivier Poitrey <rs@dailymotion.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+#import "SDWebImagePrefetcher.h"
+#import "SDWebImageManager.h"
+
+@interface SDWebImagePrefetcher ()
+@property (nonatomic, retain) NSArray *prefetchURLs;
+@end
+
+@implementation SDWebImagePrefetcher
+
+static SDWebImagePrefetcher *instance;
+
+@synthesize prefetchURLs;
+@synthesize maxConcurrentDownloads;
+
++ (SDWebImagePrefetcher *)sharedImagePrefetcher
+{
+ if (instance == nil)
+ {
+ instance = [[SDWebImagePrefetcher alloc] init];
+ instance.maxConcurrentDownloads = 3;
+ }
+
+ return instance;
+}
+
+- (void)startPrefetchingAtIndex:(NSUInteger)index withManager:(SDWebImageManager *)imageManager
+{
+ if (index >= [self.prefetchURLs count]) return;
+ _requestedCount++;
+ [imageManager downloadWithURL:[self.prefetchURLs objectAtIndex:index] delegate:self options:SDWebImageLowPriority];
+}
+
+- (void)reportStatus
+{
+ NSUInteger total = [self.prefetchURLs count];
+ NSLog(@"Finished prefetching (%d successful, %d skipped, timeElasped %.2f)", total - _skippedCount, _skippedCount, CFAbsoluteTimeGetCurrent() - _startedTime);
+}
+
+- (void)prefetchURLs:(NSArray *)urls
+{
+ [self cancelPrefetching]; // Prevent duplicate prefetch request
+ _startedTime = CFAbsoluteTimeGetCurrent();
+ self.prefetchURLs = urls;
+