- Home /
ObjectiveC: How to show a custom UIView on top of Unity
Hi!
I'd like to show a custom view on top of the Unity UIWindow (the main application window).
Here's the code for showing and hiding the view:
- (void) showCustomView { UnitySetAudioSessionActive(true); UnityPause(true);
self._customUIViewController = [[UIViewController alloc] autorelease];
[[self getTopApplicationWindow] addSubview:self._customUIViewController.view];
if(self._customUIViewController != nil)
{
FAWController *fawView = [[FAWController alloc] initWithAPIKey:@"d0d" delegate:self]; //some custom initialization
[self._customUIViewController presentModalViewController:fawView animated:YES];
[fawView release];
}
}
-(void)hideCustomView {
if(self._customUIViewController != nil)
{
[self._customUIViewController dismissModalViewControllerAnimated:YES];
}
[self._customUIViewController.view removeFromSuperview];
self._customUIViewController = nil;
UnityPause(false);
UnitySetAudioSessionActive(true);
}
//helper function
- (UIWindow*) getTopApplicationWindow
{
UIApplication* clientApp = [UIApplication sharedApplication];
NSArray* windows = [clientApp windows];
UIWindow* topWindow = nil;
if (windows && [windows count] > 0)
topWindow = [[clientApp windows] objectAtIndex:0];
return topWindow;
}
The View shows just fine, and it dismisses nicely when I close it, but then Unity doesn't receive any more input (as if something invisible is still being displayed on top).
I'm pretty new to Objective-C, and I'd appreciate any help on this, to dismiss the custom view properly.
Thanks a lot, Bogdan
did anyone ever figure it out? Can I ask how you even got this code to happen? The manual tells me to write stuff in a c# class that doesn't even exist. [DllImport("__Internal")] just gives compile errors.
Answer by saofl · Jul 29, 2016 at 02:47 PM
I realize this is a really old thread, but there are still people watching it so I'll add my .02 cents. The issue you're seeing is because you are trying to present the view of one view controller in another view controller without using view controller containment.
What you need to do is get a reference to the topmost view controller, then add your custom view controller as a child of that controller. This informs the custom view controller that it's view will be managed by another controller.
Below is a code block that gives you an idea of how it works. I have a class I created for Unity code to call for my plugin. This file consists of 2 files iOSPluginClient.h and iOSPluginClient.mm. These files are located in the unity directory Assets/Plugins/iOS. The code sample below is from the .mm file.
Here's the code:
#import "iOSPluginClient.h"
#import <UIKit/UIKit.h>
#import "DemoViewController.h"
@interface iOSPluginClient()
// tracker variable for when we add a contained view controller
@property (nonatomic, strong) UIViewController *presentedController;
@end
@implementation iOSPluginClient
+(instancetype) singleton
{
static id _sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^
{
_sharedInstance = [self new];
});
return _sharedInstance;
}
-(void) showDemoView
{
// if you displayed as a contained controller and removed it then self.presentedController will be nil
if (self.presentedController == nil)
{
// either load as contained controller or as presented controller
// change this to see both ways of displaying your content then remove
// when you know what you want to use
BOOL loadAsContained = true;
// this view controller is defined in a storyboard, get a reference to the containing storyboard
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];
// instantiate the view controller from the storyboard
DemoViewController *demo = [storyboard instantiateViewControllerWithIdentifier:@"DemoVC"];
if (loadAsContained)
{
// add this view controller as a contained controller (child) of the presented view controller
[self addContainedController:demo];
}
else
{
// if you don't want to display as a child, and instead want to present the view controller
// on top of the currently presented controller then use this method instead of the previous one
[[self getTopViewController] presentViewController:demo animated:YES completion:nil];
}
}
else
{
[self removeContainedController:self.presentedController];
}
}
// condensed version
- (UIWindow*) getTopApplicationWindow
{
// grabs the top most window
NSArray* windows = [[UIApplication sharedApplication] windows];
return ([windows count] > 0) ? windows[0] : nil;
}
- (UIViewController*) getTopViewController
{
// get the top most window
UIWindow *window = [self getTopApplicationWindow];
// get the root view controller for the top most window
UIViewController *vc = window.rootViewController;
// check if this view controller has any presented view controllers, if so grab the top most one.
while (vc.presentedViewController != nil)
{
// drill to topmost view controller
vc = vc.presentedViewController;
}
return vc;
}
-(void) addContainedController:(UIViewController*)controller
{
// get a reference to the current presented view controller
UIViewController *parent = [self getTopViewController];
// notify of containment
[controller willMoveToParentViewController:parent];
// add content as child
[parent addChildViewController:controller];
// set frame of child content (for demo inset by 100px padding on all sides)
controller.view.frame = CGRectMake(100.0, 100.0, parent.view.bounds.size.width - 200.0, parent.view.bounds.size.height - 200.0);
// get fancy, lets animate in
controller.view.alpha = 0.0;
// add as subview
[parent.view addSubview:controller.view];
// animation duration
CGFloat duration = 0.3;
// animate the alpha in and bring top views to top
[UIView animateWithDuration:duration
animations:^
{
controller.view.alpha = 1.0;
}
completion:nil
];
// set our tracker variable
self.presentedController = controller;
}
-(void) removeContainedController:(UIViewController*)controller
{
// if fade out our view here just because
[UIView animateWithDuration:0.3
animations:^
{
controller.view.alpha = 0;
}
completion:^(BOOL finished)
{
// inform the child it is being removed by passing nil here
[controller willMoveToParentViewController:nil];
// remove the view
[controller.view removeFromSuperview];
// remove view controller from container
[controller removeFromParentViewController];
// nil out tracker
self.presentedController = nil;
}];
}
}
@end
/*******************************************************
*/
#pragma mark - C String Helpers
/*
********************************************************/
// Converts C style string to NSString
NSString* CreateNSString (const char* string)
{
if (string)
return [NSString stringWithUTF8String: string];
else
return [NSString stringWithUTF8String: ""];
}
NSArray* ExplodeNSStringFromCString (const char* string)
{
if (string)
return [[NSString stringWithUTF8String: string] componentsSeparatedByString:@","];
else
return [NSArray new];
}
// Helper method to create C string copy
char* MakeStringCopy (const char* string)
{
if (string == NULL)
return NULL;
char* res = (char*)malloc(strlen(string) + 1);
strcpy(res, string);
return res;
}
extern "C"
{
// function definition, called from c# or javascript unity code
void displayDemo()
{
// calls the method to display our view controller over the unity controller
[[iOSPluginClient singleton] showDemoView];
}
} // end of extern C block
The c# code I have to call my objective c code is in a file called TestPlugin.cs. The code from that file looks as follows:
using UnityEngine;
// We need this one for importing our IOS functions
using System.Runtime.InteropServices;
public class TestPlugin : MonoBehaviour
{
// Use this #if so that if you run this code on a different platform, you won't get errors.
#if UNITY_IPHONE
// For the most part, your imports match the function defined in the iOS code
[DllImport ("__Internal")]
private static extern string showDemoView();
#endif
// Now make c# methods that can provide the iOS functionality
static void ShowMyCustomView()
{
// We check for UNITY_IPHONE again so we don't try this if it isn't iOS platform.
#if UNITY_IPHONE
// Now we check that it's actually an iOS device/simulator, not the Unity Player. You only get plugins on the actual device or iOS Simulator.
if (Application.platform == RuntimePlatform.IPhonePlayer)
{
// this calls our native method defined above
showDemoView();
}
#endif
}
void Update ()
{
// for demo purposes, tap the screen to present the view controller
if (Input.GetMouseButtonDown(0))
{
// call unity function that triggers call to native iOS code
ShowMyCustomView();
}
}
}
Change
[DllImport ("__Internal")] private static extern string showDemoView();
to
[DllImport ("__Internal")] private static extern void showDemoView();
Took about a full day of debugging to notice this lol
Answer by AlKir · Dec 15, 2010 at 10:28 PM
I had the similar task, and this post was very useful for me http://forum.unity3d.com/threads/56755-Unity-plugin-to-handle-loading-native-Cocoa-UI-s-with-ease
Answer by kannan_nile · Sep 01, 2011 at 01:18 PM
on the -(void)hideCustomView function set dismissModalViewControllerAnimated property to NO instead of YES, it works fine. Unity receives input fine.
Answer by adfx01zoe · Jun 17, 2012 at 03:14 PM
I also have the same issue. I've tried setting the dismiss modal view : no but it doesn't work. Anyone has a solution ?