Xamarin.iOS WebView disappears after 10th reload - xamarin

I got a basic WebView in Xamarin.Forms like this:
<WebView AbsoluteLayout.LayoutBounds="0, 0, 1, 1" AbsoluteLayout.LayoutFlags="All" x:Name="ChartWebView" Style="{StaticResource BackgroundStyle}">
<WebView.Source>
<HtmlWebViewSource Html="{Binding ChartHTML}" />
</WebView.Source>
</WebView>
I use the WebView to display a Chart.js diagram which works perfectly fine. On my page i got a button where the user can request data.
private async Task RequestData()
{
//Doing some stuff to get data ...
BuildChartHTML();
}
private void BuildChartHTML()
{
var html = GetChartJSConfigString();
Device.BeginInvokeOnMainThread(() =>
{
ChartWebView.Source = new HtmlWebViewSource
{
Html = html
};
});
}
However, on iOS only, everytime on exactly the 10th request/reload the WebView gets blank. It also stays blank after that. I need to re-navigate to this page to get it up and running again. I tried adjusting the height on WebView.Navigated as described in this thread, without success.
The problem doesn't accure on Android with the same code base.
Open to any workarounds if this is a framework bug.
PS: I obviously made sure that the html I'm loading is not broken. I loaded the same data 10 times and i compared the html loaded on the 10th time where the WebView broke. It was exactly the same HTML string.

Turned out to be a problem with iOS WKWebView and the use of HTML canvas (chart.js is renderered on a canvas).
After debugging into my WebView (stupid me not thinking about that possiblity before), i found out that the canvas memory exceeded the limit. With the help of a solution posted here i managed to avoid this problem.
Keep in mind this solution is a little bit hacky. Basically what I've done is, as pointed out in the link above, i "deleted" the canvas everytime i reload my chart. By "deleting" i mean setting the canvas to width and height to zero. Applied to my code the solution looks like this:
Inside my javascript that runs in the browser i added a function like this:
function deleteCanvas() {
//Get the main canvas where the chart is rendered to
var chart = document.getElementById('chart');
chart.height = 0;
chart.width = 0;
}
And then everytime i call my BuildChartHTML() function i call this method in my c# code like this:
Device.BeginInvokeOnMainThread(() =>
{
ChartWebView.Source = new HtmlWebViewSource
{
Html = html
};
ChartWebView.Eval("deleteCanvas()");
});

Related

Instagram/FB-like back navigation w/ animation

I need to add back navigation on swipe. I can do that fairly easily by just adding a swipe listener to the page view and calling goBack. But I really would like the animation that goes with it (in Instagram or FB) where as soon as you start dragging your thumb, the page translates to the right and the previous page starts to translate into view. And then once you get to a certain point it actually performs the navigation.
I tried animating the page, as well as the frame to the right figuring since the view isn't being destroyed it might work. But it doesn't display the page Im navigating back to.
Looking for help on how to accomplish this!
I guess you might have come across the other SO thread answering this question natively.
All you have to do is modify the default gesture recogniser on iOS frame.
export function onNavigatedFrom(args: EventData) {
console.log("Adding gesture...");
const frame = (<Page>args.object).frame;
if (frame.ios && !(<any>frame)._gestureRecognizer) {
const controller = frame.ios.controller;
const popGestureRecognizer = controller.interactivePopGestureRecognizer;
const targets = popGestureRecognizer.valueForKey("targets");
if (targets) {
let gestureRecognizer = UIPanGestureRecognizer.alloc().init();
gestureRecognizer.setValueForKey(targets, "targets");
frame.nativeView.addGestureRecognizer(gestureRecognizer);
(<any>frame)._gestureRecognizer = gestureRecognizer;
}
}
}
export function onNavigatedTo(args: EventData) {
console.log("Back to root page, removing gesture...");
const frame = (<Page>args.object).frame;
if (frame.ios && (<any>frame)._gestureRecognizer) {
frame.nativeView.removeGestureRecognizer((<any>frame)._gestureRecognizer);
(<any>frame)._gestureRecognizer = null;
}
}
Playground Sample

