monotouch dispose UIWebView - iphone

Using a UIWebView in my application and I cant seem to release the memory it's using.
I have a UIButton which loads a view controller which holds the UIWebView.
When the webView is loaded the Real Memory (checking it using Instruments) rises but doesn't get lower after my attempts to release it.
When the button is pressed:
if (childBroswer != null)
{
childBroswer.Dispose();
childBroswer = null;
}
childBroswer = new ChildBrowser(url);
AppDelegate d = (AppDelegate)UIApplication.SharedApplication.Delegate;
if ( d.rootNavigationController.RespondsToSelector(
new MonoTouch.ObjCRuntime.Selector("presentViewController:animated:completion:")))
{
d.rootNavigationController.PresentViewController(childBroswer, true, null);
}
else
{
d.rootNavigationController.PresentModalViewController(childBroswer, true);
}
Child browser class:
public partial class ChildBrowser : UIViewController
{
public string _url {get;set;}
UIActivityIndicatorView activityIndicator;
UIWebView webBrowser;
NSUrl nsURL;
NSUrlRequest nsURLRequest;
public ChildBrowser (string url) : base ("ChildBrowser", null)
{
nsURL = new NSUrl(url);
nsURLRequest = new NSUrlRequest(nsURL);
}
public override void DidReceiveMemoryWarning ()
{
// Releases the view if it doesn't have a superview.
base.DidReceiveMemoryWarning ();
// Release any cached data, images, etc that aren't in use.
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
if (webBrowser == null)
{
webBrowser = new UIWebView(new RectangleF(0,41,320,380));
}
this.Add(webBrowser);
webBrowser.LoadFinished += (object sender, EventArgs e) => {
activityIndicator.StopAnimating();
} ;
webBrowser.LoadStarted += (object sender, EventArgs e) => {
if (activityIndicator == null)
{
activityIndicator = new UIActivityIndicatorView(new RectangleF(UIScreen.MainScreen.Bounds.Width / 2 - 50,
UIScreen.MainScreen.Bounds.Height / 2 - 30,100,100));
activityIndicator.Color = UIColor.Black;
}
activityIndicator.StartAnimating();
this.Add(activityIndicator);
} ;
webBrowser.LoadHtmlString("<html><head></head><body></body></html>",null); //stringByEvaluatingJavaScriptFromString:#"document.body.innerHTML = \"\";"];
webBrowser.LoadRequest(nsURLRequest);
closeBrowser.TouchUpInside += handleClose;
// Perform any additional setup after loading the view, typically from a nib.
}
private void handleClose(object sender, EventArgs e)
{
webBrowser.Delegate = null;
webBrowser.StopLoading();
webBrowser.Dispose();
DismissViewController(true,delegate {
closeBrowser.TouchUpInside -= handleClose;
webBrowser.RemoveFromSuperview();
this.Dispose();
} );
}
public override void ViewWillAppear (bool animated)
{
base.ViewWillAppear (animated);
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(true);
//webBrowser.RemoveFromSuperview();
}
public void webViewDidFinishLoad(UIWebView webView)
{
//string u = "";
}
protected override void Dispose(bool disposing)
{
ReleaseDesignerOutlets();
base.Dispose(disposing);
}
What am i missing?
Thank you!

Memory for native objects is not reclaimed when you call Dispose. Calling Dispose only drops the managed reference - but the ObjC runtime (reference counted) won't free the object until there's no native reference as well. See this Q&A for more details...
This means that code such as:
this.Add(webBrowser);
will create such a native reference and is likely (i.e. if not removed elsewhere) to keep the webBrowser instance alive as long as this is alive. If you keep Adding then you memory usage will keep growing.

I'm the OP - It seems that releasing the memory usage of UIWebView is quite hard, even when doing so in Objective-C.
What I have eventually done, which didn't solve the problem but improved the memory release is adding the following lines when closing the UIWebView:
NSUrlCache.SharedCache.RemoveAllCachedResponses();
NSUrlCache.SharedCache.DiskCapacity = 0;
NSUrlCache.SharedCache.MemoryCapacity = 0;
webView.LoadHtmlString("",null);
webView.EvaluateJavascript("var body=document.getElementsByTagName('body')[0];body.style.backgroundColor=(body.style.backgroundColor=='')?'white':'';");
webView.EvaluateJavascript("document.open();document.close()");
webView.StopLoading();
webView.Delegate = null;
webView.RemoveFromSuperview();
webView.Dispose();

Related

requestReview() deprecated in iOS 14, Alternative for xamarin.forms?

I'm using
SKStoreReviewController.RequestReview ();
in a dependency service for Xamarin.Forms. However this is now deprecated from iOS 14. And I want to know how to integrate with the new
UIWindowScene
requestReview(in windowScene: UIWindowScene)
in Xamarin.Forms
First, you can try to add SKStoreReviewController.RequestReview(Window.WindowScene) in the FinishedLaunching method of your xxx.ios->AppDelegate.cs; start the project to see if it is correct.
If the above method goes wrong, using DependencyService is the right way to go.
Here is the interface code:
public interface MyInterface
{
void RequestReview();
}
Here is the implementation method of the interface in ios:
[assembly: Dependency(typeof(MyInterfaceImpl))]
namespace App19.iOS
{
public class MyInterfaceImpl : MyInterface
{
public void RequestReview()
{
var myv = UIDevice.CurrentDevice.CheckSystemVersion(14, 0);
if (myv)
{
UIWindow window = UIApplication.SharedApplication.Delegate.GetWindow();
SKStoreReviewController.RequestReview(window.WindowScene);
}
else
{
SKStoreReviewController.RequestReview();
}
}
}
}
You can call it in the OnStart method inside APP.xaml.cs:
protected override void OnStart()
{
if (Device.RuntimePlatform == Device.iOS)
{
DependencyService.Get<IReviewService>().RequestReview();
}
}
I might have a solution:
using StoreKit;
using UIKit;
public void RequestReview(){
var scene = UIApplication.SharedApplication.KeyWindow.WindowScene;
if (scene is null)
{
//handle the scene being null here
return;
}
SKStoreReviewController.RequestReview(scene);
}
I've found multiple ways of getting the current scene and they both worked when I tested them:
var scene = UIApplication.SharedApplication.KeyWindow.WindowScene;
var alsoScene = UIApplication.SharedApplication.Delegate.GetWindow()?.WindowScene;
You can read more about the difference between the two in this issue
The complete solution where you check the iOS version in order to call the right thing might look like this
using StoreKit;
using UIKit;
public void RequestReview()
{
var isIos14OrAbove = UIDevice.CurrentDevice.CheckSystemVersion(14, 0);
if (isIos14OrAbove)
{
var scene = UIApplication.SharedApplication.KeyWindow.WindowScene;
if (scene is null)
{
//handle the scene being null here
return;
}
SKStoreReviewController.RequestReview(scene);
}
else
{
SKStoreReviewController.RequestReview();
}
}
Hope this helps.

How to play video in iPhone without fullscreen mode using WebView in Xamarin.Forms?

I am using WebView for playing video in my Xamarin.Forms project. On iPhone the video is playing in fullscreen, but I need to play the video within the WebView without going to fullscreen.
I have this issue only on the iPhone. On Android, Windows, and iPad devices the video is playing without fullscreen.
Case 1: Normal WebView
<WebView
x:Name="web_view"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"/>
web_view.Source = videoUrl;
Case 2: Custom webview
On some part, I am using a custom WebView. In this case the video is also being played in full-screen mode.
public class MyWebView : WebView
{
public static readonly BindableProperty UrlProperty = BindableProperty.Create(
propertyName: "Url",
returnType: typeof(string),
declaringType: typeof(MyWebView),
defaultValue: default(string));
public string Url
{
get { return (string)GetValue(UrlProperty); }
set { SetValue(UrlProperty, value); }
}
}
In the above 2 cases, I need to play the video without full-screen mode on the iPhone.
You could set AllowsInlineMediaPlayback as True in Custom Renderer
[assembly: ExportRenderer(typeof(MyWebView), typeof(MyWebViewRenderer))]
namespace CustomWebview.iOS
{
public class MyWebViewRenderer : ViewRenderer<MyWebView, WKWebView>
{
WKWebView wkWebView;
protected override void OnElementChanged(ElementChangedEventArgs<MyWebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
WKWebViewConfiguration configuration = new WKWebViewConfiguration();
configuration.AllowsInlineMediaPlayback = true;
wkWebView = new WKWebView(CGRect.Empty, configuration);
if (Element.Url != null)
{
wkWebView.LoadRequest(new NSUrlRequest(new NSUrl((Element).Url)));
}
SetNativeControl(wkWebView);
}
if (e.NewElement != null)
{
CallVideo();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals(nameof(Element.Url)))
{
wkWebView.LoadRequest(new NSUrlRequest(new NSUrl((Element).Url)));
}
}
public override void Draw(CGRect rect)
{
base.Draw(rect);
Control.Frame = rect;
}
}
}
Added an if statement to exclude the null condition. And then load the URL when you set it in Forms. OnElementPropertyChanged will be called when the property of webview has been changed. So when you set its URL in forms, we could load requests in this method.

