MvvmCross: RaisePropertyChanged not updating binding - ios

I am instantiating a view with a ViewModel like this:
var myView = new MyView { DataContext = new MyViewModel() };
I want to make this view accessible from anywhere in the application so I am adding the view to the rootviewcontroller:
window.RootViewController.View.Add(myView.View);
Where ViewModel inherits from MvxViewModel and MyView inherist from MvxViewController
Inside the MyView I am binding a UILabel to a text property like this
this.CreateBinding(myLabel).To<MyViewModel>(vm => vm.MyTextProp).Apply();
The text property is defined inside the ViewModel like this
private string myTextProp;
public string MyTextProp
{
get { return myTextProp; }
set
{
myTextProp = value;
RaisePropertyChanged(() => MyTextProp);
}
}
The binding works initially when loaded. But when I change the MyTextProp property and RaisePropertyChanged is called the UILabel is not being updated.
I am also binding to an ICommand which works fine and triggers normally.

Instead of doing var myView = new MyView { DataContext = new MyViewModel() };
Let MvvmCross construct your MvxViewController by doing this:
var viewController = this.CreateViewControllerFor<MyViewModel>();
CreateViewController is an extensions method for IMvxCanCreateTouchView, so the class where you do your view construction should be implementing that Inteface, otherwise that method will not be available.
I know IMvxCanCreateTouchView is implemented by MvxTouchViewPresenter and MvxViewController so you can call that method from your Presenter or from another MxvViewController.

Related

Unable to cast UIKit.UIView to type UIKit.UILabel?

In my project I am trying to get a reference to a label within a view. The view has 2 items in it both of which are UILabels.
var headerViews = NSBundle.MainBundle.LoadNib(nibName,this,null);
// Only 1 view in the XIB as shown in the image below.
UIView headerView = headerViews.GetItem<UIView>(0);
UILabel nameLabel = (UILabel)headerView.ViewWithTag(1);
This is throwing the runtime error that it cannot cast the view to a label. Why not? This cast is valid in Objective-C and Swift.
This is what headerView looks like:
I would suggest to work with a more safe approach.
Create a UIView subclass, e.g. MyDialogView, in your XIB designer set this class to the main View and add Outlets for each control.
Then add a method that you can pass a model class or values you want to update the View with.
In your code you can work using your MyDialogView class and invoke the method to update the view with data.
This way you don't need to work with casting and tags.
[EDIT] Code Example.
Create a class and derive from UIView, add the Register attribute to make it visible in XCode. It should be like the below.
[Register("MyCustomView")]
public partial class MyCustomView : UIView
{
CGSize _intrinsicContentSize;
public UIView View { get; set; }
public string Text {
get { return myLabel.Text; }
set { myLabel.Text = value; }
}
public MyCustomView ()
{
SetupView ();
}
public MyCustomView (IntPtr handle) : base (handle)
{
SetupView ();
}
private UIView LoadViewFromXib()
{
NSBundle bundle = NSBundle.FromClass (this.Class);
UINib nib = UINib.FromName ("MyCustomView", bundle);
return nib.Instantiate (this, null)[0] as UIView;
}
private void SetupView()
{
this.View = LoadViewFromXib ();
AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
this.Bounds = this.View.Bounds;
_intrinsicContentSize = this.Bounds.Size;
TranslatesAutoresizingMaskIntoConstraints = false;
AddSubview (this.View);
}
public override CGSize IntrinsicContentSize {
get {
return _intrinsicContentSize;
}
}
}
In your XIB file don't set the View's class to MyCustomView but the File's Owner to MyCustomView.
In your ViewController somewhere, e.g. ViewDidLoad add the following.
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Perform any additional setup after loading the view, typically from a nib.
MyCustomView customView = new MyCustomView ();
customView.Text = "Test Label Text";
Add (customView);
}
Run and you should see the update label in your custom view.

ViewModel is null during ViewDidLoad

I am getting started with MvvmCross in iOS.
public class MainView : MvxTabBarViewController
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
var vm = (MainViewModel)this.ViewModel;
if (vm == null)
return;
}
}
Setting a breakpoint to the line where access the ViewModel, shows me, that ViewModel is null.
I can workaround this by calling ViewDidLoad() in the constructor. Then, ViewModel is null during the constructor call, but valid in the default ViewDidLoad call. But that looks like a workaround. can anybody help?
I'm guessing here the problem here will be specific to the way that TabBarViewController is constructed.
ViewDidLoad is a virtual method and it is called the first time the View is accessed.
In the case of TabBarViewController this happens during the iOS base View constructor - i.e. it occurs before the class itself has had its constructor called.
The only way around this I've found is to add a check against the situation in ViewDidLoad, and to make a second call to ViewDidLoad during the class constructor.
You can see this in action N-25 - https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/blob/976ede3aafd3a7c6e06717ee48a9a45f08eedcd0/N-25-Tabbed/Tabbed.Touch/Views/FirstView.cs#L17
Something like:
public class MainView : MvxTabBarViewController
{
private bool _constructed;
public MainView()
{
_constructed = true;
// need this additional call to ViewDidLoad because UIkit creates the view before the C# hierarchy has been constructed
ViewDidLoad();
}
public override void ViewDidLoad()
{
if (!_constructed)
return;
base.ViewDidLoad();
var vm = (MainViewModel)this.ViewModel;
if (vm == null)
return;
}
}