p5.js global and instance mode confusion

I have two p5 sketches, and I want to use them in the same space of a website. So that after clicking a button, the sketches switch. So I made three different files. The first two, are the sketches, and they are wrapped in a function (instance mode). The third sketch is a code written in global mode, because It has the information that loads a button right when the website is first accesed, and it has the information containing what the button is supposed to do: call the other sketches.
The code looks like this:
var playing = false;
var button;
function setup() {
var col = color(185,185,185,150);
button = createButton('Play');
button.position(20,20);
button.parent('black');
button.style("background-color", col);
button.style("padding", "10px 20px");
button.style("font-size", "14px");
button.mousePressed(toggleCanvas); // attach button listener
}
function mousePressed() {
}
function toggleCanvas() {
if (playing) {
if(myp5r){
myp5r.remove();
runSketch();
button.html('Stop');
}else{
runSketch();
button.html('Stop');
}
}else {
if(myp5){myp5.remove();
stopSketch();
button.html('Play');
}else{
stopSketch();
button.html('Play');}
}
playing = !playing;
}
I have been warned, to not mix global and instance mode, although my "common sense" says that it is ok to use a global mode for the button. Because I want it to be loading first, along with the page.
I noticed that weird things happen, though. Like, I get a blank space added beneath the footer, it looks horrible. And, even more worse, button is not appearing in the correct spot in the site. I already ckecked that css has position:relative for example. And the design works in localhost, but not in the site.

canvas.toDataURL() - For bigger canvas-image

I would like to two show a Chart and also present an url with a link to a bigger Chart. The small preview-image looks and works fine:
<canvas id="image"></canvas>
var ct1 = document.getElementById("image").getContext("2d");
ct1.canvas.width = document.getElementById("image").offsetWidth;
ct1.canvas.height = document.getElementById("image").offsetHeight;
var Chart1 = new Chart(ct1).Line(lineChartData1,options);
The canvas is wrapped in a div, that's why offsetWidth and offsetHeight (to fill this additional div-element). Cause of the responsive-design there is no fixed image. Anyway, this works perfectly. For the URL to the "bigger" image I want to have the URL. I know the toDataURL() will help.
var url = document.getElementById("image").toDataURL();
document.write(url);
There are two disturbing problems with it:
The URL with this way exisists and, but the image has no content.
I also want to give the canvas-image a new size, like I managed with ct1.canvas.width and ct1.canvas.height, but it seems I cannot add this to the toDataURL.
What's wrong with the code?
Okay, I think I got it. Chart.js is animating the charts, so the toDataURL() I mentioned in my first question rendered only an empty image. We have to initiate the toDataURL, not before the animation is done. We handle that with the options:
var options = {
onAnimationComplete: done
}
and a tiny function:
function done() {
console.log('done');
var url=document.getElementById("canvas").toDataURL();
document.getElementById("canvas_link").href=url;
}
I think that's all.

MVC Action getting called twice?

