Detecting when UITableViewCells have completely finished - ios

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!

Related

iOS MVVM with RxSwft: what is the drawback with viewmodel everywhere?

RxSwft is very suitable for iOS MVVM.
Putting viewmodel everywhere, disobeys Law of Demeter ( The Least Knowledge Principle ).
What is the other drawbacks?
Will it leads to Memory Leakage?
Here is an example:
ViewController has a viewModel
ViewModel has some event signals, like the following back event
class ViewModel{
let backSubject = PublishSubject<String>()
}
ViewController has contentView and viewModel, and contentView init with viewModel
lazy var contentView: ContentView = {
let view = ContentView(viewModel)
view.backgroundColor = .clear
return view
}()
and ViewModel's various subject are subscribed in viewController to handle other part view
viewController is a Dispatch center.
ViewModel is Event Transfer station. ViewModel is in everywhere, in Controller, in View, to collect different event triggers.
the code is quite spaghetti
in ContentView, user tap rx event , binds to the viewModel in viewController
tapAction.bind(to: viewModel.backSubject).disposed(by: rx.disposeBag)
user events wires up easily.
But there is memory leakage actually.
So what's the other disadvantages?
ViewModel doesn't break the Law of Demeter but it does break the Single Responsibility Principle. The way you solve that is to use multiple view models, one for each feature, instead of a single view model for the entire screen. This will make view models more reusable and composable.
If you setup your view model as a single function that takes a number of Observables as input and returns a single Observable, you will also remove any possibility of a memory leak.
For example:
func textFieldsFilled(fields: [Observable<String?>]) -> Observable<Bool> {
Observable.combineLatest(fields)
.map { $0.allSatisfy { !($0 ?? "").isEmpty } }
}
You can attach the above to any scene where you want to enable a button based on whether all the text fields have been filled out.
You satisfy the SRP and since object allocation is handled automatically, there's no concern that the above will leak memory.
You are right, there are some drawbacks, if you want just a data-binding, I would suggest to use Combine instead, since no 3rd party libraries need, you have it already. RxSwift is a very powerful tool when you use it as a part of language, not just for data binding.
Some of suggestions from my experience working with RxSwift:
Try to make VMs as a structs, not classes.
Avoid having DisposeBag in your VM, rather make VC subscribe to everything(much better for avoiding memory leaks).
Make its own VMs for subview, cells, child VC, and not shared ones.
Since your VC is a dispatch centre, I would make a separate VM for your content view and make a communication between ContentView VM and ViewController VM through your controller.

Using property observers to modify UI components in Swift