How do I style RootElement

I need a way to style Monotouch Dialogs RootElement. I need to change the background and font color.
I'm have created a custom RootElement as below
public class ActivityRootElement : RootElement
{
public ActivityRootElement (string caption) : base (caption)
{
}
public ActivityRootElement(string caption, Func<RootElement, UIViewController> createOnSelected) : base (caption, createOnSelected)
{
}
public ActivityRootElement(string caption, int section, int element) : base (caption, section, element)
{
}
public ActivityRootElement(string caption, Group group) : base (caption, group)
{
}
public override UITableViewCell GetCell (UITableView tv)
{
tv.BackgroundColor = Settings.RootBackgroundColour;
return base.GetCell (tv);
}
protected override void PrepareDialogViewController(UIViewController dvc)
{
dvc.View.BackgroundColor = Settings.RootBackgroundColour;
base.PrepareDialogViewController(dvc);
}
}
I am then calling the custom root element as below passing in a custom DialogController
section.Add (new ActivityRootElement(activity.Name, (RootElement e) => {
return new ActivityHistoryDialogViewController (e,true);
}));
The root Element style is not been applied. Any help would be apprciated!!
If you want the color to be the only thing you see in the TableViewController, you need to set the BackgrounView to null. There is a view overtop to apply the styling, which will hide the color you are looking for.
Try this:
public override UITableViewCell GetCell (UITableView tv)
{
tv.BackgroundColor = Settings.RootBackgroundColour;
tv.BackgroundView = null;
return base.GetCell (tv);
}
protected override void PrepareDialogViewController(UIViewController dvc)
{
dvc.TableView.BackgroundColor = Settings.RootBackgroundColour;
dvc.TableView.BackgroundView = null;
base.PrepareDialogViewController(dvc);
}
In order to get this to work, I had to override the MakeViewController method and cast the UIViewController that it normally returns to a UITableViewController, then make my edits.
protected override UIViewController MakeViewController()
{
var vc = (UITableViewController) base.MakeViewController();
vc.TableView.BackgroundView = null;
vc.View.BackgroundColor = UIColor.Red; //or whatever color you like
return vc;
}

