The aim of my application is to be able, in offline mode, to open an html archive downloaded earlier in an html wrapper consisting of an iframe, or else in online mode to simply display a URL in the webview (no problem with the the online mode).
So, for offline mode, I have an html file defined in MauiAsset, consisting of an iframe in which I’m going to replace the source with the path to my local file
All this works perfectly on Android, and also on iOS simulator.
On a physical iPhone, however, it’s impossible. All I get is a blank screen.
Here the HTML Wrapper asset :
<!DOCTYPE html>
<html>
<head>
<meta name="viewport"
content="width=device-width" />
<meta http-equiv="Content-Type"
content="text/html; charset=utf-8" />
<style>
body,
html {
margin: 0;
padding: 0;
border: 0;
overflow: hidden;
height: 100%;
max-height: 100%;
}
.center {
margin: auto;
width: 50%;
border: 3px solid green;
padding: 10px;
float: right;
}
</style>
</head>
<body>
<script type="text/javascript">
[SOME JAVASCRIPT]
</script>
<iframe name="mosplayer"
scrolling="no"
frameborder="0"
src="{2}" <!-- I replaced the src by the path of my local file : file://[PATH]-->
style="width:100%;height:100%;"></iframe>
</body>
</html>
Here’s the XAML code :
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:Mos.ScormComponents.MAUI.Controls.ViewModels"
x:Name="this"
x:Class="Mos.ScormComponents.MAUI.Controls.ScormWebviewControl">
<WebView x:Name="myWebView" Navigating="myWebView_Navigating">
</WebView>
</ContentView>
Here’s the C# code
private const string BRIDGE_NAME = "jsbridge";
public static readonly BindableProperty SourceUrlProperty = BindableProperty.Create(
nameof(SourceUrl),
typeof(string),
typeof(ScormWebviewControl), defaultValue: null, propertyChanged: OnSourceUrlPropertyChanged);
public string SourceUrl
{
get => (string)GetValue(SourceUrlProperty);
set => SetValue(SourceUrlProperty, value);
}
public static readonly BindableProperty TrackingDatasProperty = BindableProperty.Create(
nameof(TrackingDatas),
typeof(IList<TrackingData>),
typeof(ScormWebviewControl), defaultValue: new List<TrackingData>(), propertyChanged: OnTrackingDatasPropertyChanged);
public IReadOnlyCollection<TrackingData> TrackingDatas
{
get => (IReadOnlyCollection<TrackingData>)GetValue(TrackingDatasProperty);
set => SetValue(TrackingDatasProperty, value);
}
public static readonly BindableProperty IsTerminatedCommandProperty = BindableProperty.Create(
nameof(IsTerminatedCommand),
typeof(ICommand),
typeof(ScormWebviewControl));
public ICommand IsTerminatedCommand
{
get => (ICommand)GetValue(IsTerminatedCommandProperty);
set => SetValue(IsTerminatedCommandProperty, value);
}
private bool _isLocalFile = false;
private bool _isFinishing = false;
internal readonly ScormBusinessLogic scormBusinessLogic;
public ScormWebviewControl()
{
InitializeComponent();
scormBusinessLogic = new();
}
protected override void OnHandlerChanged()
{
base.OnHandlerChanged();
#if ANDROID
Android.Webkit.WebView androidWebview = myWebView.Handler.PlatformView as Android.Webkit.WebView;
androidWebview.Settings.JavaScriptEnabled = true;
androidWebview.Settings.AllowFileAccess = true;
androidWebview.Settings.AllowFileAccessFromFileURLs = true;
androidWebview.Settings.AllowUniversalAccessFromFileURLs = true;
androidWebview.ClearCache(true);
#elif MACCATALYST || IOS
if (myWebView.Handler.PlatformView is WebKit.WKWebView wkWebView)
{
wkWebView.Configuration.Preferences.SetValueForKey(Foundation.NSObject.FromObject(true), (Foundation.NSString)"allowFileAccessFromFileURLs");
wkWebView.Configuration.DefaultWebpagePreferences.AllowsContentJavaScript = true;
wkWebView.Configuration.Preferences.JavaScriptEnabled = true;
wkWebView.Configuration.Preferences.JavaScriptCanOpenWindowsAutomatically = true;
}
#endif
}
bool _isTrackingDatasSet = false;
bool _isSourceSet = false;
private static async void OnSourceUrlPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var strValue = newValue?.ToString() ?? string.Empty;
var control = bindable as ScormWebviewControl;
control._isSourceSet = strValue != string.Empty;
if (control._isSourceSet && control._isTrackingDatasSet)
await control.SetSourceAsync();
}
private static async void OnTrackingDatasPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = bindable as ScormWebviewControl;
control._isTrackingDatasSet = newValue != null;
if (control._isSourceSet && control._isTrackingDatasSet)
await control.SetSourceAsync();
}
private async Task SetSourceAsync()
{
_isLocalFile = IsLocalPath(SourceUrl); //Determine if we should
if (_isLocalFile)
{
await GetOfflineSourceAsync(SourceUrl);
}
else
{
myWebView.Source = SourceUrl;
}
}
private async Task GetOfflineSourceAsync(string folderPath)
{
if (!Directory.Exists(folderPath))
throw new DirectoryNotFoundException();
myWebView.Source = new HtmlWebViewSource()
{
BaseUrl = folderPath,
Html = await scormBusinessLogic.GetHtmlContentAsync(folderPath, TrackingDatas) // Get the html formatted content
};
}
My iPhone console displays this error message:
WebPageProxy::didFailProvisionalLoadForFrame: frameID=25, isMainFrame=0, domain=<private>, code=-1100, isMainFrame=0
So I decided to test only the iFrame’s operation by having it display any web page, and I got exactly the same error
Even with :
<iframe name="mosplayer"
scrolling="no"
frameborder="0"
src="https://www.google.com"
style="width:100%;height:100%;"></iframe>
And if I set the source of my webview directly on www.google.com, it works.
Here my info.plist file :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/appicon.appiconset</string>
<key>UILaunchStoryboardName</key>
<string>MauiSplash</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>google.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
</dict>
</dict>
</dict>
</plist>
And how I get the html formatted content :
internal class ScormBusinessLogic
{
private const string LOCAL_HTML_FILE = "Assets/HtmlWrapper.html";
private const string IMS_MANIFEST_FILE_NAME = "imsmanifest.xml";
private readonly ScormBeanFactory scormBeanFactory;
private readonly ScoJsParamsFactory scoJsParamsFactory;
public ScormBusinessLogic() {
scormBeanFactory = new();
scoJsParamsFactory = new();
}
public async Task<string> GetHtmlContentAsync(string folderScormPath, IReadOnlyCollection<TrackingData> trackingDatas)
{
var imsManifestFilepath = Path.Combine(folderScormPath, IMS_MANIFEST_FILE_NAME); //Irrelevant for the issue
var dataDictionnary = XmlUtils.DeserializeXmlToDictionnary(imsManifestFilepath); //Irrelevant for the issue
string htmlContent = await FileUtils.ReadAssetFile(LOCAL_HTML_FILE); //Read the MauiAssetFile
var bean = scormBeanFactory.CreateModel(dataDictionnary, folderScormPath); //Irrelevant for the issue
var jsparams = scoJsParamsFactory.CreateModel(bean, trackingDatas ?? new List<TrackingData>()); //Irrelevant for the issue
var htmlContentFormatted = htmlContent.Replace("{0}", bean.ScoId)
.Replace("'{1}'", jsparams.ToJavascriptObject())
.Replace("{2}", $"file://{bean.IndexFile}"); //bean.IndexFile = the path on the device of the downloaded file
Console.WriteLine(htmlContentFormatted);
return htmlContentFormatted;
}
I’m completely lost and I don’t really understand what’s going on, I have no other idea but to come and post here.
I’ve tried to add some keys to the info.plist, add some properties to the wkWebView, to load other urls, everything I found on the net, I’ve no idea what’s going on
Laodhy is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.