Application de mesure reseau pour iOS
| |||
---|---|---|---|
Project | Project of speciality 2A | ||
Sub-project | Networking | ||
Students | Antoine SPITAELS (TEL)
Simon GUIGNOUARD (TEL) Siwar KRIAA (TEL) | ||
Promo | 2012 | ||
Tutor | Franck ROUSSEAU Franck.Rousseau@imag.fr |
Introduction
The aim of this project is to develop an iPhone application for network performance measurement in real time enabling wireless mobility. IPMT, a tool developped in C by the team Drakkar was used as a basis. IPMT is a tool initially implemented on Unix plateforms that is composed of two programs for sending traffic respectively in TCP or in UDP and two programs for receiving data. We have to adapt the IPMT code in order to be compatible with an iOS plateform, and to build an user interface to remplace the command line interface.
This project could be divided in two parts:
- Adaptation from Posix plateform to iOS
- Build a Graphical interface
To get IPMT source code:
svn checkout svn://svn.ligforge.imag.fr/svnroot/ipmt svn checkout https://svn.ligforge.imag.fr/svnroot/ipmt
From a posix plateform to iOS
Flags insertion
The application is initially implemented in C-language and run under several OS (Solaris, FreeBSD, MacOS X and Windows/Cygwin). The first step consists on adapting this application for iOS. The language for developing applications for iPhone is Objective-C,an overlayer of C. We don't virtually have to rewrite the entire application but rather check the compatibility of all C libraries and all IPMT functions with the iOS plateform.
Using flags such as #ifdef __FreeBSD_, #ifdef __APPLE_ enables you to force the compiler to compile or not a certain piece of code depending on the OS.
First of all, you will need to include the following header "TargetConditionals.h" for Apple plateforms as shown below:
#ifdef __APPLE__ #include "TargetConditionals.h" #endif
This librairy allow you to use the Apple's Flag to target a specific device (TARGET_IPHONE_SIMULATOR,TARGET_OS_IPHONE,TARGET_MAC_OS...)
For example:
#if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_IPHONE exit(1); #else return 0; #endif
It this example, the application will quit when this function is executed on Unix OS: exit(0) and the command line will be given back to the user but under iOS this code implies only the end of the function.
Management of executables
If your application is implemented on a Posix plateform then it might include many executables which are linked within a Makefile. For iPhone, only one executable is allowed. Then, to adapt your application to iOS two solutions are possible:
- Keep several executables and use NSTask
- Replace each main by a function while coding for iPhone. Only one main should be left.
We choose the simplest solution: using a function instead of an executable:
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE int function(int argc,char **argv) #else int main(int argc,char **argv) #endif
To launch a function or a NSTask without losing the user interaction we have to launch the IPMT function in a new thread which is liberated when we quit the application. In order to create a thread we choose to use NSThread
Reset of parameters
In order to allow relaunch of IPMT tools in iOS, we have to make some changes to the IPMT code. The first consists on resetting every variable and function used in the IPMT program. To adapt a command line tool to a graphical application we have to reset the arguments analyzer. << int getopt(int, char * const [], const char *) >> "getopt" is a function that analyses the arguments of the command line. The first argument "int" indicates the number of elements of the table "char*[] " containing arguments transmitted to the main(). An option is preceded by "-". "optind" is a variable which indicates the next element to be analyzed in the char*[]. The system set the value of this variable to 1. Under iOS, to be able to analyze the same table of strings many times ie each time we launch a request, we have to reset "getopt" by setting the value of "optind" to 1.
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE optind=1; #endif while ((ch = getopt(argc, argv, "abdegHhi:jM:m:r:o:p:t:vk46q")) != -1)
You can get further information on this issue on this page [1]
Timer
We meet some issues in iOS with timer, the C Timer causes a OS crash.
So we decide to replace the initial timer of IPMT by a NSTimer.
Our NSTimer have two main goal:
- update the result display
- replace the initial timer of IPMT which allows the user to control the bitrate of the test
graphical interface
Integration of command lines
Under a Posix plateform the application is launched thanks to command lines directly from a terminal. This method is not possible under iPhone because there is natively no shell or another way to access to the network layer, and especially, typing command lines with a tiny virtual keyboard is not very convenient. Therefore, the first thing to do is to provide a user-friendly graphical interface.
For beginners in Objective-C, the Interface Builder tool integrated in Xcode is quite helpful to quickly set a simple and functional GUI. Classic objects of user interface (UITextfield, UIToolbar, UIButton etc.) can be graphically manipulated. Then, objects have just to be linked to their declaration.
In our application, we retrieve arguments entered by the user thanks to different objects (UITextfield, UISegmentedControl, UISlider) that we store in a table. The following example shows how to add an option to the table of arguments.
Code:
//Allocation of the table arg=calloc(nb_param,sizeof(char*)); for (i=1;i<nb_param;i=i+2) arg[i]=calloc(50,sizeof(char)); arg[nb_param-1]=calloc(50,sizeof(char)); i=1; if (![portdst.text isEqual:@""]) //if the textfield portdst is not empty { arg[i]="-p"; //we store "-p" sprintf(arg[i+1],"%s",[portdst.text UTF8String]); //we store the value entered by user in the textfield portdst i=i+2; }
Finally, our table will contain the command line that the user would have entered in the terminal. (eg: tcpmt -p 13000 -d 10 10.0.2.10) Then, we just have to call the function with the number of parameters and the table as arguments. In our case, functions are coded in C, so for example we call: tcpmt(nb_param,arg);
Tricks about editable UITextField
Make the view slide up/down
When the user tap on an editable textfield, the keyboard shows up and can hide the textfield if it is on the bottom part of the view. This is quite bothering if the user wants to see what he is typing. The solution is to make the entire view slide up on the background, in order that the textfield appears on the visible part of the view (above the keyboard).
Thus, we have to implement two methods (textFieldShouldBeginEditing and textFieldShouldEndEditing) to get the desired effect.
See code below.
Code:
CGFloat animatedDistance; static const CGFloat KEYBOARD_ANIMATION_DURATION = 0.3; static const CGFloat MINIMUM_SCROLL_FRACTION = 0.2; static const CGFloat MAXIMUM_SCROLL_FRACTION = 0.8; static const CGFloat PORTRAIT_KEYBOARD_HEIGHT = 216; static const CGFloat LANDSCAPE_KEYBOARD_HEIGHT = 162;
//Executed when textField begins being edited //Slides up the view -(BOOL)textFieldShouldBeginEditing:(UITextField *)textField{ CGRect textFieldRect = [self.view.window convertRect:textField.bounds fromView:textField]; CGRect viewRect = [self.view.window convertRect:self.view.bounds fromView:self.view]; CGFloat midline = textFieldRect.origin.y + 0.5 * textFieldRect.size.height; CGFloat numerator = midline - viewRect.origin.y - MINIMUM_SCROLL_FRACTION * viewRect.size.height; CGFloat denominator = (MAXIMUM_SCROLL_FRACTION - MINIMUM_SCROLL_FRACTION) * viewRect.size.height; CGFloat heightFraction = numerator / denominator; if (heightFraction < 0.0) { heightFraction = 0.0; } else if (heightFraction > 1.0) { heightFraction = 1.0; } animatedDistance = floor(LANDSCAPE_KEYBOARD_HEIGHT * heightFraction); CGRect viewFrame = self.view.frame; viewFrame.origin.y -= animatedDistance; [UIView beginAnimations:nil context:NULL]; [UIView setAnimationBeginsFromCurrentState:YES]; [UIView setAnimationDuration:KEYBOARD_ANIMATION_DURATION]; [self.view setFrame:viewFrame]; [UIView commitAnimations]; return YES; }
//Executed when textField begins being edited //Slides up the view -(BOOL)textFieldShouldEndEditing:(UITextField *)textField{ CGRect textFieldRect = [self.view.window convertRect:textField.bounds fromView:textField]; CGRect viewRect = [self.view.window convertRect:self.view.bounds fromView:self.view]; CGFloat midline = textFieldRect.origin.y + 0.5 * textFieldRect.size.height; CGFloat numerator = midline - viewRect.origin.y - MINIMUM_SCROLL_FRACTION * viewRect.size.height; CGFloat denominator = (MAXIMUM_SCROLL_FRACTION - MINIMUM_SCROLL_FRACTION) * viewRect.size.height; CGFloat heightFraction = numerator / denominator; if (heightFraction < 0.0) { heightFraction = 0.0; } else if (heightFraction > 1.0) { heightFraction = 1.0; } animatedDistance = floor(PORTRAIT_KEYBOARD_HEIGHT * heightFraction); CGRect viewFrame = self.view.frame; viewFrame.origin.y += animatedDistance; [UIView beginAnimations:nil context:NULL]; [UIView setAnimationBeginsFromCurrentState:YES]; [UIView setAnimationDuration:KEYBOARD_ANIMATION_DURATION]; [self.view setFrame:viewFrame]; [UIView commitAnimations]; return YES; }
Add a button to the keyboard
Done button | |
---|---|
|
We wanted to accept only numbers in several textfields so we set default keyboard to numpad which provide the advantage to ease the typing of numbers and to oblige the user to enter numbers. However, numpad keyboard does not provide a return button. Therefore we needed to add a "Done" button to the keyboard in order to make it disappear when the button is tapped. See code below.
-(void)addButtonToKeyboard { create custom button doneButton.frame = CGRectMake(0, 163, 106, 53); doneButton.adjustsImageWhenHighlighted = NO; [doneButton setImage:[UIImage imageNamed:@"DoneUp3.png"] forState:UIControlStateNormal]; [doneButton setImage:[UIImage imageNamed:@"DoneDown3.png"] forState:UIControlStateHighlighted]; [doneButton addTarget:self action:@selector(doneButton:) forControlEvents:UIControlEventTouchUpInside]; // locate keyboard view UIWindow* tempWindow = [[[UIApplication sharedApplication] windows] objectAtIndex:1]; UIView* keyboard; for(int i=0; i<[tempWindow.subviews count]; i++) { keyboard = [tempWindow.subviews objectAtIndex:i]; // keyboard found, add the button if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 3.2) { if([[keyboard description] hasPrefix:@"<UIPeripheralHost"] == YES) [keyboard addSubview:doneButton]; } else { if([[keyboard description] hasPrefix:@"<UIKeyboard"] == YES) [keyboard addSubview:doneButton]; } } }
Rool down the keyboard
In order to make the keyboard disappear when typing is done we have to implement a little method, that has to be linked to the Did end on exit event of the textfield.
-(IBAction) doneEditing:(id)sender { [sender resignFirstResponder]; }
Graphic plotting
CorePlot is a tierce plotting framework that enables creating graphs in an iPhone application. There are many versions of CorePlot. In our case, we have chosen the recent one available on Google Code [2]. To install this framework on Xcode 4, you have to follow these instructions:
- Copy the CorePlotHeaders directory to your Xcode project.
- Copy the Frameworks file to your Xcode project
- Open your apps Target Build Settings, and for Other Linker Flags include this: -ObjC -all_load
- Add the QuartzCore framework to the project.
This is the static method for installing the framewok. Thus, you are not supposed to re-install CorePlot each time you change the device in order to test the application. All the previous configuration will figure on you GIT deposit. Finally, you should import the following header "CorePlot-CocoaTouch.h" in the appropriate files to be able to call classes of CorePlot.
Example of a basic graph
//create a CPGraphHostingView object that will host the graph graphView = [ [ CPGraphHostingView alloc ] initWithFrame:CGRectMake(0.0, 40.0, 320.0, 395.0-20.0) ]; //add the graphView to the current view [ self.view addSubview: graphView]; //create a graph and make graphView host it graph = [[ [ CPXYGraph alloc ] initWithFrame: self.view.bounds ] autorelease]; graphView.hostedGraph = graph; //Create a scatter plot CPScatterPlot *plot1 = [[[CPScatterPlot alloc]initWithFrame:self.view.bounds] autorelease]; //add it to the graph [graph addPlot:plot1]; //define the plotting space CPXYPlotSpace *plotSpace = (CPXYPlotSpace *)graph.defaultPlotSpace; //Defines the number of points to plot - (NSUInteger)numberOfRecordsForPlot:(CPPlot *)plot { return 10; } //Value of points - example - (NSNumber *)numberForPlot:(CPPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index if (fieldEnum == CPScatterPlotFieldX) { return [ NSNumber numberWithInteger:-10.0+index ]; } else if (fieldEnum == CPScatterPlotFieldY) { return [ NSNumber numberWithFloat:(-10.0+index)/2.0 ]; } return [ NSNumber numberWithFloat:0.0 ]; }