Visual Studio Mac – Intellisense & Update woes

I’ve been using Visual Studio on Mac quite a lot recently, and it’s always updating itself – a lot more often than I’d like to be honest – but almost every-time it updates, when I load my project I’m greeted with a screen full of red squiggles from Intellisense.

It seems that on my system the old cache files are not purged and there is a mismatch that makes Intellisense go crazy. The code still compiles and runs, but it’s hard to work with.

For me, the fix I’ve found has been to just dump the cache folder into the trashcan & re-launch Visual Studio.

For anyone wanting to do this, just navigate to your ~/Library folder, then Caches, and finally scroll down to the Visual Studio folder there. Just dump that entire folder in the trash and Intellisense will be much happier when you restart.

ReactNative – coloring text with a prop string

Update: While exploring further, I discovered that my issue was a lack of curly braces and not something more complex! Turns out you don’t need to do this whole thing with a new object, you can just use the var directly in a style parameter, as follows:

      <View style={{ flex: 1, flexDirection: 'row', backgroundColor:this.props.background }}>
        {this.props.children}
      </View>

Or even append it to an existing style:

<View style={[styles.CalcButton, {backgroundColor: this.props.background}]}>

I’ve been exploring ReactNative recently, as an alternative to using Unity3D to develop multi-platform apps for iOS/Android.

While I’m not a big fan of JavaScript, this framework seems to be worth the pain for non-graphic intense apps. I’ve been working through the tutorials and exploring the system myself for my own education. One thing that wasn’t obvious was how to set the color of a piece of text without adding an entry to the style-sheet.

I have a simple hello-world type component that renders a simple greeting with the passed name in props and I wanted to be able to add a color to the greeting, without adding a style-sheet entry.

Ideally I wanted something that looked like this:

<Greeting name="Colin" color="red"/>;

Acting on the name value is pretty trivial, but passing the color string and converting it to a style required some more google searching. I found similar questions and answers but nothing direct, so after a little experimentation, I got the following code to work:

class Greeting extends React.Component{
  render() {
    var colorStyle = Object.assign(
      {color : this.props.color}
    );
    return (
      <Text style={colorStyle}>
        Hello {this.props.name}!</Text>
    );
  }
}

This works by creating an object with a key:value pair of color and the color string passed. This style is then used in the Text object to set the color of the text.

This will work with a hex color too.

Unity3D and background audio

I recently developed an app for a friend that plays a lot of background audio for both iOS and Android.

For iOS, it was pretty easy to just use the normal Unity3D audio methods, and even when the app was minimized, or the device was sleeping, the audio would continue in the background and even follow looping and the SetScheduledEndTime settings.

However, for Android the situation was very different. Audio would stop playing when the App was minimized, or the device was put in sleep mode. This was true even when I set the background flag.

I ended up getting around this by creating a NativeAudio class that hooked into a plugin for the Android version. This plugin used the native Android methods associated with the MediaPlayer class. I also moved all the audio files to the Streaming Assets folder so the native player could access them.

For iOS I created a version of the NativeAudio class that just hooked back into the  standard AudioSource class, but i modified the loading of the AudioClips to ‘download’ them from the Streaming Assets folder.

Maybe one day I’ll put together a video tutorial on this.

Visual Studio Community Mac 7.5 and Quick Fix issues

I use VSC for all my C# work on my Mac, and noticed that the quick-fix menu had stopped working properly. It used to suggest Using statements, or even prefixes to add to objects that came from another namespace.

I finally found a post that came up with a fix:

https://developercommunity.visualstudio.com/content/problem/249423/using-imports-no-longer-suggested-in-the-quick-fix.html

So, I needed to enable Source Analysis under Text Editor. Now it works as before!

Using Android WebView to display a webpage on top of the Unity App view

Hello and welcome to my tutorial on how to show a WebView on top of your Android Unity App, while still allowing the user to interact with your Unity UI.

You can watch the video of this tutorial at https://youtu.be/r1hLo5C50wE.

This tutorial assumes a reasonable knowledge of Unity, C#, Android Studio and Java. The source code for this tutorial can be found at https://github.com/cwgtech/androidwebview.

The plan is to extend the plugin created in my previous tutorials by adding a method that will create an android layout containing an Android WebView object and a blank TextView. We’ll adjust the height of the TextView to create space at the top of the layout that will allow the user to still see and interact with a portion of the Unity viewspace. We’ll add this layout to our App’s content view which will place it on top of the Unity view.

We’ll also add a method to remove this layout from the content view, returning the full screen to Unity.