I have created a subclass of a UICollectionViewCell that shows some information. I have one property in with type Weather. When an instance of that is set I want to update the cell. Is the approach below bad? I am thinking of that I may trigger the view to be created to early if I access the UI components before it is loaded. Or is that non sense and only applies to UIViewController (with regard to using view property to early)?
If this is bad, what would be the correct way?
var weather: Weather? {
didSet {
if let weather = weather {
dayLabel.text = dayFormatter.stringFromDate(weather.fromDate)
// ... more code like this
}
}
}
You may want an else clause, though, clearing the text field if weather was nil. Likewise, if you might update this from a background thread, you might want to dispatch that UI update back to the main thread.
Be aware that this observer is not called when you set weather in the cell's init (nor would be the #IBOutlet be configure at that point, anyway). So make sure that you're not relying upon that.
Also, if Weather is mutable, recognize that if you change the fromDate of the existing Weather object, this won't capture that. (If Weather was mutable, you'd really want to capture its changing properties via KVO, a delegate-protocol pattern, or what have you.) But if you make Weather immutable, you should be fine.
So, technically, that's the answer to the question, but this raises a few design considerations:
One generally should strive to have different types loosely coupled, namely that one type should not be too reliant on the internal behavior of another. But here we have an observer within the cell which is dependent upon the mutability of Weather.
This use of a stored property to store a model object within view is inadvisable. Cells are reused as they scroll offscreen, but you probably want a separate model that captures the relevant model objects, the controller then handles the providing of the appropriate model object to the view object (the cell) as needed.
Bottom line, it's not advisable to use a stored property for "model" information inside a "view".
You can tackle both of these considerations by writing code which makes it clear that you're only using this weather parameter solely for the purpose of updating UI controls, but not for the purposes of storing anything. So rather that a stored property, I would just use a method:
func updateWithWeather(weather: Weather?) {
if let weather = weather {
dayLabel.text = dayFormatter.stringFromDate(weather.fromDate)
// ... more code like this
} else {
dayLabel.text = nil
// ... more code like this
}
}
And this would probably only be called from within collectionView:cellForItemAtIndexPath:.
But, this makes it clear that you're just updating controls based upon the weather parameter, but not trying to do anything beyond that. And, coincidentally, the mutability of the weather object is now irrelevant, as it should be. And if the model changes, call reloadItemsAtIndexPaths:, which will trigger your collectionView:cellForItemAtIndexPath: to be called.
There are times where a stored property with didSet observer is a useful pattern. But this should be done only when the property is truly a property of view. For example, consider a custom view that draws some shape. You might have stored properties that specify, for example, the width and the color of the stroke to be used when drawing the path. Then, having stored properties for lineWidth and strokeColor might make sense, and then you might have a didSet that calls setNeedsDisplay() (which triggers the redrawing of the view).
So, the pattern you suggest does have practical applications, it's just that it should be limited to those situations where the property is truly a property of the view object.
I would use a property observer if I planned up updating the value during the users session. If this is a value that only gets updated when the user first loads, I would just simply call a method when my view is initially loaded.
If you use a property observer, you can give it an initial value when you define it so the data is there when the user needs it. Also, if you're updating the user interface, make sure you do it on the main queue.
var weather: Weather = data {
didSet {
dispatch_async(dispatch_get_main_queue(),{
if let weather = weather {
dayLabel.text = dayFormatter.stringFromDate(weather.fromDate)
// ... more code like this
}
})
}
}

No ItemSelected call in my UICollectionView?

So I'm trying out Xamarin Forms, and I made a control that has a renderer that outputs a UICollectionView. The collection view in question was ported from an Objective C version. In the Objective C version, I implemented collectionView:didSelectItemAtIndexPath: and when I clicked on the item that method was called. But in the Xamarin version, this does not seem to be the case and the ItemSelected method is not called. I've tried using both the Delegate and WeakDelegate versions to no avail. I made sure AllowsSelection is true.
I tried adding a UIButton to the cell, and was able to get a log entry from its TouchUpInside handler, so I don't think it's a matter of another view on top stealing the touches. Pans and such work. And in Simulator I had it highlight the drawn layers and didn't notice anything fishy.
Anyone have any ideas of stuff to try? Thanks.
I don't know if this can help you, but i get the same problem using the GridView control of the Xamarin.Forms.Labs.
I see that in delegate, the method "ItemSelected" is not called, but is call the "ItemHighlighted" method. So I used this to make the GridView selectitem works.
I hope this can be helpfull. :)
Double check that you have assigned the UICollectionViewSource object to ColelctionView.Source and NOT ColelctionView.DataSource by mistake
I did eventually fix this. What was happening is that Xamarin.Forms adds a gesture recognizer to the root of the page which cancels touches in the page. This works well in general, but does not work well in the case of UICollectionViews where it interferes with the UICollectionView calling ItemSelected.
What I ended up doing was to create this custom renderer:
public class IosTapFixPageRenderer : PageRenderer {
public override void ViewDidLoad()
{
base.ViewDidLoad();
foreach (var g in View.GestureRecognizers) {
g.CancelsTouchesInView = false;
}
}
}
Then I assigned this as the renderer for problem pages containing UICollectionViews using the usual attribute method:
[assembly: ExportRenderer(typeof(CalendarPage), typeof(IosTapFixPageRenderer))]

