├── SimpleCocoaAnalytics ├── en.lproj │ ├── InfoPlist.strings │ └── Credits.rtf ├── SimpleCocoaAnalytics-Prefix.pch ├── main.m ├── AppDelegate.h ├── AnalyticsEvent.h ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── AppDelegate.m ├── SimpleCocoaAnalytics-Info.plist ├── AnalyticsEvent.m ├── AnalyticsHelper.h ├── AnalyticsHelper.m └── Base.lproj │ └── MainMenu.xib ├── SimpleCocoaAnalyticsTests ├── en.lproj │ └── InfoPlist.strings ├── AnalyticsHelper+Testing.h ├── SimpleCocoaAnalyticsTests-Info.plist ├── AnalyticsHelperNetworkProtocol.h └── SimpleCocoaAnalyticsTests.m ├── .gitignore ├── SimpleCocoaAnalytics.podspec ├── SimpleCocoaAnalytics.xcodeproj ├── xcuserdata │ └── steve.xcuserdatad │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── SimpleCocoaAnalytics.xcscheme └── project.pbxproj ├── README.md └── LICENSE.md /SimpleCocoaAnalytics/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /SimpleCocoaAnalyticsTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.xccheckout 3 | 4 | *.xcworkspacedata 5 | 6 | *.xcuserstate 7 | 8 | *.xcscheme 9 | 10 | xcuserdata 11 | -------------------------------------------------------------------------------- /SimpleCocoaAnalytics/SimpleCocoaAnalytics-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 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /SimpleCocoaAnalytics/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SimpleCocoaAnalytics 4 | // 5 | // Created by Stephen Lind on 10/28/13. 6 | // Copyright (c) 2013 foxnsox. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) 12 | { 13 | return NSApplicationMain(argc, argv); 14 | } 15 | -------------------------------------------------------------------------------- /SimpleCocoaAnalytics/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // SimpleCocoaAnalytics 4 | // 5 | // Created by Stephen Lind on 10/28/13. 6 | // Copyright (c) 2013 foxnsox. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : NSObject 12 | 13 | @property (assign) IBOutlet NSWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /SimpleCocoaAnalytics/en.lproj/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} 2 | {\colortbl;\red255\green255\blue255;} 3 | \paperw9840\paperh8400 4 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural 5 | 6 | \f0\b\fs24 \cf0 Engineering: 7 | \b0 \ 8 | Some people\ 9 | \ 10 | 11 | \b Human Interface Design: 12 | \b0 \ 13 | Some other people\ 14 | \ 15 | 16 | \b Testing: 17 | \b0 \ 18 | Hopefully not nobody\ 19 | \ 20 | 21 | \b Documentation: 22 | \b0 \ 23 | Whoever\ 24 | \ 25 | 26 | \b With special thanks to: 27 | \b0 \ 28 | Mom\ 29 | } 30 | -------------------------------------------------------------------------------- /SimpleCocoaAnalytics.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "SimpleCocoaAnalytics" 3 | s.version = "0.1.0" 4 | s.summary = "A simple set of classes of using Google Analytics to track your OS X app." 5 | s.homepage = "https://github.com/stephenlind/SimpleCocoaGoogleAnalytics" 6 | s.license = { :type => "MIT", :file => "LICENSE.md" } 7 | s.author = { "stephenlind" => "" } 8 | s.source = { :git => "https://github.com/stephenlind/SimpleCocoaGoogleAnalytics.git", 9 | :tag => "#{s.version}" } 10 | s.platform = :osx 11 | s.source_files = "SimpleCocoaAnalytics/Analytics*.{h,m}" 12 | s.requires_arc = true 13 | end 14 | -------------------------------------------------------------------------------- /SimpleCocoaAnalyticsTests/AnalyticsHelper+Testing.h: -------------------------------------------------------------------------------- 1 | // 2 | // AnalyticsHelper+Testing.h 3 | // DraftControl 4 | // 5 | // Created by Stephen Lind on 9/30/13. 6 | // Copyright (c) 2013 Stephen Lind. All rights reserved. 7 | // 8 | 9 | #import "AnalyticsHelper.h" 10 | #import "AnalyticsHelperNetworkProtocol.h" 11 | 12 | @interface AnalyticsHelper (Testing) 13 | 14 | - (NSArray*)recordedEvents; 15 | - (void)clearRecordedEvents; 16 | 17 | - (NSUInteger)getErrorCount; 18 | - (void)clearErrors; 19 | - (void)recordSendError; 20 | - (void)sendReport; 21 | - (void)saveCachedEventsToDisk; 22 | - (void)createAndSendReport:(id)sender; 23 | - (void)testSetNetworkHandler:(id)handler; 24 | @end 25 | -------------------------------------------------------------------------------- /SimpleCocoaAnalytics.xcodeproj/xcuserdata/steve.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SimpleCocoaAnalytics.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | AA8008AE181F3EC500225F5A 16 | 17 | primary 18 | 19 | 20 | AA8008CF181F3EC500225F5A 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SimpleCocoaAnalyticsTests/SimpleCocoaAnalyticsTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.foxnsox.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SimpleCocoaAnalytics/AnalyticsEvent.h: -------------------------------------------------------------------------------- 1 | // 2 | // AnalyticsEvent.h 3 | // 4 | // Created by Stephen Lind on 9/30/13. 5 | // Copyright (c) 2013 Stephen Lind. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | /*! 11 | @brief A serializable object for storing analytics events on disk. 12 | 13 | @description This object is an Objective-C construct to create type-safe data 14 | which can be stored within NSUserDefaults until it is eventually sent to the 15 | analytics server. 16 | */ 17 | @interface AnalyticsEvent : NSObject 18 | 19 | @property (strong) NSString *category; 20 | @property (strong) NSString *action; 21 | @property (strong) NSString *label; 22 | @property (strong) NSNumber *value; 23 | 24 | + (AnalyticsEvent*)analyticsEventWithDictionary:(NSDictionary*)dict; 25 | - (NSDictionary*)dictionaryRepresenation; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /SimpleCocoaAnalyticsTests/AnalyticsHelperNetworkProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // AnalyticsHelperNetworkProtocol.h 3 | // DraftControl 4 | // 5 | // Created by Stephen Lind on 9/30/13. 6 | // Copyright (c) 2013 Stephen Lind. All rights reserved. 7 | // 8 | 9 | #import "AnalyticsHelper.h" 10 | 11 | /* 12 | This protocol enables testing of the Analytics Helper. You can create a production 13 | NetworkHandler to actually send / receive data, or use a mock handler for testing purposes. 14 | */ 15 | 16 | @class AnalyticsEvent; 17 | @protocol AnalyticsNetworkHandler 18 | - (BOOL)sendPayload:(NSData*)payload; 19 | - (NSData*)createEventPayload:(AnalyticsEvent*)analyticsEvent machineIdentifier:(NSString*)machineIdentifier; 20 | - (NSData*)createScreenViewPayload:(NSString*)viewName machineIdentifier:(NSString*)machineIdentifier appVersion:(NSString*)appVersion; 21 | @end 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SimpleCocoaGoogleAnalytics 2 | ========================== 3 | 4 | A simple set of classes of using Google Analytics to track your OS X app's usage. 5 | 6 | ## Usage: 7 | 8 | Add these to your app's project: 9 | * AnalyticsHelper.h 10 | * AnalyticsHelper.m 11 | * AnalyticsEvent.h 12 | * AnalyticsEvent.m 13 | 14 | Add reporting code to app launch and quit, and on relevant events. 15 | 16 | See AppDelegate.m in the example app for how to begin reporting and send events. 17 | 18 | If you want to use this project in an application for the Mac App store 19 | remember that there is a policy guideline against data collection: *Apps cannot 20 | transmit data about a user without obtaining the user's prior permission and 21 | providing the user with access to information about how and where the data will 22 | be used*. Your app has to ask the user for permission to collect data or it may 23 | be rejected during the approval process. 24 | 25 | 26 | ## License 27 | 28 | This project uses the [MIT License](LICENSE.md). 29 | 30 | ## Contribution 31 | 32 | There are many features of google analytics that I haven't implemented. Pull requests are welcome. 33 | 34 | -------------------------------------------------------------------------------- /SimpleCocoaAnalytics/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | SimpleCocoaGoogleAnalytics License 2 | ================================== 3 | 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2013 Stephen Lind 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | this software and associated documentation files (the "Software"), to deal in 10 | the Software without restriction, including without limitation the rights to 11 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 | of the Software, and to permit persons to whom the Software is furnished to do 13 | so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /SimpleCocoaAnalytics/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // SimpleCocoaAnalytics 4 | // 5 | // Created by Stephen Lind on 10/28/13. 6 | 7 | #import "AppDelegate.h" 8 | #import "AnalyticsHelper.h" 9 | 10 | @implementation AppDelegate 11 | 12 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 13 | { 14 | AnalyticsHelper *analyticsHelper = [AnalyticsHelper sharedInstance]; 15 | [analyticsHelper beginPeriodicReportingWithAccount:@"UA-XXXXXXXXX-1" 16 | name:@"My App" 17 | version:@"0.1"]; 18 | 19 | 20 | } 21 | 22 | - (void)applicationWillTerminate:(NSNotification *)notification { 23 | [AnalyticsHelper.sharedInstance handleApplicationWillClose]; 24 | [NSUserDefaults.standardUserDefaults synchronize]; 25 | } 26 | 27 | - (IBAction)eventButtonClicked:(id)sender { 28 | [AnalyticsHelper.sharedInstance recordCachedEventWithCategory:@"My Category" 29 | action:@"My Action" 30 | label:@"My Label" 31 | value:@1]; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /SimpleCocoaAnalytics/SimpleCocoaAnalytics-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.foxnsox.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | ${MACOSX_DEPLOYMENT_TARGET} 27 | NSHumanReadableCopyright 28 | Copyright © 2013 foxnsox. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /SimpleCocoaAnalytics/AnalyticsEvent.m: -------------------------------------------------------------------------------- 1 | // 2 | // AnalyticsEvent.m 3 | // DraftControl 4 | // 5 | // Created by Stephen Lind on 9/30/13. 6 | // Copyright (c) 2013 Stephen Lind. All rights reserved. 7 | // 8 | 9 | #import "AnalyticsEvent.h" 10 | 11 | static NSString *const kEventActionKey = @"eventAction"; 12 | static NSString *const kEventCategory = @"eventCategory"; 13 | static NSString *const kEventLabelKey = @"eventLabel"; 14 | static NSString *const kEventValue = @"eventValue"; 15 | 16 | @implementation AnalyticsEvent 17 | 18 | + (AnalyticsEvent*)analyticsEventWithDictionary:(NSDictionary*)dict { 19 | AnalyticsEvent *analyticsEvent = [[AnalyticsEvent alloc] init]; 20 | 21 | analyticsEvent.action = [dict objectForKey:kEventActionKey]; 22 | analyticsEvent.category = [dict objectForKey:kEventCategory]; 23 | analyticsEvent.value = [dict objectForKey:kEventValue]; 24 | analyticsEvent.label = [dict objectForKey:kEventLabelKey]; 25 | 26 | return analyticsEvent; 27 | } 28 | 29 | - (NSDictionary*)dictionaryRepresenation { 30 | // We expect all of the values to be filled in here. If not, BOOM. 31 | return @{ 32 | kEventCategory:self.category, 33 | kEventActionKey:self.action, 34 | kEventLabelKey:self.label, 35 | kEventValue:self.value 36 | }; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /SimpleCocoaAnalytics/AnalyticsHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // AnalyticsHelper.h 3 | // 4 | // Created by Stephen Lind on 9/26/13. 5 | // 6 | 7 | #import 8 | 9 | /* 10 | SWL Sept 2013 11 | 12 | AnalyticsHelper is a singleton interface for recording and sending events to your google analytics account. 13 | 14 | This class is *not* thread safe. All calls to it should be performed from the main thread. Reports are sent on a background thread. If the send fails, an error event will be sent the next time analytics are reported. 15 | */ 16 | 17 | @interface AnalyticsHelper : NSObject 18 | 19 | /*! 20 | @brief create/access the AnalyticsHelper singleton object. 21 | 22 | @description This object should *only* be accessed through this method. 23 | */ 24 | + (AnalyticsHelper*)sharedInstance; 25 | 26 | 27 | /*! 28 | @brief Begin the session with your Google account + app data 29 | 30 | @desc This begins the reporting interval, which will allow the app to appear used 31 | in the Google Analytics real-time views. This also begins the periodic reporting 32 | interval which will send cached events to the server. 33 | */ 34 | - (void)beginPeriodicReportingWithAccount:(NSString*)googleAccountIdentifier 35 | name:(NSString*)appName 36 | version:(NSString*)appVersion; 37 | 38 | 39 | /*! 40 | @brief Shut-down method to keep track of application uptime 41 | 42 | @desc Calculates how long the app has been open and stores the in-memory data for later reports. 43 | This should be called when applicationWillTerminate, and should be accompanied by a 44 | [NSUserDefaults.standardUserDefaults synchronize]; 45 | */ 46 | - (void)handleApplicationWillClose; 47 | 48 | 49 | /*! 50 | @brief OS X version of the user's current "screen" 51 | 52 | @desc On OS X, the user can actually have multiple screens up. So it is 53 | the caller's job to use this call wisely in order to get sensible 54 | data on the Google Analytics reporting screens. 55 | */ 56 | - (void)recordScreenWithName:(NSString*)screenName; 57 | 58 | 59 | 60 | /*! 61 | @brief Record a Google Analytics "event" with the supplied data 62 | 63 | @desc These events are cached locally, not sent immediately. Once 64 | the reporting interval elapses, they will be sent all at once. 65 | */ 66 | - (void)recordCachedEventWithCategory:(NSString*)eventCategory 67 | action:(NSString*)eventAction 68 | label:(NSString*)eventLabel 69 | value:(NSNumber*)eventValue; 70 | 71 | 72 | @end -------------------------------------------------------------------------------- /SimpleCocoaAnalytics.xcodeproj/xcuserdata/steve.xcuserdatad/xcschemes/SimpleCocoaAnalytics.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 | -------------------------------------------------------------------------------- /SimpleCocoaAnalyticsTests/SimpleCocoaAnalyticsTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleCocoaAnalyticsTests.m 3 | // SimpleCocoaAnalyticsTests 4 | // 5 | // Created by Stephen Lind on 10/28/13. 6 | // Copyright (c) 2013 foxnsox. All rights reserved. 7 | 8 | #import 9 | 10 | #import "AnalyticsEvent.h" 11 | #import "AnalyticsHelper.h" 12 | #import "AnalyticsHelper+Testing.h" 13 | #import "AnalyticsHelperNetworkProtocol.h" 14 | 15 | 16 | /*! 17 | TestingNetworkHandler 18 | Rather than sending test payloads to an actual server, just pretend we did and 19 | record success or failure, depending on what we want to simulate. 20 | */ 21 | @interface TestingNetworkHandler : NSObject 22 | - (BOOL)sendPayload:(NSData *)payload; 23 | - (NSData*)createEventPayload:(AnalyticsEvent*)analyticsEvent machineIdentifier:(NSString*)machineIdentifier; 24 | - (NSData*)createScreenViewPayload:(NSString*)viewName machineIdentifier:(NSString*)machineIdentifier appVersion:(NSString *)appVersion; 25 | 26 | @property NSUInteger sentPayloadCount; 27 | @property BOOL returnSuccess; 28 | 29 | @end 30 | 31 | 32 | @interface SimpleCocoaAnalyticsTests : XCTestCase 33 | @property (strong) TestingNetworkHandler *testingNetworkHandler; 34 | @end 35 | 36 | 37 | @implementation SimpleCocoaAnalyticsTests 38 | 39 | - (void)setUp 40 | { 41 | [super setUp]; 42 | AnalyticsHelper *helper = AnalyticsHelper.sharedInstance; 43 | [helper saveCachedEventsToDisk]; 44 | [helper clearErrors]; 45 | [helper clearRecordedEvents]; 46 | 47 | _testingNetworkHandler = [[TestingNetworkHandler alloc] init]; 48 | [helper testSetNetworkHandler:_testingNetworkHandler]; 49 | self.testingNetworkHandler.sentPayloadCount = 0; 50 | self.testingNetworkHandler.returnSuccess = NO; 51 | } 52 | 53 | - (void)tearDown 54 | { 55 | // Put teardown code here. 56 | [super tearDown]; 57 | } 58 | 59 | /*! 60 | @brief record some events, make sure they appear in the analytics helper's queue 61 | */ 62 | - (void)testRecordEvents 63 | { 64 | AnalyticsHelper *helper = AnalyticsHelper.sharedInstance; 65 | 66 | NSArray *recordedEvents = [helper recordedEvents]; 67 | XCTAssertTrue([recordedEvents count] == 0, @"There should not be any events"); 68 | 69 | [helper recordCachedEventWithCategory:@"Event" action:@"Action" label:@"Label" value:@3]; 70 | [helper recordCachedEventWithCategory:@"Event" action:@"Action" label:@"Label" value:@3]; 71 | [helper recordCachedEventWithCategory:@"Event" action:@"Action" label:@"Label" value:@3]; 72 | [helper handleApplicationWillClose]; 73 | 74 | recordedEvents = [helper recordedEvents]; 75 | NSMutableArray *eventsMinusLaunchEvent = [recordedEvents mutableCopy]; 76 | [eventsMinusLaunchEvent removeLastObject]; 77 | 78 | XCTAssertTrue([eventsMinusLaunchEvent count] == 3, @"there should be 3 events, found %lu", [recordedEvents count]); 79 | for (AnalyticsEvent *event in eventsMinusLaunchEvent) { 80 | XCTAssertTrue([event.category isEqualToString:@"Event"], @"recorded event has the wrong category"); 81 | XCTAssertTrue([event.action isEqualToString:@"Action"], @"recorded event has the wrong action"); 82 | XCTAssertTrue([event.label isEqualToString:@"Label"], @"recorded event has the wrong label"); 83 | } 84 | 85 | [helper clearRecordedEvents]; 86 | recordedEvents = [helper recordedEvents]; 87 | XCTAssertTrue([recordedEvents count] == 0, @"There should not be any events"); 88 | } 89 | 90 | /*! 91 | @brief Attempt to 'send' some payloads to our testing handler, which should return failure. Errors should then be recorded by the AnalyticsHelper 92 | */ 93 | - (void)testErrorIncrements { 94 | AnalyticsHelper *helper = AnalyticsHelper.sharedInstance; 95 | 96 | NSUInteger errorCount = [helper getErrorCount]; 97 | XCTAssertTrue(errorCount == 0, @"There should be no errors, found %lu", errorCount); 98 | 99 | [helper recordSendError]; 100 | errorCount = [helper getErrorCount]; 101 | XCTAssertTrue(errorCount == 1, @"There should be 1 error, found %lu", errorCount); 102 | 103 | [helper clearErrors]; 104 | errorCount = [helper getErrorCount]; 105 | XCTAssertTrue(errorCount == 0, @"There should be no errors, found %lu", errorCount); 106 | 107 | const NSUInteger expErrors = 100; 108 | for (int i=0; i 32 | - (BOOL)sendPayload:(NSData*)payload; 33 | - (NSData*)createEventPayload:(AnalyticsEvent*)analyticsEvent 34 | machineIdentifier:(NSString*)machineIdentifier 35 | accountIdentifier:(NSString*)accountIdentifier 36 | appName:(NSString*)appName 37 | appVersion:(NSString*)appVersion; 38 | 39 | - (NSData*)createScreenViewPayload:(NSString*)viewName 40 | machineIdentifier:(NSString*)machineIdentifier 41 | accountIdentifier:(NSString*)accountIdentifier 42 | appName:(NSString*)appName 43 | appVersion:(NSString*)appVersion; 44 | @end 45 | 46 | @interface ProductionNetworkHandler : NSObject 47 | - (BOOL)sendPayload:(NSData *)payload; 48 | - (NSData*)createScreenViewPayload:(NSString*)viewName 49 | machineIdentifier:(NSString*)machineIdentifier 50 | accountIdentifier:(NSString*)accountIdentifier 51 | appName:(NSString*)appName 52 | appVersion:(NSString*)appVersion; 53 | 54 | - (NSData*)createEventPayload:(AnalyticsEvent*)analyticsEvent 55 | machineIdentifier:(NSString*)machineIdentifier 56 | accountIdentifier:(NSString*)accountIdentifier 57 | appName:(NSString*)appName 58 | appVersion:(NSString*)appVersion; 59 | @end 60 | 61 | @interface AnalyticsHelper() 62 | @property (strong) id networkHandler; 63 | @property (strong) NSMutableArray *cachedScreens; 64 | @property (strong) NSMutableArray *cachedEvents; 65 | @property NSUInteger sendInterval; 66 | @property (strong) NSString *googleAccountIdentifier; 67 | @property (strong) NSString *appVersion; 68 | @property (strong) NSString *appName; 69 | 70 | @property (strong) NSDate* launchDate; 71 | @property (strong) NSTimer* timer; 72 | @property BOOL allowReporting; 73 | @property BOOL reportInProgress; 74 | @end 75 | 76 | @implementation AnalyticsHelper 77 | 78 | + (id)allocWithZone:(NSZone *)zone { 79 | return [AnalyticsHelper sharedInstance]; 80 | } 81 | 82 | + (AnalyticsHelper*)sharedInstance { 83 | static AnalyticsHelper *analyticsHelper; 84 | static dispatch_once_t onceToken; 85 | dispatch_once(&onceToken, ^{ 86 | analyticsHelper = [[super allocWithZone:nil] init]; 87 | }); 88 | 89 | return analyticsHelper; 90 | } 91 | 92 | - (id)init { 93 | self = [super init]; 94 | if (self) { 95 | _launchDate = NSDate.date; 96 | _networkHandler = [[ProductionNetworkHandler alloc] init]; 97 | _cachedScreens = [NSMutableArray new]; 98 | _cachedEvents = [NSMutableArray new]; 99 | _sendInterval = kDefaultSendInterval; 100 | _appVersion = @"0.0"; 101 | _appName = @"My App"; 102 | } 103 | return self; 104 | } 105 | 106 | - (void)setReportInterval:(NSUInteger)intervalInSeconds { 107 | self.sendInterval = intervalInSeconds; 108 | [self resetTimer]; 109 | } 110 | 111 | - (NSUInteger)getErrorCount { 112 | return [[NSUserDefaults.standardUserDefaults objectForKey:kSendErrorCountKey] unsignedIntegerValue]; 113 | } 114 | 115 | - (void)recordSendError { 116 | [self recordSendErrorWithCount:1]; 117 | } 118 | 119 | - (void)recordSendErrorWithCount:(NSUInteger)count { 120 | NSUInteger prevErrorCount = [self getErrorCount]; 121 | NSNumber *errorCount = [NSNumber numberWithUnsignedInteger:prevErrorCount + count]; 122 | [NSUserDefaults.standardUserDefaults setObject:errorCount forKey:kSendErrorCountKey]; 123 | } 124 | 125 | - (void)clearErrors { 126 | [NSUserDefaults.standardUserDefaults removeObjectForKey:kSendErrorCountKey]; 127 | } 128 | 129 | - (void)handleApplicationWillClose { 130 | // We are about to close, stop any future reports 131 | self.allowReporting = NO; 132 | 133 | // Calculate how long the app has been open (really this class) and store an 134 | // event with the duration as the value. 135 | NSNumber *durationInSeconds = [NSNumber numberWithUnsignedInteger:(NSUInteger)[NSDate.date timeIntervalSinceDate:self.launchDate]]; 136 | NSString *dateString = [NSDateFormatter localizedStringFromDate:self.launchDate dateStyle:NSDateFormatterShortStyle timeStyle:NSDateFormatterShortStyle]; 137 | [self recordCachedEventWithCategory:@"Application" action:@"Close" label:dateString value:durationInSeconds]; 138 | 139 | // This must be called last 140 | [self saveCachedScreensToDisk]; 141 | [self saveCachedEventsToDisk]; 142 | } 143 | 144 | - (void)recordScreenWithName:(NSString*)screenName { 145 | [self.cachedScreens addObject:screenName]; 146 | } 147 | 148 | - (void)recordCachedEventWithCategory:(NSString*)eventCategory 149 | action:(NSString*)eventAction 150 | label:(NSString*)eventLabel 151 | value:(NSNumber*)eventValue 152 | { 153 | AnalyticsEvent *analyticsEvent = [[AnalyticsEvent alloc] init]; 154 | analyticsEvent.category = eventCategory; 155 | analyticsEvent.action = eventAction; 156 | analyticsEvent.label = eventLabel; 157 | analyticsEvent.value = eventValue; 158 | 159 | [self.cachedEvents addObject:analyticsEvent]; 160 | } 161 | 162 | - (void)saveCachedScreensToDisk { 163 | // Get the previously recorded screen names 164 | NSMutableArray *recordedScreens = [NSMutableArray new]; 165 | NSArray *previouslyRecordedScreens = [self recordedEventDictionaries]; 166 | if (previouslyRecordedScreens) { 167 | [recordedScreens addObjectsFromArray:previouslyRecordedScreens]; 168 | } 169 | 170 | // Convert the events to dictionaries for storage, add to the existing events 171 | for (NSString *screenName in self.cachedScreens) { 172 | [recordedScreens addObject:screenName]; 173 | } 174 | 175 | [NSUserDefaults.standardUserDefaults setObject:recordedScreens forKey:kRecordedScreensKey]; 176 | [self.cachedScreens removeAllObjects]; 177 | } 178 | 179 | - (void)saveCachedEventsToDisk { 180 | // Get the previously recorded events 181 | NSMutableArray *recordedEvents = [NSMutableArray new]; 182 | NSArray *previouslyRecordedEvents = [self recordedEventDictionaries]; 183 | if (previouslyRecordedEvents) { 184 | [recordedEvents addObjectsFromArray:previouslyRecordedEvents]; 185 | } 186 | 187 | // Convert the events to dictionaries for storage, add to the existing events 188 | for (AnalyticsEvent *event in self.cachedEvents) { 189 | NSDictionary *eventDict = [event dictionaryRepresenation]; 190 | [recordedEvents addObject:eventDict]; 191 | } 192 | 193 | [NSUserDefaults.standardUserDefaults setObject:recordedEvents forKey:kRecordedEventsKey]; 194 | [self.cachedEvents removeAllObjects]; 195 | } 196 | 197 | - (void)clearRecordedScreens { 198 | [NSUserDefaults.standardUserDefaults removeObjectForKey:kRecordedScreensKey]; 199 | } 200 | 201 | - (void)clearRecordedEvents { 202 | [NSUserDefaults.standardUserDefaults removeObjectForKey:kRecordedEventsKey]; 203 | } 204 | 205 | - (NSArray*)recordedEventDictionaries { 206 | return [NSUserDefaults.standardUserDefaults objectForKey:kRecordedEventsKey]; 207 | } 208 | 209 | - (NSArray*)recordedScreens { 210 | return [NSUserDefaults.standardUserDefaults objectForKey:kRecordedScreensKey]; 211 | } 212 | 213 | - (NSArray*)recordedEvents { 214 | // Get the list of recorded events from disk 215 | NSArray *eventDicts = [self recordedEventDictionaries]; 216 | NSMutableArray *events = [[NSMutableArray alloc] init]; 217 | for (NSDictionary *eventDict in eventDicts) { 218 | AnalyticsEvent *e = [AnalyticsEvent analyticsEventWithDictionary:eventDict]; 219 | [events addObject:e]; 220 | } 221 | 222 | return [NSArray arrayWithArray:events]; 223 | } 224 | 225 | - (NSString*)getUniqueMachineIdentifier { 226 | NSString *uniqueId = [NSUserDefaults.standardUserDefaults stringForKey:kMachineIdentifierKey]; 227 | if (uniqueId == nil) { 228 | uniqueId = [NSUUID.UUID UUIDString]; 229 | [NSUserDefaults.standardUserDefaults setObject:uniqueId forKey:kMachineIdentifierKey]; 230 | } 231 | 232 | return uniqueId; 233 | } 234 | 235 | - (void)sendReport { 236 | if (self.reportInProgress == NO) { 237 | self.reportInProgress = YES; 238 | 239 | // Retrieve any stored screens 240 | NSArray *screens = [self recordedScreens]; 241 | 242 | // Retrieve any stored events 243 | NSArray *events = [self recordedEvents]; 244 | 245 | // Retrieve analytics send error count 246 | NSUInteger recordedErrorCount = [self getErrorCount]; 247 | 248 | NSString *machineIdentifier = [self getUniqueMachineIdentifier]; 249 | 250 | // Clear non-error stored screens 251 | [self clearRecordedScreens]; 252 | 253 | // Clear non-error stored events 254 | [self clearRecordedEvents]; 255 | 256 | dispatch_async(dispatch_get_global_queue(0,0), ^{ 257 | // A count for errors occurred in this report 258 | NSUInteger errorCount = 0; 259 | 260 | BOOL errorsSent = NO; 261 | 262 | // Send recorded screen events 263 | for (NSString *screenName in screens) { 264 | NSData *screenPayload = [self.networkHandler createScreenViewPayload:screenName 265 | machineIdentifier:machineIdentifier 266 | accountIdentifier:self.googleAccountIdentifier 267 | appName:self.appName 268 | appVersion:self.appVersion]; 269 | BOOL success = [self.networkHandler sendPayload:screenPayload]; 270 | if (!success) { 271 | errorCount += 1; 272 | } 273 | } 274 | 275 | // Send the other recorded events 276 | for (AnalyticsEvent *event in events) { 277 | NSData *eventPayload = [self.networkHandler createEventPayload:event 278 | machineIdentifier:machineIdentifier 279 | accountIdentifier:self.googleAccountIdentifier 280 | appName:self.appName appVersion:self.appVersion]; 281 | BOOL success = [self.networkHandler sendPayload:eventPayload]; 282 | if (!success) { 283 | errorCount += 1; 284 | } 285 | } 286 | 287 | if (recordedErrorCount > 0) { 288 | AnalyticsEvent *errorEvent = [[AnalyticsEvent alloc] init]; 289 | errorEvent.category = @"Error"; 290 | errorEvent.action = @"Analytics Send Failure"; 291 | errorEvent.value = [NSNumber numberWithUnsignedInteger:recordedErrorCount]; 292 | 293 | NSData *errorPayload = [self.networkHandler createEventPayload:errorEvent 294 | machineIdentifier:machineIdentifier 295 | accountIdentifier:self.googleAccountIdentifier 296 | appName:self.appName 297 | appVersion:self.appVersion]; 298 | 299 | errorsSent = [self.networkHandler sendPayload:errorPayload]; 300 | } 301 | dispatch_async(dispatch_get_main_queue(), ^{ 302 | if (errorCount > 0) { 303 | [self recordSendErrorWithCount:errorCount]; 304 | } else if (errorsSent) { 305 | [self clearErrors]; 306 | } 307 | self.reportInProgress = NO; 308 | }); 309 | }); 310 | } 311 | } 312 | 313 | - (void)beginPeriodicReportingWithAccount:(NSString *)googleAccountIdentifier 314 | name:(NSString *)appName 315 | version:(NSString *)appVersion { 316 | 317 | self.googleAccountIdentifier = googleAccountIdentifier; 318 | self.appName = appName; 319 | self.appVersion = appVersion; 320 | self.allowReporting = YES; 321 | [self resetTimer]; 322 | [self createAndSendReport:nil]; 323 | } 324 | 325 | - (void)createAndSendReport:(id)sender { 326 | if (self.allowReporting) { 327 | [self saveCachedScreensToDisk]; 328 | [self saveCachedEventsToDisk]; 329 | [self sendReport]; 330 | } 331 | } 332 | 333 | - (void)resetTimer { 334 | [self.timer invalidate]; 335 | self.timer = [NSTimer scheduledTimerWithTimeInterval:self.sendInterval target:self selector:@selector(createAndSendReport:) userInfo:nil repeats:YES]; 336 | } 337 | 338 | #pragma mark testing only 339 | 340 | - (void)testSetNetworkHandler:(id)handler { 341 | self.networkHandler = handler; 342 | } 343 | 344 | @end 345 | 346 | @implementation ProductionNetworkHandler 347 | 348 | - (NSString*)platformHeaderString { 349 | // Generate a platform string http header. Found here: 350 | // 351 | // https://github.com/Coppertino/AnalyticEverything/blob/master/AnalyticEverything/GATracking.m 352 | // 353 | NSDictionary *osInfo = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"]; 354 | 355 | NSLocale *currentLocale = [NSLocale autoupdatingCurrentLocale]; 356 | NSString *headerString = [NSString stringWithFormat:@"GoogleAnalytics/2.0 (Macintosh; Intel %@ %@; %@-%@)", 357 | osInfo[@"ProductName"], [osInfo[@"ProductVersion"] stringByReplacingOccurrencesOfString:@"." withString:@"_"], 358 | [currentLocale objectForKey:NSLocaleLanguageCode], [currentLocale objectForKey:NSLocaleCountryCode]]; 359 | 360 | return headerString; 361 | } 362 | 363 | 364 | - (BOOL)sendPayload:(NSData *)payload { 365 | BOOL ok = NO; 366 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] 367 | initWithURL:[NSURL URLWithString:kAnalyticsUrl]]; 368 | 369 | [request setHTTPMethod:@"POST"]; 370 | [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"content-type"]; 371 | [request setValue:[self platformHeaderString] forHTTPHeaderField:@"User-Agent"]; 372 | [request setHTTPBody:payload]; 373 | 374 | NSError *error = nil; 375 | [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error]; 376 | 377 | if (error) { 378 | //NSLog(@"could not send payload error: %@", error); 379 | } else { 380 | ok = YES; 381 | } 382 | return ok; 383 | } 384 | 385 | - (NSData*)createEventPayload:(AnalyticsEvent*)analyticsEvent 386 | machineIdentifier:(NSString*)machineIdentifier 387 | accountIdentifier:(NSString*)accountIdentifier 388 | appName:(NSString*)appName 389 | appVersion:(NSString*)appVersion { 390 | 391 | /* 392 | SWL url constructed from: 393 | https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide 394 | 395 | v=1 // Version. 396 | &tid=UA-XXXX-Y // Tracking ID / Web property / Property ID. 397 | &cid=555 // Anonymous Client ID. 398 | 399 | &an=funTimes // App name. 400 | 401 | &t=event // Event hit type 402 | &ec=video // Event Category. Required. 403 | &ea=play // Event Action. Required. 404 | */ 405 | 406 | NSString *payloadString = [NSString stringWithFormat:@"v=1&tid=%@&cid=%@&an=%@&t=event&ec=%@&ea=%@&el=%@&ev=%@", 407 | accountIdentifier, // account id 408 | machineIdentifier, // client id 409 | appName, // app name 410 | analyticsEvent.category, 411 | analyticsEvent.action, 412 | analyticsEvent.label, 413 | analyticsEvent.value 414 | ]; 415 | 416 | payloadString = [payloadString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 417 | 418 | return [payloadString dataUsingEncoding:NSUTF8StringEncoding]; 419 | } 420 | 421 | 422 | - (NSData*)createScreenViewPayload:(NSString*)viewName 423 | machineIdentifier:(NSString*)machineIdentifier 424 | accountIdentifier:(NSString*)accountIdentifier 425 | appName:(NSString*)appName 426 | appVersion:(NSString*)appVersion { 427 | /* 428 | SWL url constructed from: 429 | https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide 430 | 431 | v=1 // Version. 432 | &tid=UA-XXXX-Y // Tracking ID / Web property / Property ID. 433 | &cid=555 // Anonymous Client ID. 434 | 435 | &t=appview // Appview hit type. 436 | &an=funTimes // App name. 437 | &av=4.2.0 // App version. 438 | 439 | &cd=Home // Screen name / content description. 440 | */ 441 | 442 | NSString *payloadString = [NSString stringWithFormat:@"v=1&tid=%@&cid=%@&t=appview&an=%@&av=%@&cd=%@", 443 | accountIdentifier, // account id 444 | machineIdentifier, // client id 445 | appName, // app name 446 | appVersion, // app version 447 | viewName // Screen name 448 | ]; 449 | 450 | payloadString = [payloadString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 451 | 452 | return [payloadString dataUsingEncoding:NSUTF8StringEncoding]; 453 | } 454 | 455 | 456 | @end 457 | -------------------------------------------------------------------------------- /SimpleCocoaAnalytics.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AA8008B3181F3EC500225F5A /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA8008B2181F3EC500225F5A /* Cocoa.framework */; }; 11 | AA8008BD181F3EC500225F5A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AA8008BB181F3EC500225F5A /* InfoPlist.strings */; }; 12 | AA8008BF181F3EC500225F5A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AA8008BE181F3EC500225F5A /* main.m */; }; 13 | AA8008C3181F3EC500225F5A /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = AA8008C1181F3EC500225F5A /* Credits.rtf */; }; 14 | AA8008C6181F3EC500225F5A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AA8008C5181F3EC500225F5A /* AppDelegate.m */; }; 15 | AA8008C9181F3EC500225F5A /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA8008C7181F3EC500225F5A /* MainMenu.xib */; }; 16 | AA8008CB181F3EC500225F5A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA8008CA181F3EC500225F5A /* Images.xcassets */; }; 17 | AA8008D2181F3EC500225F5A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA8008D1181F3EC500225F5A /* XCTest.framework */; }; 18 | AA8008D3181F3EC500225F5A /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA8008B2181F3EC500225F5A /* Cocoa.framework */; }; 19 | AA8008DB181F3EC500225F5A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AA8008D9181F3EC500225F5A /* InfoPlist.strings */; }; 20 | AA8008DD181F3EC500225F5A /* SimpleCocoaAnalyticsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AA8008DC181F3EC500225F5A /* SimpleCocoaAnalyticsTests.m */; }; 21 | AA8008E8181F3EEB00225F5A /* AnalyticsHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = AA8008E7181F3EEB00225F5A /* AnalyticsHelper.m */; }; 22 | AA8008E9181F3EEB00225F5A /* AnalyticsHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = AA8008E7181F3EEB00225F5A /* AnalyticsHelper.m */; }; 23 | AA8008EC181F3F2800225F5A /* AnalyticsEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = AA8008EB181F3F2800225F5A /* AnalyticsEvent.m */; }; 24 | AA8008ED181F3F2800225F5A /* AnalyticsEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = AA8008EB181F3F2800225F5A /* AnalyticsEvent.m */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXContainerItemProxy section */ 28 | AA8008D4181F3EC500225F5A /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = AA8008A7181F3EC500225F5A /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = AA8008AE181F3EC500225F5A; 33 | remoteInfo = SimpleCocoaAnalytics; 34 | }; 35 | /* End PBXContainerItemProxy section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | AA8008AF181F3EC500225F5A /* SimpleCocoaAnalytics.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimpleCocoaAnalytics.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | AA8008B2181F3EC500225F5A /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 40 | AA8008B5181F3EC500225F5A /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 41 | AA8008B6181F3EC500225F5A /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 42 | AA8008B7181F3EC500225F5A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 43 | AA8008BA181F3EC500225F5A /* SimpleCocoaAnalytics-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SimpleCocoaAnalytics-Info.plist"; sourceTree = ""; }; 44 | AA8008BC181F3EC500225F5A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 45 | AA8008BE181F3EC500225F5A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 46 | AA8008C0181F3EC500225F5A /* SimpleCocoaAnalytics-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SimpleCocoaAnalytics-Prefix.pch"; sourceTree = ""; }; 47 | AA8008C2181F3EC500225F5A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; 48 | AA8008C4181F3EC500225F5A /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 49 | AA8008C5181F3EC500225F5A /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 50 | AA8008C8181F3EC500225F5A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 51 | AA8008CA181F3EC500225F5A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 52 | AA8008D0181F3EC500225F5A /* SimpleCocoaAnalyticsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SimpleCocoaAnalyticsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | AA8008D1181F3EC500225F5A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 54 | AA8008D8181F3EC500225F5A /* SimpleCocoaAnalyticsTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SimpleCocoaAnalyticsTests-Info.plist"; sourceTree = ""; }; 55 | AA8008DA181F3EC500225F5A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 56 | AA8008DC181F3EC500225F5A /* SimpleCocoaAnalyticsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SimpleCocoaAnalyticsTests.m; sourceTree = ""; }; 57 | AA8008E6181F3EEB00225F5A /* AnalyticsHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AnalyticsHelper.h; sourceTree = ""; }; 58 | AA8008E7181F3EEB00225F5A /* AnalyticsHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AnalyticsHelper.m; sourceTree = ""; }; 59 | AA8008EA181F3F2800225F5A /* AnalyticsEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AnalyticsEvent.h; sourceTree = ""; }; 60 | AA8008EB181F3F2800225F5A /* AnalyticsEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AnalyticsEvent.m; sourceTree = ""; }; 61 | AA87D0C01C62FA70004DE4F5 /* AnalyticsHelper+Testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AnalyticsHelper+Testing.h"; sourceTree = ""; }; 62 | AA87D0C11C62FAE0004DE4F5 /* AnalyticsHelperNetworkProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AnalyticsHelperNetworkProtocol.h; sourceTree = ""; }; 63 | /* End PBXFileReference section */ 64 | 65 | /* Begin PBXFrameworksBuildPhase section */ 66 | AA8008AC181F3EC500225F5A /* Frameworks */ = { 67 | isa = PBXFrameworksBuildPhase; 68 | buildActionMask = 2147483647; 69 | files = ( 70 | AA8008B3181F3EC500225F5A /* Cocoa.framework in Frameworks */, 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | AA8008CD181F3EC500225F5A /* Frameworks */ = { 75 | isa = PBXFrameworksBuildPhase; 76 | buildActionMask = 2147483647; 77 | files = ( 78 | AA8008D3181F3EC500225F5A /* Cocoa.framework in Frameworks */, 79 | AA8008D2181F3EC500225F5A /* XCTest.framework in Frameworks */, 80 | ); 81 | runOnlyForDeploymentPostprocessing = 0; 82 | }; 83 | /* End PBXFrameworksBuildPhase section */ 84 | 85 | /* Begin PBXGroup section */ 86 | AA8008A6181F3EC500225F5A = { 87 | isa = PBXGroup; 88 | children = ( 89 | AA8008B8181F3EC500225F5A /* SimpleCocoaAnalytics */, 90 | AA8008D6181F3EC500225F5A /* SimpleCocoaAnalyticsTests */, 91 | AA8008B1181F3EC500225F5A /* Frameworks */, 92 | AA8008B0181F3EC500225F5A /* Products */, 93 | ); 94 | sourceTree = ""; 95 | }; 96 | AA8008B0181F3EC500225F5A /* Products */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | AA8008AF181F3EC500225F5A /* SimpleCocoaAnalytics.app */, 100 | AA8008D0181F3EC500225F5A /* SimpleCocoaAnalyticsTests.xctest */, 101 | ); 102 | name = Products; 103 | sourceTree = ""; 104 | }; 105 | AA8008B1181F3EC500225F5A /* Frameworks */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | AA8008B2181F3EC500225F5A /* Cocoa.framework */, 109 | AA8008D1181F3EC500225F5A /* XCTest.framework */, 110 | AA8008B4181F3EC500225F5A /* Other Frameworks */, 111 | ); 112 | name = Frameworks; 113 | sourceTree = ""; 114 | }; 115 | AA8008B4181F3EC500225F5A /* Other Frameworks */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | AA8008B5181F3EC500225F5A /* AppKit.framework */, 119 | AA8008B6181F3EC500225F5A /* CoreData.framework */, 120 | AA8008B7181F3EC500225F5A /* Foundation.framework */, 121 | ); 122 | name = "Other Frameworks"; 123 | sourceTree = ""; 124 | }; 125 | AA8008B8181F3EC500225F5A /* SimpleCocoaAnalytics */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | AA8008C4181F3EC500225F5A /* AppDelegate.h */, 129 | AA8008C5181F3EC500225F5A /* AppDelegate.m */, 130 | AA8008EE181F3F3000225F5A /* Analytics */, 131 | AA8008C7181F3EC500225F5A /* MainMenu.xib */, 132 | AA8008CA181F3EC500225F5A /* Images.xcassets */, 133 | AA8008B9181F3EC500225F5A /* Supporting Files */, 134 | ); 135 | path = SimpleCocoaAnalytics; 136 | sourceTree = ""; 137 | }; 138 | AA8008B9181F3EC500225F5A /* Supporting Files */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | AA8008BA181F3EC500225F5A /* SimpleCocoaAnalytics-Info.plist */, 142 | AA8008BB181F3EC500225F5A /* InfoPlist.strings */, 143 | AA8008BE181F3EC500225F5A /* main.m */, 144 | AA8008C0181F3EC500225F5A /* SimpleCocoaAnalytics-Prefix.pch */, 145 | AA8008C1181F3EC500225F5A /* Credits.rtf */, 146 | ); 147 | name = "Supporting Files"; 148 | sourceTree = ""; 149 | }; 150 | AA8008D6181F3EC500225F5A /* SimpleCocoaAnalyticsTests */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | AA87D0C11C62FAE0004DE4F5 /* AnalyticsHelperNetworkProtocol.h */, 154 | AA87D0C01C62FA70004DE4F5 /* AnalyticsHelper+Testing.h */, 155 | AA8008DC181F3EC500225F5A /* SimpleCocoaAnalyticsTests.m */, 156 | AA8008D7181F3EC500225F5A /* Supporting Files */, 157 | ); 158 | path = SimpleCocoaAnalyticsTests; 159 | sourceTree = ""; 160 | }; 161 | AA8008D7181F3EC500225F5A /* Supporting Files */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | AA8008D8181F3EC500225F5A /* SimpleCocoaAnalyticsTests-Info.plist */, 165 | AA8008D9181F3EC500225F5A /* InfoPlist.strings */, 166 | ); 167 | name = "Supporting Files"; 168 | sourceTree = ""; 169 | }; 170 | AA8008EE181F3F3000225F5A /* Analytics */ = { 171 | isa = PBXGroup; 172 | children = ( 173 | AA8008EA181F3F2800225F5A /* AnalyticsEvent.h */, 174 | AA8008EB181F3F2800225F5A /* AnalyticsEvent.m */, 175 | AA8008E6181F3EEB00225F5A /* AnalyticsHelper.h */, 176 | AA8008E7181F3EEB00225F5A /* AnalyticsHelper.m */, 177 | ); 178 | name = Analytics; 179 | sourceTree = ""; 180 | }; 181 | /* End PBXGroup section */ 182 | 183 | /* Begin PBXNativeTarget section */ 184 | AA8008AE181F3EC500225F5A /* SimpleCocoaAnalytics */ = { 185 | isa = PBXNativeTarget; 186 | buildConfigurationList = AA8008E0181F3EC500225F5A /* Build configuration list for PBXNativeTarget "SimpleCocoaAnalytics" */; 187 | buildPhases = ( 188 | AA8008AB181F3EC500225F5A /* Sources */, 189 | AA8008AC181F3EC500225F5A /* Frameworks */, 190 | AA8008AD181F3EC500225F5A /* Resources */, 191 | ); 192 | buildRules = ( 193 | ); 194 | dependencies = ( 195 | ); 196 | name = SimpleCocoaAnalytics; 197 | productName = SimpleCocoaAnalytics; 198 | productReference = AA8008AF181F3EC500225F5A /* SimpleCocoaAnalytics.app */; 199 | productType = "com.apple.product-type.application"; 200 | }; 201 | AA8008CF181F3EC500225F5A /* SimpleCocoaAnalyticsTests */ = { 202 | isa = PBXNativeTarget; 203 | buildConfigurationList = AA8008E3181F3EC500225F5A /* Build configuration list for PBXNativeTarget "SimpleCocoaAnalyticsTests" */; 204 | buildPhases = ( 205 | AA8008CC181F3EC500225F5A /* Sources */, 206 | AA8008CD181F3EC500225F5A /* Frameworks */, 207 | AA8008CE181F3EC500225F5A /* Resources */, 208 | ); 209 | buildRules = ( 210 | ); 211 | dependencies = ( 212 | AA8008D5181F3EC500225F5A /* PBXTargetDependency */, 213 | ); 214 | name = SimpleCocoaAnalyticsTests; 215 | productName = SimpleCocoaAnalyticsTests; 216 | productReference = AA8008D0181F3EC500225F5A /* SimpleCocoaAnalyticsTests.xctest */; 217 | productType = "com.apple.product-type.bundle.unit-test"; 218 | }; 219 | /* End PBXNativeTarget section */ 220 | 221 | /* Begin PBXProject section */ 222 | AA8008A7181F3EC500225F5A /* Project object */ = { 223 | isa = PBXProject; 224 | attributes = { 225 | LastUpgradeCheck = 0500; 226 | ORGANIZATIONNAME = foxnsox; 227 | TargetAttributes = { 228 | AA8008CF181F3EC500225F5A = { 229 | TestTargetID = AA8008AE181F3EC500225F5A; 230 | }; 231 | }; 232 | }; 233 | buildConfigurationList = AA8008AA181F3EC500225F5A /* Build configuration list for PBXProject "SimpleCocoaAnalytics" */; 234 | compatibilityVersion = "Xcode 3.2"; 235 | developmentRegion = English; 236 | hasScannedForEncodings = 0; 237 | knownRegions = ( 238 | en, 239 | Base, 240 | ); 241 | mainGroup = AA8008A6181F3EC500225F5A; 242 | productRefGroup = AA8008B0181F3EC500225F5A /* Products */; 243 | projectDirPath = ""; 244 | projectRoot = ""; 245 | targets = ( 246 | AA8008AE181F3EC500225F5A /* SimpleCocoaAnalytics */, 247 | AA8008CF181F3EC500225F5A /* SimpleCocoaAnalyticsTests */, 248 | ); 249 | }; 250 | /* End PBXProject section */ 251 | 252 | /* Begin PBXResourcesBuildPhase section */ 253 | AA8008AD181F3EC500225F5A /* Resources */ = { 254 | isa = PBXResourcesBuildPhase; 255 | buildActionMask = 2147483647; 256 | files = ( 257 | AA8008BD181F3EC500225F5A /* InfoPlist.strings in Resources */, 258 | AA8008CB181F3EC500225F5A /* Images.xcassets in Resources */, 259 | AA8008C3181F3EC500225F5A /* Credits.rtf in Resources */, 260 | AA8008C9181F3EC500225F5A /* MainMenu.xib in Resources */, 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | }; 264 | AA8008CE181F3EC500225F5A /* Resources */ = { 265 | isa = PBXResourcesBuildPhase; 266 | buildActionMask = 2147483647; 267 | files = ( 268 | AA8008DB181F3EC500225F5A /* InfoPlist.strings in Resources */, 269 | ); 270 | runOnlyForDeploymentPostprocessing = 0; 271 | }; 272 | /* End PBXResourcesBuildPhase section */ 273 | 274 | /* Begin PBXSourcesBuildPhase section */ 275 | AA8008AB181F3EC500225F5A /* Sources */ = { 276 | isa = PBXSourcesBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | AA8008C6181F3EC500225F5A /* AppDelegate.m in Sources */, 280 | AA8008EC181F3F2800225F5A /* AnalyticsEvent.m in Sources */, 281 | AA8008BF181F3EC500225F5A /* main.m in Sources */, 282 | AA8008E8181F3EEB00225F5A /* AnalyticsHelper.m in Sources */, 283 | ); 284 | runOnlyForDeploymentPostprocessing = 0; 285 | }; 286 | AA8008CC181F3EC500225F5A /* Sources */ = { 287 | isa = PBXSourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | AA8008E9181F3EEB00225F5A /* AnalyticsHelper.m in Sources */, 291 | AA8008ED181F3F2800225F5A /* AnalyticsEvent.m in Sources */, 292 | AA8008DD181F3EC500225F5A /* SimpleCocoaAnalyticsTests.m in Sources */, 293 | ); 294 | runOnlyForDeploymentPostprocessing = 0; 295 | }; 296 | /* End PBXSourcesBuildPhase section */ 297 | 298 | /* Begin PBXTargetDependency section */ 299 | AA8008D5181F3EC500225F5A /* PBXTargetDependency */ = { 300 | isa = PBXTargetDependency; 301 | target = AA8008AE181F3EC500225F5A /* SimpleCocoaAnalytics */; 302 | targetProxy = AA8008D4181F3EC500225F5A /* PBXContainerItemProxy */; 303 | }; 304 | /* End PBXTargetDependency section */ 305 | 306 | /* Begin PBXVariantGroup section */ 307 | AA8008BB181F3EC500225F5A /* InfoPlist.strings */ = { 308 | isa = PBXVariantGroup; 309 | children = ( 310 | AA8008BC181F3EC500225F5A /* en */, 311 | ); 312 | name = InfoPlist.strings; 313 | sourceTree = ""; 314 | }; 315 | AA8008C1181F3EC500225F5A /* Credits.rtf */ = { 316 | isa = PBXVariantGroup; 317 | children = ( 318 | AA8008C2181F3EC500225F5A /* en */, 319 | ); 320 | name = Credits.rtf; 321 | sourceTree = ""; 322 | }; 323 | AA8008C7181F3EC500225F5A /* MainMenu.xib */ = { 324 | isa = PBXVariantGroup; 325 | children = ( 326 | AA8008C8181F3EC500225F5A /* Base */, 327 | ); 328 | name = MainMenu.xib; 329 | sourceTree = ""; 330 | }; 331 | AA8008D9181F3EC500225F5A /* InfoPlist.strings */ = { 332 | isa = PBXVariantGroup; 333 | children = ( 334 | AA8008DA181F3EC500225F5A /* en */, 335 | ); 336 | name = InfoPlist.strings; 337 | sourceTree = ""; 338 | }; 339 | /* End PBXVariantGroup section */ 340 | 341 | /* Begin XCBuildConfiguration section */ 342 | AA8008DE181F3EC500225F5A /* Debug */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | ALWAYS_SEARCH_USER_PATHS = NO; 346 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 347 | CLANG_CXX_LIBRARY = "libc++"; 348 | CLANG_ENABLE_OBJC_ARC = YES; 349 | CLANG_WARN_BOOL_CONVERSION = YES; 350 | CLANG_WARN_CONSTANT_CONVERSION = YES; 351 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 352 | CLANG_WARN_EMPTY_BODY = YES; 353 | CLANG_WARN_ENUM_CONVERSION = YES; 354 | CLANG_WARN_INT_CONVERSION = YES; 355 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 356 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 357 | COPY_PHASE_STRIP = NO; 358 | GCC_C_LANGUAGE_STANDARD = gnu99; 359 | GCC_DYNAMIC_NO_PIC = NO; 360 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 361 | GCC_OPTIMIZATION_LEVEL = 0; 362 | GCC_PREPROCESSOR_DEFINITIONS = ( 363 | "DEBUG=1", 364 | "$(inherited)", 365 | ); 366 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 367 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 368 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 369 | GCC_WARN_UNDECLARED_SELECTOR = YES; 370 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 371 | GCC_WARN_UNUSED_FUNCTION = YES; 372 | GCC_WARN_UNUSED_VARIABLE = YES; 373 | MACOSX_DEPLOYMENT_TARGET = 10.8; 374 | ONLY_ACTIVE_ARCH = YES; 375 | SDKROOT = macosx; 376 | }; 377 | name = Debug; 378 | }; 379 | AA8008DF181F3EC500225F5A /* Release */ = { 380 | isa = XCBuildConfiguration; 381 | buildSettings = { 382 | ALWAYS_SEARCH_USER_PATHS = NO; 383 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 384 | CLANG_CXX_LIBRARY = "libc++"; 385 | CLANG_ENABLE_OBJC_ARC = YES; 386 | CLANG_WARN_BOOL_CONVERSION = YES; 387 | CLANG_WARN_CONSTANT_CONVERSION = YES; 388 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 389 | CLANG_WARN_EMPTY_BODY = YES; 390 | CLANG_WARN_ENUM_CONVERSION = YES; 391 | CLANG_WARN_INT_CONVERSION = YES; 392 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 393 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 394 | COPY_PHASE_STRIP = YES; 395 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 396 | ENABLE_NS_ASSERTIONS = NO; 397 | GCC_C_LANGUAGE_STANDARD = gnu99; 398 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 399 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 400 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 401 | GCC_WARN_UNDECLARED_SELECTOR = YES; 402 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 403 | GCC_WARN_UNUSED_FUNCTION = YES; 404 | GCC_WARN_UNUSED_VARIABLE = YES; 405 | MACOSX_DEPLOYMENT_TARGET = 10.8; 406 | SDKROOT = macosx; 407 | }; 408 | name = Release; 409 | }; 410 | AA8008E1181F3EC500225F5A /* Debug */ = { 411 | isa = XCBuildConfiguration; 412 | buildSettings = { 413 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 414 | COMBINE_HIDPI_IMAGES = YES; 415 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 416 | GCC_PREFIX_HEADER = "SimpleCocoaAnalytics/SimpleCocoaAnalytics-Prefix.pch"; 417 | INFOPLIST_FILE = "SimpleCocoaAnalytics/SimpleCocoaAnalytics-Info.plist"; 418 | PRODUCT_NAME = "$(TARGET_NAME)"; 419 | WRAPPER_EXTENSION = app; 420 | }; 421 | name = Debug; 422 | }; 423 | AA8008E2181F3EC500225F5A /* Release */ = { 424 | isa = XCBuildConfiguration; 425 | buildSettings = { 426 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 427 | COMBINE_HIDPI_IMAGES = YES; 428 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 429 | GCC_PREFIX_HEADER = "SimpleCocoaAnalytics/SimpleCocoaAnalytics-Prefix.pch"; 430 | INFOPLIST_FILE = "SimpleCocoaAnalytics/SimpleCocoaAnalytics-Info.plist"; 431 | PRODUCT_NAME = "$(TARGET_NAME)"; 432 | WRAPPER_EXTENSION = app; 433 | }; 434 | name = Release; 435 | }; 436 | AA8008E4181F3EC500225F5A /* Debug */ = { 437 | isa = XCBuildConfiguration; 438 | buildSettings = { 439 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SimpleCocoaAnalytics.app/Contents/MacOS/SimpleCocoaAnalytics"; 440 | COMBINE_HIDPI_IMAGES = YES; 441 | FRAMEWORK_SEARCH_PATHS = ( 442 | "$(DEVELOPER_FRAMEWORKS_DIR)", 443 | "$(inherited)", 444 | ); 445 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 446 | GCC_PREFIX_HEADER = "SimpleCocoaAnalytics/SimpleCocoaAnalytics-Prefix.pch"; 447 | GCC_PREPROCESSOR_DEFINITIONS = ( 448 | "DEBUG=1", 449 | "$(inherited)", 450 | ); 451 | INFOPLIST_FILE = "SimpleCocoaAnalyticsTests/SimpleCocoaAnalyticsTests-Info.plist"; 452 | PRODUCT_NAME = "$(TARGET_NAME)"; 453 | TEST_HOST = "$(BUNDLE_LOADER)"; 454 | WRAPPER_EXTENSION = xctest; 455 | }; 456 | name = Debug; 457 | }; 458 | AA8008E5181F3EC500225F5A /* Release */ = { 459 | isa = XCBuildConfiguration; 460 | buildSettings = { 461 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SimpleCocoaAnalytics.app/Contents/MacOS/SimpleCocoaAnalytics"; 462 | COMBINE_HIDPI_IMAGES = YES; 463 | FRAMEWORK_SEARCH_PATHS = ( 464 | "$(DEVELOPER_FRAMEWORKS_DIR)", 465 | "$(inherited)", 466 | ); 467 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 468 | GCC_PREFIX_HEADER = "SimpleCocoaAnalytics/SimpleCocoaAnalytics-Prefix.pch"; 469 | INFOPLIST_FILE = "SimpleCocoaAnalyticsTests/SimpleCocoaAnalyticsTests-Info.plist"; 470 | PRODUCT_NAME = "$(TARGET_NAME)"; 471 | TEST_HOST = "$(BUNDLE_LOADER)"; 472 | WRAPPER_EXTENSION = xctest; 473 | }; 474 | name = Release; 475 | }; 476 | /* End XCBuildConfiguration section */ 477 | 478 | /* Begin XCConfigurationList section */ 479 | AA8008AA181F3EC500225F5A /* Build configuration list for PBXProject "SimpleCocoaAnalytics" */ = { 480 | isa = XCConfigurationList; 481 | buildConfigurations = ( 482 | AA8008DE181F3EC500225F5A /* Debug */, 483 | AA8008DF181F3EC500225F5A /* Release */, 484 | ); 485 | defaultConfigurationIsVisible = 0; 486 | defaultConfigurationName = Release; 487 | }; 488 | AA8008E0181F3EC500225F5A /* Build configuration list for PBXNativeTarget "SimpleCocoaAnalytics" */ = { 489 | isa = XCConfigurationList; 490 | buildConfigurations = ( 491 | AA8008E1181F3EC500225F5A /* Debug */, 492 | AA8008E2181F3EC500225F5A /* Release */, 493 | ); 494 | defaultConfigurationIsVisible = 0; 495 | defaultConfigurationName = Release; 496 | }; 497 | AA8008E3181F3EC500225F5A /* Build configuration list for PBXNativeTarget "SimpleCocoaAnalyticsTests" */ = { 498 | isa = XCConfigurationList; 499 | buildConfigurations = ( 500 | AA8008E4181F3EC500225F5A /* Debug */, 501 | AA8008E5181F3EC500225F5A /* Release */, 502 | ); 503 | defaultConfigurationIsVisible = 0; 504 | defaultConfigurationName = Release; 505 | }; 506 | /* End XCConfigurationList section */ 507 | }; 508 | rootObject = AA8008A7181F3EC500225F5A /* Project object */; 509 | } 510 | -------------------------------------------------------------------------------- /SimpleCocoaAnalytics/Base.lproj/MainMenu.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 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | Default 523 | 524 | 525 | 526 | 527 | 528 | 529 | Left to Right 530 | 531 | 532 | 533 | 534 | 535 | 536 | Right to Left 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | Default 548 | 549 | 550 | 551 | 552 | 553 | 554 | Left to Right 555 | 556 | 557 | 558 | 559 | 560 | 561 | Right to Left 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | --------------------------------------------------------------------------------