Get started by loading up the previous version of this project in Unity and the MyPlugin project in Android Studio. If you don’t have it, you can download it from https://github.com/cwgtech/AndroidActivityResult.

Using Android Studio, open the MyPlugin java source and add the following variable declarations above the first method definition:

private LinearLayout webLayout;
private TextView webTextView;
private WebView webView;

We’re going to use these vars to store references to the objects we create when the webview is displayed. This will allow the plugin to close and deallocate those objects when the webview is closed.

Add the following method to the body of the plugin:

public void showWebView(final String webURL, final int pixelSpace)
{
    mainActivity.runOnUiThread(new Runnable() {
    @Override
    public void run() {
        Log.i(LOGTAG,"Want to open webview for " + webURL);
        if (webTextView==null)
            webTextView = new TextView(mainActivity);
        webTextView.setText("");
        if (webLayout==null)
            webLayout = new LinearLayout(mainActivity);
        webLayout.setOrientation(LinearLayout.VERTICAL);
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
        LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
        mainActivity.addContentView(webLayout,layoutParams);
        if (webView==null)
            webView = new WebView(mainActivity);
        webView.setWebViewClient(new WebViewClient());
        layoutParams.weight = 1.0f;
        webView.setLayoutParams(layoutParams);
        webView.loadUrl(webURL);
        webLayout.addView(webTextView);
        webLayout.addView(webView);
        if (pixelSpace&gt;0)
            webTextView.setHeight(pixelSpace);
        }
    });
}

The method showWebView takes two parameters, the URL of the webpage you want to display, and the number of screen pixels the layout needs to reserve for the Unity UI. This version assumes that the Unity UI is at the top of the screen and pushes the WebView down, you’ll need to modify the order the views are added to the layout if this is not what you want.

First, we create the TextView and set its contents to an empty string.

Next we create the LinearLayout and set its orientation to vertical, and its layout so that it will fill its parent object, and then add it to the ContentView for our activity.

Lastly, we create the actual WebView and we assign the WebViewClient to be a default WebViewClient. This tells our WebView how to handle links, and with the default client, the links will be opened in our WebView. Without this, when the user clicks on a link, Android will pop-up a chooser asking the user what app they want to send the link to.

We also set the weight of the WebView to 1, which means the layout system will give our WebView as much space as it can. Finally, we tell the WebView to load the URL passed to this method.

The TextView and WebView are added to the LinearLayout object, with the order they are added determining the order they will appear on screen, and the height of the TextView is set to the number of screen pixels we want to push the WebView down by.

We need to add one more method that will allow our Unity app to remove the Layout when it’s no longer needed. Add the following code:

public void closeWebView(final ShareImageCallback callback)
{
    mainActivity.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            if (webLayout!=null)
            {
                webLayout.removeAllViews();
                webLayout.setVisibility(View.GONE);
                webLayout = null;
                webView = null;
                webTextView = null;
                callback.onShareComplete(1);
            }
            else
                callback.onShareComplete(0);
        }
    });
}

This method is going to reuse the ShareImage callback interface. We could create a new interface just for this method, but there is no harm in using an existing interface that can do the same job, which is to let Unity know when we’ve closed the layout.

To remove the layout, first remove all it’s child views, then set it’s visibility state to GONE. This will cause it to remove itself from its parent and mark it for garbage collection. Setting the vars that hold the references to our views to NULL will also allow the garbage collection system to free the memory used by them.

Lastly, trigger the supplied callback passing a 1 if the close happened as expected, or a 0 if the LinearLayout had already been closed.

That completes the modifications to the plugin, so you can go ahead and let Gradle build it and copy the updated AAR to the Plugin folder in the Unity project.

Switch back to Unity where we’ll modify the canvas object in the hierarchy view to include a new layer for our WebView, but first double click the script PluginTest to open it in Visual Studio.
Add the following two lines to the C# code, after the other public UI vars:

public RectTransform webPanel;
public RectTransform buttonStrip;

These will hold references to the UI objects we’ll create later. The webPanel is the root UI object that will contain all the objects that will be displayed when the WebView is on screen, and buttonStrip holds the title text, and the close button.

Now add the following methods that will call our Java methods, but only if we’re on an Android platform:

public void OpenWebView(string url, int pixelShift)
{
    if (Application.platform == RuntimePlatform.Android)
    {
        PluginInstance.Call("showWebView", new object[] { url, pixelShift });
    }
}

public void CloseWebView(System.Action<int> closeComplete)
{
    if (Application.platform == RuntimePlatform.Android)
    {
        PluginInstance.Call("closeWebView", new object[] { new ShareImageCallback(closeComplete) });
    }
    else
        closeComplete(0);
}

