MvxViewModelRequest is null in ViewDidLoad - ios

I'm using MvvmCross 3.0.12 in an iPad project. Currently, I'm getting a NPE in MvxViewControllerExtensionMethods.LoadViewModel, because touchView.Request is null. This is only happening in a view controller that is inheriting from MvxTableViewController; view controllers inheriting from MvxViewController load and display just fine.
I've set breakpoint in MvxTouchViewsContainer.CreateView -> MyTableViewController.Ctor -> MvxBaseTouchViewPresenter.Show -> MyTableViewController.LoadView; which have all referenced the same instance of the class. Then when I hit a breakpoint in ViewDidLoad, its a new instance and all of the properties including Request are null.
Is this a bug with Xamarin.iOS or am I doing something wrong?

This sometimes happens for view controllers like table, tab bar and collection.
It's caused, I think, by something in the Objective C base class init referencing the View - and thus triggering ViewDidLoad before the C# construction is fully complete. This is a bit like what can happen in 'pure' c# if a base class constructor references a virtual method.
To check this is occurring for your app, put 2 breakpoints in your app - one in the ViewController constructor and one in ViewDidLoad - if ViewDidLoad is fired first, then you know this is the case.
The only way around this I've found is to code around this by triggering a second ViewDidLoad call in the constructor.
public FirstView()
{
// need this additional call to ViewDidLoad because UIkit creates the view before the C# hierarchy has been constructed
ViewDidLoad();
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
if (ViewModel == null)
return;
// ...
If it helps:
I believe I've talked about this in at least one N+1 - maybe N=25 - see https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/blob/master/N-25-Tabbed/Tabbed.Touch/Views/FirstView.cs and http://mvvmcross.wordpress.com
The same type of thing happens in pure objective C code - eg see initialize UITableViewController with [super initWithStyle:UITableViewStyleGrouped]

Related

Custom UITabBar subclass without Interface Builder

Using IB (be it with a Storyboard or a XIB), one can easily have the UITabBarController use a subclass of UITabBar, by editing the class name in the Identity Inspector -> Custom Class.
How to mimic this "custom class" feature of IB, without using it at all ?
I tried the following (in my subclass of UITabBarController) :
var customTabBar = MyCustomTabBarSubclass()
override var tabBar: UITabBar {
return customTabBar
}
To no avail – the tab bar is displayed, but blank. The issue is not elsewhere since returning super.tabBar from the overriden var fixes it.
The issue, I guess, is that I'm not setting up my customTabBar (frame, position, adding it to the view hierarchy), but I'd like to have the UITabBarController loadView (I think that's the one, not sure) do it for me, the same way it sets up any other UITabBar.
Ideas ?
I think you can't. For this question, I had some time to scan through the documentations of UITabBar and UITabBarController.
The tabBar property of UITabBarController is a get-only.
#available(iOS 3.0, *)
open var tabBar: UITabBar { get } // Provided for -[UIActionSheet showFromTabBar:]. Attempting to modify the contents of the tab bar directly will throw an exception.
Furthermore, it is stated in UITabBarController's documentation that you shouldn't manipulate such property.
You should never attempt to manipulate the UITabBar object itself
stored in this property. If you attempt to do so, the tab bar view
throws an exception. To configure the items for your tab bar
interface, you should instead assign one or more custom view
controllers to the viewControllers property. The tab bar collects the
needed tab bar items from the view controllers you specify.
The tab bar view provided by this property is only for situations
where you want to display an action sheet using the show(from:) method
of the UIActionSheet class.
Just wanna add: but unlike this UITabBarController, subclassing the UINavigationController gives you the power to init such subclass with a subclass of UINavigationBar:
UINavigationController(navigationBarClass: <#T##AnyClass?#>, toolbarClass: <#T##AnyClass?#>)
Unfortunately, UITabBarController does not have such kind of init method.
This works, but there's a chance you might not pass App Store review because it's setting a value that public API doesn't allow you to set.
Inside your UITabBarController's subclass' initWithNibName:bundle: or viewDidLoad, add this:
MyCustomTabBar *customTabBar = [[MyCustomTabBar alloc] initWithFrame:CGRectZero];
customTabBar.delegate = self;
[self setValue:customTabBar forKey:#"tabBar"];
Consider this just a proof of concept, not something you should necessarily use in your production app because it's technically using a private setTabBar: method.
If you choose to disregard what Apple says about what's supported (using a custom UITabBar subclass with Interface Builder, and only with it), here's a dirty solution (that works) :
It requires mild knowledge of the ObjC runtime, because we're going to swizzle stuff around... Essentially, the issue is that I can't force UITabBarController to instantiate the class I want it to instantiate (here, MyCustomTabBarSubclass). Instead, it always instantiates UITabBar.
But I know how it instantiates it : by calling -[[UITabBar alloc] initWithFrame:]. And I also know that all functions belonging to the init family are allowed to return either an instance of their class, or of a subclass (that's the basis of Class Clusters).
So, I'm going to use this. I'm going to swizzle (= replace the implementation) of UITabBar's -initWithFrame: method with my custom version of it, that, instead of calling up (self = [super initWithFrame:]) will call "down" (self = [MyCustomTabBarSubclass.alloc initWithFrame:]). Thus, the returned object will be of class MyCustomTabBarSubclass, which is what I'm trying to achieve.
Note how I'm calling MyCustomTabBarSubclass.alloc – this is because my subclass potentially has ivars that UITabBar does not have, thus making it larger in its memory layout. I might have to release self before reallocating it, otherwise I could be leaking the allocated memory, but I'm not sure at all (and ARC "forbids" me to do call -release, so I'd have to use another step of trickery to call it).
EDIT
(First thing, this method would also work for any case where IB's custom classes are of use).
Also note that implementing this requires writing ObjC code, as Swift does not allow us to call alloc, for instance – no pun intended. Here's the code :
IMP originalImp = NULL;
id __Swizzle_InitWithFrame(id self, SEL _cmd, CGRect frame)
{
Class c = NSClassFromString(#"MyBundleName.MyCustomTabBarSubclass");
self = [c alloc]; //so that we'll return an instance of MyCustomTabBarSubclass
if (self) {
id (*castedImp)(id, SEL, CGRect) = (id (*)(id, SEL, CGRect))originalImp;
self = castedImp(self, _cmd, frame); //-[super initWithFrame:]
}
return self;
}
You'll also have to ensure that the actual swizzling operation is only performed once (such as, dispatch_once). Here's the code that actually swizzles :
Method method = class_getInstanceMethod(NSClassFromString(#"UITabBar"), #selector(initWithFrame:));
IMP swizzleImp = (IMP)__Swizzle_InitWithFrame;
originalImp = method_setImplementation(method, swizzleImp);
So that's it for the ObjC side.
Swift-side :
#objc class MyCustomTabBarSubclass: UITabBar {
lazy var anIvar: Int = 0 //just a example obviously
// don't forget to make all your ivars lazy or optional
// because the initialisers WILL NOT BE CALLED, as we are
// circumventing the Swift runtime normal init mechanism
}
And before you initialise your UITabBarController, don't forget to call the ObjC code that performs the swizzling.
That's it ! You have cheated UITabBarController into instantiating your own subclass of UITabBar, and not the vanilla one. If you're working in pure ObjC, things are even easier (no messing with bridging headers, a subject I didn't cover here).
Obligatory DISCLAIMER : Messing with the ObjectiveC runtime is obviously not something to do lightly. Ensure you have no better solution – IMHO, using a XIB only for the purpose of avoiding such tinkering is a better idea than implementing my suggestion.
A example of issue that could arise : if you're using multiple tab bars in your app, you might not want all of them to be MyCustomTabBarSubclass instances. Using my code above without modifications would result in all tab bars to be instances of MyCustomTabBarSubclass, so you'd have to find a way to tell __Swizzle_InitWithFrame directly call the original implementation, or not.

Add analytics event to log each ViewController's name in the project

I have a project in which I need to log an analytics event whenever any View Controller (log the name of the View Controller) comes on screen.
I was trying to avoid littering all of my existing View Controller classes with call to the analytics SDK.
I tried making an AnalyticsViewController and all my View Controllers would subclass this View Controller, and then I add analytics event in AnalyticsViewController class's viewDidLoad method. But the problem with this approach is that AnalyticsViewController does not which child View Controller is the call coming from.
I am using Swift 3.0. I believe that Swift with its powerful language features should be able provide me with an abstraction of some sorts.
Is there any way through this problem without littering all the View Controllers?
You were on the right track. Making a UIViewController parent class is a good idea.
In viewDidLoad method you can just add this:
let className = NSStringFromClass(self.classForCoder)
It will give you the name of current loaded view controller and then you can use that name in your event to specify which view controller was actually loaded.
Edit: added example.
So your parent's viewDidLoad would look something like this:
override func viewDidLoad() {
super.viewDidLoad()
let className = NSStringFromClass(self.classForCoder)
sendEvent(withViewControllerName: className)
}
The answer given by #JPetric is an amazing starting point. I just had to do a little modification to get it to work.
I've put this in my AnalyticsViewController to retrieve the name of the current subclass.
private func currentClassName() -> String? {
return NSStringFromClass(self.classForCoder).components(separatedBy: ".").last
}

How to clean up a UIViewController in Xamarin.iOS?

Consider we have a custom UIViewContoller.
Now we have to make some clean up on UIViewController unload. For example, if we use UIWebView as a subview in our view controller, it's recommended to set its delegate to null and call StopLoading() method on unload.
Some sources say it's not recommended to make clean up overriding Dispose() method, because it concerns only managed object lifecycle.
Other sources say it's not recommended to use ViewDidDissappear() for these purposes because it can be called twice.
How to handle it right?
ViewDidDisappear() and ViewWillDisappear() will not be called multiple times; at least not without a balanced call to ViewDidAppear() and ViewWillAppear(). If you see more calls to the disappear methods than to the appear methods, you have a bug in your code. If you want to make sure, your cleanup happens only once (for sanity), but isn't the solution something as simple as this:
bool myCleanupIsDone = false;
public override void ViewDidDisappear()
{
if(myCleanupIsDone)
{
return;
}
myCleanupIsDone = true;
CleanUpWhateverNeedsToBeCleanedUp();
}
You should almost never need to override Dispose() unless you are dealing with unmanaged resources. Xamarin.iOS does that a lot internally but for your code and in your case it is not relevant.
Normally you would create your required objects in ViewWillAppear() and clean them up in the disappear methods. That way you would not need to check if something has already been cleaned up.
ViewWillDisappear and ViewDidDisappear can actually be called multiple times, on iPhone for example every time when a new controller is presented or pushed. But I think it's still the right place to do all the necessary cleanups.
Krumelur's proposal would end in a null delegate of your UIWebView once your controller has been disappeared for the first time – if the user comes back, it would probably crash.
So instead of setting a flag you could check, if the controller you want to clean up is beeing popped or dismissed – if that's the case, you can safely do all the work.
Here's some code I found in one of my projects (seems I've been through this before ;)):
public override void ViewDidDisappear(bool animated)
{
if ((NavigationController == null && IsMovingFromParentViewController) ||
(ParentViewController != null && ParentViewController.IsBeingDismissed))
{
CleanUpAfterDisappearing();
}
base.ViewDidDisappear(animated);
}
Maybe you can just cleanup resources when the operating system instructs your app to do so via overriding the DidReceiveMemoryWarning() method in your custom view controllers: http://iosapi.xamarin.com/index.aspx?link=M%3AMonoTouch.UIKit.UIViewController.DidReceiveMemoryWarning

MonoTouch custom UIButton crashes on load: Selector invoked from objective-c on a managed object that has been GC'd

I am using MonoTouch and have created a class MyButton that extends UIButton. This is actually a simplified example of the same problem I was having with a custom UIScrollView. MyButton extends UIButton and adds some methods to do something simple like increment and decrement a counter (this is just a test case). I also include '[Register("MyButton")]' before my class definition so that it registers with Interface Builder (IB) so that I can add a UIButton and specify its class as MyButton.
What works:
I can add an instance of MyButton programmatically to my main view and it displays properly and functions as expected.
What doesn't work:
If I try to add a UIButton from IB and specify its class as MyButton, then add an outlet and also specify it is a MyButton type then my app crashes while trying to load the main view with the exception: Unhandled managed exception: Selector invoked from objective-c on a managed object of type iOSCustomUIView.MyButton (0xC2F40D0) that has been GC'ed (System.Exception).
I have found similar questions that have this problem but with view controllers, not views. So I understand that the error is similar, but in the case of a view controller references are lost when pushing new controllers onto the stack, etc. In my case, I don't know why my object has been GC'd before anything has loaded.
I have tried creating additional private member variables to reference my MyButton outlet (btnIncrement), but this did not help. I set breakpoints and the crash occurs when trying to instantiate a new instance of my view controller: (from AppDelegate.cs)
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
window = new UIWindow (UIScreen.MainScreen.Bounds);
viewController = new iOSCustomUIViewViewController ();
window.RootViewController = viewController;
window.MakeKeyAndVisible ();
return true;
}
Is there anything I am missing in order to define a custom UIButton class and use it in IB? Is this a bug in MonoTouch?
I got a great response from adamkemp on the Xamarin forum. The solution is that I need to write a constructor that accepts an IntPtr p argument since that is the constructor called when view objects are created from xib files. In my case, I simply define the following constructor:
public MyButton(IntPtr p) : base(p)
{
// Do initialization stuff here
}
Since I did not have this constructor defined, my object was likely never created. The log message saying that my object has been GC'd was a little misleading in this case. Now, my app views all load successfully.

Detecting when UITableViewCells have completely finished

I am attempting to structure my application using the MVVM pattern. Therefor I have ViewModels that raise events when data changes, and the UI is expected to react to those events and update the visible UI controls.
I have a derived UITableViewCell that gets initialized with a certain ViewModel every time a new cell is created or dequeued (very similar to miguel's example here). One main difference being part of the initializing relies on subscribing to an event of the ViewModel. This creates a reference from the long lived ViewModel to this specific cell, holding it in memory for the lifetime of the ViewModel. When the cell is re-used, the old subscription is cleaned up and a new one created to the new ViewModel, that works fine.
However the problem is there doesn't seem to be any opportunity to clean up the last subscription once the cell is completely finished, which means it is held in memory for the lifetime of the ViewModel (much longer than I want it to be).
'Completely finished' depends on the VC hierarchy, but in this case the derived DialogViewController that contains the TableView with custom cells has been popped from the UINavigationController stack and has been disposed.
willMoveToSuperview is never called (I was hoping it would be with 'null' being passed in).
removeFromSuperview is never called.
Dispose on each cell is never called.
Disposing of the UITableViewController doesn't dispose each cell.
Disposing of the TableView within the controller doesn't even dispose each cell.
The only way I can manually dispose each cell (and hence clean up subscriptions) is by enumerating the cells manually myself in each of my derived UIViewControllers, something I want to avoid.
Has anyone has similar troubles like this? I cant be the first using the MVVM pattern with UITableViewCells.
Is this a bug with the Dispose pattern in the base MonoTouch UIKit wrappers?
EDIT:
Here is cut-down version of one of the custom UITableViewCells. Note I'm taking a pragmatic approach where I explicitly subscribe to events of properties I know may change, not a full MVVM bind every property to the UI. So my binding code consists of just standard event subscriptions:
public class MyCustomCell : UITableViewCell
{
private InvoiceViewModel currentViewModel;
private readonly UILabel label1;
private readonly UILabel label2;
public MyCustomCell(NSString reuseId)
: base(UITableViewCellStyle.Default, reuseId)
{
Accessory = UITableViewCellAccessory.DisclosureIndicator;
SelectedBackgroundView = new UIView()
{
BackgroundColor = UIColor.FromRGB(235,235,235),
};
label1 = new UILabel();
ContentView.Add(label1);
// The rest of the UI setup...
}
public void Update(MyViewModel viewModel)
{
if ( currentViewModel == viewModel )
return;
if ( currentViewModel != null )
{
// Cleanup old bindings.
currentViewModel.UnacknowledgedRemindersChanged -= HandleNotificationsChanged;
}
currentViewModel = viewModel;
if ( viewModel != null )
{
viewModel.UnacknowledgedRemindersChanged += HandleNotificationsChanged;
label1.Text = viewModel.SomeProperty;
// Update the rest of the UI with the current view model.
}
}
private void HandleNotificationsChanged()
{
// Event can fire on background thread.
BeginInvokeOnMainThread(() =>
{
// Relevant UI updates go here.
});
}
protected override void Dispose(bool disposing)
{
// Unsubscribes from ViewModel events.
Update(null);
base.Dispose(disposing);
}
}
And my derived MT.D element class has a 1:1 element:viewmodel, so the GetCell method looks like this:
public override UITableViewCell GetCell (UITableView tv)
{
var cell = (MyCustomCell) tv.DequeueReusableCell(key);
if (cell == null)
cell = new MyCustomCell(key);
cell.Update(viewModel);
return cell;
}
You're definitely not the first to do Mvvm table cells with MonoTouch.
I've blogged about it recently at http://slodge.blogspot.co.uk/2013/01/uitableviewcell-using-xib-editor.html
Before that there have been projects at NDC (search for "Flights of Norway") and there was a long running Mvvm project built on top of MonoTouch.Dialog.
In MvvmCross apps, we use tables bound to ObservableCollections and to other IList classes a lot.
Within these we generally don't hit many problems with left-living references, but that's because we generally don't encourage people to use long-living ViewModels - we try to create a new instance of ViewModel data to go with each View. However, I do understand that that may not be suitable for all appications.
When an Mvx user finds themselves with this type of problem, then some of the approaches we've tried are:
in iOS5 we did use the ViewDidUnload method to clean up bindings - but obviously that is now gone in iOS6.
depending on the UI presentation style (modal, splitview, navigationcontroller, popup, etc) we have tried manually detecting when views are 'popped' and using this to clear up bindings
again depending on the UI presentation style, we have tried using the ViewDidAppear, ViewDidDisappear events to add and tidy up the bindings
for all UIKit classes with data-binding we've always used Dispose as an extra place to try to clear up bindings
we've looked at using WeakReferences (especially from the ViewModel to the View) in order to get around the issues where the UIKit objects are owned by both iOS/ObjC and by MonoTouch/.Net - these problems are particularly hard to debug.
this weak reference code is likely to be one the key changes in the next release.
An example discussion about this (in Droid rather than in Touch) is on https://github.com/slodge/MvvmCross/issues/17
I'm sorry I can't offer you any specific advice at this moment. If you post some more example code about how you are creating and storing your bindings, I might be able to help more - but I can't really visualise what bindings you are creating right now.
I've got a few more links I'll add to this answer later - on mobile at present - too hard to add them here!
Update - for some more explanation on the WeakReference ideas, this is where we're heading now in v3 for MvvmCross - https://github.com/slodge/MvvmCross/tree/vNextDialog/Cirrious/Cirrious.MvvmCross.Binding/WeakSubscription - basically the idea is to use disposable weakreference event subscriptions - which won't keep the UIKit objects in RAM. It's not properly tested code yet. When it is, then I'll blog and talk about it more fully!

Resources