UIWebview does not get cookies

My UIWebview is not saving cookies at all. It is stuck in cookieless mode and as a result, the webpage is putting the cookie information in the url instead.
Here is what I am doing. I have a singleton for my UIWebview and 4 UITabBar buttons on the bottom. Each tabbar button takes the user to a different page on the site. Now, when the user is navigating the site through the webview itself, everything is fine and session persists. But the second a user clicks on a tabbar button, the session gets reset.
I even set NSHttpCookieStorage to always accept cookies. Still no go.
Here is the code for my singleton for UIWebview and NSMutableUrlRequest
public static UIWebView instance;
public static NSMutableUrlRequest urlRequest;
public static NSUrlConnection connection;
public static NSHttpCookieStorage cookie;
static bool TokenSent = false;
public UIWebViewSingleton () {}
public static UIWebView Instance
{
get
{
if (instance == null)
{
Debugger.Debug ("new uiwebview created");
cookie = NSHttpCookieStorage.SharedStorage;
cookie.AcceptPolicy = NSHttpCookieAcceptPolicy.Always;
connection = new NSUrlConnection();
instance = new UIWebView(new RectangleF(0f, 0f, 320f, 416f));
instance.ScalesPageToFit = true;
instance.LoadStarted += delegate {
UIApplication.SharedApplication.NetworkActivityIndicatorVisible = true;
};
instance.LoadFinished += delegate {
UIApplication.SharedApplication.NetworkActivityIndicatorVisible = false;
AcquireUserId();
};
instance.MultipleTouchEnabled = false;
}
return instance;
}
}
public static NSMutableUrlRequest UrlRequest
{
get
{
if (urlRequest == null)
{
urlRequest = new NSMutableUrlRequest();
}
return urlRequest;
}
}
This is how I change to a page based on which button was pressed. The back button is working fine.
case BTN_BACK:
btn_ret.TouchUpInside += delegate(object sender, EventArgs e) {
UIWebViewSingleton.Instance.GoBack();
};
break;
case BTN_GUIDE:
btn_ret.TouchUpInside += delegate(object sender, EventArgs e) {
UIWebViewSingleton.UrlRequest.Url = new NSUrl(StaticFileNames.GuideUrl);
UIWebViewSingleton.Instance.LoadRequest (UIWebViewSingleton.UrlRequest);
};
break;
case BTN_HOME:
btn_ret.TouchUpInside += delegate(object sender, EventArgs e) {
UIWebViewSingleton.UrlRequest.Url = new NSUrl(StaticFileNames.BaseUrl);
UIWebViewSingleton.Instance.LoadRequest (UIWebViewSingleton.UrlRequest);
};
break;
Does anyone have any ideas as to why my UIWebView is not accepting cookies? I'm still confused about NSHttpCookieStorage and if I'm using that correctly.
Thanks a lot.
I went with this work-around which was sufficient.
Since the cookie is in the url, I just parse it out and put programmatically put it into the urls that the tabbar buttons point to. Problem solved.

Cant redraw UILabel after updating text

I'm trying to create a timer which updates a label every tick, This is what I have so far:
public override void ViewDidLoad ()
{
timer = new Timer ();
timer.Elapsed += new ElapsedEventHandler (TimerOnTick);
timer.Interval = 1000;
timer.Start ();
}
private void TimerOnTick (object obj, EventArgs ea)
{
timerLabel.Text = DateTime.Now.ToString ();
timerLabel.SetNeedsDisplay ();
}
But this doesn't update the label. In debug I can see that timerLabel.Text is being set, but I cant get the view to update or redraw.
How can I get my view to redraw after I update my timerLabel.text?
The label is not being updated because System.Timers.Timer invokes the handler on a separate thread. You cannot change ui elements in a thread other than the main.
Enclose your handler code to be executed on the main thread:
private void TimerOnTick (object obj, EventArgs ea)
{
this.InvokeOnMainThread(delegate {
timerLabel.Text = DateTime.Now.ToString ();
timerLabel.SetNeedsDisplay ();
});
}