These methods are just wrappers for the Java code and pass the parameters directly to the plugin.
Next, add the method we’ll connect to a UI button that will figure out how much space to reserve at the top of the display and then pass that with the URL to our Java wrapper.

public void OpenWebViewTapped()
{
    Canvas parentCanvas = buttonStrip.GetComponentInParent<canvas>();
    int stripHeight = (int)(buttonStrip.rect.height * parentCanvas.scaleFactor + 0.5f);
    webPanel.gameObject.SetActive(true);
    OpenWebView("http://www.cwgtech.com", stripHeight);
}

We get a reference to the Canvas object that our buttonStrip belongs to, and then use it’s scaling factor along with the height of our ButtonStrip to calculate how many screen pixels we need to push the webview down by. Enable the WebPanel and pass the URL and height to our Java wrapper.

Add the following method:

public void CloseWebViewTapped()
{
    CloseWebView((int result) =>
    {
        webPanel.gameObject.SetActive(false);
    });
}

This method will be connected to the close button child of the buttonStrip. It simply calls our Java wrapper, using the inline function to hide the WebPanel object once the Android views have been cleaned up and removed.

Save the file and return to Unity. Wait a few seconds to let Unity recompile the C# code and then expand the Canvas object.

Right click on the Canvas object, and select UI, then Button and left click. This will create a button in the middle of the screen called Button (1). Rename it to browseButton and expand it. Change the default text on the child Text object to ‘Browse’.

Highlight the browseButton again click the + button on the On Click list of the button script. Now drag the Main Camera object into the reference holder. Click the function selector, click PluginTest and then OpenWebViewTapped.

Right click on the Canvas object, then click UI, then Panel. Rename the panel created to WebPanel. Click on the color gizmo and set the color to pink (#FFD4F7) and alpha to 255.

Right click on WebPanel and then click UI, Image. Click on the Rect Transform gizmo, and select top center, horizontal stretch while hold shift and alt (option on mac). This will move the image to the top of the screen and make it fill the view horizontally. Set the height to 50 and the color to black. Rename the Image to ButtonStrip.

Right click on ButtonStrip and then UI, Text. Click the Rect Transform gizmo and set stretch for vertical and horizontal, while holding shift & alt (option on mac). This will cause the Text object to fill the image area. Set the font size to 28, the color to White, and the text to ‘Web Page View’.

Click the checkboxes to center the text both horizontally and vertically.

Right click on the ButtonStrip again and click UI,Button. Click the Rect Transform gizmo and then top right anchor with shift & alt held (option on mac). Set the width and height of the button to 24. Expand the button object and set the default text of the Text child to ‘X’. Rename the button to ‘CloseButton’.

Just like we did for the BrowseButton, connect the OnClick event for the CloseButton to the CloseWebViewTapped method of PluginTest.

Highlight the Main Camera, and in the PluginTest script object, drag the WebPanel object to the Web Panel holder and the ButtonStrip object to the Button Strip holder. Highlight the WebPanel object in the hierarchy and disable it.

Save and run the scene. If you click the Browse button, you’ll see the WebPanel appear and the close button will hide it. There will be no actual webview as we’re not yet running on an Android platform. Stop execution of the player.

Click ‘File’ and then ‘Build Settings’. Click on ‘Player Settings’ and then ‘Other Settings’. Scroll down to the configuration area and change ‘Internet Access’ from ‘Auto’ to ‘Require’.

Now click ‘Build and Run’ to build the Android version and run it on your connected device. In my case, I’m running it on an emulator I started earlier. We need to tell Unity to include the Internet permission as Unity is unaware that our plugin is making calls to fetch content from the web and won’t add it by itself.

With the app running, tapping on the browse button will make the WebPanel appear, which is why we made it pink, and if you tap a link in the web content, you’ll see the webview follow the link. Tapping the close button will close the Android webView and also disable the WebPanel, allowing our app to behave as before.

I hope you found this tutorial useful. You can use this to show a help page, or information page directly in your Unity app that is either stored as a HTML file, or is downloaded from a website. You can add more controls to the Unity Canvas to allow you to navigate forwards and backwards, and maybe jump to a specific URL.

As always, you can follow me on Twitter @cwgtech, or check out my youtube channel at https://www.youtube.com/channel/UCdrrB0J4ovI4xQkqiK4HEiw. Please feel free to leave any comments or suggestions below, or let me know how you customized this technique for your own purpose. Subscribe to my youtube channel to get notified when I post a new tutorial.