Reference UIViewController within ContainerView

I have created a ContainerView in Xamarin, which automatically created a new ViewController.
I have created the class for this called Test1ViewController:
using System;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
namespace test1
{
public partial class Test2ViewController : UIViewController
{
public Test2ViewController (IntPtr handle) : base (handle)
{
}
}
}
I am trying to reference this view controller in the ViewDidLoad() method of the main view controller. However if I put the following:
Test2ViewController.PresentViewController(picker, true, null);
I get a static error message, which makes sense as I am trying to reference the class not the specific object. Am I missing something, how do I reference the UIViewController in the ContainerView from the parent UIViewController?
What I am trying to achieve, is including the Scandit Barcode scanner within the container view:
// Setup the barcode scanner
var picker = new ScanditSDK.SIBarcodePicker ("API-KEY");
picker.OverlayController.Delegate = new BarcodeScanner ();
Test2ViewController.PresentViewController(picker, true, null);
picker.StartScanning ();
Assuming that variable picker is supposed to represent a Test2ViewController instance:
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.picker = new Test2ViewController();
this.PresentViewController(picker, true, null);
}

MonoTouch Instantiating a ViewController programmatically for ContainerView

I am trying to work with a container view in MonoTouch and I am following some tutorials online. They talk about adding and removing view controller programmatically from the container. I created a viewcontroller and view in the storyboard of my project and attached a few outlets and one action (for labels and buttons respectively). I created an overloaded construc
Here is the code in the view controller that I am trying to add viewControllers into the container view.
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
ContainerView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight;
_controllerOne = new IngredientsController("Perishables");
_controllerTwo = new IngredientsController("Spices");
AddChildViewController(_controllerOne);
ContainerView.AddSubview(_controllerOne.View);
_controllerOne.DidMoveToParentViewController(this)
}
When I add the subview for _controllerOne I get an error because the elements on my controller are marked null. Is MonoTouch incapable of having view controllers being programmatically created if the controller was made in Interface Builder? Below are the two constructors for the Ingredient Controller. When the segue is used then all of the UI controls are initialized properly. Do I need to create the controller programmatically and then instantiate it that way? Any help would be appreciated.
//This ctor does not work
public IngredientsController (string title) : base(NSObjectFlag.Empty)
{
_ingredientTitle = title;
}
//This ctor works
public IngredientsController (IntPtr handle) : base (handle)
{
}
Try to swap the AddSubView() and DidMoveToParentViewController() methods like below:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
ContainerView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight;
_controllerOne = new IngredientsController("Perishables");
_controllerTwo = new IngredientsController("Spices");
this.AddChildViewController(_controllerOne); // Root child controller.
_controllerOne.DidMoveToParentViewController(this); // Confirm the rooting.
ContainerView.AddSubview(_controllerOne.View); // Access the view.
}
Try instantiating the view controller like this:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
this.ContainerView.AutoresizingMask = UIViewAutoresizing.FlexibleHeight;
var newController = this.Storyboard.InstantiateViewController("IngredientsController");
this.AddChildViewController (newController);
this.ContainerView.AddSubview (mapController.View);
}
Make sure you set the Storyboard Id in the properties panel for the ViewController

In MonoTouch, how do I assign a custom view to my view controller?

If I want to use a custom view object with my view controller, instead of just using the one that's initialized by default, can I just assign it to the view controller's View property. For example, is the following the correct/safe approach?
public class MyView : UIView
{
}
public class MyController : UIViewController
{
// Constructors.
public MyController()
{
View = new MyView();
}
}
Seems to work in a simple test, but I don't want to be introducing any time-bombs.
Or, should I be adding my custom view as a subview of the existing view in ViewDidLoad?
You should be adding the custom views as subviews.
public class MyView : UIView
{
}
public class MyController : UIViewController
{
public override void ViewDidLoad()
{
var myView = new MyView();
this.View.AddSubview(myView);
}
}

Resources