MvxViewModelRequest is null in ViewDidLoad

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]

How are NSTableCellViews supposed to be laid out?

I have a fairly basic MainWindow.xib with a source list-style sidebar. I created it by dragging the Source List template into the window, which already contains two NSTableCellViews: HeaderCell and DataCell.
The latter consists of an icon (using NSImageView) and a label (NSTextField). Instead, I want the label and another, smaller label underneath. In IB, this looks as follows:
If I focus on just DataCell, it highlights accordingly:
Thing is, actually running the program, it looks nothing like the template:
Notice how the two NSTextFields just get smashed together into one. My understanding was that view-based NSOutlineViews (and view-based NSTableViews, for that matter) are supposed to be designed as a template from within IB. Instead, the dimensions from the template seem to get mostly ignored.
Here's the code that sets the view's values from the data source:
public class TourSourceListDelegate : NSOutlineViewDelegate
{
public override bool IsGroupItem(NSOutlineView outlineView, MonoMac.Foundation.NSObject item)
{
return (item as TourSourceListDataSource.Item).IsHeader;
}
public override NSView GetView(NSOutlineView outlineView, NSTableColumn tableColumn, MonoMac.Foundation.NSObject item)
{
if (IsGroupItem(outlineView, item))
{
return outlineView.MakeView("HeaderCell", this);
}
else
{
var data = item as TourSourceListDataSource.Item;
var dataView = outlineView.MakeView("DataCell", this);
(dataView.Subviews[0] as NSTextField).StringValue = data.Name;
(dataView.Subviews[1] as NSTextField).StringValue = data.Date_start.ToShortDateString();
return dataView;
}
}
}
I've tried overriding GetRowHeight, but that doesn't seem to resolve the problem (it makes more room, but still doesn't let the views distribute themselves properly), nor does it seem necessary.
I've also tried playing with the various Autosizing, Autoresizes Subviews, etc. toggles in IB, but that doesn't seem to produce intuitive results, and again, it doesn't seem necessary — the view as presented in IB is exactly what I want, just with slightly longer labels in practice.
I haven't tried converting this to AutoLayout yet.
What obvious step am I missing?
Some more info that probably doesn't make a difference: this is a Xamarin.Mac/MonoMac project with Xcode 5.0, MacOSX10.8.sdk, Xamarin Studio 4.0.12, Xamarin.Mac 4.0.12, and Mono 3.2.3 (targeting Mono / .NET 4.0). I've also enabled App Sandboxing.
What's important in interface builder is the view hierarchy. What kind of view is that cell? Are those labels really subviews of the cellview or not? The hierarchy should look something like:
One thing that's fishy that I see is accessing dataView.Subviews[0] and [1]. If you're adding subviews to your cells then should be creating your own NSTableViewCell subclasses, with each view connecting to the subclass' IBOutlet properties. The subclass doesn't need any code in its implementation, just the declaration of its properties in #interface, such as titleField and descriptionField, and an empty #implementation that auto-synthesizes them.
Then makeViewWithIdentifier (or apprently the glue MakeView in Xamarin) when passed the right identifier should create your NSTableViewCell subclass, and at runtime you can verify that using po dataView in the debugger. Then you access the subviews using the properties of your NSTableViewCell subclass' interface instead of assuming which view is in which position with the subview array, using dataView.titleField and dataView.descriptionField.
If your cell view has one text field then you can use NSTableViewCell without subclassing, but do connect up the textField outlet (its connected by default as long as you don't delete & recreate the cell view's label view) so you can access it through the property, again instead of having to dive into the subviews array.
All that said, it's not really clear why you're seeing what you are. It looks like those aren't the subviews you expect, and might even look like the wrong fonts as well as in the wrong positions. Using a custom subclass of NSTableViewCell and verifying its class at runtime is a good way of making sure it's creating the view you expect, but you can also dump the subview within the debugger using po [dataView _subtreeDescription].

Resources