Earlier this week I mentioned that I had published my TXT-2-PARK app for Android in the Google Play Store. Today I published the iOS version to the Apple App Store (still awaiting approval at this stage). As with the Android version, I implemented the iOS version using Oxygene, and things proved a little less straightforward.
First of all, there is a key platform difference that directly affected the implementation of my app.
On Android (and I believe Windows Phone, which I have already started on) it is perfectly possible for an application to send an SMS without any involvement from the user. This however is not the case on iOS. There are ways to achieve it through private API’s but these require a jailbroken device. If you want to publish your app in the App Store then there is no way around it.
The best you can hope to achieve on iOS is to pre-fill an SMS message form with the user then required to hit Send to confirm that the message should be sent.
To achieve this is very straightforward:
var sms := new MFMessageComposeViewController; sms.messageComposeDelegate := self; sms.recipients := ['7275']; sms.body := DigitStrip.Code; presentViewController(sms) animated(true) completion(nil);
We instantiate an MSMessageComposeViewController, set the recipients (an array of strings) and set the message body. We also assign a delegate to handle the completion callback. We then present the view controller (the built-in SMS composer view).
Instantiation of the view controller is made more straightforward in Oxygene, thanks to the support for constructor style instantiation using the new statement. The first line in the above code is equivalent to the Objective-C:
MFMessageComposeViewController sms = [[MFMessageComposeViewController alloc] init];
This allocates an instance via the static class method alloc, and then calls the default initializer, init, on the resulting instance.
Similarly, the assignment of an array of strings for the recipients is handled for us by the fact that the Oxygene compiler transparently maps the syntax for an explicit array onto the appropriate code for instantiating a platform appropriate array class and initializing it.
So this simple and intuitive assignment:
sms.recipients := ['7275'];
Which corresponds directly to the far more cumbersome Objective-C code:
sms.recipients = [NSArray arrayWithObjects:@"7275", nil];
The end result is that the user is presented with the standard iOS SMS message composition UI, with the recipient (7275 / PARK) and payment code that they entered already filled in. They then simply have to confirm that the message should be sent, or cancel it. Whatever they choose, once they have done this the delegate method is then called in which we can determine what happened.
Declaring that a class implements a delegate protocol is syntactically identical to implementing an interface (for those more familiar with that in Delphi):
RootViewController = class(UIViewController, IMFMessageComposeViewControllerDelegate) .. method messageComposeViewController(aController: MFMessageComposeViewController) didFinishWithResult(aResult: MessageComposeResult); end;
In this instance, the protocol involved has only one method, which we implement to handle the fact that the user has dismissed the SMS view controller, either sending the message or cancelling it. This demonstrates the support for multi-part method names in Oxygene (and RO C#) compilers. Multi-part method names take a bit of getting used to, but I for one have found myself really enjoying the clarity and expressiveness they bring.
The feel very “Pascally’ somehow. To me at least. 🙂
Of course, not being content with simply supporting multi-part methods names for Cocoa, as of the 7.x compilers, RemObjects provide this support on both Java and .NET platforms as well (though with some limitations on .NET, namely that such methods are not consumable by other .NET languages other than those RO compiler).
The implementation of the protocol method is equally straightforward:
method RootViewController.messageComposeViewController(aController: MFMessageComposeViewController) didFinishWithResult(aResult: MessageComposeResult); begin if (aResult = MessageComposeResult.MessageComposeResultFailed) then begin var alert := new UIAlertView withTitle('Unexpected Error') message('The SMS message could not be sent.') &delegate(nil) cancelButtonTitle('OK') otherButtonTitles(nil); alert.show; end else dismissViewControllerAnimated(true) completion(nil); end;
If the result of the operation was a failure (the user confirmed that the message should be sent, but something went wrong) then we report this to the user via a UIAlertView (pop-up message).
The instantiation of the UIAlertView used to present this message demonstrates yet more of the syntactic sugar that Oxygene liberally dusts over the Objective-C API’s, particularly w.r.t constructors.
In this case, I am not invoking the default initializer (init), but rather one that takes several parameters. In Objective-C this would be:
UIAlertView alert = [[UIAlertView alloc] initWithTitle: .. message: ... ...etc... ];
In Oxygene the alloc static method call is replace by the new statement, as before, and on this occasion the specialised initializer is invoked by the special “dot-less” invocation and without the “init” prefix on the initializer method name:
var alert := new UIAlertView withTitle(...) ... etc;
The code completion offered by Oxygene in Visual Studio ensures that correct initializer methods are shown in this context. If you find yourself presented with an “initXXX” method in a statement involving new, then you have made a mistake and will end up double-initializing the new instance (which will result in a runtime exception).
As a contrived example of how not to do things:
var alert := new UIAlertView().initWithTitle(..) .... ;
Without the parentheses after the class name, code completion will not offer the initializer methods for code completion at all, neither with the init prefix nor without. If you add these parentheses then you effectively complete the initialization of the new instance, using the default initializer. Since initializers are instance methods, code completion will now offer these initializers, complete with their init prefixes since you are no longer in the special case “constructor” mode that would have these removed.
Of course, if you prefer you can also use the more explicit alloc/init combination directly, even in Oxygene, in which case new is not involved at all and you invoke the initializers using their full name:
var alert := UIAlertView.alloc.initWithTitle(..) .... ;
So that’s if there is a problem. But if the operation was successful or cancelled, we simply dismiss the view controller. At this point the current view is the application view, since the message compose view has already been dismissed, so the user is returned to the home screen.
As I understand it, Apple take a dim view of an application forcibly exiting under any circumstances by any other means, even if there is no point in the application hanging around further. Though at least in this case my app is just about as frugal as it is possible to be, w.r.t consuming system resources. 🙂
Vive La Difference!
As a result of the imposed SMS composer view on iOS, this means that there is a further, subtle difference between the iOS and Android versions of the app, one that I chose to introduce.
On Android, since the app is able to send the SMS directly without user intervention, the app exits immediately that the SMS has been sent. When confirmation that the SMS was sent by the device, a popup toast message confirms this for the user. If there is an error, then the popup toast message tells them of that too.
Worth bearing in mind of course is that the user knows full well that my application sends SMS messages. Not only is it the entire raison d’etre of the application, but “Sends SMS” is the one and ONLY permission that the app requires when it is installed !
In the iOS app, since the user has already had to effectively confirm the sending of the message, I don’t bother distracting them with a further confirmation of the successful sending of the message, only of any problem that prevented it from being sent.
Currently this means that if the Android app experiences a problem sending the TXT then the user will have to re-launch the app and re-enter the code to try again.
But there is another significant difference between the Android and iOS platforms which means I don’t consider this a problem (and actively chose this approach).
Lean and Agile
On Android, the small size of the app and perhaps differences in the way apps are launched, means that the app launches all but instantaneously, so on the probably rare occasions that there are problems sending an SMS, this shouldn’t be a problem (of course, I can revisit this approach if I get complaints or simply decide to make the change anyway).
On iOS, the application is significantly larger (1.9 MB on iOS vs 22 KB on Android) and the iOS launcher seems to work in a quite different way, presenting a launch screen (which I am sure is subject to a system imposed delay to allow the user to appreciate it!) before presenting the application itself.
Frustratingly, the launch screen also appears to be the mechanism by which iOS determines the screen sizes supported by your app and having this reported correctly to my app is crucial to the scaling of my UI on different devices, so it seems I have to provide launch screens for each device, even if I suspect this is contributing to the startup-time of my app.
Hey ho. (and if anyone knows any different, I’d love to hear from you!)