├── .gitignore ├── Classes ├── AppDelegate.h ├── AppDelegate.m ├── Button.h ├── Button.m ├── Concurrency-Info.plist ├── Concurrency-Prefix.pch ├── CubeController+Wiggle.h ├── CubeController+Wiggle.m ├── Currencies.h ├── Currencies.m ├── Currency.h ├── Currency.m ├── CurrencyCell.h ├── CurrencyCell.m ├── CurrencyCell.xib ├── Main.storyboard ├── MainViewController.h ├── MainViewController.m ├── MainViewController.xib ├── NumberField.h ├── NumberField.m ├── NumberPad.h ├── NumberPad.m ├── NumberPad.xib ├── PickerCellView.h ├── PickerCellView.m ├── PickerCellView.xib ├── PickerView.h ├── PickerView.m ├── PickerView.xib ├── SearchBar.h ├── SearchBar.m ├── Settings.h ├── Settings.m ├── SettingsViewController.h ├── SettingsViewController.m ├── SettingsViewController.xib ├── UIView+TintColor.h ├── UIView+TintColor.m ├── UIViewController+Gradient.h ├── UIViewController+Gradient.m └── main.m ├── Concurrency.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── Concurrency.xcscheme ├── LICENCE.md ├── Libraries ├── BaseModel │ ├── BaseModel.h │ └── BaseModel.m ├── ColorUtils │ ├── ColorUtils.h │ └── ColorUtils.m ├── CubeController │ ├── CubeController.h │ └── CubeController.m ├── ViewUtils │ ├── ViewUtils.h │ └── ViewUtils.m ├── XMLDictionary │ ├── XMLDictionary.h │ └── XMLDictionary.m ├── iCarousel │ ├── iCarousel.h │ └── iCarousel.m ├── iRate │ ├── iRate.bundle │ │ ├── ar.lproj │ │ │ └── Localizable.strings │ │ ├── bn.lproj │ │ │ └── Localizable.strings │ │ ├── cs.lproj │ │ │ └── Localizable.strings │ │ ├── da.lproj │ │ │ └── Localizable.strings │ │ ├── de-AT.lproj │ │ │ └── Localizable.strings │ │ ├── de.lproj │ │ │ └── Localizable.strings │ │ ├── el.lproj │ │ │ └── Localizable.strings │ │ ├── en.lproj │ │ │ └── Localizable.strings │ │ ├── es.lproj │ │ │ └── Localizable.strings │ │ ├── fa.lproj │ │ │ └── Localizable.strings │ │ ├── fr.lproj │ │ │ └── Localizable.strings │ │ ├── he.lproj │ │ │ └── Localizable.strings │ │ ├── hi.lproj │ │ │ └── Localizable.strings │ │ ├── id.lproj │ │ │ └── Localizable.strings │ │ ├── it.lproj │ │ │ └── Localizable.strings │ │ ├── ja.lproj │ │ │ └── Localizable.strings │ │ ├── ko.lproj │ │ │ └── Localizable.strings │ │ ├── nl.lproj │ │ │ └── Localizable.strings │ │ ├── no.lproj │ │ │ └── Localizable.strings │ │ ├── pa.lproj │ │ │ └── Localizable.strings │ │ ├── pl.lproj │ │ │ └── Localizable.strings │ │ ├── pt.lproj │ │ │ └── Localizable.strings │ │ ├── ru.lproj │ │ │ └── Localizable.strings │ │ ├── sk.lproj │ │ │ └── Localizable.strings │ │ ├── sl.lproj │ │ │ └── Localizable.strings │ │ ├── sv.lproj │ │ │ └── Localizable.strings │ │ ├── th.lproj │ │ │ └── Localizable.strings │ │ ├── tr.lproj │ │ │ └── Localizable.strings │ │ ├── uk.lproj │ │ │ └── Localizable.strings │ │ ├── ur-IN.lproj │ │ │ └── Localizable.strings │ │ ├── ur-PK.lproj │ │ │ └── Localizable.strings │ │ ├── ur.lproj │ │ │ └── Localizable.strings │ │ ├── vi.lproj │ │ │ └── Localizable.strings │ │ ├── zh-Hans.lproj │ │ │ └── Localizable.strings │ │ └── zh-Hant.lproj │ │ │ └── Localizable.strings │ ├── iRate.h │ └── iRate.m └── iVersion │ ├── iVersion.bundle │ ├── da.lproj │ │ └── Localizable.strings │ ├── de.lproj │ │ └── Localizable.strings │ ├── el.lproj │ │ └── Localizable.strings │ ├── en.lproj │ │ └── Localizable.strings │ ├── es.lproj │ │ └── Localizable.strings │ ├── fr.lproj │ │ └── Localizable.strings │ ├── it.lproj │ │ └── Localizable.strings │ ├── ja.lproj │ │ └── Localizable.strings │ ├── pt-PT.lproj │ │ └── Localizable.strings │ ├── pt.lproj │ │ └── Localizable.strings │ ├── ru.lproj │ │ └── Localizable.strings │ ├── tr.lproj │ │ └── Localizable.strings │ └── zh-Hans.lproj │ │ └── Localizable.strings │ ├── iVersion.h │ └── iVersion.m ├── README.md └── Resources ├── Data └── Currencies.plist └── Images.xcassets ├── AppIcon.appiconset ├── Contents.json ├── Icon-40.png ├── Icon-40@2x-1.png ├── Icon-40@2x.png ├── Icon-40@3x.png ├── Icon-60@2x.png ├── Icon-60@3x.png ├── Icon-72.png ├── Icon-72@2x.png ├── Icon-76.png ├── Icon-76@2x.png ├── Icon-Small-1.png ├── Icon-Small-50.png ├── Icon-Small-50@2x.png ├── Icon-Small.png ├── Icon-Small@2x-1.png ├── Icon-Small@2x.png ├── Icon-Small@3x.png ├── Icon.png └── Icon@2x.png └── LaunchImage.launchimage ├── Contents.json ├── Default-568h@2x-1.png ├── Default-568h@2x.png ├── Default-667h@2x.png ├── Default-736h@3x.png ├── Default.png ├── Default@2x-1.png └── Default@2x.png /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # CocoaPods 23 | Pods 24 | *.xcworkspacedata 25 | -------------------------------------------------------------------------------- /Classes/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 26/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Classes/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 26/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "CubeController.h" 11 | #import "MainViewController.h" 12 | #import "SettingsViewController.h" 13 | #import "CubeController+Wiggle.h" 14 | #import "UIViewController+Gradient.h" 15 | #import "Currencies.h" 16 | #import "ViewUtils.h" 17 | 18 | 19 | @interface AppDelegate () 20 | 21 | @property (nonatomic, strong) id visibleAlert; 22 | 23 | @end 24 | 25 | 26 | @implementation AppDelegate 27 | 28 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 29 | { 30 | //set window tint (does nothing on iOS 6) 31 | self.window.tintColor = [UIColor colorWithRed:100.0f/255 green:200.0f/255 blue:100.0f/255 alpha:1]; 32 | 33 | //set up cube controller 34 | CubeController *controller = (CubeController *)self.window.rootViewController; 35 | controller.dataSource = self; 36 | controller.delegate = self; 37 | controller.view.backgroundColor = [UIColor whiteColor]; 38 | 39 | //add window gradient 40 | dispatch_async(dispatch_get_main_queue(), ^(void) { 41 | [controller addGradientLayer]; 42 | }); 43 | 44 | //add tap gesture for cancelling wiggle animation 45 | UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:nil action:NULL]; 46 | gesture.delegate = self; 47 | [controller.view addGestureRecognizer:gesture]; 48 | 49 | //wiggle the cube controller 50 | [controller wiggleWithCompletionBlock:^(BOOL finished) { 51 | [controller.view removeGestureRecognizer:gesture]; 52 | }]; 53 | 54 | return YES; 55 | } 56 | 57 | - (void)applicationDidBecomeActive:(UIApplication *)application 58 | { 59 | self.window.rootViewController.view.frame = self.window.bounds; 60 | } 61 | 62 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch 63 | { 64 | [gestureRecognizer.view removeGestureRecognizer:gestureRecognizer]; 65 | [(CubeController *)self.window.rootViewController cancelWiggle]; 66 | return NO; 67 | } 68 | 69 | - (NSInteger)numberOfViewControllersInCubeController:(CubeController *)cubeController 70 | { 71 | return 2; 72 | } 73 | 74 | - (UIViewController *)cubeController:(CubeController *)cubeController 75 | viewControllerAtIndex:(NSInteger)index 76 | { 77 | switch (index) 78 | { 79 | case 0: 80 | { 81 | return [[MainViewController alloc] init]; 82 | } 83 | case 1: 84 | { 85 | return [[SettingsViewController alloc] init]; 86 | } 87 | } 88 | return nil; 89 | } 90 | 91 | - (void)cubeControllerCurrentViewControllerIndexDidChange:(CubeController *)cubeController 92 | { 93 | UIResponder *field = [cubeController.view firstResponder]; 94 | if ([field respondsToSelector:@selector(setSelectedTextRange:)]) 95 | { 96 | //prevents weird misalignment of selection handles 97 | [field setValue:nil forKey:@"selectedTextRange"]; 98 | } 99 | [field resignFirstResponder]; 100 | } 101 | 102 | - (void)cubeControllerDidEndDecelerating:(CubeController *)cubeController 103 | { 104 | if (cubeController.currentViewControllerIndex == 0 && 105 | [[Currencies sharedInstance].enabledCurrencies count] == 0) 106 | { 107 | [cubeController scrollToViewControllerAtIndex:1 animated:YES]; 108 | } 109 | } 110 | 111 | - (void)cubeControllerDidEndScrollingAnimation:(CubeController *)cubeController 112 | { 113 | if (cubeController.currentViewControllerIndex == 1 && 114 | [[Currencies sharedInstance].enabledCurrencies count] == 0) 115 | { 116 | dispatch_async(dispatch_get_main_queue(), ^{ 117 | 118 | if (!self.visibleAlert) 119 | { 120 | NSString *title = @"No Currencies Selected"; 121 | NSString *message = @"Please select at least two currencies in order to use the converter."; 122 | NSString *button = @"OK"; 123 | 124 | if ([UIAlertController class]) 125 | { 126 | self.visibleAlert = [UIAlertController alertControllerWithTitle:title 127 | message:message 128 | preferredStyle:UIAlertControllerStyleAlert]; 129 | 130 | [self.visibleAlert addAction:[UIAlertAction actionWithTitle:button style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { 131 | self.visibleAlert = nil; 132 | }]]; 133 | 134 | [self.window.rootViewController presentViewController:self.visibleAlert animated:YES completion:NULL]; 135 | } 136 | else 137 | { 138 | self.visibleAlert = [[UIAlertView alloc] initWithTitle:title 139 | message:message 140 | delegate:self 141 | cancelButtonTitle:button 142 | otherButtonTitles:nil]; 143 | [self.visibleAlert show]; 144 | } 145 | } 146 | }); 147 | } 148 | } 149 | 150 | - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex 151 | { 152 | if (alertView == self.visibleAlert) 153 | { 154 | self.visibleAlert = nil; 155 | } 156 | } 157 | 158 | @end 159 | -------------------------------------------------------------------------------- /Classes/Button.h: -------------------------------------------------------------------------------- 1 | // 2 | // Button.h 3 | // Currency 4 | // 5 | // Created by Nick Lockwood on 05/11/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface Button : UIButton 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Classes/Button.m: -------------------------------------------------------------------------------- 1 | // 2 | // Button.m 3 | // Currency 4 | // 5 | // Created by Nick Lockwood on 05/11/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "Button.h" 10 | 11 | @implementation Button 12 | 13 | - (void)setUp 14 | { 15 | self.layer.cornerRadius = 8; 16 | self.clipsToBounds = YES; 17 | self.adjustsImageWhenHighlighted = NO; 18 | } 19 | 20 | - (UIColor *)tintColor 21 | { 22 | //for iOS 6 23 | return super.tintColor ?: self.window.tintColor; 24 | } 25 | 26 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 27 | { 28 | [super touchesBegan:touches withEvent:event]; 29 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(fadeToWhite) object:nil]; 30 | self.backgroundColor = self.tintColor; 31 | self.titleLabel.textColor = [UIColor whiteColor]; 32 | } 33 | 34 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 35 | { 36 | [super touchesEnded:touches withEvent:event]; 37 | self.titleLabel.textColor = [UIColor whiteColor]; 38 | [self performSelector:@selector(fadeToWhite) withObject:nil afterDelay:0.1]; 39 | } 40 | 41 | - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event 42 | { 43 | [super touchesCancelled:touches withEvent:event]; 44 | self.titleLabel.textColor = [UIColor whiteColor]; 45 | [self performSelector:@selector(fadeToWhite) withObject:nil afterDelay:0.1]; 46 | } 47 | 48 | - (void)fadeToWhite 49 | { 50 | [self crossfadeWithDuration:0.4]; 51 | self.titleLabel.textColor = [self titleColorForState:UIControlStateNormal]; 52 | self.backgroundColor = [UIColor whiteColor]; 53 | } 54 | 55 | - (void)setHighlighted:(BOOL)highlighted 56 | { 57 | //does nothing 58 | } 59 | 60 | - (id)initWithFrame:(CGRect)frame 61 | { 62 | if (self = [super initWithFrame:frame]) 63 | { 64 | [self setUp]; 65 | } 66 | return self; 67 | } 68 | 69 | - (id)initWithCoder:(NSCoder *)aDecoder 70 | { 71 | if (self = [super initWithCoder:aDecoder]) 72 | { 73 | [self setUp]; 74 | } 75 | return self; 76 | } 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /Classes/Concurrency-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFiles 12 | 13 | Icon 14 | Icon-Small 15 | Icon-Small-50 16 | Icon-76 17 | Icon-72 18 | Icon-60 19 | Icon-40 20 | 21 | CFBundleIdentifier 22 | com.charcoaldesign.${PRODUCT_NAME:rfc1034identifier} 23 | CFBundleInfoDictionaryVersion 24 | 6.0 25 | CFBundleName 26 | ${PRODUCT_NAME} 27 | CFBundlePackageType 28 | APPL 29 | CFBundleShortVersionString 30 | 1.2 31 | CFBundleSignature 32 | ???? 33 | CFBundleVersion 34 | 1.2 35 | LSRequiresIPhoneOS 36 | 37 | UIMainStoryboardFile 38 | Main 39 | UIRequiredDeviceCapabilities 40 | 41 | armv7 42 | 43 | UIStatusBarStyle 44 | UIStatusBarStyleBlackTranslucent 45 | UISupportedInterfaceOrientations 46 | 47 | UIInterfaceOrientationPortrait 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Classes/Concurrency-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | 15 | #import 16 | #import 17 | 18 | #import "ViewUtils.h" 19 | #import "iCarousel.h" 20 | #import "BaseModel.h" 21 | #import "CubeController.h" 22 | #import "XMLDictionary.h" 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Classes/CubeController+Wiggle.h: -------------------------------------------------------------------------------- 1 | // 2 | // CubeController+Wiggle.h 3 | // Concurrency 4 | // 5 | // Created by Nick Lockwood on 02/01/2014. 6 | // Copyright (c) 2014 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "CubeController.h" 10 | 11 | @interface CubeController (Wiggle) 12 | 13 | - (void)wiggleWithCompletionBlock:(void (^)(BOOL finished))block; 14 | - (void)cancelWiggle; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Classes/CubeController+Wiggle.m: -------------------------------------------------------------------------------- 1 | // 2 | // CubeController+Wiggle.m 3 | // Concurrency 4 | // 5 | // Created by Nick Lockwood on 02/01/2014. 6 | // Copyright (c) 2014 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "CubeController+Wiggle.h" 10 | #import 11 | 12 | 13 | @interface CubeController (Wiggle_Private) 14 | 15 | @property (nonatomic, getter = isWiggleCancelled) BOOL wiggleCancelled; 16 | 17 | @end 18 | 19 | 20 | @implementation CubeController (Wiggle) 21 | 22 | - (void)wiggleWithCompletionBlock:(void (^)(BOOL finished))block 23 | { 24 | double speed = 1.5; 25 | double amplitude = 1.0; 26 | self.wiggleCancelled = NO; 27 | 28 | [UIView animateWithDuration:0.5 / speed delay:0.5 options:UIViewAnimationOptionAllowUserInteraction animations:^{ 29 | 30 | self.scrollView.contentOffset = CGPointMake(80 * amplitude, 0); 31 | 32 | } completion:^(BOOL finished) { 33 | 34 | if (!finished || self.wiggleCancelled) 35 | { 36 | if (block) block(NO); 37 | return; 38 | } 39 | [UIView animateWithDuration:0.7 / speed delay:0.0 options:UIViewAnimationOptionAllowUserInteraction animations:^{ 40 | 41 | self.scrollView.contentOffset = CGPointMake(-50 * amplitude, 0); 42 | 43 | } completion:^(BOOL finished) { 44 | 45 | if (!finished || self.wiggleCancelled) 46 | { 47 | if (block) block(NO); 48 | return; 49 | } 50 | [UIView animateWithDuration:0.5 / speed delay:0.0 options:UIViewAnimationOptionAllowUserInteraction animations:^{ 51 | 52 | self.scrollView.contentOffset = CGPointMake(20 * amplitude, 0); 53 | 54 | } completion:^(BOOL finished) { 55 | 56 | if (!finished || self.wiggleCancelled) 57 | { 58 | if (block) block(NO); 59 | return; 60 | } 61 | [UIView animateWithDuration:0.7 / speed delay:0.0 options:UIViewAnimationOptionAllowUserInteraction animations:^{ 62 | 63 | self.scrollView.contentOffset = CGPointZero; 64 | 65 | } completion:block]; 66 | }]; 67 | }]; 68 | }]; 69 | } 70 | 71 | - (void)setWiggleCancelled:(BOOL)wiggleCancelled 72 | { 73 | objc_setAssociatedObject(self, @selector(isWiggleCancelled), @(wiggleCancelled), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 74 | } 75 | 76 | - (BOOL)isWiggleCancelled 77 | { 78 | return [objc_getAssociatedObject(self, @selector(isWiggleCancelled)) boolValue]; 79 | } 80 | 81 | - (void)cancelWiggle 82 | { 83 | self.wiggleCancelled = YES; 84 | } 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /Classes/Currencies.h: -------------------------------------------------------------------------------- 1 | // 2 | // Currencies.h 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 27/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BaseModel.h" 11 | #import "Currency.h" 12 | 13 | 14 | extern NSString *const CurrenciesUpdatedNotification; 15 | 16 | 17 | @interface Currencies : BaseModel 18 | 19 | @property (nonatomic, copy, readonly) NSArray *currencies; 20 | @property (nonatomic, copy, readonly) NSArray *enabledCurrencies; 21 | @property (nonatomic, strong, readonly) NSDate *lastUpdated; 22 | 23 | - (Currency *)currencyForCode:(NSString *)code; 24 | - (NSArray *)currenciesMatchingSearchString:(NSString *)searchString; 25 | - (void)updateWithBlock:(void (^)(void))block; 26 | - (void)update; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Classes/Currencies.m: -------------------------------------------------------------------------------- 1 | // 2 | // Currencies.m 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 27/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "Currencies.h" 10 | #import "XMLDictionary.h" 11 | 12 | 13 | NSString *const CurrenciesUpdatedNotification = @"CurrenciesUpdatedNotification"; 14 | static NSString *const UpdateURL = @"http://themoneyconverter.com/rss-feed/EUR/rss.xml"; 15 | 16 | 17 | @implementation Currencies 18 | { 19 | NSMutableDictionary *_currenciesByCode; 20 | } 21 | 22 | + (void)load 23 | { 24 | //auto-initialize 25 | [self performSelectorOnMainThread:@selector(sharedInstance) withObject:nil waitUntilDone:NO]; 26 | } 27 | 28 | + (BMFileFormat)saveFormat 29 | { 30 | return BMFileFormatXMLPropertyList; 31 | } 32 | 33 | - (void)setWithDictionary:(NSDictionary *)dict 34 | { 35 | //set last updated date 36 | NSDate *date = dict[@"lastUpdated"]; 37 | if (_lastUpdated && [_lastUpdated timeIntervalSinceDate:date] >= 0) 38 | { 39 | //merge enabled 40 | for (NSDictionary *entry in dict[@"currencies"]) 41 | { 42 | Currency *currency = [self currencyForCode:entry[@"code"]]; 43 | [currency setValue:entry[@"enabled"] forKey:@"enabled"]; 44 | } 45 | return; 46 | } 47 | _lastUpdated = date; 48 | 49 | //set currencies 50 | _currencies = [[Currency instancesWithArray:dict[@"currencies"]] sortedArrayUsingComparator:^NSComparisonResult(Currency *a, Currency *b) { 51 | 52 | return [a.name caseInsensitiveCompare:b.name]; 53 | }]; 54 | 55 | //set currencies by code 56 | _currenciesByCode = [NSMutableDictionary dictionaryWithObjects:_currencies forKeys:[_currencies valueForKeyPath:@"code"]]; 57 | 58 | //download update 59 | [self update]; 60 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(update) name:UIApplicationWillEnterForegroundNotification object:nil]; 61 | } 62 | 63 | - (Currency *)currencyForCode:(NSString *)code 64 | { 65 | return _currenciesByCode[code]; 66 | } 67 | 68 | - (NSArray *)currenciesMatchingSearchString:(NSString *)searchString 69 | { 70 | if ([searchString length]) 71 | { 72 | searchString = [searchString lowercaseString]; 73 | return [self.currencies filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(Currency *currency, __unused id bindings) { 74 | return [[currency.name lowercaseString] rangeOfString:searchString].length || [[currency.code lowercaseString] hasPrefix:searchString]; 75 | }]]; 76 | } 77 | else 78 | { 79 | return self.currencies; 80 | } 81 | } 82 | 83 | - (NSArray *)enabledCurrencies 84 | { 85 | return [_currencies filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"enabled=YES"]]; 86 | } 87 | 88 | - (void)updateWithBlock:(void (^)(void))block 89 | { 90 | NSURL *URL = [NSURL URLWithString:UpdateURL]; 91 | NSURLRequest *request = [NSURLRequest requestWithURL:URL]; 92 | [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { 93 | 94 | NSDictionary *xmlDict = [NSDictionary dictionaryWithXMLData:data]; 95 | if (xmlDict) 96 | { 97 | for (NSDictionary *entry in [xmlDict valueForKeyPath:@"channel.item"]) 98 | { 99 | NSString *code = [[entry[@"title"] componentsSeparatedByString:@"/"] firstObject]; 100 | if (!code) continue; 101 | 102 | NSString *description = [entry[@"description"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 103 | if (![description hasPrefix:@"1 Euro = "]) continue; 104 | 105 | description = [description substringFromIndex:9]; 106 | NSArray *parts = [description componentsSeparatedByString:@" "]; 107 | NSString *rate = [[parts firstObject] stringByReplacingOccurrencesOfString:@"," withString:@""]; 108 | if ([rate doubleValue] < 0.000001) continue; 109 | 110 | Currency *currency = [self currencyForCode:code]; 111 | if (!currency) 112 | { 113 | currency = [Currency instance]; 114 | [currency setValue:code forKey:@"code"]; 115 | _currenciesByCode[code] = currency; 116 | } 117 | 118 | NSString *name = [[parts subarrayWithRange:NSMakeRange(1, [parts count] - 1)] componentsJoinedByString:@" "]; 119 | if (name) [currency setValue:name forKey:@"name"]; 120 | [currency setValue:rate forKey:@"rate"]; 121 | } 122 | 123 | _lastUpdated = [NSDate date]; 124 | _currencies = [[_currenciesByCode allValues] sortedArrayUsingComparator:^NSComparisonResult(Currency *a, Currency *b) { 125 | 126 | return [a.name caseInsensitiveCompare:b.name]; 127 | }]; 128 | 129 | [self save]; 130 | } 131 | 132 | if (block) block(); 133 | }]; 134 | } 135 | 136 | - (void)update 137 | { 138 | [self updateWithBlock:NULL]; 139 | } 140 | 141 | - (BOOL)save 142 | { 143 | [[NSNotificationCenter defaultCenter] postNotificationName:CurrenciesUpdatedNotification object:self]; 144 | return [super save]; 145 | } 146 | 147 | - (void)dealloc 148 | { 149 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 150 | } 151 | 152 | @end 153 | -------------------------------------------------------------------------------- /Classes/Currency.h: -------------------------------------------------------------------------------- 1 | // 2 | // Currency.h 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 30/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "BaseModel.h" 10 | 11 | @interface Currency : BaseModel 12 | 13 | @property (nonatomic, copy, readonly) NSString *code; 14 | @property (nonatomic, copy, readonly) NSString *symbol; 15 | @property (nonatomic, copy, readonly) NSString *name; 16 | @property (nonatomic, assign, readonly) double rate; 17 | @property (nonatomic, assign, getter = isEnabled) BOOL enabled; 18 | 19 | + (instancetype)nullCurrency; 20 | 21 | - (double)valueInEuros:(double)value; 22 | - (double)valueFromEuros:(double)euroValue; 23 | - (double)value:(double)value convertedToCurrency:(Currency *)currency; 24 | - (NSString *)localisedStringFromValue:(double)value; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Classes/Currency.m: -------------------------------------------------------------------------------- 1 | // 2 | // Currency.m 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 30/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "Currency.h" 10 | #import "Currencies.h" 11 | 12 | 13 | @implementation Currency 14 | { 15 | //not a property, because we don't want to save it 16 | NSNumberFormatter *_numberFormatter; 17 | } 18 | 19 | + (instancetype)nullCurrency 20 | { 21 | static Currency *currency = nil; 22 | static dispatch_once_t onceToken; 23 | dispatch_once(&onceToken, ^{ 24 | currency = [[Currency alloc] init]; 25 | }); 26 | 27 | return currency; 28 | } 29 | 30 | - (NSNumberFormatter *)numberFormatter 31 | { 32 | if (!_numberFormatter) 33 | { 34 | _numberFormatter = [[NSNumberFormatter alloc] init]; 35 | [_numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; 36 | [_numberFormatter setCurrencySymbol:@""]; //currency symbol is displayed separately 37 | } 38 | return _numberFormatter; 39 | } 40 | 41 | - (void)setCode:(NSString *)code 42 | { 43 | _code = code; 44 | 45 | NSString *localeIdentifier = [NSLocale localeIdentifierFromComponents:@{NSLocaleCurrencyCode: code}]; 46 | NSLocale *locale = [NSLocale localeWithLocaleIdentifier:localeIdentifier]; 47 | [[self numberFormatter] setLocale:locale]; 48 | } 49 | 50 | - (void)setSymbol:(NSString *)symbol 51 | { 52 | _symbol = [symbol length]? symbol: nil; 53 | } 54 | 55 | - (double)valueInEuros:(double)value 56 | { 57 | return _rate? (value / _rate): 0.0; 58 | } 59 | 60 | - (double)valueFromEuros:(double)euroValue 61 | { 62 | return euroValue * _rate; 63 | } 64 | 65 | - (double)value:(double)value convertedToCurrency:(Currency *)currency 66 | { 67 | return [currency valueFromEuros:[self valueInEuros:value]]; 68 | } 69 | 70 | - (NSString *)localisedStringFromValue:(double)value 71 | { 72 | return [[self numberFormatter] stringFromNumber:@(value)]; 73 | } 74 | 75 | - (NSUInteger)hash 76 | { 77 | return [_code hash]; 78 | } 79 | 80 | - (BOOL)isEqual:(Currency *)object 81 | { 82 | if (object == self) return YES; 83 | if (![object isKindOfClass:[self class]]) return NO; 84 | return [object.code isEqualToString:self.code]; 85 | } 86 | 87 | - (BOOL)save 88 | { 89 | return [[Currencies sharedInstance] save]; 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /Classes/CurrencyCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // CurrencyCell.h 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 01/07/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Currency.h" 11 | 12 | @interface CurrencyCell : UITableViewCell 13 | 14 | + (instancetype)dequeueInstanceWithTableView:(UITableView *)tableView; 15 | - (void)configureWithCurrency:(Currency *)currency; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Classes/CurrencyCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // CurrencyCell.m 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 01/07/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "CurrencyCell.h" 10 | #import "ColorUtils.h" 11 | 12 | 13 | @interface CurrencyCell () 14 | 15 | @property (nonatomic, weak) IBOutlet UILabel *symbolLabel; 16 | @property (nonatomic, weak) IBOutlet UILabel *nameLabel; 17 | 18 | @end 19 | 20 | 21 | @implementation CurrencyCell 22 | 23 | + (UIColor *)tintColor 24 | { 25 | return [[[UIApplication sharedApplication] delegate] window].tintColor; 26 | } 27 | 28 | + (instancetype)dequeueInstanceWithTableView:(UITableView *)tableView 29 | { 30 | static UINib *nib = nil; 31 | if (!nib) nib = [UINib nibWithNibName:NSStringFromClass(self) bundle:nil]; 32 | CurrencyCell *cell = [tableView dequeueReusableCellWithIdentifier:[self reuseIdentifier]]; 33 | if (!cell) 34 | { 35 | cell = [[nib instantiateWithOwner:nil options:nil] firstObject]; 36 | cell.selectedBackgroundView.backgroundColor = [self tintColor]; 37 | cell.tintColor = [self tintColor]; 38 | } 39 | return cell; 40 | } 41 | 42 | + (NSString *)reuseIdentifier 43 | { 44 | return NSStringFromClass(self); 45 | } 46 | 47 | - (NSString *)reuseIdentifier 48 | { 49 | return [[self class] reuseIdentifier]; 50 | } 51 | 52 | - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated 53 | { 54 | [super setHighlighted:highlighted animated:animated]; 55 | self.tintColor = highlighted? [UIColor whiteColor]: [[self class] tintColor]; 56 | } 57 | 58 | - (void)configureWithCurrency:(Currency *)currency 59 | { 60 | self.symbolLabel.text = currency.symbol ?: currency.code ?: @"-"; 61 | self.symbolLabel.font = [self.symbolLabel.font fontWithSize:currency.symbol? 30: 17]; 62 | self.nameLabel.text = currency.name ?: @"-"; 63 | self.nameLabel.text = currency.name; 64 | self.accessoryType = currency.enabled? UITableViewCellAccessoryCheckmark: UITableViewCellAccessoryNone; 65 | self.backgroundView.backgroundColor = currency.enabled? [[[self class] tintColor] colorBlendedWithColor:[UIColor whiteColor] factor: 0.85]: [UIColor whiteColor]; 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /Classes/CurrencyCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Classes/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Classes/MainViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.h 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 26/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MainViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Classes/MainViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.m 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 26/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "MainViewController.h" 10 | #import "PickerView.h" 11 | #import "Currencies.h" 12 | #import "NumberPad.h" 13 | #import "ViewUtils.h" 14 | #import "Settings.h" 15 | 16 | 17 | @interface MainViewController () 18 | 19 | @property (nonatomic, weak) IBOutlet PickerView *topPicker; 20 | @property (nonatomic, weak) IBOutlet PickerView *bottomPicker; 21 | @property (nonatomic, weak) IBOutlet UILabel *fromCurrencyLabel; 22 | @property (nonatomic, weak) IBOutlet UILabel *toCurrencyLabel; 23 | @property (nonatomic, strong) NumberPad *numberPad; 24 | 25 | @end 26 | 27 | 28 | @implementation MainViewController 29 | 30 | - (void)viewDidLoad 31 | { 32 | [super viewDidLoad]; 33 | 34 | //set up number pad 35 | self.numberPad = [NumberPad instance]; 36 | self.numberPad.width = [UIScreen mainScreen].bounds.size.width; 37 | self.numberPad.layer.rasterizationScale = 2; 38 | 39 | //set up labels 40 | self.fromCurrencyLabel.font = [self.fromCurrencyLabel.font fontWithSize:13]; 41 | self.toCurrencyLabel.font = [self.toCurrencyLabel.font fontWithSize:13]; 42 | 43 | //update pickers 44 | [self.topPicker reloadData]; 45 | [self.bottomPicker reloadData]; 46 | 47 | //restore state 48 | self.topPicker.selectedIndex = [Settings sharedInstance].topPickerIndex; 49 | self.bottomPicker.selectedIndex = [Settings sharedInstance].bottomPickerIndex; 50 | self.topPicker.selected = ![Settings sharedInstance].bottomPickerSelected; 51 | self.bottomPicker.selected = [Settings sharedInstance].bottomPickerSelected; 52 | if (self.topPicker.selected) 53 | { 54 | self.topPicker.currencyValue = [Settings sharedInstance].currencyValue; 55 | [self.bottomPicker setValue:self.topPicker.currencyValue forCurrency:self.topPicker.currency]; 56 | } 57 | else 58 | { 59 | self.bottomPicker.currencyValue = [Settings sharedInstance].currencyValue; 60 | [self.topPicker setValue:self.bottomPicker.currencyValue forCurrency:self.bottomPicker.currency]; 61 | } 62 | 63 | //observe currency updates 64 | [[NSNotificationCenter defaultCenter] addObserver:self 65 | selector:@selector(currenciesUpdated) 66 | name:CurrenciesUpdatedNotification 67 | object:nil]; 68 | } 69 | 70 | - (void)viewDidUnload 71 | { 72 | [[NSNotificationCenter defaultCenter] removeObserver:self 73 | name:CurrenciesUpdatedNotification 74 | object:nil]; 75 | } 76 | 77 | - (void)dealloc 78 | { 79 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 80 | } 81 | 82 | - (void)currenciesUpdated 83 | { 84 | [self.topPicker reloadData]; 85 | [self.bottomPicker reloadData]; 86 | [self updateCurrencyLabels]; 87 | if (self.topPicker.selected) 88 | { 89 | [self.bottomPicker setValue:self.topPicker.currencyValue forCurrency:self.topPicker.currency]; 90 | } 91 | else 92 | { 93 | [self.topPicker setValue:self.bottomPicker.currencyValue forCurrency:self.bottomPicker.currency]; 94 | } 95 | 96 | //persist state 97 | [Settings sharedInstance].topPickerIndex = self.topPicker.selectedIndex; 98 | [Settings sharedInstance].bottomPickerIndex = self.bottomPicker.selectedIndex; 99 | } 100 | 101 | - (void)updateCurrencyLabels 102 | { 103 | NSString *from = self.topPicker.currency.name; 104 | NSString *to = self.bottomPicker.currency.name; 105 | if (self.bottomPicker.selected) 106 | { 107 | NSString *temp = from; 108 | from = to; 109 | to = temp; 110 | } 111 | self.fromCurrencyLabel.text = from; 112 | self.toCurrencyLabel.text = to; 113 | } 114 | 115 | - (IBAction)dismissKeyboard 116 | { 117 | [self.topPicker dismissKeyboard]; 118 | [self.bottomPicker dismissKeyboard]; 119 | } 120 | 121 | - (void)pickerViewDidResignFirstResponder:(PickerView *)pickerView 122 | { 123 | if (pickerView == self.bottomPicker) 124 | { 125 | [UIView animateWithDuration:0.4 animations:^{ 126 | self.numberPad.bottom = 0.0f; 127 | } completion:^(BOOL finished) { 128 | [self.numberPad removeFromSuperview]; 129 | }]; 130 | } 131 | else 132 | { 133 | [UIView animateWithDuration:0.4 animations:^{ 134 | self.numberPad.top = self.view.bounds.size.height; 135 | } completion:^(BOOL finished) { 136 | [self.numberPad removeFromSuperview]; 137 | }]; 138 | } 139 | } 140 | 141 | - (void)pickerViewDidAcceptFirstResponder:(PickerView *)pickerView inputField:(id)inputField 142 | { 143 | [self.view addSubview:self.numberPad]; 144 | self.numberPad.inputField = inputField; 145 | if (pickerView == self.bottomPicker) 146 | { 147 | self.numberPad.layer.shouldRasterize = NO; 148 | self.numberPad.bottom = 0.0f; 149 | [UIView animateWithDuration:0.4 animations:^{ 150 | 151 | self.numberPad.top = 0.0f; 152 | 153 | } completion:^(BOOL finished) { 154 | 155 | self.numberPad.layer.shouldRasterize = YES; 156 | self.numberPad.layer.rasterizationScale = 2; 157 | }]; 158 | } 159 | else 160 | { 161 | self.numberPad.layer.shouldRasterize = NO; 162 | self.numberPad.top = self.view.bounds.size.height; 163 | [UIView animateWithDuration:0.4 animations:^{ 164 | 165 | self.numberPad.bottom = self.view.bounds.size.height; 166 | 167 | } completion:^(BOOL finished) { 168 | 169 | self.numberPad.layer.shouldRasterize = YES; 170 | }]; 171 | } 172 | 173 | if (pickerView == self.topPicker) 174 | { 175 | self.bottomPicker.selected = NO; 176 | } 177 | else 178 | { 179 | self.topPicker.selected = NO; 180 | } 181 | 182 | //update labels 183 | [self pickerViewCurrencyDidChange:pickerView]; 184 | 185 | //persist state 186 | [Settings sharedInstance].bottomPickerSelected = (pickerView == self.bottomPicker); 187 | } 188 | 189 | - (void)pickerViewCurrencyDidChange:(PickerView *)pickerView 190 | { 191 | [self updateCurrencyLabels]; 192 | 193 | //persist state 194 | if (pickerView == self.topPicker) 195 | { 196 | [Settings sharedInstance].topPickerIndex = pickerView.selectedIndex; 197 | } 198 | else 199 | { 200 | [Settings sharedInstance].bottomPickerIndex = pickerView.selectedIndex; 201 | } 202 | } 203 | 204 | - (void)pickerViewValueDidChange:(PickerView *)pickerView 205 | { 206 | //sync values 207 | if (pickerView == self.topPicker) 208 | { 209 | [self.bottomPicker setValue:pickerView.currencyValue forCurrency:pickerView.currency]; 210 | } 211 | else 212 | { 213 | [self.topPicker setValue:pickerView.currencyValue forCurrency:pickerView.currency]; 214 | } 215 | 216 | //persist state 217 | [Settings sharedInstance].currencyValue = pickerView.currencyValue; 218 | } 219 | 220 | @end 221 | -------------------------------------------------------------------------------- /Classes/MainViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 60 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Classes/NumberField.h: -------------------------------------------------------------------------------- 1 | // 2 | // NumberField.h 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 26/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NumberField : UITextField 12 | 13 | @property (nonatomic, assign) double doubleValue; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Classes/NumberField.m: -------------------------------------------------------------------------------- 1 | // 2 | // NumberField.m 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 26/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "NumberField.h" 10 | #import "NumberPad.h" 11 | 12 | @implementation NumberField 13 | 14 | - (instancetype)initWithCoder:(NSCoder *)coder 15 | { 16 | if (self = [super initWithCoder:coder]) 17 | { 18 | //disable default input view 19 | self.inputView = [[UIView alloc] init]; 20 | } 21 | return self; 22 | } 23 | 24 | - (void)insertText:(NSString *)text 25 | { 26 | NSString *previousText = self.text; 27 | 28 | UITextRange *selection = [self selectedTextRange]; 29 | [super insertText:text]; 30 | if ([[self.text stringByReplacingOccurrencesOfString:@"^[0-9]{0,8}(\\.[0-9]{0,2})?$" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, [self.text length])] length] > 0) 31 | { 32 | [self selectAll:nil]; 33 | [super insertText:previousText]; 34 | [self setSelectedTextRange:selection]; 35 | } 36 | } 37 | 38 | - (void)setDoubleValue:(double)doubleValue 39 | { 40 | self.text = [NSString stringWithFormat:@"%0.2f", doubleValue]; 41 | } 42 | 43 | - (double)doubleValue 44 | { 45 | return [self.text doubleValue]; 46 | } 47 | 48 | - (BOOL)canPerformAction:(SEL)action withSender:(id)sender 49 | { 50 | if (action == @selector(cut:) || 51 | action == @selector(copy:) || 52 | action == @selector(paste:) || 53 | action == @selector(select:)) 54 | { 55 | return [super canPerformAction:action withSender:sender]; 56 | } 57 | return NO; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /Classes/NumberPad.h: -------------------------------------------------------------------------------- 1 | // 2 | // NumberPad.h 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 26/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NumberPad : UIView 12 | 13 | @property (nonatomic, weak) UITextField *inputField; 14 | 15 | + (instancetype)instance; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Classes/NumberPad.m: -------------------------------------------------------------------------------- 1 | // 2 | // NumberPad.m 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 26/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "NumberPad.h" 10 | 11 | 12 | @interface NumberPad () 13 | 14 | @end 15 | 16 | 17 | @implementation NumberPad 18 | 19 | + (instancetype)instance 20 | { 21 | return [self instanceWithNibName:nil bundle:nil owner:nil]; 22 | } 23 | 24 | - (UIColor *)tintColor 25 | { 26 | return [UIApplication sharedApplication].delegate.window.tintColor; 27 | } 28 | 29 | - (void)awakeFromNib 30 | { 31 | [super awakeFromNib]; 32 | [(UIButton *)[self viewWithTag:12] setTitleColor:self.tintColor forState:UIControlStateNormal]; 33 | } 34 | 35 | - (IBAction)pressedButton:(UIButton *)sender 36 | { 37 | [self.inputField insertText:sender.titleLabel.text]; 38 | } 39 | 40 | - (IBAction)pressedDelete 41 | { 42 | [self.inputField deleteBackward]; 43 | } 44 | 45 | - (IBAction)heldDelete 46 | { 47 | self.inputField.text = nil; 48 | } 49 | 50 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 51 | { 52 | //prevents accidental side-swipe when keyboard is open 53 | return YES; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /Classes/PickerCellView.h: -------------------------------------------------------------------------------- 1 | // 2 | // PickerCellView.h 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 27/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Currency.h" 11 | 12 | @interface PickerCellView : UIView 13 | 14 | + (UINib *)nib; 15 | + (CGFloat)height; 16 | - (void)configureWithCurrency:(Currency *)currency value:(double)value; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Classes/PickerCellView.m: -------------------------------------------------------------------------------- 1 | // 2 | // PickerCellView.m 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 27/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "PickerCellView.h" 10 | 11 | 12 | @interface PickerCellView () 13 | 14 | @property (nonatomic, weak) IBOutlet UILabel *symbolLabel; 15 | @property (nonatomic, weak) IBOutlet UILabel *valueLabel; 16 | 17 | @end 18 | 19 | 20 | @implementation PickerCellView 21 | 22 | + (UINib *)nib 23 | { 24 | static UINib *nib = nil; 25 | if (!nib) 26 | { 27 | nib = [UINib nibWithNibName:NSStringFromClass(self) bundle:nil]; 28 | } 29 | return nib; 30 | } 31 | 32 | + (CGFloat)height 33 | { 34 | return 40; 35 | } 36 | 37 | - (void)configureWithCurrency:(Currency *)currency value:(double)value 38 | { 39 | self.symbolLabel.text = currency.symbol ?: currency.code ?: @"-"; 40 | self.symbolLabel.font = [self.symbolLabel.font fontWithSize:currency.symbol? 30: 17]; 41 | self.valueLabel.text = [currency localisedStringFromValue:value]; 42 | self.valueLabel.font = [self.valueLabel.font fontWithSize:30]; 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Classes/PickerCellView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 23 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Classes/PickerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // PickerView.h 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 27/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Currency.h" 11 | #import "NumberField.h" 12 | 13 | 14 | @class PickerView; 15 | 16 | 17 | @protocol PickerViewDelegate 18 | 19 | - (void)pickerViewDidAcceptFirstResponder:(PickerView *)pickerView inputField:(id)inputView; 20 | - (void)pickerViewDidResignFirstResponder:(PickerView *)pickerView; 21 | - (void)pickerViewCurrencyDidChange:(PickerView *)pickerView; 22 | - (void)pickerViewValueDidChange:(PickerView *)pickerView; 23 | 24 | @end 25 | 26 | 27 | @interface PickerView : UIView 28 | 29 | @property (nonatomic, weak) IBOutlet id delegate; 30 | 31 | @property (nonatomic, assign) NSUInteger selectedIndex; 32 | @property (nonatomic, readonly) Currency *currency; 33 | @property (nonatomic, assign) double currencyValue; 34 | @property (nonatomic, assign) BOOL selected; 35 | 36 | - (void)setValue:(double)value forCurrency:(Currency *)currency; 37 | 38 | - (IBAction)dismissKeyboard; 39 | - (IBAction)reloadData; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Classes/PickerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // PickerView.m 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 27/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "PickerView.h" 10 | #import "iCarousel.h" 11 | #import "Currencies.h" 12 | #import "PickerCellView.h" 13 | 14 | 15 | @interface PickerView () 16 | 17 | @property (nonatomic, copy) NSArray *currencies; 18 | @property (nonatomic, strong) IBOutlet iCarousel *carousel; 19 | @property (nonatomic, strong) IBOutlet NumberField *inputField; 20 | @property (nonatomic, strong) IBOutlet UIView *overlayView; 21 | @property (nonatomic, strong) IBOutlet UIView *spacerView; 22 | 23 | @end 24 | 25 | 26 | @implementation PickerView 27 | 28 | - (void)setUp 29 | { 30 | [self loadContentsWithNibName:nil bundle:nil]; 31 | self.carousel.vertical = YES; 32 | self.carousel.type = iCarouselTypeCylinder; 33 | self.carousel.centerItemWhenSelected = NO; 34 | self.carousel.perspective = 0; 35 | 36 | self.inputField.font = [self.inputField.font fontWithSize:30]; 37 | 38 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange) name:UITextFieldTextDidChangeNotification object:self.inputField]; 39 | 40 | [self reloadData]; 41 | } 42 | 43 | - (void)dealloc 44 | { 45 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 46 | } 47 | 48 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 49 | { 50 | if (self = [super initWithCoder:aDecoder]) 51 | { 52 | [self setUp]; 53 | } 54 | return self; 55 | } 56 | 57 | - (instancetype)initWithFrame:(CGRect)frame 58 | { 59 | if (self = [super initWithFrame:frame]) 60 | { 61 | [self setUp]; 62 | } 63 | return self; 64 | } 65 | 66 | - (void)setSelected:(BOOL)selected 67 | { 68 | _selected = selected; 69 | if (selected) 70 | { 71 | self.inputField.doubleValue = self.currencyValue; 72 | self.inputField.backgroundColor = [UIColor whiteColor]; 73 | } 74 | [self.overlayView crossfadeWithDuration:0.4 completion:^{ 75 | 76 | if (!selected) 77 | { 78 | self.inputField.text = @""; 79 | self.inputField.backgroundColor = nil; 80 | } 81 | }]; 82 | self.overlayView.alpha = selected? 1.0f: 0.0f; 83 | } 84 | 85 | - (void)setSelectedIndex:(NSUInteger)selectedIndex 86 | { 87 | self.carousel.currentItemIndex = selectedIndex; 88 | } 89 | 90 | - (NSUInteger)selectedIndex 91 | { 92 | return self.carousel.currentItemIndex; 93 | } 94 | 95 | - (IBAction)dismissKeyboard 96 | { 97 | [self.inputField resignFirstResponder]; 98 | } 99 | 100 | - (IBAction)reloadData 101 | { 102 | //update currencies 103 | NSMutableArray *currencies = [NSMutableArray array]; 104 | if ([[Currencies sharedInstance].enabledCurrencies count]) 105 | { 106 | while ([currencies count] < 6) 107 | { 108 | [currencies addObjectsFromArray:[Currencies sharedInstance].enabledCurrencies]; 109 | } 110 | } 111 | else 112 | { 113 | while ([currencies count] < 6) 114 | { 115 | [currencies addObject:[Currency nullCurrency]]; 116 | } 117 | } 118 | self.currencies = currencies; 119 | 120 | //reload cells 121 | if (self.carousel.numberOfItems == [self numberOfItemsInCarousel:self.carousel]) 122 | { 123 | [self.carousel.indexesForVisibleItems enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 124 | [self.carousel reloadItemAtIndex:[obj integerValue] animated:NO]; 125 | }]; 126 | } 127 | else 128 | { 129 | [self.carousel reloadData]; 130 | } 131 | 132 | //update selected currency 133 | Currency *oldCurrency = self.currency; 134 | [self updateCurrency]; 135 | 136 | //scroll to previously selected currency (if appropriate) 137 | NSInteger index = [self.currencies indexOfObject:oldCurrency]; 138 | if (index != NSNotFound && oldCurrency != self.currencies[self.carousel.currentItemIndex]) 139 | { 140 | [self.carousel scrollToItemAtIndex:index animated:NO]; 141 | } 142 | } 143 | 144 | - (void)updateCurrency 145 | { 146 | _currency = [self.currencies count]? self.currencies[self.selectedIndex]: nil; 147 | } 148 | 149 | - (void)setCurrencyValue:(double)currencyValue 150 | { 151 | [self setCurrencyValue:currencyValue updateField:YES]; 152 | } 153 | 154 | - (void)setCurrencyValue:(double)currencyValue updateField:(BOOL)update 155 | { 156 | if (_currencyValue != currencyValue) 157 | { 158 | _currencyValue = currencyValue; 159 | if (update && self.selected) self.inputField.doubleValue = _currencyValue; 160 | [self reloadData]; 161 | } 162 | } 163 | 164 | - (void)setValue:(double)value forCurrency:(Currency *)currency 165 | { 166 | self.currencyValue = [currency value:value convertedToCurrency:self.currency]; 167 | } 168 | 169 | - (BOOL)resignFirstResponder 170 | { 171 | return [self.inputField resignFirstResponder]; 172 | } 173 | 174 | - (void)textDidChange 175 | { 176 | [self setCurrencyValue:[self.inputField.text doubleValue] updateField:NO]; 177 | [self.delegate pickerViewValueDidChange:self]; 178 | } 179 | 180 | - (void)textFieldDidBeginEditing:(NumberField *)textField 181 | { 182 | self.selected = YES; 183 | [textField performSelectorOnMainThread:@selector(selectAll:) withObject:nil waitUntilDone:NO]; 184 | 185 | [self.delegate pickerViewDidAcceptFirstResponder:self inputField:self.inputField]; 186 | } 187 | 188 | - (BOOL)textFieldShouldReturn:(NumberField *)textField 189 | { 190 | [self resignFirstResponder]; 191 | return NO; 192 | } 193 | 194 | - (void)textFieldDidEndEditing:(NumberField *)textField 195 | { 196 | textField.doubleValue = textField.doubleValue; 197 | self.currencyValue = textField.doubleValue; 198 | [self.delegate pickerViewValueDidChange:self]; 199 | [self.delegate pickerViewDidResignFirstResponder:self]; 200 | } 201 | 202 | - (NSInteger)numberOfItemsInCarousel:(iCarousel *)carousel 203 | { 204 | return [self.currencies count]; 205 | } 206 | 207 | - (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(PickerCellView *)view 208 | { 209 | if (!view) 210 | { 211 | view = [[[PickerCellView nib] instantiateWithOwner:self options:nil] firstObject]; 212 | } 213 | 214 | Currency *currency = self.currencies[index]; 215 | double value = [self.currency value:self.currencyValue convertedToCurrency:currency]; 216 | [view configureWithCurrency:currency value:value]; 217 | 218 | view.width = self.spacerView.width; 219 | return view; 220 | } 221 | 222 | - (void)carouselCurrentItemIndexDidChange:(iCarousel *)carousel 223 | { 224 | Currency *oldCurrency = self.currency; 225 | [self updateCurrency]; 226 | 227 | if (self.selected) 228 | { 229 | [self.delegate pickerViewValueDidChange:self]; 230 | [self.carousel.indexesForVisibleItems enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 231 | [self.carousel reloadItemAtIndex:[obj integerValue] animated:NO]; 232 | }]; 233 | } 234 | else 235 | { 236 | self.currencyValue = [oldCurrency value:self.currencyValue convertedToCurrency:self.currency]; 237 | } 238 | [self.delegate pickerViewCurrencyDidChange:self]; 239 | } 240 | 241 | - (BOOL)carousel:(iCarousel *)carousel shouldSelectItemAtIndex:(NSInteger)index 242 | { 243 | return NO; 244 | } 245 | 246 | - (CGFloat)carousel:(iCarousel *)carousel valueForOption:(iCarouselOption)option withDefault:(CGFloat)value 247 | { 248 | switch (option) 249 | { 250 | case iCarouselOptionCount: 251 | { 252 | return 16; 253 | } 254 | case iCarouselOptionFadeMin: 255 | case iCarouselOptionFadeMax: 256 | { 257 | return 0.0f; 258 | } 259 | case iCarouselOptionFadeRange: 260 | { 261 | return 2.5f; 262 | } 263 | default: 264 | { 265 | return value; 266 | } 267 | } 268 | } 269 | 270 | @end 271 | -------------------------------------------------------------------------------- /Classes/PickerView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Classes/SearchBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // SearchBar.h 3 | // Concurrency 4 | // 5 | // Created by Nick Lockwood on 03/01/2014. 6 | // Copyright (c) 2014 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SearchBar : UISearchBar 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Classes/SearchBar.m: -------------------------------------------------------------------------------- 1 | // 2 | // SearchBar.m 3 | // Concurrency 4 | // 5 | // Created by Nick Lockwood on 03/01/2014. 6 | // Copyright (c) 2014 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "SearchBar.h" 10 | 11 | @implementation SearchBar 12 | 13 | - (void)awakeFromNib 14 | { 15 | [super awakeFromNib]; 16 | 17 | if ([[[UIDevice currentDevice] systemVersion] floatValue] < 7.0f) 18 | { 19 | //remove the search bar background view on iOS 6 20 | [[self.subviews firstObject] removeFromSuperview]; 21 | } 22 | 23 | //use "return" button on keyboard because blue button looks fugly, especially on iOS 7 24 | ((UITextField *)[self viewOfClass:[UITextField class]]).returnKeyType = UIReturnKeyDefault; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Classes/Settings.h: -------------------------------------------------------------------------------- 1 | // 2 | // Settings.h 3 | // Concurrency 4 | // 5 | // Created by Nick Lockwood on 22/01/2014. 6 | // Copyright (c) 2014 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "BaseModel.h" 10 | 11 | 12 | @interface Settings : BaseModel 13 | 14 | @property (nonatomic, assign) BOOL bottomPickerSelected; 15 | @property (nonatomic, assign) NSUInteger topPickerIndex; 16 | @property (nonatomic, assign) NSUInteger bottomPickerIndex; 17 | @property (nonatomic, assign) double currencyValue; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Classes/Settings.m: -------------------------------------------------------------------------------- 1 | // 2 | // Settings.m 3 | // Concurrency 4 | // 5 | // Created by Nick Lockwood on 22/01/2014. 6 | // Copyright (c) 2014 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "Settings.h" 10 | 11 | 12 | @implementation Settings 13 | { 14 | BOOL _saveScheduled; 15 | } 16 | 17 | + (BMFileFormat)saveFormat 18 | { 19 | return BMFileFormatUserDefaults; 20 | } 21 | 22 | - (void)setUp 23 | { 24 | //add observers for properties, so we can save automatically 25 | for (NSString *key in [[self class] codablePropertyKeys]) 26 | { 27 | [self addObserver:self forKeyPath:key options:(NSKeyValueObservingOptions)0 context:NULL]; 28 | } 29 | } 30 | 31 | - (void)tearDown 32 | { 33 | //remove observers 34 | for (NSString *key in [[self class] codablePropertyKeys]) 35 | { 36 | [self removeObserver:self forKeyPath:key]; 37 | } 38 | } 39 | 40 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(__unused NSDictionary *)change context:(__unused void *)context 41 | { 42 | if (!_saveScheduled) 43 | { 44 | _saveScheduled = YES; 45 | [self performSelectorOnMainThread:@selector(save) withObject:nil waitUntilDone:NO]; 46 | } 47 | } 48 | 49 | - (BOOL)save 50 | { 51 | _saveScheduled = NO; 52 | return [super save]; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /Classes/SettingsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.h 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 30/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SettingsViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Classes/SettingsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.m 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 30/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "SettingsViewController.h" 10 | #import "Currencies.h" 11 | #import "CurrencyCell.h" 12 | #import "ViewUtils.h" 13 | 14 | 15 | @interface SettingsViewController () 16 | 17 | @property (nonatomic, strong) IBOutlet UITableView *tableView; 18 | @property (nonatomic, strong) IBOutlet UILabel *footer; 19 | @property (nonatomic, strong) UIRefreshControl *refreshControl; 20 | @property (nonatomic, copy) NSArray *currencies; 21 | @property (nonatomic, copy) NSString *searchString; 22 | 23 | @end 24 | 25 | 26 | @implementation SettingsViewController 27 | 28 | - (void)viewDidLoad 29 | { 30 | [super viewDidLoad]; 31 | 32 | //add refresh control 33 | self.refreshControl = [[UIRefreshControl alloc] init]; 34 | self.refreshControl.hidden = YES; //hide control initially 35 | [self.refreshControl addTarget:self action:@selector(refresh) forControlEvents:UIControlEventValueChanged]; 36 | [self.tableView addSubview:self.refreshControl]; 37 | 38 | //set up data 39 | [self update]; 40 | } 41 | 42 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView 43 | { 44 | //show refresh control as soon as scrolling begins 45 | self.refreshControl.hidden = NO; 46 | } 47 | 48 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 49 | { 50 | return [self.currencies count]; 51 | } 52 | 53 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 54 | { 55 | CurrencyCell *cell = [CurrencyCell dequeueInstanceWithTableView:tableView]; 56 | [cell configureWithCurrency:self.currencies[indexPath.row]]; 57 | return cell; 58 | } 59 | 60 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 61 | { 62 | Currency *currency = self.currencies[indexPath.row]; 63 | currency.enabled = !currency.enabled; 64 | [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; 65 | [currency save]; 66 | } 67 | 68 | - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText 69 | { 70 | self.searchString = searchText; 71 | [self update]; 72 | } 73 | 74 | - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar 75 | { 76 | [searchBar resignFirstResponder]; 77 | } 78 | 79 | - (void)refresh 80 | { 81 | [self.refreshControl beginRefreshing]; 82 | [[Currencies sharedInstance] updateWithBlock:^{ 83 | [self update]; 84 | }]; 85 | } 86 | 87 | - (void)update 88 | { 89 | self.currencies = [[Currencies sharedInstance] currenciesMatchingSearchString:self.searchString]; 90 | [self.refreshControl endRefreshing]; 91 | [self.tableView reloadData]; 92 | [self updateFooter]; 93 | } 94 | 95 | - (void)updateFooter 96 | { 97 | static NSDateFormatter *formatter = nil; 98 | if (!formatter) 99 | { 100 | formatter = [[NSDateFormatter alloc] init]; 101 | formatter.dateStyle = NSDateFormatterMediumStyle; 102 | formatter.timeStyle = NSDateFormatterMediumStyle; 103 | } 104 | 105 | NSString *date = [formatter stringFromDate:[Currencies sharedInstance].lastUpdated]; 106 | self.footer.text = [NSString stringWithFormat:@"Last Updated: %@", date]; 107 | } 108 | 109 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch 110 | { 111 | if (![touch.view isSubviewOfView:self.tableView.tableHeaderView]) 112 | { 113 | [self.tableView.tableHeaderView resignFirstResponder]; 114 | } 115 | return NO; 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /Classes/SettingsViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /Classes/UIView+TintColor.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+TintColor.h 3 | // Concurrency 4 | // 5 | // Created by Nick Lockwood on 03/01/2014. 6 | // Copyright (c) 2014 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIView (TintColor) 12 | 13 | @end -------------------------------------------------------------------------------- /Classes/UIView+TintColor.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+TintColor.m 3 | // Concurrency 4 | // 5 | // Created by Nick Lockwood on 03/01/2014. 6 | // Copyright (c) 2014 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "UIView+TintColor.h" 10 | #import 11 | 12 | 13 | @implementation UIView (TintColor) 14 | 15 | + (void)load 16 | { 17 | if (![self instancesRespondToSelector:@selector(tintColor)]) 18 | { 19 | //implement tintColor on iOS 6 20 | Method m = class_getInstanceMethod(self, @selector(CCC_tintColor)); 21 | class_addMethod(self, @selector(tintColor), method_getImplementation(m), method_getTypeEncoding(m)); 22 | m = class_getInstanceMethod(self, @selector(CCC_setTintColor:)); 23 | class_addMethod(self, @selector(setTintColor:), method_getImplementation(m), method_getTypeEncoding(m)); 24 | } 25 | } 26 | 27 | - (UIColor *)CCC_tintColor 28 | { 29 | return [UIColor colorWithRed:0 green:85.0/255 blue:165.0/255 alpha:1]; 30 | } 31 | 32 | - (void)CCC_setTintColor:(UIColor *)color 33 | { 34 | //does nothing 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /Classes/UIViewController+Gradient.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+Gradient.h 3 | // Concurrency 4 | // 5 | // Created by Nick Lockwood on 10/01/2014. 6 | // Copyright (c) 2014 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIViewController (Gradient) 12 | 13 | - (void)addGradientLayer; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Classes/UIViewController+Gradient.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+Gradient.m 3 | // Concurrency 4 | // 5 | // Created by Nick Lockwood on 10/01/2014. 6 | // Copyright (c) 2014 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import "UIViewController+Gradient.h" 10 | 11 | @implementation UIViewController (Gradient) 12 | 13 | - (void)addGradientLayer 14 | { 15 | CAGradientLayer *layer = [CAGradientLayer layer]; 16 | layer.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 25); 17 | layer.startPoint = CGPointZero; 18 | layer.endPoint = CGPointMake(0.0f, 1.0f); 19 | layer.colors = @[(__bridge id)[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f].CGColor, 20 | (__bridge id)[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.9f].CGColor, 21 | (__bridge id)[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.0f].CGColor]; 22 | 23 | [self.view.layer addSublayer:layer]; 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Classes/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CurrencyConverter 4 | // 5 | // Created by Nick Lockwood on 26/06/2013. 6 | // Copyright (c) 2013 Charcoal Design. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool 16 | { 17 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Concurrency.xcodeproj/xcshareddata/xcschemes/Concurrency.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | Concurrency 2 | 3 | Version 1.1.1, December 11th, 2013 4 | 5 | Copyright (C) 2013 Charcoal Design 6 | 7 | Redistribution in binary form is not permitted without specific prior written 8 | permission from the copyright holders. 9 | 10 | Redistribution and use in source form with or without modification, is 11 | permitted provided that the following conditions are met: Redistributions of the 12 | source code must retain this copyright notice and the following disclaimer. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Libraries/BaseModel/BaseModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // BaseModel.h 3 | // 4 | // Version 2.6.3 5 | // 6 | // Created by Nick Lockwood on 25/06/2011. 7 | // Copyright 2011 Charcoal Design 8 | // 9 | // Distributed under the permissive zlib license 10 | // Get the latest version from here: 11 | // 12 | // https://github.com/nicklockwood/BaseModel 13 | // 14 | // This software is provided 'as-is', without any express or implied 15 | // warranty. In no event will the authors be held liable for any damages 16 | // arising from the use of this software. 17 | // 18 | // Permission is granted to anyone to use this software for any purpose, 19 | // including commercial applications, and to alter it and redistribute it 20 | // freely, subject to the following restrictions: 21 | // 22 | // 1. The origin of this software must not be misrepresented; you must not 23 | // claim that you wrote the original software. If you use this software 24 | // in a product, an acknowledgment in the product documentation would be 25 | // appreciated but is not required. 26 | // 27 | // 2. Altered source versions must be plainly marked as such, and must not be 28 | // misrepresented as being the original software. 29 | // 30 | // 3. This notice may not be removed or altered from any source distribution. 31 | // 32 | 33 | 34 | #import 35 | 36 | 37 | extern NSString *const BaseModelSharedInstanceUpdatedNotification; 38 | extern NSString *const BaseModelException; 39 | 40 | 41 | typedef NS_ENUM(NSUInteger, BMFileFormat) 42 | { 43 | BMFileFormatKeyedArchive = 0, //default 44 | BMFileFormatXMLPropertyList, 45 | BMFileFormatBinaryPropertyList, 46 | BMFileFormatJSON, 47 | BMFileFormatUserDefaults, 48 | BMFileFormatKeychain, //requires FXKeychain library 49 | BMFileFormatCryptoCoding, //requires CryptoCoding library 50 | BMFileFormatHRCodedXML, //requires HRCoder library 51 | BMFileFormatHRCodedJSON, //requires HRCoder library 52 | BMFileFormatHRCodedBinary, //requires HRCoder library 53 | BMFileFormatFastCoding, //requires FastCoding library 54 | }; 55 | 56 | 57 | //use the BaseModel class as the base class for any of your 58 | //model objects. BaseModels can be standalone objects, or 59 | //act as sub-properties of a larger object 60 | 61 | @interface BaseModel : NSObject 62 | 63 | //loading sequence: 64 | //setUp called first 65 | //then setWithDictionary/Coder/etc if resource file exists 66 | //then setWithDictionary/Coder/etc again if save file exists 67 | //tearDown is called prior to dealloc (but only if setUp was called) 68 | 69 | - (void)setUp; 70 | - (void)setWithDictionary:(NSDictionary *)dict; 71 | - (void)setWithCoder:(NSCoder *)decoder; 72 | - (void)tearDown; 73 | 74 | //new autoreleased instance 75 | + (instancetype)instance; 76 | 77 | //shared (singelton) instance 78 | + (instancetype)sharedInstance; 79 | + (BOOL)hasSharedInstance; 80 | + (void)setSharedInstance:(BaseModel *)instance; 81 | + (void)reloadSharedInstance; 82 | 83 | //creating instances from a configuration object 84 | + (instancetype)instanceWithObject:(id)object; 85 | - (instancetype)initWithObject:(id)object; 86 | + (NSArray *)instancesWithArray:(NSArray *)array; 87 | 88 | //creating an instance using NSCoding 89 | + (instancetype)instanceWithCoder:(NSCoder *)decoder; 90 | - (instancetype)initWithCoder:(NSCoder *)decoder; 91 | 92 | //loading and saving the model from a data file 93 | + (instancetype)instanceWithContentsOfFile:(NSString *)path; 94 | - (instancetype)initWithContentsOfFile:(NSString *)path; 95 | - (BOOL)writeToFile:(NSString *)path atomically:(BOOL)atomically; 96 | - (BOOL)writeToFile:(NSString *)path format:(BMFileFormat)format atomically:(BOOL)atomically; 97 | 98 | //get model properties 99 | + (NSArray *)allPropertyKeys; 100 | + (NSArray *)codablePropertyKeys; 101 | - (NSDictionary *)dictionaryRepresentation; 102 | 103 | //resourceFile is a file, typically within the resource bundle that 104 | //is used to initialise any BaseModel instance 105 | //saveFile is a path, typically within application support that 106 | //is used to save the shared instance of the model 107 | //saveFormat is the preferred format to use when saving the file 108 | + (NSString *)resourceFile; 109 | + (NSString *)saveFile; 110 | + (BMFileFormat)saveFormat; 111 | 112 | //save the model 113 | - (BOOL)save; 114 | 115 | //generate unique identifier 116 | //useful for creating universally unique 117 | //identifiers and filenames for model objects 118 | + (NSString *)newUniqueIdentifier; 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /Libraries/ColorUtils/ColorUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // ColorUtils.h 3 | // 4 | // Version 1.1.2 5 | // 6 | // Created by Nick Lockwood on 19/11/2011. 7 | // Copyright (c) 2011 Charcoal Design 8 | // 9 | // Distributed under the permissive zlib License 10 | // Get the latest version from here: 11 | // 12 | // https://github.com/nicklockwood/ColorUtils 13 | // 14 | // This software is provided 'as-is', without any express or implied 15 | // warranty. In no event will the authors be held liable for any damages 16 | // arising from the use of this software. 17 | // 18 | // Permission is granted to anyone to use this software for any purpose, 19 | // including commercial applications, and to alter it and redistribute it 20 | // freely, subject to the following restrictions: 21 | // 22 | // 1. The origin of this software must not be misrepresented; you must not 23 | // claim that you wrote the original software. If you use this software 24 | // in a product, an acknowledgment in the product documentation would be 25 | // appreciated but is not required. 26 | // 27 | // 2. Altered source versions must be plainly marked as such, and must not be 28 | // misrepresented as being the original software. 29 | // 30 | // 3. This notice may not be removed or altered from any source distribution. 31 | // 32 | 33 | 34 | #pragma GCC diagnostic push 35 | #pragma GCC diagnostic ignored "-Wauto-import" 36 | #pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis" 37 | 38 | 39 | @interface UIColor (ColorUtils) 40 | 41 | @property (nonatomic, readonly) CGFloat red; 42 | @property (nonatomic, readonly) CGFloat green; 43 | @property (nonatomic, readonly) CGFloat blue; 44 | @property (nonatomic, readonly) CGFloat alpha; 45 | 46 | + (void)registerColor:(UIColor *)color forName:(NSString *)name; 47 | 48 | + (instancetype)colorWithString:(NSString *)string; 49 | + (instancetype)colorWithRGBValue:(uint32_t)rgb; 50 | + (instancetype)colorWithRGBAValue:(uint32_t)rgba; 51 | - (instancetype)initWithString:(NSString *)string; 52 | - (instancetype)initWithRGBValue:(uint32_t)rgb; 53 | - (instancetype)initWithRGBAValue:(uint32_t)rgba; 54 | 55 | - (uint32_t)RGBValue; 56 | - (uint32_t)RGBAValue; 57 | - (NSString *)stringValue; 58 | 59 | - (BOOL)isMonochromeOrRGB; 60 | - (BOOL)isEquivalent:(id)object; 61 | - (BOOL)isEquivalentToColor:(UIColor *)color; 62 | 63 | - (instancetype)colorWithBrightness:(CGFloat)brightness; 64 | - (instancetype)colorBlendedWithColor:(UIColor *)color factor:(CGFloat)factor; 65 | 66 | @end 67 | 68 | 69 | #pragma GCC diagnostic pop 70 | -------------------------------------------------------------------------------- /Libraries/ColorUtils/ColorUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // ColorUtils.m 3 | // 4 | // Version 1.1.2 5 | // 6 | // Created by Nick Lockwood on 19/11/2011. 7 | // Copyright (c) 2011 Charcoal Design 8 | // 9 | // Distributed under the permissive zlib License 10 | // Get the latest version from here: 11 | // 12 | // https://github.com/nicklockwood/ColorUtils 13 | // 14 | // This software is provided 'as-is', without any express or implied 15 | // warranty. In no event will the authors be held liable for any damages 16 | // arising from the use of this software. 17 | // 18 | // Permission is granted to anyone to use this software for any purpose, 19 | // including commercial applications, and to alter it and redistribute it 20 | // freely, subject to the following restrictions: 21 | // 22 | // 1. The origin of this software must not be misrepresented; you must not 23 | // claim that you wrote the original software. If you use this software 24 | // in a product, an acknowledgment in the product documentation would be 25 | // appreciated but is not required. 26 | // 27 | // 2. Altered source versions must be plainly marked as such, and must not be 28 | // misrepresented as being the original software. 29 | // 30 | // 3. This notice may not be removed or altered from any source distribution. 31 | // 32 | 33 | 34 | #import "ColorUtils.h" 35 | 36 | 37 | #pragma GCC diagnostic ignored "-Wformat-non-iso" 38 | #pragma GCC diagnostic ignored "-Wconversion" 39 | #pragma GCC diagnostic ignored "-Wgnu" 40 | 41 | 42 | #import 43 | #if !__has_feature(objc_arc) 44 | #error This class requires automatic reference counting 45 | #endif 46 | 47 | 48 | @implementation UIColor (ColorUtils) 49 | 50 | + (NSMutableDictionary *)standardColors 51 | { 52 | static NSMutableDictionary *colors = nil; 53 | static dispatch_once_t onceToken; 54 | dispatch_once(&onceToken, ^{ 55 | 56 | colors = [@{@"black": [self blackColor], // 0.0 white 57 | @"darkgray": [self darkGrayColor], // 0.333 white 58 | @"lightgray": [self lightGrayColor], // 0.667 white 59 | @"white": [self whiteColor], // 1.0 white 60 | @"gray": [self grayColor], // 0.5 white 61 | @"red": [self redColor], // 1.0, 0.0, 0.0 RGB 62 | @"green": [self greenColor], // 0.0, 1.0, 0.0 RGB 63 | @"blue": [self blueColor], // 0.0, 0.0, 1.0 RGB 64 | @"cyan": [self cyanColor], // 0.0, 1.0, 1.0 RGB 65 | @"yellow": [self yellowColor], // 1.0, 1.0, 0.0 RGB 66 | @"magenta": [self magentaColor], // 1.0, 0.0, 1.0 RGB 67 | @"orange": [self orangeColor], // 1.0, 0.5, 0.0 RGB 68 | @"purple": [self purpleColor], // 0.5, 0.0, 0.5 RGB 69 | @"brown": [self brownColor], // 0.6, 0.4, 0.2 RGB 70 | @"clear": [self clearColor]} mutableCopy]; 71 | }); 72 | 73 | return colors; 74 | } 75 | 76 | - (void)getRGBAComponents:(CGFloat[4])rgba 77 | { 78 | CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(self.CGColor)); 79 | const CGFloat *components = CGColorGetComponents(self.CGColor); 80 | switch (model) 81 | { 82 | case kCGColorSpaceModelMonochrome: 83 | { 84 | rgba[0] = components[0]; 85 | rgba[1] = components[0]; 86 | rgba[2] = components[0]; 87 | rgba[3] = components[1]; 88 | break; 89 | } 90 | case kCGColorSpaceModelRGB: 91 | { 92 | rgba[0] = components[0]; 93 | rgba[1] = components[1]; 94 | rgba[2] = components[2]; 95 | rgba[3] = components[3]; 96 | break; 97 | } 98 | case kCGColorSpaceModelCMYK: 99 | case kCGColorSpaceModelDeviceN: 100 | case kCGColorSpaceModelIndexed: 101 | case kCGColorSpaceModelLab: 102 | case kCGColorSpaceModelPattern: 103 | case kCGColorSpaceModelXYZ: 104 | case kCGColorSpaceModelUnknown: 105 | { 106 | 107 | #ifdef DEBUG 108 | 109 | //unsupported format 110 | NSLog(@"Unsupported color model: %i", model); 111 | #endif 112 | rgba[0] = 0.0f; 113 | rgba[1] = 0.0f; 114 | rgba[2] = 0.0f; 115 | rgba[3] = 1.0f; 116 | break; 117 | } 118 | } 119 | } 120 | 121 | + (void)registerColor:(UIColor *)color forName:(NSString *)name 122 | { 123 | name = [name lowercaseString]; 124 | 125 | #ifdef DEBUG 126 | 127 | //don't allow re-registration 128 | NSAssert([self standardColors][name] == nil || [[self standardColors][name] isEquivalentToColor:color], 129 | @"Cannot re-register the color '%@' as this is already assigned", name); 130 | 131 | #endif 132 | 133 | [self standardColors][[name lowercaseString]] = color; 134 | } 135 | 136 | + (instancetype)colorWithString:(NSString *)string 137 | { 138 | //convert to lowercase 139 | string = [string lowercaseString]; 140 | 141 | //try standard colors first 142 | UIColor *color = [self standardColors][string]; 143 | if (color) 144 | { 145 | return color; 146 | } 147 | 148 | //create new instance 149 | return [[self alloc] initWithString:string]; 150 | } 151 | 152 | + (instancetype)colorWithRGBValue:(uint32_t)rgb 153 | { 154 | return [[self alloc] initWithRGBValue:rgb]; 155 | } 156 | 157 | + (instancetype)colorWithRGBAValue:(uint32_t)rgba 158 | { 159 | return [[self alloc] initWithRGBAValue:rgba]; 160 | } 161 | 162 | - (instancetype)initWithString:(NSString *)string 163 | { 164 | //convert to lowercase 165 | string = [string lowercaseString]; 166 | 167 | //try standard colors 168 | UIColor *color = [[self class] standardColors][string]; 169 | if (color) 170 | { 171 | return ((self = color)); 172 | } 173 | 174 | //try hex 175 | string = [string stringByReplacingOccurrencesOfString:@"#" withString:@""]; 176 | string = [string stringByReplacingOccurrencesOfString:@"0x" withString:@""]; 177 | switch ([string length]) 178 | { 179 | case 0: 180 | { 181 | string = @"00000000"; 182 | break; 183 | } 184 | case 3: 185 | { 186 | NSString *red = [string substringWithRange:NSMakeRange(0, 1)]; 187 | NSString *green = [string substringWithRange:NSMakeRange(1, 1)]; 188 | NSString *blue = [string substringWithRange:NSMakeRange(2, 1)]; 189 | string = [NSString stringWithFormat:@"%1$@%1$@%2$@%2$@%3$@%3$@ff", red, green, blue]; 190 | break; 191 | } 192 | case 6: 193 | { 194 | string = [string stringByAppendingString:@"ff"]; 195 | break; 196 | } 197 | case 8: 198 | { 199 | //do nothing 200 | break; 201 | } 202 | default: 203 | { 204 | 205 | #ifdef DEBUG 206 | 207 | //unsupported format 208 | NSLog(@"Unsupported color string format: %@", string); 209 | #endif 210 | return nil; 211 | } 212 | } 213 | uint32_t rgba; 214 | NSScanner *scanner = [NSScanner scannerWithString:string]; 215 | [scanner scanHexInt:&rgba]; 216 | return [self initWithRGBAValue:rgba]; 217 | } 218 | 219 | - (instancetype)initWithRGBValue:(uint32_t)rgb 220 | { 221 | CGFloat red = ((rgb & 0xFF0000) >> 16) / 255.0f; 222 | CGFloat green = ((rgb & 0x00FF00) >> 8) / 255.0f; 223 | CGFloat blue = (rgb & 0x0000FF) / 255.0f; 224 | return [self initWithRed:red green:green blue:blue alpha:1.0f]; 225 | } 226 | 227 | - (instancetype)initWithRGBAValue:(uint32_t)rgba 228 | { 229 | CGFloat red = ((rgba & 0xFF000000) >> 24) / 255.0f; 230 | CGFloat green = ((rgba & 0x00FF0000) >> 16) / 255.0f; 231 | CGFloat blue = ((rgba & 0x0000FF00) >> 8) / 255.0f; 232 | CGFloat alpha = (rgba & 0x000000FF) / 255.0f; 233 | return [self initWithRed:red green:green blue:blue alpha:alpha]; 234 | } 235 | 236 | - (uint32_t)RGBValue 237 | { 238 | CGFloat rgba[4]; 239 | [self getRGBAComponents:rgba]; 240 | uint32_t red = rgba[0]*255; 241 | uint32_t green = rgba[1]*255; 242 | uint32_t blue = rgba[2]*255; 243 | return (red << 16) + (green << 8) + blue; 244 | } 245 | 246 | - (uint32_t)RGBAValue 247 | { 248 | CGFloat rgba[4]; 249 | [self getRGBAComponents:rgba]; 250 | uint8_t red = rgba[0]*255; 251 | uint8_t green = rgba[1]*255; 252 | uint8_t blue = rgba[2]*255; 253 | uint8_t alpha = rgba[3]*255; 254 | return (red << 24) + (green << 16) + (blue << 8) + alpha; 255 | } 256 | 257 | - (NSString *)stringValue 258 | { 259 | //try standard colors 260 | NSUInteger index = [[[[self class] standardColors] allValues] indexOfObject:self]; 261 | if (index != NSNotFound) 262 | { 263 | return [[[self class] standardColors] allKeys][index]; 264 | } 265 | 266 | //convert to hex 267 | if (self.alpha < 1.0f) 268 | { 269 | //include alpha component 270 | return [NSString stringWithFormat:@"#%.8x", self.RGBAValue]; 271 | } 272 | else 273 | { 274 | //don't include alpha component 275 | return [NSString stringWithFormat:@"#%.6x", self.RGBValue]; 276 | } 277 | } 278 | 279 | - (CGFloat)red 280 | { 281 | CGFloat rgba[4]; 282 | [self getRGBAComponents:rgba]; 283 | return rgba[0]; 284 | } 285 | 286 | - (CGFloat)green 287 | { 288 | CGFloat rgba[4]; 289 | [self getRGBAComponents:rgba]; 290 | return rgba[1]; 291 | } 292 | 293 | - (CGFloat)blue 294 | { 295 | CGFloat rgba[4]; 296 | [self getRGBAComponents:rgba]; 297 | return rgba[2]; 298 | } 299 | 300 | - (CGFloat)alpha 301 | { 302 | return CGColorGetAlpha(self.CGColor); 303 | } 304 | 305 | - (BOOL)isMonochromeOrRGB 306 | { 307 | CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(self.CGColor)); 308 | return model == kCGColorSpaceModelMonochrome || model == kCGColorSpaceModelRGB; 309 | } 310 | 311 | - (BOOL)isEquivalent:(id)object 312 | { 313 | if ([object isKindOfClass:[self class]]) 314 | { 315 | return [self isEquivalentToColor:object]; 316 | } 317 | return NO; 318 | } 319 | 320 | - (BOOL)isEquivalentToColor:(UIColor *)color 321 | { 322 | if ([self isMonochromeOrRGB] && [color isMonochromeOrRGB]) 323 | { 324 | return self.RGBAValue == color.RGBAValue; 325 | } 326 | return [self isEqual:color]; 327 | } 328 | 329 | - (instancetype)colorWithBrightness:(CGFloat)brightness 330 | { 331 | brightness = MAX(brightness, 0.0f); 332 | 333 | CGFloat rgba[4]; 334 | [self getRGBAComponents:rgba]; 335 | 336 | return [[self class] colorWithRed:rgba[0] * brightness 337 | green:rgba[1] * brightness 338 | blue:rgba[2] * brightness 339 | alpha:rgba[3]]; 340 | } 341 | 342 | - (instancetype)colorBlendedWithColor:(UIColor *)color factor:(CGFloat)factor 343 | { 344 | factor = MIN(MAX(factor, 0.0f), 1.0f); 345 | 346 | CGFloat fromRGBA[4], toRGBA[4]; 347 | [self getRGBAComponents:fromRGBA]; 348 | [color getRGBAComponents:toRGBA]; 349 | 350 | return [[self class] colorWithRed:fromRGBA[0] + (toRGBA[0] - fromRGBA[0]) * factor 351 | green:fromRGBA[1] + (toRGBA[1] - fromRGBA[1]) * factor 352 | blue:fromRGBA[2] + (toRGBA[2] - fromRGBA[2]) * factor 353 | alpha:fromRGBA[3] + (toRGBA[3] - fromRGBA[3]) * factor]; 354 | } 355 | 356 | @end 357 | -------------------------------------------------------------------------------- /Libraries/CubeController/CubeController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CubeController.h 3 | // 4 | // Version 1.2.1 5 | // 6 | // Created by Nick Lockwood on 30/06/2013. 7 | // Copyright (c) 2013 Charcoal Design 8 | // 9 | // Distributed under the permissive zlib License 10 | // Get the latest version from here: 11 | // 12 | // https://github.com/nicklockwood/CubeController 13 | // 14 | // This software is provided 'as-is', without any express or implied 15 | // warranty. In no event will the authors be held liable for any damages 16 | // arising from the use of this software. 17 | // 18 | // Permission is granted to anyone to use this software for any purpose, 19 | // including commercial applications, and to alter it and redistribute it 20 | // freely, subject to the following restrictions: 21 | // 22 | // 1. The origin of this software must not be misrepresented; you must not 23 | // claim that you wrote the original software. If you use this software 24 | // in a product, an acknowledgment in the product documentation would be 25 | // appreciated but is not required. 26 | // 27 | // 2. Altered source versions must be plainly marked as such, and must not be 28 | // misrepresented as being the original software. 29 | // 30 | // 3. This notice may not be removed or altered from any source distribution. 31 | // 32 | 33 | 34 | #pragma GCC diagnostic push 35 | #pragma GCC diagnostic ignored "-Wauto-import" 36 | #pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis" 37 | 38 | 39 | #import 40 | 41 | 42 | #undef weak_delegate 43 | #if __has_feature(objc_arc) && __has_feature(objc_arc_weak) 44 | #define weak_delegate weak 45 | #else 46 | #define weak_delegate unsafe_unretained 47 | #endif 48 | 49 | 50 | @protocol CubeControllerDataSource, CubeControllerDelegate; 51 | 52 | 53 | @interface CubeController : UIViewController 54 | 55 | @property (nonatomic, weak_delegate) IBOutlet id dataSource; 56 | @property (nonatomic, weak_delegate) IBOutlet id delegate; 57 | @property (nonatomic, readonly) UIScrollView *scrollView; 58 | @property (nonatomic, readonly) NSInteger numberOfViewControllers; 59 | @property (nonatomic, assign) NSInteger currentViewControllerIndex; 60 | @property (nonatomic, getter = isWrapEnabled) BOOL wrapEnabled; 61 | 62 | - (void)reloadData; 63 | - (void)reloadViewControllerAtIndex:(NSInteger)index animated:(BOOL)animated; 64 | 65 | - (void)scrollToViewControllerAtIndex:(NSInteger)index animated:(BOOL)animated; 66 | - (void)scrollForwardAnimated:(BOOL)animated; 67 | - (void)scrollBackAnimated:(BOOL)animated; 68 | 69 | @end 70 | 71 | 72 | @protocol CubeControllerDataSource 73 | 74 | - (NSInteger)numberOfViewControllersInCubeController:(CubeController *)cubeController; 75 | - (UIViewController *)cubeController:(CubeController *)cubeController 76 | viewControllerAtIndex:(NSInteger)index; 77 | 78 | @end 79 | 80 | 81 | @protocol CubeControllerDelegate 82 | @optional 83 | 84 | - (void)cubeControllerDidScroll:(CubeController *)cubeController; 85 | - (void)cubeControllerCurrentViewControllerIndexDidChange:(CubeController *)cubeController; 86 | - (void)cubeControllerWillBeginDragging:(CubeController *)cubeController; 87 | - (void)cubeControllerDidEndDragging:(CubeController *)cubeController willDecelerate:(BOOL)decelerate; 88 | - (void)cubeControllerWillBeginDecelerating:(CubeController *)cubeController; 89 | - (void)cubeControllerDidEndDecelerating:(CubeController *)cubeController; 90 | - (void)cubeControllerDidEndScrollingAnimation:(CubeController *)cubeController; 91 | 92 | @end 93 | 94 | 95 | @interface UIViewController (CubeController) 96 | 97 | @property (nonatomic, readonly) CubeController *cubeController; 98 | 99 | @end 100 | 101 | 102 | #pragma GCC diagnostic pop 103 | -------------------------------------------------------------------------------- /Libraries/CubeController/CubeController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CubeController.m 3 | // 4 | // Version 1.2.1 5 | // 6 | // Created by Nick Lockwood on 30/06/2013. 7 | // Copyright (c) 2013 Charcoal Design 8 | // 9 | // Distributed under the permissive zlib License 10 | // Get the latest version from here: 11 | // 12 | // https://github.com/nicklockwood/CubeController 13 | // 14 | // This software is provided 'as-is', without any express or implied 15 | // warranty. In no event will the authors be held liable for any damages 16 | // arising from the use of this software. 17 | // 18 | // Permission is granted to anyone to use this software for any purpose, 19 | // including commercial applications, and to alter it and redistribute it 20 | // freely, subject to the following restrictions: 21 | // 22 | // 1. The origin of this software must not be misrepresented; you must not 23 | // claim that you wrote the original software. If you use this software 24 | // in a product, an acknowledgment in the product documentation would be 25 | // appreciated but is not required. 26 | // 27 | // 2. Altered source versions must be plainly marked as such, and must not be 28 | // misrepresented as being the original software. 29 | // 30 | // 3. This notice may not be removed or altered from any source distribution. 31 | // 32 | 33 | 34 | #pragma GCC diagnostic ignored "-Wauto-import" 35 | #pragma GCC diagnostic ignored "-Warc-repeated-use-of-weak" 36 | #pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis" 37 | #pragma GCC diagnostic ignored "-Wdirect-ivar-access" 38 | #pragma GCC diagnostic ignored "-Wconversion" 39 | #pragma GCC diagnostic ignored "-Wgnu" 40 | 41 | 42 | #import "CubeController.h" 43 | #import 44 | 45 | 46 | @implementation NSObject (CubeControllerDelegate) 47 | 48 | - (void)cubeControllerDidScroll:(__unused CubeController *)cc {} 49 | - (void)cubeControllerCurrentViewControllerIndexDidChange:(__unused CubeController *)cc {} 50 | - (void)cubeControllerWillBeginDragging:(__unused CubeController *)cc {} 51 | - (void)cubeControllerDidEndDragging:(__unused CubeController *)cc willDecelerate:(__unused BOOL)dc {} 52 | - (void)cubeControllerWillBeginDecelerating:(__unused CubeController *)cc {} 53 | - (void)cubeControllerDidEndDecelerating:(__unused CubeController *)cc {} 54 | - (void)cubeControllerDidEndScrollingAnimation:(__unused CubeController *)cc {} 55 | 56 | @end 57 | 58 | 59 | @interface CubeController () 60 | 61 | @property (nonatomic, strong) NSMutableDictionary *controllers; 62 | @property (nonatomic, strong) UIScrollView *scrollView; 63 | @property (nonatomic, assign) NSInteger numberOfViewControllers; 64 | @property (nonatomic, assign) CGFloat scrollOffset; 65 | @property (nonatomic, assign) CGFloat previousOffset; 66 | @property (nonatomic, assign) BOOL suppressScrollEvent; 67 | 68 | @end 69 | 70 | 71 | @implementation CubeController 72 | 73 | - (void)viewDidLoad 74 | { 75 | [super viewDidLoad]; 76 | 77 | self.scrollView.frame = self.view.bounds; 78 | [self.view addSubview:self.scrollView]; 79 | 80 | [self reloadData]; 81 | } 82 | 83 | - (UIScrollView *)scrollView 84 | { 85 | if (!_scrollView) 86 | { 87 | CGRect frame = [self isViewLoaded]? self.view.bounds: [UIScreen mainScreen].bounds; 88 | _scrollView = [[UIScrollView alloc] initWithFrame:frame]; 89 | _scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 90 | _scrollView.showsHorizontalScrollIndicator = NO; 91 | _scrollView.pagingEnabled = YES; 92 | _scrollView.directionalLockEnabled = YES; 93 | _scrollView.autoresizesSubviews = NO; 94 | _scrollView.delegate = self; 95 | } 96 | return _scrollView; 97 | } 98 | 99 | - (void)setWrapEnabled:(BOOL)wrapEnabled 100 | { 101 | _wrapEnabled = wrapEnabled; 102 | [self.view layoutIfNeeded]; 103 | } 104 | 105 | - (void)setCurrentViewControllerIndex:(NSInteger)currentViewControllerIndex 106 | { 107 | [self scrollToViewControllerAtIndex:currentViewControllerIndex animated:NO]; 108 | } 109 | 110 | - (void)scrollToViewControllerAtIndex:(NSInteger)index animated:(BOOL)animated 111 | { 112 | CGFloat offset = (CGFloat)index; 113 | if (_wrapEnabled) 114 | { 115 | //using > here instead of >= may look like a fencepost bug, but it isn't 116 | if (offset > _numberOfViewControllers) 117 | { 118 | offset = (NSInteger)offset % _numberOfViewControllers; 119 | } 120 | offset = MAX(-1, offset) + 1; 121 | } 122 | else if (animated && _scrollView.bounces) 123 | { 124 | offset = MAX(-0.1f, MIN(offset, _numberOfViewControllers - 0.9f)); 125 | } 126 | else 127 | { 128 | offset = MAX(0, MIN(offset, _numberOfViewControllers - 1)); 129 | } 130 | [_scrollView setContentOffset:CGPointMake(self.view.bounds.size.width * offset, 0) animated:animated]; 131 | } 132 | 133 | - (void)scrollForwardAnimated:(BOOL)animated 134 | { 135 | [self scrollToViewControllerAtIndex:_currentViewControllerIndex + 1 animated:animated]; 136 | } 137 | 138 | - (void)scrollBackAnimated:(BOOL)animated 139 | { 140 | [self scrollToViewControllerAtIndex:_currentViewControllerIndex - 1 animated:animated]; 141 | } 142 | 143 | - (void)reloadData 144 | { 145 | for (UIViewController *controller in [_controllers allValues]) 146 | { 147 | [controller viewWillDisappear:NO]; 148 | [controller.view removeFromSuperview]; 149 | [controller removeFromParentViewController]; 150 | [controller viewDidDisappear:NO]; 151 | } 152 | _controllers = [NSMutableDictionary dictionary]; 153 | _numberOfViewControllers = [self.dataSource numberOfViewControllersInCubeController:self]; 154 | [self.view layoutIfNeeded]; 155 | } 156 | 157 | - (void)reloadViewControllerAtIndex:(NSInteger)index animated:(BOOL)animated 158 | { 159 | UIViewController *controller = _controllers[@(index)]; 160 | if (controller) 161 | { 162 | CATransform3D transform = controller.view.layer.transform; 163 | CGPoint center = controller.view.center; 164 | 165 | if (animated) 166 | { 167 | CATransition *animation = [CATransition animation]; 168 | animation.type = kCATransitionFade; 169 | [_scrollView.layer addAnimation:animation forKey:nil]; 170 | } 171 | 172 | [controller.view removeFromSuperview]; 173 | [controller removeFromParentViewController]; 174 | controller = [_dataSource cubeController:self viewControllerAtIndex:index]; 175 | [_controllers setObject:controller forKey:@(index)]; 176 | controller.view.layer.doubleSided = NO; 177 | [self addChildViewController:controller]; 178 | [_scrollView addSubview:controller.view]; 179 | 180 | controller.view.layer.transform = transform; 181 | controller.view.center = center; 182 | controller.view.userInteractionEnabled = (index == _currentViewControllerIndex); 183 | } 184 | } 185 | 186 | - (void)updateContentOffset 187 | { 188 | CGFloat offset = _scrollOffset; 189 | if (_wrapEnabled && _numberOfViewControllers > 1) 190 | { 191 | offset += 1.0f; 192 | while (offset < 1.0f) offset += 1; 193 | while (offset >= _numberOfViewControllers + 1) offset -= _numberOfViewControllers; 194 | } 195 | _previousOffset = offset; 196 | 197 | _suppressScrollEvent = YES; 198 | _scrollView.contentOffset = CGPointMake(self.view.bounds.size.width * offset, 0.0f); 199 | _suppressScrollEvent = NO; 200 | } 201 | 202 | - (void)updateLayout 203 | { 204 | for (NSNumber *index in _controllers) 205 | { 206 | UIViewController *controller = _controllers[index]; 207 | if (controller && !controller.parentViewController) 208 | { 209 | controller.view.autoresizingMask = UIViewAutoresizingNone; 210 | controller.view.layer.doubleSided = NO; 211 | [self addChildViewController:controller]; 212 | [_scrollView addSubview:controller.view]; 213 | } 214 | 215 | NSInteger i = [index integerValue]; 216 | CGFloat angle = (_scrollOffset - i) * M_PI_2; 217 | while (angle < 0) angle += M_PI * 2; 218 | while (angle > M_PI * 2) angle -= M_PI * 2; 219 | CATransform3D transform = CATransform3DIdentity; 220 | if (angle != 0.0f) 221 | { 222 | transform.m34 = -1.0/500; 223 | transform = CATransform3DTranslate(transform, 0, 0, -self.view.bounds.size.width / 2.0f); 224 | transform = CATransform3DRotate(transform, -angle, 0, 1, 0); 225 | transform = CATransform3DTranslate(transform, 0, 0, self.view.bounds.size.width / 2.0f); 226 | } 227 | 228 | CGPoint contentOffset = CGPointZero; 229 | BOOL isScrollView = [controller.view isKindOfClass:[UIScrollView class]]; 230 | if (isScrollView) 231 | { 232 | contentOffset = ((UIScrollView *)controller.view).contentOffset; 233 | } 234 | controller.view.bounds = self.view.bounds; 235 | controller.view.center = CGPointMake(self.view.bounds.size.width / 2.0f + _scrollView.contentOffset.x, self.view.bounds.size.height / 2.0f); 236 | controller.view.layer.transform = transform; 237 | if (isScrollView) 238 | { 239 | ((UIScrollView *)controller.view).contentOffset = contentOffset; 240 | } 241 | } 242 | } 243 | 244 | - (void)loadUnloadControllers 245 | { 246 | //calculate visible indices 247 | NSMutableSet *visibleIndices = [NSMutableSet setWithObject:@(_currentViewControllerIndex)]; 248 | if (_wrapEnabled || _currentViewControllerIndex < _numberOfViewControllers - 1) 249 | { 250 | [visibleIndices addObject:@(_currentViewControllerIndex + 1)]; 251 | } 252 | if (_currentViewControllerIndex > 0) 253 | { 254 | [visibleIndices addObject:@(_currentViewControllerIndex - 1)]; 255 | } 256 | else if (_wrapEnabled) 257 | { 258 | [visibleIndices addObject:@(-1)]; 259 | } 260 | 261 | //remove hidden controllers 262 | for (NSNumber *index in [_controllers allKeys]) 263 | { 264 | if (![visibleIndices containsObject:index]) 265 | { 266 | UIViewController *controller = _controllers[index]; 267 | [controller.view removeFromSuperview]; 268 | [controller removeFromParentViewController]; 269 | [_controllers removeObjectForKey:index]; 270 | } 271 | } 272 | 273 | //load controllers 274 | for (NSNumber *index in visibleIndices) 275 | { 276 | NSInteger i = [index integerValue]; 277 | UIViewController *controller = _controllers[index]; 278 | if (!controller && _numberOfViewControllers) 279 | { 280 | controller = [self.dataSource cubeController:self viewControllerAtIndex:(i + _numberOfViewControllers) % _numberOfViewControllers]; 281 | _controllers[index] = controller; 282 | } 283 | } 284 | } 285 | 286 | - (void)updateInteraction 287 | { 288 | for (NSNumber *index in _controllers) 289 | { 290 | UIViewController *controller = _controllers[index]; 291 | NSInteger i = [index integerValue]; 292 | controller.view.userInteractionEnabled = (i == _currentViewControllerIndex); 293 | } 294 | } 295 | 296 | - (void)viewDidLayoutSubviews 297 | { 298 | [super viewDidLayoutSubviews]; 299 | if (_scrollView) 300 | { 301 | NSInteger pages = _numberOfViewControllers; 302 | if (_wrapEnabled && _numberOfViewControllers > 1) pages += 2; 303 | _suppressScrollEvent = YES; 304 | _scrollView.contentSize = CGSizeMake(self.view.bounds.size.width * pages, self.view.bounds.size.height); 305 | _suppressScrollEvent = NO; 306 | [self updateContentOffset]; 307 | [self loadUnloadControllers]; 308 | [self updateLayout]; 309 | [self updateInteraction]; 310 | } 311 | } 312 | 313 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView 314 | { 315 | if (!_suppressScrollEvent) 316 | { 317 | //update scroll offset 318 | CGFloat offset = scrollView.contentOffset.x / self.view.bounds.size.width; 319 | _scrollOffset += (offset - _previousOffset); 320 | if (_wrapEnabled) 321 | { 322 | while (_scrollOffset < 0.0f) _scrollOffset += _numberOfViewControllers; 323 | while (_scrollOffset >= _numberOfViewControllers) _scrollOffset -= _numberOfViewControllers; 324 | } 325 | _previousOffset = offset; 326 | 327 | //prevent error accumulation 328 | if (offset - floor(offset) == 0.0f) _scrollOffset = round(_scrollOffset); 329 | 330 | //update index 331 | NSInteger previousViewControllerIndex = _currentViewControllerIndex; 332 | _currentViewControllerIndex = MAX(0, MIN(_numberOfViewControllers - 1, (NSInteger)(round(_scrollOffset)))); 333 | 334 | //update content 335 | [self updateContentOffset]; 336 | [self loadUnloadControllers]; 337 | [self updateLayout]; 338 | 339 | //update delegate 340 | [_delegate cubeControllerDidScroll:self]; 341 | if (_currentViewControllerIndex != previousViewControllerIndex) 342 | { 343 | [_delegate cubeControllerCurrentViewControllerIndexDidChange:self]; 344 | } 345 | 346 | //enable/disable interaction 347 | [self updateInteraction]; 348 | } 349 | } 350 | 351 | - (void)scrollViewWillBeginDragging:(__unused UIScrollView *)scrollView 352 | { 353 | if (!_suppressScrollEvent) [_delegate cubeControllerWillBeginDragging:self]; 354 | } 355 | 356 | - (void)scrollViewDidEndDragging:(__unused UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 357 | { 358 | if (!_suppressScrollEvent) [_delegate cubeControllerDidEndDragging:self willDecelerate:decelerate]; 359 | } 360 | 361 | - (void)scrollViewWillBeginDecelerating:(__unused UIScrollView *)scrollView 362 | { 363 | if (!_suppressScrollEvent) [_delegate cubeControllerWillBeginDecelerating:self]; 364 | } 365 | 366 | - (void)scrollViewDidEndDecelerating:(__unused UIScrollView *)scrollView 367 | { 368 | if (!_suppressScrollEvent) [_delegate cubeControllerDidEndDecelerating:self]; 369 | } 370 | 371 | - (void)scrollViewDidEndScrollingAnimation:(__unused UIScrollView *)scrollView 372 | { 373 | CGFloat nearestIntegralOffset = roundf(_scrollOffset); 374 | if (ABS(_scrollOffset - nearestIntegralOffset) > 0.0f) 375 | { 376 | [self scrollToViewControllerAtIndex:_currentViewControllerIndex animated:YES]; 377 | } 378 | if (!_suppressScrollEvent) [_delegate cubeControllerDidEndScrollingAnimation:self]; 379 | } 380 | 381 | @end 382 | 383 | 384 | @implementation UIViewController (CubeController) 385 | 386 | - (CubeController *)cubeController 387 | { 388 | if ([self.parentViewController isKindOfClass:[CubeController class]]) 389 | { 390 | return (CubeController *)self.parentViewController; 391 | } 392 | return [self.parentViewController cubeController]; 393 | } 394 | 395 | @end 396 | -------------------------------------------------------------------------------- /Libraries/ViewUtils/ViewUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewUtils.h 3 | // 4 | // Version 1.1.3 5 | // 6 | // Created by Nick Lockwood on 19/11/2011. 7 | // Copyright (c) 2011 Charcoal Design 8 | // 9 | // Distributed under the permissive zlib License 10 | // Get the latest version from here: 11 | // 12 | // https://github.com/nicklockwood/ViewUtils 13 | // 14 | // This software is provided 'as-is', without any express or implied 15 | // warranty. In no event will the authors be held liable for any damages 16 | // arising from the use of this software. 17 | // 18 | // Permission is granted to anyone to use this software for any purpose, 19 | // including commercial applications, and to alter it and redistribute it 20 | // freely, subject to the following restrictions: 21 | // 22 | // 1. The origin of this software must not be misrepresented; you must not 23 | // claim that you wrote the original software. If you use this software 24 | // in a product, an acknowledgment in the product documentation would be 25 | // appreciated but is not required. 26 | // 27 | // 2. Altered source versions must be plainly marked as such, and must not be 28 | // misrepresented as being the original software. 29 | // 30 | // 3. This notice may not be removed or altered from any source distribution. 31 | // 32 | 33 | 34 | #import 35 | 36 | @interface UIView (ViewUtils) 37 | 38 | //nib loading 39 | 40 | + (id)instanceWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)bundleOrNil owner:(id)owner; 41 | - (void)loadContentsWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)bundleOrNil; 42 | 43 | //hierarchy 44 | 45 | - (UIView *)viewMatchingPredicate:(NSPredicate *)predicate; 46 | - (UIView *)viewWithTag:(NSInteger)tag ofClass:(Class)viewClass; 47 | - (UIView *)viewOfClass:(Class)viewClass; 48 | - (NSArray *)viewsMatchingPredicate:(NSPredicate *)predicate; 49 | - (NSArray *)viewsWithTag:(NSInteger)tag; 50 | - (NSArray *)viewsWithTag:(NSInteger)tag ofClass:(Class)viewClass; 51 | - (NSArray *)viewsOfClass:(Class)viewClass; 52 | 53 | - (UIView *)firstSuperviewMatchingPredicate:(NSPredicate *)predicate; 54 | - (UIView *)firstSuperviewOfClass:(Class)viewClass; 55 | - (UIView *)firstSuperviewWithTag:(NSInteger)tag; 56 | - (UIView *)firstSuperviewWithTag:(NSInteger)tag ofClass:(Class)viewClass; 57 | 58 | - (BOOL)viewOrAnySuperviewMatchesPredicate:(NSPredicate *)predicate; 59 | - (BOOL)viewOrAnySuperviewIsKindOfClass:(Class)viewClass; 60 | - (BOOL)isSuperviewOfView:(UIView *)view; 61 | - (BOOL)isSubviewOfView:(UIView *)view; 62 | 63 | - (UIViewController *)firstViewController; 64 | - (UIView *)firstResponder; 65 | 66 | //frame accessors 67 | 68 | @property (nonatomic, assign) CGPoint origin; 69 | @property (nonatomic, assign) CGSize size; 70 | @property (nonatomic, assign) CGFloat top; 71 | @property (nonatomic, assign) CGFloat left; 72 | @property (nonatomic, assign) CGFloat bottom; 73 | @property (nonatomic, assign) CGFloat right; 74 | @property (nonatomic, assign) CGFloat width; 75 | @property (nonatomic, assign) CGFloat height; 76 | @property (nonatomic, assign) CGFloat x; 77 | @property (nonatomic, assign) CGFloat y; 78 | 79 | //bounds accessors 80 | 81 | @property (nonatomic, assign) CGSize boundsSize; 82 | @property (nonatomic, assign) CGFloat boundsWidth; 83 | @property (nonatomic, assign) CGFloat boundsHeight; 84 | 85 | //content getters 86 | 87 | @property (nonatomic, readonly) CGRect contentBounds; 88 | @property (nonatomic, readonly) CGPoint contentCenter; 89 | 90 | //additional frame setters 91 | 92 | - (void)setLeft:(CGFloat)left right:(CGFloat)right; 93 | - (void)setWidth:(CGFloat)width right:(CGFloat)right; 94 | - (void)setTop:(CGFloat)top bottom:(CGFloat)bottom; 95 | - (void)setHeight:(CGFloat)height bottom:(CGFloat)bottom; 96 | 97 | //animation 98 | 99 | - (void)crossfadeWithDuration:(NSTimeInterval)duration; 100 | - (void)crossfadeWithDuration:(NSTimeInterval)duration completion:(void (^)(void))completion; 101 | 102 | @end 103 | 104 | -------------------------------------------------------------------------------- /Libraries/ViewUtils/ViewUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewUtils.m 3 | // 4 | // Version 1.1.3 5 | // 6 | // Created by Nick Lockwood on 19/11/2011. 7 | // Copyright (c) 2011 Charcoal Design 8 | // 9 | // Distributed under the permissive zlib License 10 | // Get the latest version from here: 11 | // 12 | // https://github.com/nicklockwood/ViewUtils 13 | // 14 | // This software is provided 'as-is', without any express or implied 15 | // warranty. In no event will the authors be held liable for any damages 16 | // arising from the use of this software. 17 | // 18 | // Permission is granted to anyone to use this software for any purpose, 19 | // including commercial applications, and to alter it and redistribute it 20 | // freely, subject to the following restrictions: 21 | // 22 | // 1. The origin of this software must not be misrepresented; you must not 23 | // claim that you wrote the original software. If you use this software 24 | // in a product, an acknowledgment in the product documentation would be 25 | // appreciated but is not required. 26 | // 27 | // 2. Altered source versions must be plainly marked as such, and must not be 28 | // misrepresented as being the original software. 29 | // 30 | // 3. This notice may not be removed or altered from any source distribution. 31 | // 32 | 33 | #import "ViewUtils.h" 34 | #import 35 | 36 | 37 | #pragma GCC diagnostic ignored "-Wgnu" 38 | 39 | 40 | #import 41 | #if !__has_feature(objc_arc) 42 | #error This class requires automatic reference counting 43 | #endif 44 | 45 | 46 | @implementation UIView (ViewUtils) 47 | 48 | //nib loading 49 | 50 | + (id)instanceWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)bundleOrNil owner:(id)owner 51 | { 52 | //default values 53 | NSString *nibName = nibNameOrNil ?: NSStringFromClass(self); 54 | NSBundle *bundle = bundleOrNil ?: [NSBundle mainBundle]; 55 | 56 | //cache nib to prevent unnecessary filesystem access 57 | static NSCache *nibCache = nil; 58 | if (nibCache == nil) 59 | { 60 | nibCache = [[NSCache alloc] init]; 61 | } 62 | NSString *pathKey = [NSString stringWithFormat:@"%@.%@", bundle.bundleIdentifier, nibName]; 63 | UINib *nib = [nibCache objectForKey:pathKey]; 64 | if (nib == nil) 65 | { 66 | NSString *nibPath = [bundle pathForResource:nibName ofType:@"nib"]; 67 | if (nibPath) nib = [UINib nibWithNibName:nibName bundle:bundle]; 68 | [nibCache setObject:nib ?: [NSNull null] forKey:pathKey]; 69 | } 70 | else if ([nib isKindOfClass:[NSNull class]]) 71 | { 72 | nib = nil; 73 | } 74 | 75 | if (nib) 76 | { 77 | //attempt to load from nib 78 | UIView *view = [[nib instantiateWithOwner:owner options:nil] firstObject]; 79 | NSAssert ([view isKindOfClass:self], @"First object in nib '%@' was '%@'. Expected '%@'", nibName, view, self); 80 | return view; 81 | } 82 | 83 | //return empty view 84 | return [[self alloc] init]; 85 | } 86 | 87 | - (void)loadContentsWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)bundleOrNil 88 | { 89 | NSString *nibName = nibNameOrNil ?: NSStringFromClass([self class]); 90 | UIView *view = [UIView instanceWithNibName:nibName bundle:bundleOrNil owner:self]; 91 | if (view) 92 | { 93 | if (CGSizeEqualToSize(self.frame.size, CGSizeZero)) 94 | { 95 | //if we have zero size, set size from content 96 | self.size = view.size; 97 | } 98 | else 99 | { 100 | //otherwise set content size to match our size 101 | view.frame = self.contentBounds; 102 | } 103 | [self addSubview:view]; 104 | } 105 | } 106 | 107 | //view searching 108 | 109 | - (UIView *)viewMatchingPredicate:(NSPredicate *)predicate 110 | { 111 | if ([predicate evaluateWithObject:self]) 112 | { 113 | return self; 114 | } 115 | for (UIView *view in self.subviews) 116 | { 117 | UIView *match = [view viewMatchingPredicate:predicate]; 118 | if (match) return match; 119 | } 120 | return nil; 121 | } 122 | 123 | - (UIView *)viewWithTag:(NSInteger)tag ofClass:(Class)viewClass 124 | { 125 | return [self viewMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, __unused NSDictionary *bindings) { 126 | return [evaluatedObject tag] == tag && [evaluatedObject isKindOfClass:viewClass]; 127 | }]]; 128 | } 129 | 130 | - (UIView *)viewOfClass:(Class)viewClass 131 | { 132 | return [self viewMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, __unused NSDictionary *bindings) { 133 | return [evaluatedObject isKindOfClass:viewClass]; 134 | }]]; 135 | } 136 | 137 | - (NSArray *)viewsMatchingPredicate:(NSPredicate *)predicate 138 | { 139 | NSMutableArray *matches = [NSMutableArray array]; 140 | if ([predicate evaluateWithObject:self]) 141 | { 142 | [matches addObject:self]; 143 | } 144 | for (UIView *view in self.subviews) 145 | { 146 | //check for subviews 147 | //avoid creating unnecessary array 148 | if ([view.subviews count]) 149 | { 150 | [matches addObjectsFromArray:[view viewsMatchingPredicate:predicate]]; 151 | } 152 | else if ([predicate evaluateWithObject:view]) 153 | { 154 | [matches addObject:view]; 155 | } 156 | } 157 | return matches; 158 | } 159 | 160 | - (NSArray *)viewsWithTag:(NSInteger)tag 161 | { 162 | return [self viewsMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, __unused id bindings) { 163 | return [evaluatedObject tag] == tag; 164 | }]]; 165 | } 166 | 167 | - (NSArray *)viewsWithTag:(NSInteger)tag ofClass:(Class)viewClass 168 | { 169 | return [self viewsMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, __unused id bindings) { 170 | return [evaluatedObject tag] == tag && [evaluatedObject isKindOfClass:viewClass]; 171 | }]]; 172 | } 173 | 174 | - (NSArray *)viewsOfClass:(Class)viewClass 175 | { 176 | return [self viewsMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, __unused id bindings) { 177 | return [evaluatedObject isKindOfClass:viewClass]; 178 | }]]; 179 | } 180 | 181 | - (UIView *)firstSuperviewMatchingPredicate:(NSPredicate *)predicate 182 | { 183 | if ([predicate evaluateWithObject:self]) 184 | { 185 | return self; 186 | } 187 | return [self.superview firstSuperviewMatchingPredicate:predicate]; 188 | } 189 | 190 | - (UIView *)firstSuperviewOfClass:(Class)viewClass 191 | { 192 | return [self firstSuperviewMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIView *superview, __unused id bindings) { 193 | return [superview isKindOfClass:viewClass]; 194 | }]]; 195 | } 196 | 197 | - (UIView *)firstSuperviewWithTag:(NSInteger)tag 198 | { 199 | return [self firstSuperviewMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIView *superview, __unused id bindings) { 200 | return superview.tag == tag; 201 | }]]; 202 | } 203 | 204 | - (UIView *)firstSuperviewWithTag:(NSInteger)tag ofClass:(Class)viewClass 205 | { 206 | return [self firstSuperviewMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIView *superview, __unused id bindings) { 207 | return superview.tag == tag && [superview isKindOfClass:viewClass]; 208 | }]]; 209 | } 210 | 211 | - (BOOL)viewOrAnySuperviewMatchesPredicate:(NSPredicate *)predicate 212 | { 213 | if ([predicate evaluateWithObject:self]) 214 | { 215 | return YES; 216 | } 217 | return [self.superview viewOrAnySuperviewMatchesPredicate:predicate]; 218 | } 219 | 220 | - (BOOL)viewOrAnySuperviewIsKindOfClass:(Class)viewClass 221 | { 222 | return [self viewOrAnySuperviewMatchesPredicate:[NSPredicate predicateWithBlock:^BOOL(UIView *superview, __unused id bindings) { 223 | return [superview isKindOfClass:viewClass]; 224 | }]]; 225 | } 226 | 227 | - (BOOL)isSuperviewOfView:(UIView *)view 228 | { 229 | return [self firstSuperviewMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIView *superview, __unused id bindings) { 230 | return superview == view; 231 | }]] != nil; 232 | } 233 | 234 | - (BOOL)isSubviewOfView:(UIView *)view 235 | { 236 | return [view isSuperviewOfView:self]; 237 | } 238 | 239 | //responder chain 240 | 241 | - (UIViewController *)firstViewController 242 | { 243 | id responder = self; 244 | while ((responder = [responder nextResponder])) 245 | { 246 | if ([responder isKindOfClass:[UIViewController class]]) 247 | { 248 | return responder; 249 | } 250 | } 251 | return nil; 252 | } 253 | 254 | - (UIView *)firstResponder 255 | { 256 | return [self viewMatchingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, __unused id bindings) { 257 | return [evaluatedObject isFirstResponder]; 258 | }]]; 259 | } 260 | 261 | //frame accessors 262 | 263 | - (CGPoint)origin 264 | { 265 | return self.frame.origin; 266 | } 267 | 268 | - (void)setOrigin:(CGPoint)origin 269 | { 270 | CGRect frame = self.frame; 271 | frame.origin = origin; 272 | self.frame = frame; 273 | } 274 | 275 | - (CGSize)size 276 | { 277 | return self.frame.size; 278 | } 279 | 280 | - (void)setSize:(CGSize)size 281 | { 282 | CGRect frame = self.frame; 283 | frame.size = size; 284 | self.frame = frame; 285 | } 286 | 287 | - (CGFloat)top 288 | { 289 | return self.origin.y; 290 | } 291 | 292 | - (void)setTop:(CGFloat)top 293 | { 294 | CGRect frame = self.frame; 295 | frame.origin.y = top; 296 | self.frame = frame; 297 | } 298 | 299 | - (CGFloat)left 300 | { 301 | return self.origin.x; 302 | } 303 | 304 | - (void)setLeft:(CGFloat)left 305 | { 306 | CGRect frame = self.frame; 307 | frame.origin.x = left; 308 | self.frame = frame; 309 | } 310 | 311 | - (CGFloat)right 312 | { 313 | return self.left + self.width; 314 | } 315 | 316 | - (void)setRight:(CGFloat)right 317 | { 318 | CGRect frame = self.frame; 319 | frame.origin.x = right - frame.size.width; 320 | self.frame = frame; 321 | } 322 | 323 | - (CGFloat)bottom 324 | { 325 | return self.top + self.height; 326 | } 327 | 328 | - (void)setBottom:(CGFloat)bottom 329 | { 330 | CGRect frame = self.frame; 331 | frame.origin.y = bottom - frame.size.height; 332 | self.frame = frame; 333 | } 334 | 335 | - (CGFloat)width 336 | { 337 | return self.size.width; 338 | } 339 | 340 | - (void)setWidth:(CGFloat)width 341 | { 342 | CGRect frame = self.frame; 343 | frame.size.width = width; 344 | self.frame = frame; 345 | } 346 | 347 | - (CGFloat)height 348 | { 349 | return self.size.height; 350 | } 351 | 352 | - (void)setHeight:(CGFloat)height 353 | { 354 | CGRect frame = self.frame; 355 | frame.size.height = height; 356 | self.frame = frame; 357 | } 358 | 359 | - (CGFloat)x 360 | { 361 | return self.center.x; 362 | } 363 | 364 | - (void)setX:(CGFloat)x 365 | { 366 | self.center = CGPointMake(x, self.center.y); 367 | } 368 | 369 | - (CGFloat)y 370 | { 371 | return self.center.y; 372 | } 373 | 374 | - (void)setY:(CGFloat)y 375 | { 376 | self.center = CGPointMake(self.center.x, y); 377 | } 378 | 379 | //bounds accessors 380 | 381 | - (CGSize)boundsSize 382 | { 383 | return self.bounds.size; 384 | } 385 | 386 | - (void)setBoundsSize:(CGSize)size 387 | { 388 | CGRect bounds = self.bounds; 389 | bounds.size = size; 390 | self.bounds = bounds; 391 | } 392 | 393 | - (CGFloat)boundsWidth 394 | { 395 | return self.boundsSize.width; 396 | } 397 | 398 | - (void)setBoundsWidth:(CGFloat)width 399 | { 400 | CGRect bounds = self.bounds; 401 | bounds.size.width = width; 402 | self.bounds = bounds; 403 | } 404 | 405 | - (CGFloat)boundsHeight 406 | { 407 | return self.boundsSize.height; 408 | } 409 | 410 | - (void)setBoundsHeight:(CGFloat)height 411 | { 412 | CGRect bounds = self.bounds; 413 | bounds.size.height = height; 414 | self.bounds = bounds; 415 | } 416 | 417 | //content getters 418 | 419 | - (CGRect)contentBounds 420 | { 421 | return CGRectMake(0.0f, 0.0f, self.boundsWidth, self.boundsHeight); 422 | } 423 | 424 | - (CGPoint)contentCenter 425 | { 426 | return CGPointMake(self.boundsWidth/2.0f, self.boundsHeight/2.0f); 427 | } 428 | 429 | //additional frame setters 430 | 431 | - (void)setLeft:(CGFloat)left right:(CGFloat)right 432 | { 433 | CGRect frame = self.frame; 434 | frame.origin.x = left; 435 | frame.size.width = right - left; 436 | self.frame = frame; 437 | } 438 | 439 | - (void)setWidth:(CGFloat)width right:(CGFloat)right 440 | { 441 | CGRect frame = self.frame; 442 | frame.origin.x = right - width; 443 | frame.size.width = width; 444 | self.frame = frame; 445 | } 446 | 447 | - (void)setTop:(CGFloat)top bottom:(CGFloat)bottom 448 | { 449 | CGRect frame = self.frame; 450 | frame.origin.y = top; 451 | frame.size.height = bottom - top; 452 | self.frame = frame; 453 | } 454 | 455 | - (void)setHeight:(CGFloat)height bottom:(CGFloat)bottom 456 | { 457 | CGRect frame = self.frame; 458 | frame.origin.y = bottom - height; 459 | frame.size.height = height; 460 | self.frame = frame; 461 | } 462 | 463 | //animation 464 | 465 | - (void)crossfadeWithDuration:(NSTimeInterval)duration 466 | { 467 | //jump through a few hoops to avoid QuartzCore framework dependency 468 | CAAnimation *animation = [NSClassFromString(@"CATransition") animation]; 469 | [animation setValue:@"kCATransitionFade" forKey:@"type"]; 470 | animation.duration = duration; 471 | [self.layer addAnimation:animation forKey:nil]; 472 | } 473 | 474 | - (void)crossfadeWithDuration:(NSTimeInterval)duration completion:(void (^)(void))completion 475 | { 476 | [self crossfadeWithDuration:duration]; 477 | if (completion) 478 | { 479 | dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)); 480 | dispatch_after(time, dispatch_get_main_queue(), completion); 481 | } 482 | } 483 | 484 | @end 485 | -------------------------------------------------------------------------------- /Libraries/XMLDictionary/XMLDictionary.h: -------------------------------------------------------------------------------- 1 | // 2 | // XMLDictionary.h 3 | // 4 | // Version 1.4 5 | // 6 | // Created by Nick Lockwood on 15/11/2010. 7 | // Copyright 2010 Charcoal Design. All rights reserved. 8 | // 9 | // Get the latest version of XMLDictionary from here: 10 | // 11 | // https://github.com/nicklockwood/XMLDictionary 12 | // 13 | // This software is provided 'as-is', without any express or implied 14 | // warranty. In no event will the authors be held liable for any damages 15 | // arising from the use of this software. 16 | // 17 | // Permission is granted to anyone to use this software for any purpose, 18 | // including commercial applications, and to alter it and redistribute it 19 | // freely, subject to the following restrictions: 20 | // 21 | // 1. The origin of this software must not be misrepresented; you must not 22 | // claim that you wrote the original software. If you use this software 23 | // in a product, an acknowledgment in the product documentation would be 24 | // appreciated but is not required. 25 | // 26 | // 2. Altered source versions must be plainly marked as such, and must not be 27 | // misrepresented as being the original software. 28 | // 29 | // 3. This notice may not be removed or altered from any source distribution. 30 | // 31 | 32 | #import 33 | #pragma GCC diagnostic push 34 | #pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis" 35 | 36 | 37 | typedef NS_ENUM(NSInteger, XMLDictionaryAttributesMode) 38 | { 39 | XMLDictionaryAttributesModePrefixed = 0, //default 40 | XMLDictionaryAttributesModeDictionary, 41 | XMLDictionaryAttributesModeUnprefixed, 42 | XMLDictionaryAttributesModeDiscard 43 | }; 44 | 45 | 46 | typedef NS_ENUM(NSInteger, XMLDictionaryNodeNameMode) 47 | { 48 | XMLDictionaryNodeNameModeRootOnly = 0, //default 49 | XMLDictionaryNodeNameModeAlways, 50 | XMLDictionaryNodeNameModeNever 51 | }; 52 | 53 | 54 | static NSString *const XMLDictionaryAttributesKey = @"__attributes"; 55 | static NSString *const XMLDictionaryCommentsKey = @"__comments"; 56 | static NSString *const XMLDictionaryTextKey = @"__text"; 57 | static NSString *const XMLDictionaryNodeNameKey = @"__name"; 58 | static NSString *const XMLDictionaryAttributePrefix = @"_"; 59 | 60 | 61 | @interface XMLDictionaryParser : NSObject 62 | 63 | + (XMLDictionaryParser *)sharedInstance; 64 | 65 | @property (nonatomic, assign) BOOL collapseTextNodes; // defaults to YES 66 | @property (nonatomic, assign) BOOL stripEmptyNodes; // defaults to YES 67 | @property (nonatomic, assign) BOOL trimWhiteSpace; // defaults to YES 68 | @property (nonatomic, assign) BOOL alwaysUseArrays; // defaults to NO 69 | @property (nonatomic, assign) BOOL preserveComments; // defaults to NO 70 | @property (nonatomic, assign) BOOL wrapRootNode; // defaults to NO 71 | 72 | @property (nonatomic, assign) XMLDictionaryAttributesMode attributesMode; 73 | @property (nonatomic, assign) XMLDictionaryNodeNameMode nodeNameMode; 74 | 75 | - (NSDictionary *)dictionaryWithParser:(NSXMLParser *)parser; 76 | - (NSDictionary *)dictionaryWithData:(NSData *)data; 77 | - (NSDictionary *)dictionaryWithString:(NSString *)string; 78 | - (NSDictionary *)dictionaryWithFile:(NSString *)path; 79 | 80 | @end 81 | 82 | 83 | @interface NSDictionary (XMLDictionary) 84 | 85 | + (NSDictionary *)dictionaryWithXMLParser:(NSXMLParser *)parser; 86 | + (NSDictionary *)dictionaryWithXMLData:(NSData *)data; 87 | + (NSDictionary *)dictionaryWithXMLString:(NSString *)string; 88 | + (NSDictionary *)dictionaryWithXMLFile:(NSString *)path; 89 | 90 | - (NSDictionary *)attributes; 91 | - (NSDictionary *)childNodes; 92 | - (NSArray *)comments; 93 | - (NSString *)nodeName; 94 | - (NSString *)innerText; 95 | - (NSString *)innerXML; 96 | - (NSString *)XMLString; 97 | 98 | - (NSArray *)arrayValueForKeyPath:(NSString *)keyPath; 99 | - (NSString *)stringValueForKeyPath:(NSString *)keyPath; 100 | - (NSDictionary *)dictionaryValueForKeyPath:(NSString *)keyPath; 101 | 102 | @end 103 | 104 | 105 | @interface NSString (XMLDictionary) 106 | 107 | - (NSString *)XMLEncodedString; 108 | 109 | @end 110 | 111 | 112 | #pragma GCC diagnostic pop 113 | -------------------------------------------------------------------------------- /Libraries/iCarousel/iCarousel.h: -------------------------------------------------------------------------------- 1 | // 2 | // iCarousel.h 3 | // 4 | // Version 1.8.1 5 | // 6 | // Created by Nick Lockwood on 01/04/2011. 7 | // Copyright 2011 Charcoal Design 8 | // 9 | // Distributed under the permissive zlib License 10 | // Get the latest version from here: 11 | // 12 | // https://github.com/nicklockwood/iCarousel 13 | // 14 | // This software is provided 'as-is', without any express or implied 15 | // warranty. In no event will the authors be held liable for any damages 16 | // arising from the use of this software. 17 | // 18 | // Permission is granted to anyone to use this software for any purpose, 19 | // including commercial applications, and to alter it and redistribute it 20 | // freely, subject to the following restrictions: 21 | // 22 | // 1. The origin of this software must not be misrepresented; you must not 23 | // claim that you wrote the original software. If you use this software 24 | // in a product, an acknowledgment in the product documentation would be 25 | // appreciated but is not required. 26 | // 27 | // 2. Altered source versions must be plainly marked as such, and must not be 28 | // misrepresented as being the original software. 29 | // 30 | // 3. This notice may not be removed or altered from any source distribution. 31 | // 32 | 33 | 34 | #pragma GCC diagnostic push 35 | #pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis" 36 | 37 | 38 | #import 39 | #undef weak_delegate 40 | #undef __weak_delegate 41 | #if __has_feature(objc_arc) && __has_feature(objc_arc_weak) && \ 42 | (!(defined __MAC_OS_X_VERSION_MIN_REQUIRED) || \ 43 | __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_8) 44 | #define weak_delegate weak 45 | #else 46 | #define weak_delegate unsafe_unretained 47 | #endif 48 | 49 | 50 | #import 51 | #if defined USING_CHAMELEON || defined __IPHONE_OS_VERSION_MAX_ALLOWED 52 | #define ICAROUSEL_IOS 53 | #else 54 | #define ICAROUSEL_MACOS 55 | #endif 56 | 57 | 58 | #ifdef ICAROUSEL_IOS 59 | #import 60 | #else 61 | #import 62 | typedef NSView UIView; 63 | #endif 64 | 65 | 66 | typedef NS_ENUM(NSInteger, iCarouselType) 67 | { 68 | iCarouselTypeLinear = 0, 69 | iCarouselTypeRotary, 70 | iCarouselTypeInvertedRotary, 71 | iCarouselTypeCylinder, 72 | iCarouselTypeInvertedCylinder, 73 | iCarouselTypeWheel, 74 | iCarouselTypeInvertedWheel, 75 | iCarouselTypeCoverFlow, 76 | iCarouselTypeCoverFlow2, 77 | iCarouselTypeTimeMachine, 78 | iCarouselTypeInvertedTimeMachine, 79 | iCarouselTypeCustom 80 | }; 81 | 82 | 83 | typedef NS_ENUM(NSInteger, iCarouselOption) 84 | { 85 | iCarouselOptionWrap = 0, 86 | iCarouselOptionShowBackfaces, 87 | iCarouselOptionOffsetMultiplier, 88 | iCarouselOptionVisibleItems, 89 | iCarouselOptionCount, 90 | iCarouselOptionArc, 91 | iCarouselOptionAngle, 92 | iCarouselOptionRadius, 93 | iCarouselOptionTilt, 94 | iCarouselOptionSpacing, 95 | iCarouselOptionFadeMin, 96 | iCarouselOptionFadeMax, 97 | iCarouselOptionFadeRange, 98 | iCarouselOptionFadeMinAlpha 99 | }; 100 | 101 | 102 | @protocol iCarouselDataSource, iCarouselDelegate; 103 | 104 | @interface iCarousel : UIView 105 | 106 | @property (nonatomic, weak_delegate) IBOutlet id dataSource; 107 | @property (nonatomic, weak_delegate) IBOutlet id delegate; 108 | @property (nonatomic, assign) iCarouselType type; 109 | @property (nonatomic, assign) CGFloat perspective; 110 | @property (nonatomic, assign) CGFloat decelerationRate; 111 | @property (nonatomic, assign) CGFloat scrollSpeed; 112 | @property (nonatomic, assign) CGFloat bounceDistance; 113 | @property (nonatomic, assign, getter = isScrollEnabled) BOOL scrollEnabled; 114 | @property (nonatomic, assign, getter = isPagingEnabled) BOOL pagingEnabled; 115 | @property (nonatomic, assign, getter = isVertical) BOOL vertical; 116 | @property (nonatomic, readonly, getter = isWrapEnabled) BOOL wrapEnabled; 117 | @property (nonatomic, assign) BOOL bounces; 118 | @property (nonatomic, assign) CGFloat scrollOffset; 119 | @property (nonatomic, readonly) CGFloat offsetMultiplier; 120 | @property (nonatomic, assign) CGSize contentOffset; 121 | @property (nonatomic, assign) CGSize viewpointOffset; 122 | @property (nonatomic, readonly) NSInteger numberOfItems; 123 | @property (nonatomic, readonly) NSInteger numberOfPlaceholders; 124 | @property (nonatomic, assign) NSInteger currentItemIndex; 125 | @property (nonatomic, strong, readonly) UIView *currentItemView; 126 | @property (nonatomic, strong, readonly) NSArray *indexesForVisibleItems; 127 | @property (nonatomic, readonly) NSInteger numberOfVisibleItems; 128 | @property (nonatomic, strong, readonly) NSArray *visibleItemViews; 129 | @property (nonatomic, readonly) CGFloat itemWidth; 130 | @property (nonatomic, strong, readonly) UIView *contentView; 131 | @property (nonatomic, readonly) CGFloat toggle; 132 | @property (nonatomic, assign) CGFloat autoscroll; 133 | @property (nonatomic, assign) BOOL stopAtItemBoundary; 134 | @property (nonatomic, assign) BOOL scrollToItemBoundary; 135 | @property (nonatomic, assign) BOOL ignorePerpendicularSwipes; 136 | @property (nonatomic, assign) BOOL centerItemWhenSelected; 137 | @property (nonatomic, readonly, getter = isDragging) BOOL dragging; 138 | @property (nonatomic, readonly, getter = isDecelerating) BOOL decelerating; 139 | @property (nonatomic, readonly, getter = isScrolling) BOOL scrolling; 140 | 141 | - (void)scrollByOffset:(CGFloat)offset duration:(NSTimeInterval)duration; 142 | - (void)scrollToOffset:(CGFloat)offset duration:(NSTimeInterval)duration; 143 | - (void)scrollByNumberOfItems:(NSInteger)itemCount duration:(NSTimeInterval)duration; 144 | - (void)scrollToItemAtIndex:(NSInteger)index duration:(NSTimeInterval)duration; 145 | - (void)scrollToItemAtIndex:(NSInteger)index animated:(BOOL)animated; 146 | 147 | - (UIView *)itemViewAtIndex:(NSInteger)index; 148 | - (NSInteger)indexOfItemView:(UIView *)view; 149 | - (NSInteger)indexOfItemViewOrSubview:(UIView *)view; 150 | - (CGFloat)offsetForItemAtIndex:(NSInteger)index; 151 | - (UIView *)itemViewAtPoint:(CGPoint)point; 152 | 153 | - (void)removeItemAtIndex:(NSInteger)index animated:(BOOL)animated; 154 | - (void)insertItemAtIndex:(NSInteger)index animated:(BOOL)animated; 155 | - (void)reloadItemAtIndex:(NSInteger)index animated:(BOOL)animated; 156 | 157 | - (void)reloadData; 158 | 159 | @end 160 | 161 | 162 | @protocol iCarouselDataSource 163 | 164 | - (NSInteger)numberOfItemsInCarousel:(iCarousel *)carousel; 165 | - (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view; 166 | 167 | @optional 168 | 169 | - (NSInteger)numberOfPlaceholdersInCarousel:(iCarousel *)carousel; 170 | - (UIView *)carousel:(iCarousel *)carousel placeholderViewAtIndex:(NSInteger)index reusingView:(UIView *)view; 171 | 172 | @end 173 | 174 | 175 | @protocol iCarouselDelegate 176 | @optional 177 | 178 | - (void)carouselWillBeginScrollingAnimation:(iCarousel *)carousel; 179 | - (void)carouselDidEndScrollingAnimation:(iCarousel *)carousel; 180 | - (void)carouselDidScroll:(iCarousel *)carousel; 181 | - (void)carouselCurrentItemIndexDidChange:(iCarousel *)carousel; 182 | - (void)carouselWillBeginDragging:(iCarousel *)carousel; 183 | - (void)carouselDidEndDragging:(iCarousel *)carousel willDecelerate:(BOOL)decelerate; 184 | - (void)carouselWillBeginDecelerating:(iCarousel *)carousel; 185 | - (void)carouselDidEndDecelerating:(iCarousel *)carousel; 186 | 187 | - (BOOL)carousel:(iCarousel *)carousel shouldSelectItemAtIndex:(NSInteger)index; 188 | - (void)carousel:(iCarousel *)carousel didSelectItemAtIndex:(NSInteger)index; 189 | 190 | - (CGFloat)carouselItemWidth:(iCarousel *)carousel; 191 | - (CATransform3D)carousel:(iCarousel *)carousel itemTransformForOffset:(CGFloat)offset baseTransform:(CATransform3D)transform; 192 | - (CGFloat)carousel:(iCarousel *)carousel valueForOption:(iCarouselOption)option withDefault:(CGFloat)value; 193 | 194 | @end 195 | 196 | #pragma GCC diagnostic pop 197 | 198 | -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/ar.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/ar.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/bn.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/bn.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/cs.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "iRateMessageTitle" = "Ohodnotit %@"; 2 | "iRateAppMessage" = "Pokud se vám líbí %@, našli byste si chvilku na ohodnocení aplikace? Nebude to trvat víc než minutu.\nDěkujeme za vaši podporu!"; 3 | "iRateGameMessage" = "Pokud se vám %@ líbí, našli byste si chvilku na ohodnocení hry? Nebude to trvat víc než minutu. Děkujeme za vaši podporu!"; 4 | "iRateCancelButton" = "Ne, děkuji"; 5 | "iRateRateButton" = "Ohodnotit nyní"; 6 | "iRateRemindButton" = "Připomenout později"; 7 | -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/da.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "iRateMessageTitle" = "Vurdér %@"; 2 | "iRateAppMessage" = "Hvis du kan lide at bruge %@, vil du så ikke bruge et øjeblik på at give en vurdering? Det tager ikke mere end et minut. Mange tak for hjælpen!"; 3 | "iRateGameMessage" = "Hvis du kan lide at spille %@, vil du så ikke bruge et øjeblik på at vurdere det? Det tager ikke mere end et minut. Mange tak for hjælpen!"; 4 | "iRateCancelButton" = "Nej tak"; 5 | "iRateRateButton" = "Vurdér nu"; 6 | "iRateRemindButton" = "Påmind mig senere"; 7 | -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/de-AT.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "iRateMessageTitle" = "Bewerte %@"; 2 | "iRateAppMessage" = "Wenn dir %@ gefällt, würdest Du es bitte bewerten? Dies wird nicht länger als eine Minute dauern.\nDanke für die Unterstützung!"; 3 | "iRateGameMessage" = "Wenn dir %@ gefällt, würdest Du es bitte bewerten? Dies wird nicht länger als eine Minute dauern. Danke für die Unterstützung!"; 4 | "iRateCancelButton" = "Nein, danke"; 5 | "iRateRateButton" = "Jetzt bewerten"; 6 | "iRateRemindButton" = "Später erinnern"; -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/de.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/el.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/el.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/en.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/es.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/es.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/fa.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "iRateMessageTitle" = "امتیاز دهی به %@"; 2 | "iRateAppMessage" = "اگر شما از برنامه‌ی %@ لذت بردین، عالی میشه اگه با این برنامه در اپ‌استور امتیاز دهید! این کار بیشتر از ۱ دقیقه طول نمی‌کشد. با تشکر از حمایت شما!"; 3 | "iRateGameMessage" ="اگر شما از بازی %@ لذت بردین، عالی میشه اگه با این برنامه در اپ‌استور امتیاز دهید! این کار بیشتر از ۱ دقیقه طول نمی‌کشد. با تشکر از حمایت شما!"; 4 | "iRateCancelButton" = "نه، با تشکر"; 5 | "iRateRateButton" = "می‌خوام امتیاز بدم"; 6 | "iRateRemindButton" = "بعدا یادم بنداز"; 7 | -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "iRateMessageTitle" = "Notez %@"; 2 | "iRateAppMessage" = "Si vous aimez utiliser %@, n'oubliez pas de donner votre avis sur l'App Store. Cela ne prend qu'une minute. Merci d'avance pour votre soutien !"; 3 | "iRateGameMessage" = "Si vous aimez jouer à %@, n'oubliez pas de donner votre avis sur l'App Store. Cela ne prend qu'une minute. Merci d'avance pour votre soutien !"; 4 | "iRateCancelButton" = "Non, merci"; 5 | "iRateRateButton" = "Noter maintenant"; 6 | "iRateRemindButton" = "Me le rappeler ultérieurement"; 7 | -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/he.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/he.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/hi.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/hi.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/id.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/id.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/it.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/it.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/ja.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/ja.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/ko.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/ko.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/nl.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "iRateMessageTitle" = "Beoordeel %@"; 2 | "iRateAppMessage" = "Als het gebruik van %@ je bevalt, wil je dan een moment nemen om het te beoordelen? Het duurt nog geen minuut. Bedankt voor je steun!"; 3 | "iRateGameMessage" = "Als het spelen van %@ je bevalt, wil je dan een moment nemen om het te beoordelen? Het duurt nog geen minuut. Bedankt voor je steun!"; 4 | "iRateCancelButton" = "Nee, bedankt"; 5 | "iRateRateButton" = "Beoordeel nu"; 6 | "iRateRemindButton" = "Herinner me er later aan"; -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/no.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/no.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/pa.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/pa.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/pl.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/pl.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/pt.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/pt.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/ru.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/sk.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "iRateMessageTitle" = "Ohodnotiť %@"; 2 | "iRateAppMessage" = "Ak sa vám páči %@, našli by ste si chvíľku na ohodnotenie aplikácie? Nebude to trvať viac ako minútu.\nĎakujeme za vašu podporu!"; 3 | "iRateGameMessage" = "Ak sa vám hra %@ páči, našli by ste si chvíľku na ohodnotenie aplikácie? Nebude to trvať viac ako minútu. Ďakujeme za vašu podporu!"; 4 | "iRateCancelButton" = "Nie, Ďakujem"; 5 | "iRateRateButton" = "Ohodnotiť teraz"; 6 | "iRateRemindButton" = "Pripomenúť neskôr"; -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/sl.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/sl.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/sv.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "iRateMessageTitle" = "Betygsätt %@"; 2 | "iRateAppMessage" = "Gillar du %@ och kan tänka dig att betygsätta den? Det tar inte mer än en minut. Tack för ditt stöd!"; 3 | "iRateGameMessage" = "Gillar du att spela %@ och kan tänka dig att betygsätta det? Det tar inte mer än en minut. Tack för ditt stöd!"; 4 | "iRateCancelButton" = "Nej tack"; 5 | "iRateRateButton" = "Betygsätt nu!"; 6 | "iRateRemindButton" = "Påminn mig senare"; -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/th.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/th.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/tr.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/tr.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/uk.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/uk.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/ur-IN.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/ur-IN.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/ur-PK.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/ur-PK.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/ur.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/ur.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/vi.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "iRateMessageTitle" = "Đánh giá %@"; 2 | "iRateAppMessage" = "Nếu thích sử dụng %@, bạn có muốn giành một chút thời gian để đánh giá nó? Sẽ không lâu hơn một phút. Cảm ơn sự hỗ trợ của bạn!"; 3 | "iRateGameMessage" = "Nếu thích chơi %@, bạn có muốn giành một chút thời gian để đánh giá nó? Sẽ không lâu hơn một phút. Cảm ơn sự hỗ trợ của bạn!"; 4 | "iRateCancelButton" = "Không, Cảm ơn"; 5 | "iRateRateButton" = "Đánh Giá Ngay"; 6 | "iRateRemindButton" = "Nhắc Tôi Sau"; 7 | -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/zh-Hans.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.bundle/zh-Hant.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iRate/iRate.bundle/zh-Hant.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iRate/iRate.h: -------------------------------------------------------------------------------- 1 | // 2 | // iRate.h 3 | // 4 | // Version 1.11 5 | // 6 | // Created by Nick Lockwood on 26/01/2011. 7 | // Copyright 2011 Charcoal Design 8 | // 9 | // Distributed under the permissive zlib license 10 | // Get the latest version from here: 11 | // 12 | // https://github.com/nicklockwood/iRate 13 | // 14 | // This software is provided 'as-is', without any express or implied 15 | // warranty. In no event will the authors be held liable for any damages 16 | // arising from the use of this software. 17 | // 18 | // Permission is granted to anyone to use this software for any purpose, 19 | // including commercial applications, and to alter it and redistribute it 20 | // freely, subject to the following restrictions: 21 | // 22 | // 1. The origin of this software must not be misrepresented; you must not 23 | // claim that you wrote the original software. If you use this software 24 | // in a product, an acknowledgment in the product documentation would be 25 | // appreciated but is not required. 26 | // 27 | // 2. Altered source versions must be plainly marked as such, and must not be 28 | // misrepresented as being the original software. 29 | // 30 | // 3. This notice may not be removed or altered from any source distribution. 31 | // 32 | 33 | 34 | #pragma GCC diagnostic push 35 | #pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis" 36 | 37 | 38 | #import 39 | #undef weak_delegate 40 | #if __has_feature(objc_arc_weak) && \ 41 | (TARGET_OS_IPHONE || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_8) 42 | #define weak_delegate weak 43 | #else 44 | #define weak_delegate unsafe_unretained 45 | #endif 46 | 47 | 48 | #import 49 | #if TARGET_OS_IPHONE 50 | #import 51 | #define IRATE_EXTERN UIKIT_EXTERN 52 | #else 53 | #import 54 | #define IRATE_EXTERN APPKIT_EXTERN 55 | #endif 56 | 57 | 58 | IRATE_EXTERN NSUInteger const iRateAppStoreGameGenreID; 59 | IRATE_EXTERN NSString *const iRateErrorDomain; 60 | 61 | //localisation string keys 62 | IRATE_EXTERN NSString *const iRateMessageTitleKey; //iRateMessageTitle 63 | IRATE_EXTERN NSString *const iRateAppMessageKey; //iRateAppMessage 64 | IRATE_EXTERN NSString *const iRateGameMessageKey; //iRateGameMessage 65 | IRATE_EXTERN NSString *const iRateUpdateMessageKey; //iRateUpdateMessage 66 | IRATE_EXTERN NSString *const iRateCancelButtonKey; //iRateCancelButton 67 | IRATE_EXTERN NSString *const iRateRemindButtonKey; //iRateRemindButton 68 | IRATE_EXTERN NSString *const iRateRateButtonKey; //iRateRateButton 69 | 70 | //notification keys 71 | IRATE_EXTERN NSString *const iRateCouldNotConnectToAppStore; 72 | IRATE_EXTERN NSString *const iRateDidDetectAppUpdate; 73 | IRATE_EXTERN NSString *const iRateDidPromptForRating; 74 | IRATE_EXTERN NSString *const iRateUserDidAttemptToRateApp; 75 | IRATE_EXTERN NSString *const iRateUserDidDeclineToRateApp; 76 | IRATE_EXTERN NSString *const iRateUserDidRequestReminderToRateApp; 77 | IRATE_EXTERN NSString *const iRateDidOpenAppStore; 78 | 79 | 80 | typedef NS_ENUM(NSUInteger, iRateErrorCode) 81 | { 82 | iRateErrorBundleIdDoesNotMatchAppStore = 1, 83 | iRateErrorApplicationNotFoundOnAppStore, 84 | iRateErrorApplicationIsNotLatestVersion, 85 | iRateErrorCouldNotOpenRatingPageURL 86 | }; 87 | 88 | 89 | @protocol iRateDelegate 90 | @optional 91 | 92 | - (void)iRateCouldNotConnectToAppStore:(NSError *)error; 93 | - (void)iRateDidDetectAppUpdate; 94 | - (BOOL)iRateShouldPromptForRating; 95 | - (void)iRateDidPromptForRating; 96 | - (void)iRateUserDidAttemptToRateApp; 97 | - (void)iRateUserDidDeclineToRateApp; 98 | - (void)iRateUserDidRequestReminderToRateApp; 99 | - (BOOL)iRateShouldOpenAppStore; 100 | - (void)iRateDidOpenAppStore; 101 | 102 | @end 103 | 104 | 105 | @interface iRate : NSObject 106 | 107 | + (iRate *)sharedInstance; 108 | 109 | //app store ID - this is only needed if your 110 | //bundle ID is not unique between iOS and Mac app stores 111 | @property (nonatomic, assign) NSUInteger appStoreID; 112 | 113 | //application details - these are set automatically 114 | @property (nonatomic, assign) NSUInteger appStoreGenreID; 115 | @property (nonatomic, copy) NSString *appStoreCountry; 116 | @property (nonatomic, copy) NSString *applicationName; 117 | @property (nonatomic, copy) NSString *applicationVersion; 118 | @property (nonatomic, copy) NSString *applicationBundleID; 119 | 120 | //usage settings - these have sensible defaults 121 | @property (nonatomic, assign) NSUInteger usesUntilPrompt; 122 | @property (nonatomic, assign) NSUInteger eventsUntilPrompt; 123 | @property (nonatomic, assign) float daysUntilPrompt; 124 | @property (nonatomic, assign) float usesPerWeekForPrompt; 125 | @property (nonatomic, assign) float remindPeriod; 126 | 127 | //message text, you may wish to customise these 128 | @property (nonatomic, copy) NSString *messageTitle; 129 | @property (nonatomic, copy) NSString *message; 130 | @property (nonatomic, copy) NSString *updateMessage; 131 | @property (nonatomic, copy) NSString *cancelButtonLabel; 132 | @property (nonatomic, copy) NSString *remindButtonLabel; 133 | @property (nonatomic, copy) NSString *rateButtonLabel; 134 | 135 | //debugging and prompt overrides 136 | @property (nonatomic, assign) BOOL useAllAvailableLanguages; 137 | @property (nonatomic, assign) BOOL promptForNewVersionIfUserRated; 138 | @property (nonatomic, assign) BOOL onlyPromptIfLatestVersion; 139 | @property (nonatomic, assign) BOOL onlyPromptIfMainWindowIsAvailable; 140 | @property (nonatomic, assign) BOOL promptAtLaunch; 141 | @property (nonatomic, assign) BOOL verboseLogging; 142 | @property (nonatomic, assign) BOOL previewMode; 143 | 144 | //advanced properties for implementing custom behaviour 145 | @property (nonatomic, strong) NSURL *ratingsURL; 146 | @property (nonatomic, strong) NSDate *firstUsed; 147 | @property (nonatomic, strong) NSDate *lastReminded; 148 | @property (nonatomic, assign) NSUInteger usesCount; 149 | @property (nonatomic, assign) NSUInteger eventCount; 150 | @property (nonatomic, readonly) float usesPerWeek; 151 | @property (nonatomic, assign) BOOL declinedThisVersion; 152 | @property (nonatomic, readonly) BOOL declinedAnyVersion; 153 | @property (nonatomic, assign) BOOL ratedThisVersion; 154 | @property (nonatomic, readonly) BOOL ratedAnyVersion; 155 | @property (nonatomic, weak_delegate) id delegate; 156 | 157 | //manually control behaviour 158 | - (BOOL)shouldPromptForRating; 159 | - (void)promptForRating; 160 | - (void)promptIfNetworkAvailable; 161 | - (void)promptIfAllCriteriaMet; 162 | - (void)openRatingsPageInAppStore; 163 | - (void)logEvent:(BOOL)deferPrompt; 164 | 165 | @end 166 | 167 | 168 | #pragma GCC diagnostic pop 169 | -------------------------------------------------------------------------------- /Libraries/iVersion/iVersion.bundle/da.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "iVersionInThisVersionTitle" = "Nyheder i denne version"; 2 | "iVersionUpdateAvailableTitle" = "Ny version tilgængelig"; 3 | "iVersionVersionLabelFormat" = "Version %@"; 4 | "iVersionIgnoreButton" = "Ignorér"; 5 | "iVersionDownloadButton" = "Hent"; 6 | "iVersionRemindButton" = "Påmind mig senere"; 7 | "iVersionOKButton" = "OK"; -------------------------------------------------------------------------------- /Libraries/iVersion/iVersion.bundle/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iVersion/iVersion.bundle/de.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iVersion/iVersion.bundle/el.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iVersion/iVersion.bundle/el.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iVersion/iVersion.bundle/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iVersion/iVersion.bundle/en.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iVersion/iVersion.bundle/es.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iVersion/iVersion.bundle/es.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iVersion/iVersion.bundle/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iVersion/iVersion.bundle/fr.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iVersion/iVersion.bundle/it.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iVersion/iVersion.bundle/it.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iVersion/iVersion.bundle/ja.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iVersion/iVersion.bundle/ja.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iVersion/iVersion.bundle/pt-PT.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iVersion/iVersion.bundle/pt-PT.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iVersion/iVersion.bundle/pt.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iVersion/iVersion.bundle/pt.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iVersion/iVersion.bundle/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iVersion/iVersion.bundle/ru.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iVersion/iVersion.bundle/tr.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iVersion/iVersion.bundle/tr.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iVersion/iVersion.bundle/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Libraries/iVersion/iVersion.bundle/zh-Hans.lproj/Localizable.strings -------------------------------------------------------------------------------- /Libraries/iVersion/iVersion.h: -------------------------------------------------------------------------------- 1 | // 2 | // iVersion.h 3 | // 4 | // Version 1.11.1 5 | // 6 | // Created by Nick Lockwood on 26/01/2011. 7 | // Copyright 2011 Charcoal Design 8 | // 9 | // Distributed under the permissive zlib license 10 | // Get the latest version from here: 11 | // 12 | // https://github.com/nicklockwood/iVersion 13 | // 14 | // This software is provided 'as-is', without any express or implied 15 | // warranty. In no event will the authors be held liable for any damages 16 | // arising from the use of this software. 17 | // 18 | // Permission is granted to anyone to use this software for any purpose, 19 | // including commercial applications, and to alter it and redistribute it 20 | // freely, subject to the following restrictions: 21 | // 22 | // 1. The origin of this software must not be misrepresented; you must not 23 | // claim that you wrote the original software. If you use this software 24 | // in a product, an acknowledgment in the product documentation would be 25 | // appreciated but is not required. 26 | // 27 | // 2. Altered source versions must be plainly marked as such, and must not be 28 | // misrepresented as being the original software. 29 | // 30 | // 3. This notice may not be removed or altered from any source distribution. 31 | // 32 | 33 | 34 | #pragma GCC diagnostic push 35 | #pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis" 36 | 37 | 38 | #import 39 | #undef weak_delegate 40 | #if __has_feature(objc_arc_weak) && \ 41 | (TARGET_OS_IPHONE || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_8) 42 | #define weak_delegate weak 43 | #else 44 | #define weak_delegate unsafe_unretained 45 | #endif 46 | 47 | 48 | #import 49 | #if TARGET_OS_IPHONE 50 | #import 51 | #define IVERSION_EXTERN UIKIT_EXTERN 52 | #else 53 | #import 54 | #define IVERSION_EXTERN APPKIT_EXTERN 55 | #endif 56 | 57 | 58 | #if defined(IVERSION_USE_STOREKIT) && IVERSION_USE_STOREKIT 59 | #import 60 | #endif 61 | 62 | 63 | extern NSString *const iVersionErrorDomain; 64 | 65 | //localisation string keys 66 | IVERSION_EXTERN NSString *const iVersionInThisVersionTitleKey; //iVersionInThisVersionTitle 67 | IVERSION_EXTERN NSString *const iVersionUpdateAvailableTitleKey; //iVersionUpdateAvailableTitle 68 | IVERSION_EXTERN NSString *const iVersionVersionLabelFormatKey; //iVersionVersionLabelFormat 69 | IVERSION_EXTERN NSString *const iVersionOKButtonKey; //iVersionOKButton 70 | IVERSION_EXTERN NSString *const iVersionIgnoreButtonKey; //iVersionIgnoreButton 71 | IVERSION_EXTERN NSString *const iVersionRemindButtonKey; //iVersionRemindButton 72 | IVERSION_EXTERN NSString *const iVersionDownloadButtonKey; //iVersionDownloadButton 73 | 74 | 75 | typedef NS_ENUM(NSUInteger, iVersionErrorCode) 76 | { 77 | iVersionErrorBundleIdDoesNotMatchAppStore = 1, 78 | iVersionErrorApplicationNotFoundOnAppStore, 79 | iVersionErrorOSVersionNotSupported 80 | }; 81 | 82 | 83 | typedef NS_ENUM(NSInteger, iVersionUpdatePriority) 84 | { 85 | iVersionUpdatePriorityDefault = 0, 86 | iVersionUpdatePriorityLow = 1, 87 | iVersionUpdatePriorityMedium = 2, 88 | iVersionUpdatePriorityHigh = 3 89 | }; 90 | 91 | 92 | @interface NSString(iVersion) 93 | 94 | - (NSComparisonResult)compareVersion:(NSString *)version; 95 | - (NSComparisonResult)compareVersionDescending:(NSString *)version; 96 | 97 | @end 98 | 99 | 100 | @protocol iVersionDelegate 101 | @optional 102 | 103 | - (BOOL)iVersionShouldCheckForNewVersion; 104 | - (void)iVersionDidNotDetectNewVersion; 105 | - (void)iVersionVersionCheckDidFailWithError:(NSError *)error; 106 | - (void)iVersionDidDetectNewVersion:(NSString *)version details:(NSString *)versionDetails; 107 | - (BOOL)iVersionShouldDisplayNewVersion:(NSString *)version details:(NSString *)versionDetails; 108 | - (BOOL)iVersionShouldDisplayCurrentVersionDetails:(NSString *)versionDetails; 109 | - (void)iVersionUserDidAttemptToDownloadUpdate:(NSString *)version; 110 | - (void)iVersionUserDidRequestReminderForUpdate:(NSString *)version; 111 | - (void)iVersionUserDidIgnoreUpdate:(NSString *)version; 112 | - (BOOL)iVersionShouldOpenAppStore; 113 | - (void)iVersionDidPresentStoreKitModal; 114 | - (void)iVersionDidDismissStoreKitModal; 115 | 116 | @end 117 | 118 | 119 | @interface iVersion : NSObject 120 | 121 | + (iVersion *)sharedInstance; 122 | 123 | //app store ID - this is only needed if your 124 | //bundle ID is not unique between iOS and Mac app stores 125 | @property (nonatomic, assign) NSUInteger appStoreID; 126 | 127 | //application details - these are set automatically 128 | @property (nonatomic, copy) NSString *applicationVersion; 129 | @property (nonatomic, copy) NSString *applicationBundleID; 130 | @property (nonatomic, copy) NSString *appStoreCountry; 131 | 132 | //usage settings - these have sensible defaults 133 | @property (nonatomic, assign) BOOL showOnFirstLaunch; 134 | @property (nonatomic, assign) BOOL groupNotesByVersion; 135 | @property (nonatomic, assign) float checkPeriod; 136 | @property (nonatomic, assign) float remindPeriod; 137 | 138 | //message text - you may wish to customise these 139 | @property (nonatomic, copy) NSString *inThisVersionTitle; 140 | @property (nonatomic, copy) NSString *updateAvailableTitle; 141 | @property (nonatomic, copy) NSString *versionLabelFormat; 142 | @property (nonatomic, copy) NSString *okButtonLabel; 143 | @property (nonatomic, copy) NSString *ignoreButtonLabel; 144 | @property (nonatomic, copy) NSString *remindButtonLabel; 145 | @property (nonatomic, copy) NSString *downloadButtonLabel; 146 | 147 | //debugging and prompt overrides 148 | @property (nonatomic, assign) iVersionUpdatePriority updatePriority; 149 | @property (nonatomic, assign) BOOL useAllAvailableLanguages; 150 | @property (nonatomic, assign) BOOL onlyPromptIfMainWindowIsAvailable; 151 | @property (nonatomic, assign) BOOL useAppStoreDetailsIfNoPlistEntryFound; 152 | @property (nonatomic, assign) BOOL checkAtLaunch; 153 | @property (nonatomic, assign) BOOL verboseLogging; 154 | @property (nonatomic, assign) BOOL previewMode; 155 | 156 | //advanced properties for implementing custom behaviour 157 | @property (nonatomic, copy) NSString *remoteVersionsPlistURL; 158 | @property (nonatomic, copy) NSString *localVersionsPlistPath; 159 | @property (nonatomic, copy) NSString *ignoredVersion; 160 | @property (nonatomic, strong) NSDate *lastChecked; 161 | @property (nonatomic, strong) NSDate *lastReminded; 162 | @property (nonatomic, strong) NSURL *updateURL; 163 | @property (nonatomic, assign) BOOL viewedVersionDetails; 164 | @property (nonatomic, weak_delegate) id delegate; 165 | 166 | //manually control behaviour 167 | - (BOOL)openAppPageInAppStore; 168 | - (void)checkIfNewVersion; 169 | - (NSString *)versionDetails; 170 | - (BOOL)shouldCheckForNewVersion; 171 | - (void)checkForNewVersion; 172 | 173 | @end 174 | 175 | 176 | #pragma GCC diagnostic pop 177 | 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Concurrency 2 | =========== 3 | 4 | This is the source for the Concurrency app: https://itunes.apple.com/app/concurrency/id738872892 - enjoy! 5 | 6 | Just so we're clear, I'm releasing this so you can study it and learn how to make apps (or how *not* to make them, depending on what you think of my code). 7 | 8 | This is not an invitation to take my app and release it unmodified (or mostly unmodified) under your own name. If you do that, I will find you ಠ_ಠ. 9 | 10 | If you like this, please buy my app! KTHANXBAI 11 | 12 | 13 | Release notes 14 | ------------- 15 | 16 | Version 1.1.1 17 | 18 | - Currency displays are now formatted appropriately 19 | - Improved appearance on iOS 6 20 | - UI state is now saved between launches 21 | 22 | Version 1.1 23 | 24 | - Now supports an additional 56 currencies! 25 | - Added currency search feature 26 | - New color scheme (iOS 7 only) 27 | 28 | Version 1.0.1 29 | 30 | - You can now copy converted currency values 31 | - You can now swipe to dismiss the number pad 32 | - Currency pickers are now more responsive 33 | - You can now stop the startup "wiggle" animation early by touching the interface 34 | - Various minor bug fixes 35 | 36 | Version 1.0 37 | 38 | - First release -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-Small.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "29x29", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-Small@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-Small@3x.png", 19 | "scale" : "3x" 20 | }, 21 | { 22 | "size" : "40x40", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-40@2x-1.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-40@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "57x57", 35 | "idiom" : "iphone", 36 | "filename" : "Icon.png", 37 | "scale" : "1x" 38 | }, 39 | { 40 | "size" : "57x57", 41 | "idiom" : "iphone", 42 | "filename" : "Icon@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "29x29", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-Small-1.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-Small@2x-1.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "40x40", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-40.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "40x40", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-40@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "50x50", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-Small-50.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "50x50", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-Small-50@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "72x72", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-72.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "72x72", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-72@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "76x76", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-76.png", 109 | "scale" : "1x" 110 | }, 111 | { 112 | "size" : "76x76", 113 | "idiom" : "ipad", 114 | "filename" : "Icon-76@2x.png", 115 | "scale" : "2x" 116 | }, 117 | { 118 | "idiom" : "car", 119 | "size" : "120x120", 120 | "scale" : "1x" 121 | } 122 | ], 123 | "info" : { 124 | "version" : 1, 125 | "author" : "xcode" 126 | } 127 | } -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon-40@2x-1.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon-72.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon-72@2x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-Small-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon-Small-1.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-Small-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon-Small-50.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/AppIcon.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "extent" : "full-screen", 5 | "idiom" : "iphone", 6 | "subtype" : "736h", 7 | "filename" : "Default-736h@3x.png", 8 | "minimum-system-version" : "8.0", 9 | "orientation" : "portrait", 10 | "scale" : "3x" 11 | }, 12 | { 13 | "orientation" : "landscape", 14 | "idiom" : "iphone", 15 | "extent" : "full-screen", 16 | "minimum-system-version" : "8.0", 17 | "subtype" : "736h", 18 | "scale" : "3x" 19 | }, 20 | { 21 | "extent" : "full-screen", 22 | "idiom" : "iphone", 23 | "subtype" : "667h", 24 | "filename" : "Default-667h@2x.png", 25 | "minimum-system-version" : "8.0", 26 | "orientation" : "portrait", 27 | "scale" : "2x" 28 | }, 29 | { 30 | "orientation" : "portrait", 31 | "idiom" : "iphone", 32 | "extent" : "full-screen", 33 | "minimum-system-version" : "7.0", 34 | "filename" : "Default@2x.png", 35 | "scale" : "2x" 36 | }, 37 | { 38 | "extent" : "full-screen", 39 | "idiom" : "iphone", 40 | "subtype" : "retina4", 41 | "filename" : "Default-568h@2x.png", 42 | "minimum-system-version" : "7.0", 43 | "orientation" : "portrait", 44 | "scale" : "2x" 45 | }, 46 | { 47 | "orientation" : "portrait", 48 | "idiom" : "ipad", 49 | "extent" : "full-screen", 50 | "minimum-system-version" : "7.0", 51 | "scale" : "1x" 52 | }, 53 | { 54 | "orientation" : "landscape", 55 | "idiom" : "ipad", 56 | "extent" : "full-screen", 57 | "minimum-system-version" : "7.0", 58 | "scale" : "1x" 59 | }, 60 | { 61 | "orientation" : "portrait", 62 | "idiom" : "ipad", 63 | "extent" : "full-screen", 64 | "minimum-system-version" : "7.0", 65 | "scale" : "2x" 66 | }, 67 | { 68 | "orientation" : "landscape", 69 | "idiom" : "ipad", 70 | "extent" : "full-screen", 71 | "minimum-system-version" : "7.0", 72 | "scale" : "2x" 73 | }, 74 | { 75 | "orientation" : "portrait", 76 | "idiom" : "iphone", 77 | "extent" : "full-screen", 78 | "filename" : "Default.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "orientation" : "portrait", 83 | "idiom" : "iphone", 84 | "extent" : "full-screen", 85 | "filename" : "Default@2x-1.png", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "orientation" : "portrait", 90 | "idiom" : "iphone", 91 | "extent" : "full-screen", 92 | "filename" : "Default-568h@2x-1.png", 93 | "subtype" : "retina4", 94 | "scale" : "2x" 95 | }, 96 | { 97 | "orientation" : "portrait", 98 | "idiom" : "ipad", 99 | "extent" : "to-status-bar", 100 | "scale" : "1x" 101 | }, 102 | { 103 | "orientation" : "portrait", 104 | "idiom" : "ipad", 105 | "extent" : "full-screen", 106 | "scale" : "1x" 107 | }, 108 | { 109 | "orientation" : "landscape", 110 | "idiom" : "ipad", 111 | "extent" : "to-status-bar", 112 | "scale" : "1x" 113 | }, 114 | { 115 | "orientation" : "landscape", 116 | "idiom" : "ipad", 117 | "extent" : "full-screen", 118 | "scale" : "1x" 119 | }, 120 | { 121 | "orientation" : "portrait", 122 | "idiom" : "ipad", 123 | "extent" : "to-status-bar", 124 | "scale" : "2x" 125 | }, 126 | { 127 | "orientation" : "portrait", 128 | "idiom" : "ipad", 129 | "extent" : "full-screen", 130 | "scale" : "2x" 131 | }, 132 | { 133 | "orientation" : "landscape", 134 | "idiom" : "ipad", 135 | "extent" : "to-status-bar", 136 | "scale" : "2x" 137 | }, 138 | { 139 | "orientation" : "landscape", 140 | "idiom" : "ipad", 141 | "extent" : "full-screen", 142 | "scale" : "2x" 143 | } 144 | ], 145 | "info" : { 146 | "version" : 1, 147 | "author" : "xcode" 148 | } 149 | } -------------------------------------------------------------------------------- /Resources/Images.xcassets/LaunchImage.launchimage/Default-568h@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/LaunchImage.launchimage/Default-568h@2x-1.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/LaunchImage.launchimage/Default-667h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/LaunchImage.launchimage/Default-667h@2x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/LaunchImage.launchimage/Default-736h@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/LaunchImage.launchimage/Default-736h@3x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/LaunchImage.launchimage/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/LaunchImage.launchimage/Default.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/LaunchImage.launchimage/Default@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/LaunchImage.launchimage/Default@2x-1.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/LaunchImage.launchimage/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicklockwood/Concurrency/f9bdcfd44cc36186f65b6bf7609b5f30eb283975/Resources/Images.xcassets/LaunchImage.launchimage/Default@2x.png --------------------------------------------------------------------------------