Commit 38cfdbfb authored by Alex Moore's avatar Alex Moore
Browse files

Updated workspace, project is now in folder WAMI

parent f608973e
/*
File: PutController.m
Contains: Manages the PUT tab.
Written by: DTS
Copyright: Copyright (c) 2009-2012 Apple Inc. All Rights Reserved.
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
("Apple") in consideration of your agreement to the following
terms, and your use, installation, modification or
redistribution of this Apple software constitutes acceptance of
these terms. If you do not agree with these terms, please do
not use, install, modify or redistribute this Apple software.
In consideration of your agreement to abide by the following
terms, and subject to these terms, Apple grants you a personal,
non-exclusive license, under Apple's copyrights in this
original Apple software (the "Apple Software"), to use,
reproduce, modify and redistribute the Apple Software, with or
without modifications, in source and/or binary forms; provided
that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the
following text and disclaimers in all such redistributions of
the Apple Software. Neither the name, trademarks, service marks
or logos of Apple Inc. may be used to endorse or promote
products derived from the Apple Software without specific prior
written permission from Apple. Except as expressly stated in
this notice, no other rights or licenses, express or implied,
are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or
by other works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis.
APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING
THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT,
INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY
OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY
OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
*/
#import "PutController.h"
#import "NetworkManager.h"
#if TARGET_IPHONE_SIMULATOR
static NSString * kDefaultPutURLText = @"http://localhost:9000/";
#else
static NSString * kDefaultPutURLText = @"";
#endif
@interface PutController () <UITextFieldDelegate>
@property (nonatomic, strong, readwrite) IBOutlet UITextField * urlText;
@property (nonatomic, strong, readwrite) IBOutlet UILabel * statusLabel;
@property (nonatomic, strong, readwrite) IBOutlet UIActivityIndicatorView * activityIndicator;
@property (nonatomic, strong, readwrite) IBOutlet UIBarButtonItem * cancelButton;
- (IBAction)sendAction:(UIView *)sender;
- (IBAction)cancelAction:(id)sender;
// Properties that don't need to be seen by the outside world.
@property (nonatomic, assign, readonly ) BOOL isSending;
@property (nonatomic, strong, readwrite) NSURLConnection * connection;
@property (nonatomic, strong, readwrite) NSInputStream * fileStream;
@end
@implementation PutController
@synthesize connection = _connection;
@synthesize fileStream = _fileStream;
@synthesize urlText = _urlText;
@synthesize statusLabel = _statusLabel;
@synthesize activityIndicator = _activityIndicator;
@synthesize cancelButton = _cancelButton;
#pragma mark * Status management
// These methods are used by the core transfer code to update the UI.
- (void)sendDidStart
{
self.statusLabel.text = @"Sending";
self.cancelButton.enabled = YES;
[self.activityIndicator startAnimating];
[[NetworkManager sharedInstance] didStartNetworkOperation];
}
- (void)sendDidStopWithStatus:(NSString *)statusString
{
if (statusString == nil) {
statusString = @"PUT succeeded";
}
self.statusLabel.text = statusString;
self.cancelButton.enabled = NO;
[self.activityIndicator stopAnimating];
[[NetworkManager sharedInstance] didStopNetworkOperation];
}
#pragma mark * Core transfer code
// This is the code that actually does the networking.
- (BOOL)isSending
{
return (self.connection != nil);
}
- (void)startSend:(NSString *)filePath
{
BOOL success;
NSURL * url;
NSMutableURLRequest * request;
NSNumber * contentLength;
assert(filePath != nil);
assert([[NSFileManager defaultManager] fileExistsAtPath:filePath]);
assert( [filePath.pathExtension isEqual:@"png"] || [filePath.pathExtension isEqual:@"jpg"] );
assert(self.connection == nil); // don't tap send twice in a row!
assert(self.fileStream == nil); // ditto
// First get and check the URL.
url = [[NetworkManager sharedInstance] smartURLForString:self.urlText.text];
success = (url != nil);
if (success) {
// Add the last the file name to the end of the URL to form the final
// URL that we're going to PUT to.
url = [url URLByAppendingPathComponent:[filePath lastPathComponent] isDirectory:NO];
success = (url != nil);
}
// If the URL is bogus, let the user know. Otherwise kick off the connection.
if ( ! success) {
self.statusLabel.text = @"Invalid URL";
} else {
// Open a stream for the file we're going to send. We do not open this stream;
// NSURLConnection will do it for us.
self.fileStream = [NSInputStream inputStreamWithFileAtPath:filePath];
assert(self.fileStream != nil);
// Open a connection for the URL, configured to PUT the file.
request = [NSMutableURLRequest requestWithURL:url];
assert(request != nil);
[request setHTTPMethod:@"PUT"];
[request setHTTPBodyStream:self.fileStream];
if ( [filePath.pathExtension isEqual:@"png"] ) {
[request setValue:@"image/png" forHTTPHeaderField:@"Content-Type"];
} else if ( [filePath.pathExtension isEqual:@"jpg"] ) {
[request setValue:@"image/jpeg" forHTTPHeaderField:@"Content-Type"];
} else if ( [filePath.pathExtension isEqual:@"gif"] ) {
[request setValue:@"image/gif" forHTTPHeaderField:@"Content-Type"];
} else {
assert(NO);
}
contentLength = (NSNumber *) [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL] objectForKey:NSFileSize];
assert( [contentLength isKindOfClass:[NSNumber class]] );
[request setValue:[contentLength description] forHTTPHeaderField:@"Content-Length"];
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
assert(self.connection != nil);
// Tell the UI we're sending.
[self sendDidStart];
}
}
- (void)stopSendWithStatus:(NSString *)statusString
{
if (self.connection != nil) {
[self.connection cancel];
self.connection = nil;
}
if (self.fileStream != nil) {
[self.fileStream close];
self.fileStream = nil;
}
[self sendDidStopWithStatus:statusString];
}
- (void)connection:(NSURLConnection *)theConnection didReceiveResponse:(NSURLResponse *)response
// A delegate method called by the NSURLConnection when the request/response
// exchange is complete. We look at the response to check that the HTTP
// status code is 2xx. If it isn't, we fail right now.
{
#pragma unused(theConnection)
NSHTTPURLResponse * httpResponse;
assert(theConnection == self.connection);
httpResponse = (NSHTTPURLResponse *) response;
assert( [httpResponse isKindOfClass:[NSHTTPURLResponse class]] );
if ((httpResponse.statusCode / 100) != 2) {
[self stopSendWithStatus:[NSString stringWithFormat:@"HTTP error %zd", (ssize_t) httpResponse.statusCode]];
} else {
self.statusLabel.text = @"Response OK.";
}
}
- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)data
// A delegate method called by the NSURLConnection as data arrives. The
// response data for a PUT is only for useful for debugging purposes,
// so we just drop it on the floor.
{
#pragma unused(theConnection)
#pragma unused(data)
assert(theConnection == self.connection);
// do nothing
}
- (void)connection:(NSURLConnection *)theConnection didFailWithError:(NSError *)error
// A delegate method called by the NSURLConnection if the connection fails.
// We shut down the connection and display the failure. Production quality code
// would either display or log the actual error.
{
#pragma unused(theConnection)
#pragma unused(error)
assert(theConnection == self.connection);
[self stopSendWithStatus:@"Connection failed"];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)theConnection
// A delegate method called by the NSURLConnection when the connection has been
// done successfully. We shut down the connection with a nil status, which
// causes the image to be displayed.
{
#pragma unused(theConnection)
assert(theConnection == self.connection);
[self stopSendWithStatus:nil];
}
#pragma mark * Actions
- (IBAction)sendAction:(UIView *)sender
{
assert( [sender isKindOfClass:[UIView class]] );
if ( ! self.isSending ) {
NSString * filePath;
// User the tag on the UIButton to determine which image to send.
filePath = [[NetworkManager sharedInstance] pathForTestImage:sender.tag];
assert(filePath != nil);
[self startSend:filePath];
}
}
- (IBAction)cancelAction:(id)sender
{
#pragma unused(sender)
[self stopSendWithStatus:@"Cancelled"];
}
- (void)textFieldDidEndEditing:(UITextField *)textField
// A delegate method called by the URL text field when the editing is complete.
// We save the current value of the field in our settings.
{
#pragma unused(textField)
NSString * newValue;
NSString * oldValue;
assert(textField == self.urlText);
newValue = self.urlText.text;
oldValue = [[NSUserDefaults standardUserDefaults] stringForKey:@"PutURLText"];
// Save the URL text if there is no pre-existing setting and it's not our
// default value, or if there is a pre-existing default and the new value
// is different.
if ( ((oldValue == nil) && ! [newValue isEqual:kDefaultPutURLText] )
|| ((oldValue != nil) && ! [newValue isEqual:oldValue] ) ) {
[[NSUserDefaults standardUserDefaults] setObject:newValue forKey:@"PutURLText"];
}
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
// A delegate method called by the URL text field when the user taps the Return
// key. We just dismiss the keyboard.
{
#pragma unused(textField)
assert(textField == self.urlText);
[self.urlText resignFirstResponder];
return NO;
}
#pragma mark * View controller boilerplate
- (void)viewDidLoad
{
NSString * currentURLText;
[super viewDidLoad];
assert(self.urlText != nil);
assert(self.statusLabel != nil);
assert(self.activityIndicator != nil);
assert(self.cancelButton != nil);
// Set up the URL field to be the last value we saved (or the default value
// if we have none).
currentURLText = [[NSUserDefaults standardUserDefaults] stringForKey:@"PutURLText"];
if (currentURLText == nil) {
currentURLText = kDefaultPutURLText;
}
self.urlText.text = currentURLText;
self.activityIndicator.hidden = YES;
self.statusLabel.text = @"Tap a picture to start the PUT";
self.cancelButton.enabled = NO;
}
- (void)viewDidUnload
{
[super viewDidUnload];
self.urlText = nil;
self.statusLabel = nil;
self.activityIndicator = nil;
self.cancelButton = nil;
}
- (void)dealloc
{
// Because NSURLConnection retains its delegate until the connection finishes, and
// any time the connection finishes we call -stopSendWithStatus: to clean everything
// up, we can't be deallocated with a connection in progress.
assert(self->_connection == nil);
}
@end
Read Me About SimpleURLConnections
==================================
1.2
SimpleURLConnections shows how to do simple networking using the NSURLConnection API. The goal of this sample is very limited: it does not demonstrate everything you need to implement a fully fledged networking product (more on this below), rather, its goal is to demonstrate simple uses of the NSURLConnection API.
SimpleURLConnections requires iOS 5.0 or later, although the core networking code should also work on all versions of iOS and Mac OS X 10.4 and later.
Packing List
------------
The sample contains the following items:
o Read Me About SimpleURLConnections.txt -- This file.
o SimpleURLConnections.xcodeproj -- An Xcode project for the sample.
o Resources -- The project nib, images, and so on.
o Ancillary Code -- A directory full of code that's not directly relevant to the main function of this sample.
o GetController.[hm] -- A view controller that downloads files via GET.
o PutController.[hm] -- A view controller that uploads files via PUT.
o PostController.[hm] -- A view controller that uploads files via POST.
o ImageReceiveServer.py -- A Python HTTP server for testing purposes.
Using the Sample
----------------
You can test the GET functionality very easily:
1. Run the program on a device or simulator.
2. Switch to the GET tab.
3. Either leave the URL as the default, or edit it to be the URL of an image you want to display.
3. Tap the Get button
The program will download the image and display it on screen.
Testing the send functionality is a bit trickier. You will need a server that accepts HTTP PUT and POST requests for image files. If you have such a server, make note of its URL and skip ahead. If not, you can run the enclosed (very simple, for testing purposes only) Python server included with the sample. To do this:
1. In Terminal, change into the SimpleURLConnections directory. For example:
$ cd ~/Downloads/SimpleURLConnections
2. Run the server:
$ ./ImageReceiveServer.py
Any images that are uploaded will be placed in a newly-created "images" directory. When you're done with the server, you can type ^C to quit it.
WARNING: "ImageReceiveServer.py" was designed for testing purposes only. It has not been audited for security. You must not run it on a hostile network, such as the public Internet.
Once you know the URL of your server, you can use the following steps to test the PUT code:
1. Run the program on a device or simulator.
2. Switch to the PUT tab.
3. Enter the URL of your upload server. If you're running in the simulator and you're running the server on the same Mac, you can just stick with the default URL.
4. Tap one of the image buttons to start a send.
You can use the POST tab to test the POST code in exactly the same way.
The program has four built-in test images with exponentially increasing size. Transferring the first image, shown at the top left, will be very quick. Transferring the last image, shown at the bottom right, will be very slow (a thousand times slower!). This allows you to test various things that you couldn't test otherwise, like the Cancel button. See -[NetworkManager pathForTestImage:] for more information on how these large images are created.
Building the Sample
-------------------
The sample was built using Xcode 4.3.1 on Mac OS X 10.7.5 with iOS SDK 5.1. You should be able to just open the project and choose Build from the Product menu. The resulting program should be compatible with all devices running iOS 5.0 and later. The bulk of my testing was done with an iPod touch (fourth generation) running iPhone OS 5.0.
How It Works
------------
The sample is a very simple application of the NSURLConnection API for HTTP networking. Each view controller is a mostly self-contained networking example. The GetController creates an NSURLConnection for the specified URL; the connection is run asynchronously via the runloop. The bulk of the interesting code is in the -startReceive and the various NSURLConnection delegate callback methods.
In the case of the PutController, the most interesting code is in -startSend:. This creates a URL from the user-supplied text, appends the name of the image file we're uploading, and then creates an NSMutableURLRequest from that. It configures the request with the correct HTTP method, headers, and body. Finally, it runs the connection in the standard way.
For the POST case things get a little trickier. In many respects the PostController is similar to the PutController. The key difference is that, when you do an HTTP POST, the HTTP message body does not just contain the file data. Rather, it contains some structured content, with the file data appearing in the middle of the message body. Effectively this means that the body of an HTTP POST has a header, the file data, and then a trailer. This makes things tricky because NSURLConnection requires that the body be represented by either an NSData or an NSStream. The file we're uploading is way too big for an NSData, so that's not an option. Using an NSStream for the upload is tricky because there's no easy way to create an NSStream that holds some data (the header), then the contents of a file, then some more data (the trailer).
I resolve this problem by creating a bound pair of streams, using the read stream for the HTTP body, and then generating the HTTP body contents on the fly by writing it to the write stream. The interesting code is in +createBoundInputStream:outputStream:bufferSize: and -[PostController stream:handleEvent:].
Simplifying Assumptions
-----------------------
To keep this code simple, I've made a number of simplifying assumptions:
o I've structured the code to make it easy to understand, rather than to maximize maintainability and flexibility. Specifically:
- The various controller classes share a lot of common code which should be factored out. I left this duplicated code in place because I wanted folks to be able to get a good understanding of the overall structure by looking at just one source file.
- I do all my networking at the controller layer. This is acceptable in very limited circumstances, but most real applications should do their networking at the model layer. I explain why in my WWDC 2010 presentation, "Network Apps for iPhone OS", part 1 and 2 (sessions 207 and 208).
<http://developer.apple.com/videos/wwdc/2010/>
o This sample pays no attention to security issues. A real application may need to worry about security issues like authorization and privacy. It would not be hard to extend this sample to be secure because iOS's HTTP APIs provide good support for standard HTTP security measures (authentication and HTTPS, that is, HTTP over TLS).
o There are a number of places where I choose simplicity over performance. For example, in the GetController, I write the received data directly to the file rather than buffering up data to maximize disk throughput, and I make no attempt to overlap network and file I/O. If you have a performance sensitive application you should measure your performance and optimize appropriately.
o I made no attempt to scale incoming images to fit the device's screen; this is a networking sample, not a graphics sample!
Credits and Version History
---------------------------
If you find any problems with this sample, please file a bug against it.
<http://developer.apple.com/bugreporter/>
1.0 (Sep 2009) was the first shipping version.
1.1 (Jun 2010) updated the sample for iOS 4.0.
1.2 (Mar 2012) was a significant update whose main goal was to adopt the latest tools and techniques (most notably, ARC). It also disabled the bound pair workaround that was necessary prior to iOS 5.
Share and Enjoy
Apple Developer Technical Support
Core OS/Hardware
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>com.apple.dts.${PRODUCT_NAME:identifier}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.2</string>
<key>NSMainNibFile</key>
<string>MainWindow</string>
<key>CFBundleIconFiles</key>
<array>
<string>Icon.png</string>
<string>Icon@2x.png</string>
<string>Icon-72.png</string>
<string>Icon-Small.png</string>
<string>Icon-Small-50.png</string>
<string>Icon-Small@2x.png</string>
</array>
</dict>
</plist>
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
02084D0C11E53AB800199A4D /* iTunesArtwork in Resources */ = {isa = PBXBuildFile; fileRef = 02084D0B11E53AB800199A4D /* iTunesArtwork */; };
023DC4ED11CACA8000B37A8E /* Icon-72.png in Resources */ = {isa = PBXBuildFile; fileRef = 023DC4E611CACA8000B37A8E /* Icon-72.png */; };
023DC4EE11CACA8000B37A8E /* Icon-Small-50.png in Resources */ = {isa = PBXBuildFile; fileRef = 023DC4E711CACA8000B37A8E /* Icon-Small-50.png */; };
023DC4EF11CACA8000B37A8E /* Icon-Small.png in Resources */ = {isa = PBXBuildFile; fileRef = 023DC4E811CACA8000B37A8E /* Icon-Small.png */; };
023DC4F011CACA8000B37A8E /* Icon-Small@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 023DC4E911CACA8000B37A8E /* Icon-Small@2x.png */; };
023DC4F111CACA8000B37A8E /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 023DC4EA11CACA8000B37A8E /* Icon.png */; };
023DC4F211CACA8000B37A8E /* Icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 023DC4EB11CACA8000B37A8E /* Icon@2x.png */; };
1D3623EC0D0F72F000981E51 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D3623EB0D0F72F000981E51 /* CoreGraphics.framework */; };
1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; };
1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; };
E444897D0FFBC82300D6190E /* post.png in Resources */ = {isa = PBXBuildFile; fileRef = E444897C0FFBC82300D6190E /* post.png */; };
E46C8C300FF904C400D616AF /* PutController.m in Sources */ = {isa = PBXBuildFile; fileRef = E46C8C2D0FF904C400D616AF /* PutController.m */; };
E46C8C310FF904C400D616AF /* GetController.m in Sources */ = {isa = PBXBuildFile; fileRef = E46C8C2E0FF904C400D616AF /* GetController.m */; };
E46C8C510FF9061900D616AF /* put.png in Resources */ = {isa = PBXBuildFile; fileRef = E46C8C4F0FF9061900D616AF /* put.png */; };
E46C8C520FF9061900D616AF /* get.png in Resources */ = {isa = PBXBuildFile; fileRef = E46C8C500FF9061900D616AF /* get.png */; };
E46C8C740FF9082100D616AF /* PostController.m in Sources */ = {isa = PBXBuildFile; fileRef = E46C8C720FF9082100D616AF /* PostController.m */; };
E4C116CC15175D750058A743 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = E4C116CD15175D750058A743 /* MainWindow.xib */; };
E4C116D51517682C0058A743 /* NetworkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E4C116D41517682C0058A743 /* NetworkManager.m */; };
E4C2783D0FD6A53E000CE8F0 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E4C2783B0FD6A53E000CE8F0 /* AppDelegate.m */; };
E4C2783E0FD6A53E000CE8F0 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E4C2783C0FD6A53E000CE8F0 /* main.m */; };
E4C278440FD6A598000CE8F0 /* TestImage1.png in Resources */ = {isa = PBXBuildFile; fileRef = E4C278400FD6A598000CE8F0 /* TestImage1.png */; };
E4C278450FD6A598000CE8F0 /* TestImage3.png in Resources */ = {isa = PBXBuildFile; fileRef = E4C278410FD6A598000CE8F0 /* TestImage3.png */; };
E4C278460FD6A598000CE8F0 /* TestImage2.png in Resources */ = {isa = PBXBuildFile; fileRef = E4C278420FD6A598000CE8F0 /* TestImage2.png */; };
E4C278470FD6A598000CE8F0 /* TestImage4.png in Resources */ = {isa = PBXBuildFile; fileRef = E4C278430FD6A598000CE8F0 /* TestImage4.png */; };
E4C278490FD6A5AD000CE8F0 /* NoImage.png in Resources */ = {isa = PBXBuildFile; fileRef = E4C278480FD6A5AD000CE8F0 /* NoImage.png */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
02084D0B11E53AB800199A4D /* iTunesArtwork */ = {isa = PBXFileReference; lastKnownFileType = file; name = iTunesArtwork; path = Resources/iTunesArtwork; sourceTree = "<group>"; };
023DC4E611CACA8000B37A8E /* Icon-72.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Icon-72.png"; path = "Resources/Icon-72.png"; sourceTree = "<group>"; };
023DC4E711CACA8000B37A8E /* Icon-Small-50.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Icon-Small-50.png"; path = "Resources/Icon-Small-50.png"; sourceTree = "<group>"; };