What is the proper way of changing current UIViewController without a memory leak in MonoTouch? - memory-leaks

I have a base UINavigationController class ( xib-less code ) using the singleton pattern with the fallowing code
[MonoTouch.Foundation.Register("NavController")]
public class NavController : UINavigationController
{
public static NavController Instance = new NavController();
public void ChangePage( UIViewController sender, UIViewController page, double duration )
{
this.PopViewControllerAnimated(true);
this.PushViewController( page, false );
//
SetViewControllers( new UIViewController[]{ page }, false );
if( sender != null )
{
sender.View.RemoveFromSuperview();
sender = null;
}
GC.Collect();
}
}
And also i have 4 UIWebController classes with this structure
[Register("HomePage")]
public class AViewPage : UIViewController
{
UIButton btn = new UIButton( new RectangleF( 0,0, 100, 100) );
UIImageView img = new UIImageView( new RectangleF( 100, 100, 200, 200 );
//and a lot of other widgets
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
//
btn.TouchUpInside += delegate{
AnotherViewPage page = new AnotherViewPage();
NavController.Instance.ChangePage( this, page, 1 );
}
//
this.View.AddSubview( btn );
this.View.AddSubview( img );
}
}
My problem is that with current code, memory is leaking. What am I doing wrong?
Should i override the Dispose(bool) and manually dispose everything on the UIWebController classes ?
I also have a lot of UIImages and UIImageViews on this UIViewControllers, what is the proper way of disposing them ?
I have the feeling that the ChangePage function code is ugly. Help me with a better example please.

A fix is coming in the MonoTouch 4.1 beta release, a temporary workaround is this:
Replace this line:
sender.View.RemoveFromSuperview();
With:
var super = sender.View.Superview;
sender.View.RemoveFromSuperview ();
var scratch = super.Subviews;
The correct fix in the next release will just work. Apologies for that.

Ok, stupid question from the beginning.
If you run in to the same problem, just read a little about PushViewController, PopViewControllerAnimated, ViewDidAppear, ViewDidDisappear and ViewDidLoad.

Related

Hiding the Navigation Bar in Xamarin Project using Mvvmcross

I'm using mvvmcross and implementing the view's interface in code behind. I would like to hide the navigation bar but I have not found a solution yet.
I tried
NavigationController.SetNavigationBarHidden(true, false);
and
NavigationController.NavigationBarHidden = true;
in different methods (ViewDidAppear and ViewWillAppear) but they don't have an impact on the UI.
Maybe someone could give me a hint. :-)
#Edit: Some more information:
My AppDelegate.cs
[Register("AppDelegate")]
public partial class AppDelegate : MvxApplicationDelegate
{
UIWindow _window;
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
_window = new UIWindow(UIScreen.MainScreen.Bounds);
var setup = new Setup(this, _window);
setup.Initialize();
var startup = Mvx.Resolve<IMvxAppStart>();
startup.Start();
_window.MakeKeyAndVisible();
return true;
}
}
Additionally I'm using a BaseView class which inherits from MvxViewController.
Okay, found the solution by myself:
Just paste the following code into the ViewDidLoad method in your MvxViewController class( for example FirstView.cs in many mvvmcross tutorials):
var navController = base.NavigationController;
navController.NavigationBarHidden = true;
I know it is a +6 years old question but came across finding a solution for this using MVVMCross and found out that using this into the xaml of your view should be enough: <NavigationPage.HasNavigationBar>False</NavigationPage.HasNavigationBar>
It should apply for both Xamarin Android and iOS.
This will kill it, let me know if you have questions.
[Register ("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
// class-level declarations
UIWindow window;
MyViewController viewController;
MainViewController mainViewController;
UINavigationController navController;
public UINavigationController NavController { get { return navController; }}
//
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
// visible.
//
// You have 17 seconds to return from this method, or iOS will terminate your application.
//
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
var navController = new UINavigationController();
navController.SetNavigationBarHidden (true, false);
window = new UIWindow (UIScreen.MainScreen.Bounds);
viewController = new MyViewController();
app.SetStatusBarStyle (UIStatusBarStyle.LightContent, true);
navController.PushViewController(viewController, false);
window.RootViewController = navController;
window.MakeKeyAndVisible ();
return true;
}
}
}
The default presenter uses a UINavigationController for the RootController on the window; so you can manipulate the navigation bar globally in the AppDelegate by grabbing it off the window and casting:
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
window = new UIWindow(UIScreen.MainScreen.Bounds);
new Setup(this, window).Initialize();
Mvx.Resolve<IMvxAppStart>().Start();
var navigationBar = ((UINavigationController)window.RootViewController).NavigationBar;
navigationBar.BackgroundColor = UIColor.Black;
navigationBar.BarTintColor = UIColor.Black;
navigationBar.TintColor = UIColor.White;
window.MakeKeyAndVisible();
return true;
}

