iOS App Example =============== Overview -------- This example implements a GeoIPService iOS app in Objective C. This example uses the [GeoIPService]( service, which is a live Web service that enables you to look up countries by IP address or by Context. The GUI of this example app is minimal, taking an IP address to display the country: ![the GeoIPService app](geoip-view.png) As you can see this is a very simple service. However, the instructions in this document apply to any complex SOAP/XML Web services with which iOS apps can be built with gSOAP and the Xcode SDK. Rather than focussing on GUI development, the focus of this document is to illustrate SOAP/XML Web services coding in Objective C. The [gSOAP iOS plugin]( handles the iOS web stack to exchange SOAP/XML (and JSON) messaging with gSOAP. The plugin is included with gSOAP. The source code of this Xcode project can be found in the gSOAP source code tree under `gsoap/ios_plugin/examples/GeoIPService`. [![To top](../../images/go-up.png) To top](#) General recommendations ----------------------- We recommend the following: - Objective-C source code files should use extension `.mm`. - To generate C++ proxy classes with soapcpp2 use soapcpp2 options `-j` and `-C`. - Use the gSOAP iOS plugin source code `gsoapios.h` and `` located in the `gsoap/ios_plugin` directory of the gSOAP source code tree. - Copy `gsoap/stdsoap2.h` and `gsoap/stdsoap2.cpp` from the gSOAP source code tree to your build. Compile and link the soapcpp2-generated code with `stdsoap2.cpp`. [![To top](../../images/go-up.png) To top](#) Build steps ----------- To generate code for the GeoIPService Web service, we first run the wsdl2h tool from the command line on the URL of the WSDL and use option -o to specify the name of the output interface header file to save: [command] wsdl2h -o GeoIPService.h '' This generates the [`GeoIPService.h`](GeoIPService.h) service interface header file with service operation definitions and the data types to pass along to and from the service operations. Next, we generate the client-side proxy class: [command] soapcpp2 -r -j -C -I/path/to/gsoap/import GeoIPService.h where `/path/to/gsoap/import` points to the `import` directory in the gSOAP source distribution. The `soap12.h` interface header file is imported to support SOAP 1.2 XML namespaces. In fact, the generated proxy class has operations for SOAP 1.1 and for SOAP 1.2, which appear as duplicate methods. We ignore the duplicate methods. If you want to generate proxy classes for each SOAP binding, then use wsdl2h with option `-N` and a name, say `-Ngeoip`. This command generates the following client-side code (`-C` option), a report (`-r` option) with C++ service proxy classes (`-j` option): * [`soapStub.h`](soapStub.h) a copy of the specification in plain C/C++ header file syntax without annotations. * [`soapH.h`](soapH.h) declares XML serializers. * [`soapC.cpp`](soapC.cpp) implements XML serializers. * [`soapGeoIPServiceSoapProxy.h`](soapGeoIPServiceSoapProxy.h) defines the client-side XML services API proxy class `GeoIPServiceSoapProxy`. * [`soapGeoIPServiceSoapProxy.cpp`](soapGeoIPServiceSoapProxy.cpp) implements the client-side XML services API proxy class `GeoIPServiceSoapProxy`. * [`GeoIPServiceSoap.nsmap`](GeoIPServiceSoap.nsmap) XML namespace binding table, you should #include this file. * [``](soapReadme.html) service and data binding interface details. Create a Single View-based Application project and name it `GeoIPService`. To do so, open `ViewController.xib` (from the main storyboard) in the Interface Builder. Double-click on the View item and populate it with the views listed below: - A Label: "Enter IP Address" - A Text Field - One Round Rect Button: "Find Country" button In Xcode, edit [`ViewController.h`](ViewController.h) and add the following: // // ViewController.h // #import <UIKit/UIKit.h> @interface ViewController : UIViewController { UITextField* IPAddress; } @property (strong, nonatomic) IBOutlet UITextField *IPAddress; - (IBAction) buttonPressed; @end Set the `ipAddress` outlet and the `buttonFindCountry:(id)sender` method to delegate the action of the button. In Xcode, edit [`Info.plist`](Info.plist) and add the following: [xml] <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" ""> <plist version="1.0"> <dict> <key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict> <key>CFBundleDevelopmentRegion</key> <string>en</string> <key>CFBundleExecutable</key> <string>$(EXECUTABLE_NAME)</string> <key>CFBundleIdentifier</key> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> <string>$(PRODUCT_NAME)</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> <string>1.0</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleVersion</key> <string>1</string> <key>LSRequiresIPhoneOS</key> <true/> <key>UILaunchStoryboardName</key> <string>LaunchScreen</string> <key>UIMainStoryboardFile</key> <string>Main</string> <key>UIRequiredDeviceCapabilities</key> <array> <string>armv7</string> </array> <key>UISupportedInterfaceOrientations</key> <array> <string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> </dict> </plist> These changes ensure that you can access Web services despite the App Transport Security layer. Add the source files [`soapC.cpp`](soapC.cpp), [`soapGeoIPServiceSoapProxy.cpp`](soapGeoIPServiceSoapProxy.cpp), [`soapGeoIPServiceSoapProxy.h`](soapGeoIPServiceSoapProxy.h), [`soapH.h`](soapH.h), and [`soapStub.h`](soapStub.h) generated in Step 1. Also add `gsoapios.h`, ``, `stdsoap2.h` and `stdsoap2.cpp` to the project. Edit [``]( to import the file [`GeoIPService.nsmap`](GeoIPService.nsmap). // // // #import <UIKit/UIKit.h> #import "AppDelegate.h" #include "../../GeoIPServiceSoap.nsmap" int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } Implement [``](ViewController) as follows: // // // #import "ViewController.h" #include "soapGeoIPServiceSoapProxy.h" #import "gsoapios.h" using namespace std; typedef struct _ns1__GetGeoIP RequestStruct; typedef struct _ns1__GetGeoIPResponse ResponseStruct; @implementation ViewController @synthesize IPAddress; - (IBAction)buttonPressed:(id)sender { RequestStruct ip; ResponseStruct response; // create proxy GeoIPServiceSoapProxy service(SOAP_IO_KEEPALIVE | SOAP_XML_INDENT | SOAP_XML_STRICT); // set IPAddress from input std::string ipAdd = std::string((char *)[IPAddress.text UTF8String]); ip.IPAddress = &ipAdd; // register the gSOAP iOS plugin soap_register_plugin(service.soap, soap_ios); // Optional: set the timeout interval, the default is 60.0 seconds soap_ios_settimeoutinterval(service.soap, 30.0); // invoke the web service int status = service.GetGeoIP(&ip, response); string *result; string err_msg = "Invalid IP address"; if (status == SOAP_OK) { NSString *soapResult; NSString *titleMsg; if (response.GetGeoIPResult && response.GetGeoIPResult->CountryName) result = response.GetGeoIPResult->CountryName; else result = &err_msg; soapResult = [NSString stringWithUTF8String:result->c_str()]; titleMsg = [NSString stringWithFormat: @"%@", @"Country Found"]; // display the result as an alert UIAlertController *alert = [UIAlertController alertControllerWithTitle:titleMsg message:soapResult preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *cancelButton = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action){}]; [alert addAction: cancelButton]; [self presentViewController:alert animated:YES completion:nil]; } else service.soap_stream_fault(std::cerr); service.destroy(); } // Override to allow orientations other than the default portrait orientation. - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { // Return YES for supported orientations return (interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown || interfaceOrientation == UIInterfaceOrientationLandscapeLeft || interfaceOrientation == UIInterfaceOrientationLandscapeRight); } - (void)didReceiveMemoryWarning { // release the view if it doesn't have a superview. [super didReceiveMemoryWarning]; // Release any cached data, images, etc that aren't in use. } - (void)viewDidUnload { // release any retained subviews of the main view. // e.g. self.myOutlet = nil; } - (void)dealloc { // [ipAddress release]; // [super dealloc]; } @end Build the app and run the emulator to test the app. The emulator should display the following image: ![iOS app emulator view](geoip-result.png) Readme report ------------- See the auto-generated [soapReadme](soapReadme.html) for this example. [![To top](../../images/go-up.png) To top](#)