I am using Asp.Net MVC3, for a project.
In one of the page, I am using MS Charts. In View I have a Image which shows the chart as follows:
<img src="#Url.Action("RenderCharts", "Home", new
{
XAxisColor = ViewBag.XAxisColor,
YAxisColor = ViewBag.YAxisColor,
})" alt="Charts" runat="server" />
I have 2 CheckBoxes, which is used to change Chart Axes Colors. When the checkbox is clicked, page is submitted and checkbox status is stored and based on that Chart is rendered:
bool XAxisColor = (#ViewBag.XAxisColor) ?? true;
bool YAxisColor = #ViewBag.YAxisColor ?? false;
#Html.CheckBox("chkXAxisColor", XAxisColor, new { #Id = "chkXAxisColor",
onClick = "this.form.submit();" })
X Axis Color
#Html.CheckBox("chkYAxisColor", YAxisColor, new { #Id = "chkScatter",
onClick = "this.form.submit();" })
Y Axis Color
When first time the page is loaded, RenderCharts() Action gets called and Chart is rendered.
But when i Click any of the CheckBox, RenderCharts() Action gets called twice.
I could not understand this issue. I have created a sample Application which can be downloaded from here https://www.dropbox.com/s/ig8gi3xh4cx245j/MVC_Test.zip
Any help would be appreciated. Thanks in advance.
This appears to be something to do with Internet Explorer. Using your sample application, everything works fine in both Google Chrome and Firefox, but when using IE9, there are two Action requests on a postback.
Using the F12 developer tools on the network tab, it shows an initial request to RenderCharts which appeared to be aborted:
The (aborted) line in the middle is, I suspect, the additional request you're seeing. Why this happens, I don't know!
Finally got the answer. The problem was
runat="server"
in the Img tag.
Removing runat fixed the issue.
I can eliminate the IE issue in the following manner by simply using a bit of JQuery instead. A few possible advantages...
It eliminates the cross-browser issue.
It is an unobtrusive approach (not mixing javascript and HTML in the view).
You can update the image via ajax.
Create a new file in the scripts folder (e.g. "chart.js") which will simply attach an anonymous function to the the click events of your checkboxes from the document ready function. You would obviously need to include the script reference in your page as well:
$(document).ready(function () {
// Attach a function to the click event of both checkboxes
$("#chkXAxisColor,#chkScatter").click(function () {
// Make an ajax request and send the current checkbox values.
$.ajax({
url: "/Home/RenderCharts",
type: "GET",
cache: false,
data: {
XAxisColor: $("#chkXAxisColor").attr("checked"),
YAxisColor: $("#chkScatter").attr("checked")
},
success: function (result) {
alert(result);
$("#chart").attr("src", result);
}
});
});
});
Best of all, you get to eliminate the javascript from your view :)
...
<div style="margin: 2px 0 2px 0">
#Html.CheckBox("chkXAxisColor", XAxisColor, new { #Id = "chkXAxisColor" })
X Axis Color
#Html.CheckBox("chkYAxisColor", YAxisColor, new { #Id = "chkScatter" })
Y Axis Color
</div>
...
This is of course a very basic example which does eliminate the IE issue but you could get fancier from there in terms of how you update the image + show a loading gif, etc with only a few more lines.
Hopefully it is a workable solution for you!

Image Map links not firing in a jQuery UI dialog (IE only)

I'm attempting to place an image map into a jQuery UI dialog. Initially, the and are hidden on the page so that I don't have to do any AJAX. When the dialog is triggered, the and are placed in the dialog and the hidden original content has its link to the image map removed.
There are a few links on the image map in tags and in Firefox, Chrome etc the links are positioned correctly and work.
However, in all versions of IE (the web site is SharePoint 2007 and compatibility mode is on), the links do not fire on the image map. You can hover over the rectangles and be shown the link, but the action never fires.
Code used to initialise below:
$(document).ready(function() {
$('.processDiagram').click(function() {
var phase = $(this).attr('title');
var text = $('#'+phase+' div').html();
var mapname = $('#'+phase+' map').attr('name');
$('#'+phase+' map').attr('name', ''); // null out the background map name so it doesn't get confused
var $dialog = $('<p></p>').html(text).dialog({modal:true, autoOpen:false, width:620, title:phase, beforeClose: function(event, ui) { $('#'+phase+' map').attr('name', mapname); }});
$dialog.dialog('open');
return false; // So firefox won't just follow the link
}
}
I could really do with some help here as I have no idea why the links aren't firing.
Thanks,
Steve
So, the reason is the layout being position:relative does a number on IE, moving all of the hotspots to be relative to the body and not to the image map itself.
Solution is to fix that layout issue.

Resources