Hide UIView on view load - Xamarin iOS and MvvmCross - ios

I'm trying to hide an ui element on view load. I'm using Xamarin iOS and MvvmCross (6.2.3.0). In my view, I create an uitextfield and add following binding:
public override void ViewDidLoad()
{
//adding uitextfield
var set - this.CreateBindingSet(MyViewConroller, MyViewModel>();
set.Bind(uitextfield).For("Visibility").To(vm => vm.FieldVisibility).TwoWay().WithConversion("Visibility");
}
And here is my viewmodel:
public MyViewModel : MvxViewModel
{
private bool _fieldVisibility;
public bool FieldVisibility
{
get {return _fieldVisibility;}
set {
SetProperty(ref _fieldVisibility, value);
}
public override Task Initialize() {
FieldVisibility = false;
}
But when the view is loaded, my uitextfield is still visible. I tried to call RaisePropertyChanged, but it didn't help:
private bool _fieldVisibility;
public bool FieldVisibility
{
get {return _fieldVisibility;}
set {
SetProperty(ref _fieldVisibility, value);
RaisePropertyChanged(nameof(FieldVisibility));
}
It works, when I change some other viewmodel property and simply call:
private string _otherProperty;
public string OtherProperty
{
get {return _otherProperty;}
set {
SetProperty(ref _otherProperty, value);
FieldVisibility = false;
RaisePropertyChanged(nameof(FieldVisibility));
}
but I'd like to have this uitextfield hidden on view load. Can you suggest any solution for that? I'd be very grateful for any hint. Thanks!!

We have strongly typed bindings nowadays for stuff like this:
set.Bind(uitextfield).For(v => v.BindVisible()).To(vm => vm.FieldVisibility);
You don't really need the visibility converter here, should work without it.
Remember to also call Apply() on your binding set.
Also TwoWay() won't do anything here, because there are no Binding Targets which allow for Two Way bindings for UIView visibility.

Related

Cannot implement YouTube Android Player API with Mvvm Model xamarin

I need to use YouTubePlayerView to play Youtube Video in my mvvm crossplatform project. The problem is: my view needs to inherit 2 classes while C# does not allow multiple inheritance:
VideoDetailView.cs
public class VideoDetailView : MvxAppCompatActivity, YouTubeBaseActivity
{
}
YouTubeBaseActivity to use Google Player API, to use a YouTubePlayerView, your activity must extend YouTubeBaseActivity.
MvxAppCompatActivity to be able to bind View with ViewModel.
Have been stuck in this issue for couple days, I need help!
Thanks
To implement this feature, you can create MvxYouTubeBaseActivity:
MvxYouTubeBaseActivity : YouTubeBaseActivity, IMvxEventSourceActivity, IMvxAndroidView
{
protected MvxYouTubeBaseActivity()
{
BindingContext = new MvxAndroidBindingContext(this, this);
this.AddEventListeners();
}
//Implement all methods from IMvxEventSourceActivity, IMvxAndroidView
}
And create MvxYouTubeBaseActivity<TViewModel.>:
public abstract class MvxYouTubeBaseActivity<TViewModel>: MvxYouTubeBaseActivity, IMvxAndroidView<TViewModel>
where TViewModel : class, IMvxViewModel
{
protected MvxYouTubeBaseActivity(IntPtr ptr, JniHandleOwnership ownership)
: base(ptr, ownership)
{
}
protected MvxYouTubeBaseActivity()
{
}
public new TViewModel ViewModel
{
get => (TViewModel)base.ViewModel;
set => base.ViewModel = value;
}
}
That's all, you can use it with MVVMCross

mvvmcross ViewModel's init() is called at last

I have a DetailViewModel which has several custom buttons and initialize them in Init().
public class DetailViewModel{
...
public async void Init(DetailParameter params){
...
CustomButtons.Add(new CustomButton(this, "1")); //3
CustomButtons.Add(new CustomButton(this, "2"));
...
}
}
Then in the detail view, I have several buttons and bind them with custom buttons in DetailViewModel.
public partial class DetailView{
private List<CustomButton> m_customButtons;
public List<CustomButton> CutomButtons{
get { return m_customButtons; }
set { m_customButtons = value; //2
foreach(CustomButton button in m_customButtons){
UIButton myButton = new UIButton ();
Add(myButton);
}
}
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
var set = this.CreateBindingSet<DetailView, DetailViewModel>();
set.Bind (this).For (v=>v.CustomButtons).To (vm=>vm.CustomButtons);//1
set.Apply ();
}
}
The problem is that ViewModel's Init() is called at last.
So always m_customButtons has set by 0 count, then ViewModel's Init() is called.
I know about CIRS, but how can solve this?
There's plenty you could do to change your binding to make it work in this async scenario - including using observable collections.
However, the simplest route in your current setup might just be to change the entire CustomButtons list in your ViewModel instead - e.g. something like:
var customButtons = new List<CustomButton>() {
new CustomButton(this, "1"),
new CustomButton(this, "2")
};
CustomButtons = customButtons;
RaisePropertyChanged(() => CustomButtons);
In this "one-off" scenario, this implementation should work for you. For more advanced dynamic scenarios, it might be worthwhile looking at INotifyCollectionChanged implementations too.

MvvmCross: Binding a BaseView Property to a BaseViewModel Property

What I am trying to achieve here is a global loading indicator with MvvmCross.
From what I gathered so far it appears that I can implement this by using a BaseView and BaseViewModel. The BaseViewModel should contain an IsLoading property that the BaseView can Bind to. Therefore, I can set IsLoading to true in any of my ViewModels to cause the indicator to be displayed.
The BaseViewModel looks like this:
public abstract class BaseViewModel : MvxViewModel
{
private bool _isLoading = false;
public bool IsLoading
{
get { return _isLoading; }
set { _isLoading = value; RaisePropertyChanged(() => IsLoading); }
}
private string _loadingMessage = "Loading...";
public string LoadingMessage
{
get { return _loadingMessage; }
set { _loadingMessage = value; RaisePropertyChanged(() => LoadingMessage); }
}
}
As for binding to the ViewModel, this issue was addressed here: https://stackoverflow.com/a/10930788
I was successfully able to listen to IsLoading by attaching to the PropertyChanged event, however, since this is not a "real" binding it will not fire if IsLoading is set to true before you attach to the event (Such as before the view is loaded).
Next I attempted to call AddBindings to attach to my BaseViewModel in order to create a real binding between the two properties, although this does not appear to work (no errors).
Here is what my BaseView currently looks like for iOS:
public abstract class BaseView<TViewModel> : MvxViewController where TViewModel : BaseViewModel
{
public TViewModel ViewModel
{
get { return (TViewModel)base.ViewModel; }
set { base.ViewModel = value; }
}
protected BaseView(string nib, NSBundle bundle) : base(nib, bundle)
{
}
private LoadingOverlay _loadingOverlay;
public override void ViewDidLoad()
{
base.ViewDidLoad();
_loadingOverlay = new LoadingOverlay(View.Frame);
this.AddBindings(new Dictionary<object, string>()
{
{this, "{'IsLoading':{'Path':'IsLoading'}}"},
{_loadingOverlay, "{'LoadingMessage':{'Path':'LoadingMessage'}}"}
});
}
public bool IsLoading
{
set
{
if (value)
View.Add(_loadingOverlay);
else
_loadingOverlay.Hide();
}
}
}
Are there any pitfalls to AddBindings that I may be unaware of? or is there a newer method altogether that I should be using?
I Appreciate the help, Thanks.
If you are using v3, then by default you must change over to the new 'Swiss' style bindings.
These change Json text like:
"{'LoadingMessage':{'Path':'LoadingMessage'}}"
Into simpler text like:
"LoadingMessage LoadingMessage"
For more on this, see http://blog.ostebaronen.dk/2013/01/awesome-mvvmcross-swiss-bindings-for.html
One further option you can use in v3 is 'fluent bindings'. To see these in action, see all the iOS samples in the N+1 series - http://mvvmcross.wordpress.com/, or questions like Fluent Bindings and UIButton titles
Eg something like:
this.CreateBinding().For("LoadingMessage").To("LoadingMessage").Apply();
It may also help for debugging if you enable additional trace - see MvvmCross Mvx.Trace usage

StructureMap: Configure concrete classes at run time?

I know that Concrete Types can be configured with Structure Map the following way:
ForRequestedType<Rule>().TheDefault.Is.Object(new ColorRule("Green"));
This works if you know the type ahead of time. I want to do it at run time, and there does not seem to be a way. Can some one enlighten me? What I want to do is something like the following: (This appears to be not supported by structure map)
ForRequestedType(typeof(Rule)).TheDefault.Is.Object(new ColorRule("Green"));
The reason for this is because I'm working on a wrapper for structure-map's configuration. And I will not know the type ahead of time. For the .Object(new ColorRule("Green")) I am going to be passing in a delegate instead, which would actually construct the object on request.
Recently Jeremy added the ability to configure a Func as a builder for your type. Here is an example of using a delegate/lambda as your builder.
public interface IRule
{
string Color { get; set; }
}
public class ColorfulRule : IRule
{
public string Color { get; set; }
public ColorfulRule(string color)
{
Color = color;
}
}
[TestFixture]
public class configuring_delegates
{
[Test]
public void test()
{
var color = "green";
Func<IRule> builder = () => new ColorfulRule(color);
var container = new Container(cfg=>
{
cfg.For<IRule>().Use(builder);
});
container.GetInstance<IRule>().Color.ShouldEqual("green");
color = "blue";
container.GetInstance<IRule>().Color.ShouldEqual("blue");
}
}

TextView Compound Drawable MvvmCross Binding

TextView has some DrawableXXX properties like DrawableStart, DrawableEnd, DrawableTop, etc. to show a Drawable next to the TextView text. Is it possible to bind this property using MvvmCross? I've tried using local:MvxBind="Drawablename 'check'" in my binding but it fails showing an MvxBind error:
Failed to create target binding for binding DrawableName for check
If this binding is not implemented in MvvmCross then how would I go about doing this on my own? Is the method used by N+28 still the recommended way to add a custom binding?
There is not currently any MvvmCross define target binding for adding compound drawables. However, you can easily add your own. If you are using MvvmCross 5+ you can take advantage of the new typed custom binding classes. Note, the example below has set the drawable to be placed on the left, however, you can choose to place at any direction (or multiple).
public class CompoundDrawablesDrawableNameBinding : MvxAndroidTargetBinding<TextView, string>
{
public const string BindingIdentifier = "CompoundDrawableName";
public CompoundDrawablesDrawableNameBinding(TextView target) : base(target)
{
}
public override MvxBindingMode DefaultMode => MvxBindingMode.OneWay;
protected override void SetValueImpl(TextView target, string value)
{
var resources = AndroidGlobals.ApplicationContext.Resources;
var id = resources.GetIdentifier(value, "drawable", AndroidGlobals.ApplicationContext.PackageName);
if (id == 0)
{
MvxBindingTrace.Trace(MvxTraceLevel.Warning,
"Value '{0}' was not a known compound drawable name", value);
return;
}
target.SetCompoundDrawablesWithIntrinsicBounds(
left: id,
top: 0,
right: 0,
bottom: 0);
}
}
Then register in your Setup.cs
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories(registry);
registry.RegisterCustomBindingFactory<TextView>(
CompoundDrawablesDrawableNameBinding.BindingIdentifier,
textView => new CompoundDrawablesDrawableNameBinding(textView));
}
Then in your XML you can use
local:MvxBind="CompoundDrawableName <<YOUR PROPERTY TO BIND TO>>"

Resources