├── Screenshot.png ├── Pigeon.bundle ├── en.lproj │ └── Localizable.strings ├── ru.lproj │ └── Localizable.strings └── zh-Hans.lproj │ └── Localizable.strings ├── LICENSE ├── Pigeon.h ├── README.md └── Pigeon.m /Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightory/Pigeon/HEAD/Screenshot.png -------------------------------------------------------------------------------- /Pigeon.bundle/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightory/Pigeon/HEAD/Pigeon.bundle/en.lproj/Localizable.strings -------------------------------------------------------------------------------- /Pigeon.bundle/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightory/Pigeon/HEAD/Pigeon.bundle/ru.lproj/Localizable.strings -------------------------------------------------------------------------------- /Pigeon.bundle/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightory/Pigeon/HEAD/Pigeon.bundle/zh-Hans.lproj/Localizable.strings -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 LIGHT lightory@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Pigeon.h: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2013 LIGHT lightory@gmail.com 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in 7 | // the Software without restriction, including without limitation the rights to 8 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software is furnished to do so, 10 | // subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | #import 23 | 24 | 25 | @interface Pigeon : NSObject 26 | @property (strong, nonatomic) NSString *countyCode; 27 | @property (strong, nonatomic) NSString *latestVersion; 28 | @property (strong, nonatomic) NSString *updateMessage; 29 | @property (assign, nonatomic) NSTimeInterval notifyInterval; 30 | 31 | + (instancetype)sharedInstance; 32 | - (void)enableLocalNotification; 33 | - (void)startWithAppleId:(NSString *)appleId; 34 | - (void)openInAppStore; 35 | @end 36 | 37 | 38 | static NSString *const PigeonDidFindNewVersionNotification = @"PigeonDidFindNewVersionNotification"; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pigeon 2 | ====== 3 | Detect new version from App Store, and notify user with local notifications. 4 | 5 | ## Features 6 | - Just add 3 lines of code, and it works. VERY EASY TO USE. 7 | - Detect new version from App Store automatically. No server-side required. 8 | - Notify user after finish using the app. No Bother. 9 | - Post a notification when finding a new version. You can observe it and notify users in the way you prefer, a UIAlertView for example. 10 | 11 | ## Demo 12 | 13 | ![image](https://github.com/lightory/Pigeon/raw/master/Screenshot.png) 14 | 15 | ## Quick Example 16 | 17 | ``` objective-c 18 | @implementation AppDelegate 19 | 20 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 21 | { 22 | [[Pigeon sharedInstance] enableLocalNotification]; 23 | [[Pigeon sharedInstance] startWithAppleId:@"584296227"]; 24 | 25 | return YES; 26 | } 27 | 28 | - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification 29 | { 30 | [[Pigeon sharedInstance] openInAppStore]; 31 | } 32 | 33 | @end 34 | ``` 35 | 36 | ## Customize 37 | 38 | You can also customize Pigeon as you wish. REMEMBER to set the customizable properties before you call `startWithAppleId:`; 39 | 40 | Manage the `latestVersion` yourself, so `Pigeon` won't fetch it from App Store. 41 | 42 | ``` objective-c 43 | @property (strong, nonatomic) NSString *latestVersion; 44 | ``` 45 | 46 | The message of local notification. 47 | 48 | ``` objective-c 49 | @property (strong, nonatomic) NSString *updateMessage; 50 | ``` 51 | 52 | The country code ( @"us", @"ru" for example ). You should set this code, if your application is not available in all countries. 53 | 54 | ``` objective-c 55 | @property (strong, nonatomic) NSString *countyCode; 56 | ``` 57 | 58 | The notify interval. Default values is one day. 59 | 60 | ``` objective-c 61 | @property (assign, nonatomic) NSTimeInterval notifyInterval; 62 | ``` 63 | 64 | ## One More Thing 65 | 66 | `Pigeon` will post a notification when finding a new version. You can observe it and notify users in the way you prefer, a UIAlertView for example. 67 | 68 | ``` objective-c 69 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(showNewVersionAlertView) name:PigeonDidFindNewVersionNotification object:nil]; 70 | ``` 71 | 72 | ## Who use Pigeon? 73 | 74 | If you're building your applications using `Pigeon`, please let me know! (add your application name & App Store link here and pull reuqest this README. 75 | 76 | - Curs Valutar și Convertor: https://itunes.apple.com/us/app/curs-valutar-si-convertor/id548653222 77 | 78 | 79 | ## License 80 | 81 | The MIT License (MIT) 82 | 83 | Copyright (c) 2013 LIGHT lightory@gmail.com 84 | 85 | Permission is hereby granted, free of charge, to any person obtaining a copy of 86 | this software and associated documentation files (the "Software"), to deal in 87 | the Software without restriction, including without limitation the rights to 88 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 89 | the Software, and to permit persons to whom the Software is furnished to do so, 90 | subject to the following conditions: 91 | 92 | The above copyright notice and this permission notice shall be included in all 93 | copies or substantial portions of the Software. 94 | 95 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 96 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 97 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 98 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 99 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 100 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 101 | -------------------------------------------------------------------------------- /Pigeon.m: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2013 LIGHT lightory@gmail.com 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in 7 | // the Software without restriction, including without limitation the rights to 8 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software is furnished to do so, 10 | // subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | 23 | #import "Pigeon.h" 24 | 25 | 26 | @interface Pigeon() 27 | @property (assign, nonatomic) NSString *appleId; 28 | @end 29 | 30 | 31 | @implementation Pigeon 32 | 33 | + (instancetype)sharedInstance 34 | { 35 | static Pigeon *instance; 36 | static dispatch_once_t onceToken; 37 | dispatch_once(&onceToken, ^{ 38 | instance = [[[self class] alloc] init]; 39 | instance.updateMessage = [NSString stringWithFormat:[instance localizedStringForKey:@"UpdateMessage"], [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"]]; 40 | instance.notifyInterval = 3600 * 24; 41 | }); 42 | return instance; 43 | } 44 | 45 | - (void)startWithAppleId:(NSString *)appleId 46 | { 47 | self.appleId = appleId; 48 | 49 | if (![self shouldCheck]) return; 50 | 51 | if (!self.latestVersion) { 52 | dispatch_async(dispatch_queue_create("Pigeon", NULL), ^{ 53 | [self fetchLatestVersionFromAppStore]; 54 | if (![self isLatestVersion]) { 55 | [[NSNotificationCenter defaultCenter] postNotificationName:PigeonDidFindNewVersionNotification object:nil]; 56 | } 57 | }); 58 | } else { 59 | if (![self isLatestVersion]) { 60 | [[NSNotificationCenter defaultCenter] postNotificationName:PigeonDidFindNewVersionNotification object:nil]; 61 | } 62 | } 63 | } 64 | 65 | - (void)openInAppStore 66 | { 67 | if (![NSThread isMainThread]) { 68 | [self performSelectorOnMainThread:@selector(openInAppStore) withObject:nil waitUntilDone:YES]; 69 | return; 70 | } 71 | 72 | NSString *url = [NSString stringWithFormat:@"itms-apps://itunes.apple.com/app/id%@", self.appleId]; 73 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]]; 74 | } 75 | 76 | #pragma mark 77 | - (BOOL)shouldCheck 78 | { 79 | NSTimeInterval currentTimeInterval = [[NSDate date] timeIntervalSince1970]; 80 | NSTimeInterval lastCheckedTimeInterval = [[NSUserDefaults standardUserDefaults] floatForKey:@"PIGEON_LAST_CHECKED_TIME_INTERVAL"]; 81 | if (currentTimeInterval <= lastCheckedTimeInterval + self.notifyInterval) return NO; 82 | 83 | [[NSUserDefaults standardUserDefaults] setFloat:currentTimeInterval forKey:@"PIGEON_LAST_CHECKED_TIME_INTERVAL"]; 84 | [[NSUserDefaults standardUserDefaults] synchronize]; 85 | 86 | return YES; 87 | } 88 | 89 | - (BOOL)isLatestVersion 90 | { 91 | NSString *currentVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; 92 | 93 | NSArray *latestVersionArray = [self.latestVersion componentsSeparatedByString:@"."]; 94 | NSArray *currentVersionArray = [currentVersion componentsSeparatedByString:@"."]; 95 | 96 | for (NSInteger i = 0; i < latestVersionArray.count; i++) { 97 | if (currentVersionArray.count <= i) return NO; 98 | if ([latestVersionArray[i] intValue] > [currentVersionArray[i] intValue]) return NO; 99 | if ([latestVersionArray[i] intValue] < [currentVersionArray[i] intValue]) return YES; 100 | if ([latestVersionArray[i] intValue] == [currentVersionArray[i] intValue]) continue; 101 | } 102 | 103 | return YES; 104 | } 105 | 106 | - (void)fetchLatestVersionFromAppStore 107 | { 108 | NSError *error = nil; 109 | NSString *countryCode = self.countyCode.length ? [self.countyCode stringByAppendingString:@"/"] : @""; 110 | NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://itunes.apple.com/%@lookup?id=%@", countryCode, self.appleId]]; 111 | NSURLRequest *request = [NSURLRequest requestWithURL:url]; 112 | NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error]; 113 | 114 | if (response && !error) { 115 | NSDictionary *infoDic = [NSJSONSerialization JSONObjectWithData:response options:NSJSONReadingMutableLeaves error:&error]; 116 | 117 | if (!infoDic[@"results"]) return; 118 | if ([infoDic[@"results"] count] == 0) return; 119 | self.latestVersion = infoDic[@"results"][0][@"version"]; 120 | } 121 | } 122 | 123 | #pragma mark - Local Notification 124 | - (void)enableLocalNotification 125 | { 126 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(addObserverOnApplicationDidEnterBackground) name:PigeonDidFindNewVersionNotification object:nil]; 127 | } 128 | 129 | - (void)addObserverOnApplicationDidEnterBackground 130 | { 131 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(scheduleUpdateNotification) name:UIApplicationDidEnterBackgroundNotification object:nil]; 132 | } 133 | 134 | - (void)scheduleUpdateNotification 135 | { 136 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; 137 | 138 | UILocalNotification *localNotification = [[UILocalNotification alloc] init]; 139 | localNotification.fireDate = [[[NSDate alloc] init] dateByAddingTimeInterval:3]; 140 | localNotification.timeZone = [NSTimeZone defaultTimeZone]; 141 | localNotification.alertBody = self.updateMessage; 142 | [[UIApplication sharedApplication] scheduleLocalNotification:localNotification]; 143 | } 144 | 145 | #pragma mark - Localization 146 | - (NSString *)localizedStringForKey:(NSString *)key 147 | { 148 | return [[self localizedBundle] localizedStringForKey:key value:nil table:nil]; 149 | } 150 | 151 | - (NSBundle *)localizedBundle 152 | { 153 | static NSBundle *localizedBundle = nil; 154 | if (localizedBundle == nil) { 155 | NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"Pigeon" ofType:@"bundle"]; 156 | localizedBundle = [NSBundle bundleWithPath:bundlePath] ?: [NSBundle mainBundle]; 157 | 158 | for (NSString *language in [NSLocale preferredLanguages]) { 159 | if ([[localizedBundle localizations] containsObject:language]) { 160 | bundlePath = [localizedBundle pathForResource:language ofType:@"lproj"]; 161 | localizedBundle = [NSBundle bundleWithPath:bundlePath]; 162 | break; 163 | } 164 | } 165 | 166 | if (localizedBundle == nil) { 167 | bundlePath = [localizedBundle pathForResource:@"en" ofType:@"lproj"]; 168 | localizedBundle = [NSBundle bundleWithPath:bundlePath]; 169 | } 170 | } 171 | 172 | return localizedBundle; 173 | } 174 | 175 | @end --------------------------------------------------------------------------------