ViewModel property sort of fatal with VMDisconnectedException

EDIT 2: If you're looking for an answer to a similar problem, check Stuart's answer and my comments on it.
EDIT: I am actually getting a Mono.Debugger.Soft.VMDisconnectedException. I also recently installed Windows 8.1 and Resharper (though Resharper is suspended now).
When I access a very simple list property of my view model in my MVVMCross Xamarin iOS application, the program fails. It doesn't quit most of the time: it acts like it's running. The simulator has a black screen and there is no exception. If I breakpoint on if (messagesViewModel != null) source.ItemsSource = messagesViewModel.Messages; and then type messagesViewModel.Messages into the Immediate Window, everything stops, so I can tell it is failing at this line. If instead I "step over", it never moves to the next line.
I was having similar behavior when I was toggling this code in the MvxTableViewSource:
public override int RowsInSection(UITableView tableview, int section)
{
return 1;
}
My view model looks like this:
public class MessagesViewModel : MvxViewModel
{
private List<BaseMessage> _messages = null;
public List<BaseMessage> Messages
{
get
{
return _messages; //yes, I know I'm returning null
//I wasn't at first.
}
}
public MessagesViewModel()
{
}
}
This is my ViewDIdLoad on the MvxTableViewController:
public override void ViewDidLoad()
{
base.ViewDidLoad();
var source = new MessagesTableViewSource(TableView);
//was binding here, removed it for debug purposes
//failure on second line here
var messagesViewModel = ViewModel as MessagesViewModel;
if (messagesViewModel != null) source.ItemsSource = messagesViewModel.Messages;
TableView.Source = source;
TableView.ReloadData();
}
Some initialization code:
public class App : MvxApplication
{
public App()
{
var appStart = new MvxAppStart<MessagesViewModel>();
Mvx.RegisterSingleton<IMvxAppStart>(appStart);
}
}
public partial class AppDelegate : MvxApplicationDelegate
{
//empty functions removed.
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
Window = new UIWindow(UIScreen.MainScreen.Bounds);
var presenter = new MvxTouchViewPresenter(this, Window);
var setup = new Setup(this, presenter);
setup.Initialize();
var startup = Mvx.Resolve<IMvxAppStart>();
startup.Start();
Window.MakeKeyAndVisible();
return true;
}
}
I suspect whatever the error is, it isn't in any of the code you have posted.
I just created a simple ViewModel:
public class FirstViewModel
: MvxViewModel
{
private List<string> _items = new List<string>() { "One", "Two", "Three"};
public List<string> Items
{
get { return _items; }
set { _items = value; RaisePropertyChanged(() => Items); }
}
}
And a simple View:
[Register("FirstView")]
public class FirstView : MvxTableViewController
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
// ios7 layout
if (RespondsToSelector(new Selector("edgesForExtendedLayout")))
EdgesForExtendedLayout = UIRectEdge.None;
var firstViewModel = ViewModel as FirstViewModel;
var source = new MessagesTableViewSource(TableView);
source.ItemsSource = firstViewModel.Items;
TableView.Source = source;
}
public class MessagesTableViewSource : MvxTableViewSource
{
public MessagesTableViewSource(UITableView tableView) : base(tableView)
{
tableView.RegisterClassForCellReuse(typeof(MessagesCell), new NSString("MessagesCell"));
}
protected override UITableViewCell GetOrCreateCellFor(UITableView tableView, NSIndexPath indexPath, object item)
{
return tableView.DequeueReusableCell("MessagesCell");
}
}
public class MessagesCell : MvxTableViewCell
{
public MessagesCell(IntPtr handle)
: base(handle)
{
var txt = new UILabel(new RectangleF(0, 0, 320, 44));
Add(txt);
this.DelayBind(() =>
{
this.CreateBinding(txt).Apply();
});
}
}
}
And this code runs fine...
I wouldn't completely trust the integration of Xamarin.iOS with the Immediate window - it is better now than it used to be, but I've seen several problems with it before.
Some things to possibly check:
does the above code work for you?
if it does, then what's in your BaseMessage and MessagesTableViewSource classes - perhaps they are causing the problem?
can you use Mvx.Trace("The list is {0}", messagesViewModel.Messages ?? "-null") to view the list? Can you use trace within the ViewModel property get - is it being called? Can you use trace within the ViewModel constructor?
are all your assemblies building against the same versions of things? Are all your assemblies definitely rebuilt? (Check "Build|Configuration Manager")- what version of Xamarin.iOS are you running in VS and in the Mac?

