iOS plugin – Native alert

Spoiler-Alart

The following article will build a plugin for native alert in Unity for iOS. The code requires threes languages C#, C and Objective-C. It is not necessary to master those three languages but the article won’t explain syntax.

It is recommended to read the Unity doc on iOS plugin. Though as usual, Unity only provides the surface. I wanted to use delegate instead of the UnitySendMessage.

The C# side is the consumer side, we wish to print a native alert and get feedback fo the user input. The method should take none to many naming for the buttons.  We will limit to three but you can extend as you wish.

public class test : MonoBehaviour
{
    void OnGUI()
    {
       if(GUILayout.Button("First", GUILayout.MinWidth(200), GUILayout.MinHeight(100))) {
          iOSNativeAlert.ShowDialog("AlertTitle", "Alert message", Callback , "Ok Bt";);
       }
       if(GUILayout.Button("Second", GUILayout.MinWidth(200), GUILayout.MinHeight(100))) {
          iOSNativeAlert.ShowDialog("AlertTitle", "Alert message", Callback);
       }
       if(GUILayout.Button("Third", GUILayout.MinWidth(200), GUILayout.MinHeight(100))) {
          iOSNativeAlert.ShowDialog("AlertTitle", "Alert message", CallbackOther,"Ok Button","Else","Cancel");
       }
    }

    private void Callback(string result)
    {
        Debug.Log("Ok button was pressed so it works just fine");
    }
    private void CallbackOther(string result)
    {
        if(result == "Ok Button"){
            Debug.Log("Ok button is surely pressed so it works just fine");
        }else if(result == "Cancel"){
            Debug.Log("CancelBtn pressed most likely resumed");
        }
    }
}

Here comes the C# plugin side:

using UnityEngine;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using AOT;
public static class iOSNativeAlert
{
    public delegate void AlertDelegate(string str);
    [DllImport("__Internal")]
    private static extern void _showDialog (string title, string msg,
       string actionFirst, string actionSecond,string actionThird,
AlertDelegate onCompletion);

    static Action<string>callback = null;

    [MonoPInvokeCallback(typeof(AlertDelegate))]
    private static void OnCompletionCallback(string str){
        if(callback != null) { callback(str); }
        callback = null;
    } 

    public static void ShowDialog (string title, string msg,
Action<string>onCompletion, string actionFirst = "Ok",
string actionSecond= null, string actionThird= null)
    {
callback = onCompletion;
        _showDialog(title, msg, actionFirst, actionSecond, actionThird, OnCompletionCallback);
}
}

ShowDialog is the interface of the class. The last three parameters are defaulted. So, if the user does not provide any naming for button, Ok is done by default. The callback is assigned to be called when the iOS side replies. AlertDelegate is the type to be given to iOS for the onCompletion callback.
The _showDialog comes from the C side, hence the DllImport attribute. The OnCompletionCallback gets a special attribute that will help the conversion to native side. Be helping, I mean it is doing all the work. Basically, it converts the given method into the attribute type.

Now with the native side, ww shall add a UNiOSAlert.h in the Plugin folder:

#import<Foundation/Foundation.h>
@interface UNAlertDialog : NSObject<UIAlertViewDelegate>{}
- (void) showDialogWithTitle: (NSString *) title messageContent:(NSString*) msg
      firstButtonTitle : (NSString *)actionFirstText
     secondButtonTitle : (NSString *)actionSecondText
     thirdButtonTitle : (NSString *)actionThirdText
    onCompletion : (void(*)(const char *)) func;
@end

That declares the interface of the class and convert our method and some of the types into Objective-C. For instance, a string is a NSString and our AlertDelegate has turned into a fancy (void(*)(const char*))func.

And comes the final part, the UNiOSAlert.mm, also in the plugin folder:

#import "UNiOSAlert.h"
extern "C"
{
    typedef void (*OnCompletionCallback)(const char * str);
    void _showDialog(const char *title, const char *msg,
              const char * actionFirstStr,
const char * actionSecondStr,
const char * actionThirdStr,
              OnCompletionCallback testCallback)
    {
        [UnAlertDialog showDialogWithTitle:[NSString stringWithUTF8String : title]
          message:[NSString stringWithUTF8String : msg]
          firstButtonTitle: (actionFirstStr != nil) ?  [NSString stringWithUTF8String : actionFirstStr] : nil
          secondButtonTitle:(actionSecondStr != nil) ?  [NSString stringWithUTF8String : actionSecondStr] : nil
          thirdButtonTitle:(actionThirdStr != nil) ?  [NSString stringWithUTF8String : actionThirdStr] : nil
          onCompletion: callback];
    }
}

@implementation UNAlertDialog
+ (void) showDialogWithTitle:(NSString *) title
           message:(NSString*) msg
           firstButtonTitle : (NSString *)actionFirstText
           secondButtonTitle : (NSString *)actionSecondText
           thirdButtonTitle : (NSString *)actionThirdText
           onCompletion : (void(*)(const char *)) func
{
    UIAlertController* alert = [UIAlertController
               alertControllerWithTitle: title
               message:msg
               preferredStyle:UIAlertControllerStyleAlert];
    [UNAlertDialog setAlertWith : alert actionText : actionFirstText onCompletion : func];
    [UNAlertDialog setAlertWith : alert actionText : actionSecondText onCompletion : func];
    [UNAlertdialog setAlertWith : alert actionText : actionThirdText onCompletion : func];

    UIViewController * viewCtrl = UnityGetGLViewController();
    [viewCtrl presentViewController:alert animated:YES completion:nil];

}
+(void)setAlertWith : (UIAlertController * ) alert actionText: (NSString*)text
   onCompletion : (void(*)(const char *)) func;
{
    if(text != nil )
    {
        UIAlertAction* cancelAction = [UIAlertAction
                          actionWithTitle:text
                          style:UIAlertActionStyleDefault
                          handler:^(UIAlertAction * action) {
                             if(func != nil){
                                func([text UTF8String ]);
                             }
                          }];
        [alert addAction:cancelAction];
    }
}
@end

The extern C section is the C version of our method that will create the bridge between C# and Objective-C. It is a C version of our method.
It is the method we linked in C# with the DllImport attribute. So our C# method with the attribute is a handle to that very method. This is why it was left without implementation.
The typedef is a C version of the delegate we declare in C#. Everything kinda repeats itself.
The implementation calls the showDialog in Objective-C. The rest of the implementation is actually taken from the Apple documentation and basically creates the alert, creates some buttons and assigns the onCompletion method.

So if you go down to the setAlert method, func is actually linked to our Callback and CallbackOther in test.cs. The addresses have been passed down to this call. We could have used UnitySendMessage but it has some limitations.

The only difference I had to change with the iOS original version is the ViewController line since we need to get it from Unity as the engine overwrites the original AppController.

UIViewController * viewCtrl = UnityGetGLViewController();
[viewCtrl presentViewController:alert animated:YES completion:nil];
// was
[self presentViewController:alert animated:YES completion:nil];
Advertisements

4 Comments

  1. Hi, thanks for sharing, did you tested you code on latest Unity5.4.0f3, It gives this error on callback. “IL2CPP does not support marshaling delegates that point to instance methods to native code.”

    Like

    1. I have not used or tested that code lately. Since the error mention “instance methods” you could try to pass a static method instead with the instance as parameter if needed.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s