Monotouch MKMapView UIGestureRecognizer problems

I added 3 gesture recognizers to my MapView in IB, a long press, a pan & a pinch. Their delegate is the file's owner. I set them up like so -
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
PanGestureRecognizer.AddTarget(s => { Console.WriteLine("Pan"); } );
LongPressGestureRecognizer.AddTarget(s => { Console.WriteLine("Long press"); } );
PinchGestureRecognizer.AddTarget(s => { Console.WriteLine("Pinch"); } );
}
I also implement this -
public bool ShouldRecognizeSimultaneously (UIGestureRecognizer gestureRecognizer, UIGestureRecognizer otherGestureRecognizer)
{
return true;
}
The problem is, only the Long Press gesture recognizer does anything, the others are completely ignored.
Any ideas/suggestions welcome!
Being fairly new to Monotouch, I didn't realise that when I set the delegate of the MapView in IB to my ViewController, that wouldn't actually work. I needed to create a delegate which is a subclass of UIGestureRecognizerDelegate, and set the delegate of the gestureRecognizer to this, and I added the gestureRecognizer programmatically (though that's probably not necessary) -
private class GestureRecognizerDelegate : UIGestureRecognizerDelegate
{
public override bool ShouldRecognizeSimultaneously (UIGestureRecognizer gestureRecognizer, UIGestureRecognizer otherGestureRecognizer)
{
return true;
}
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
UIPinchGestureRecognizer pinchGestureRecognizer = new UIPinchGestureRecognizer(s => { /* do stuff here */ } );
GestureRecognizerDelegate gestureRecognizerDelegate = new GestureRecognizerDelegate();
pinchGestureRecognizer.Delegate = gestureRecognizerDelegate;
MapView.AddGestureRecognizer(pinchGestureRecognizer);
}
Then, by setting the ZoomEnabled property of the MapView to false, I can control how the map zooms (in my case, I had to prevent the map zooming in beyond a certain threshold, my client wasn't happy with the way you could zoom in & it would then bounce back out to my preset value, which I had working using RegionChanged in the MapView delegate). Don't you love clients!

MonoTouch DialogViewController - why must it be in the first place of a UINavigationController?

I want to use a DialogViewController inside of a UITabViewController.
Problem: Nested elements don't show a navigation bar, and so it is not possible to go back.
When I push my class (inherited from DialogViewController) to a UINavigationController, then the behavior is correct. If I use the same class in a tab of a UITabViewController (even with an underlying UINavigationController), then the behaviour is wrong.
Can anyone help me out?
Although the question is not assisted with some code sample, I made a small example hoping to solve your question. For this example I used the Tabbed Application template which comes with Xamarin.iOS and named it TabbingTest.
The following code goes in the AppDelegate. Change the FinishedLaunching method to contain:
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
window = new UIWindow (UIScreen.MainScreen.Bounds);
var viewControllers = new UIViewController[]
{
CreateTabFor("Test", "first", new TestDialogController ()),
CreateTabFor("Second", "second", new SecondViewController ()),
};
tabBarController = new UITabBarController ();
tabBarController.ViewControllers = viewControllers;
tabBarController.SelectedViewController = tabBarController.ViewControllers[0];
window.RootViewController = tabBarController;
window.MakeKeyAndVisible ();
return true;
}
Then add the following methods:
private int _createdSoFarCount = 0;
private UIViewController CreateTabFor(string title, string imageName, UIViewController view)
{
var controller = new UINavigationController();
controller.NavigationBar.TintColor = UIColor.Black;
var screen = view;
SetTitleAndTabBarItem(screen, title, imageName);
controller.PushViewController(screen, false);
return controller;
}
private void SetTitleAndTabBarItem(UIViewController screen, string title, string imageName)
{
screen.Title = NSBundle.MainBundle.LocalizedString (title, title);
screen.TabBarItem = new UITabBarItem(title, UIImage.FromBundle(imageName),
_createdSoFarCount);
_createdSoFarCount++;
}
Create a class named TestDialogController and paste the following code inside.
using System;
using MonoTouch.Dialog;
using MonoTouch.UIKit;
namespace TabbingTest
{
public class TestDialogController : DialogViewController
{
public TestDialogController (): base(UITableViewStyle.Plain,null,false)
{
var root = new RootElement ("Tabbing test"){
new Section (){
new RootElement ("First level", 0, 0) {
new Section (null, "This is the first level."){
new RootElement ("Second level", 0, 0) {
new Section (null, "This is the second level."){
new BooleanElement ("Flipflops", false)
}
}
}
}}
};
this.Root = root;
}
}
}
Now run the application.
You can see that even the nested elements show up nicely in the navigation bar. Even with multilevel nesting.

How can I animate a View to fly in from the bottom of the screen?

I'm trying to figure out how to have a view. Let's call it ThirdView. It should slide up from the bottom of the screen when a user clicks a particular button on SecondView.
You'll want to create the ThirdView in your SecondView and present it as a modal view, passing in the secondView in the constructor. This will be the easiest way of animating it in the way you would like.
var thirdView = new ThirdView(secondView);
this.PresentModalViewController(thirdView, true);
In your third view, you'll want to call the passed-in SecondView and call:
secondView.DismissModalViewControllerAnimated(true);
Here is a complete working example. It is a tad simpler than in chrisntr's answer...though the above example is what I used to figure everything out.
The coolest thing about this method is that for an artistic custom UI (like the one I am building for a game), there is no off-the-shelf UI elements like the TabBar, Navigation bars, etc. The most creative applications don't use standard UI stuff.
In your main.cs file, in your finishedlaunching block:
ViewController myUIV = new ViewController();
window.AddSubview(myUIV.View);
window.MakeKeyAndVisble();
And then in a new code file add this code:
using System;
using System.Drawing;
using MonoTouch.UIKit;
namespace AnimationTest
{
public class ViewController : UIViewController
{
UIButton uib = new UIButton(new RectangleF(100, 100, 40, 40));
public override void ViewDidLoad()
{
Console.WriteLine("UI1");
this.View.BackgroundColor = UIColor.Blue;
uib.BackgroundColor = UIColor.White;
uib.TouchUpInside += delegate {
Console.WriteLine("Hey!");
var vc2 = new SecondController();
PresentModalViewController(vc2, true);
};
this.View.AddSubview(uib);
base.ViewDidLoad();
}
}
public class SecondController : UIViewController
{
UIButton uib = new UIButton(new RectangleF(100, 100, 40, 40));
public override void ViewDidLoad()
{
this.View.BackgroundColor = UIColor.White;
uib.BackgroundColor = UIColor.Red;
uib.TouchUpInside += delegate {
this.DismissModalViewControllerAnimated(true);
};
this.View.AddSubview(uib);
base.ViewDidLoad();
}
}

Resources