Currently having an annoying issue of my UIButton not being Bound to the ViewModel. When clicking on the Button the ViewModel's IMVXCommand is not called. The button shows up but doesn't recognize the Touch Event. The Truly weird thing is if I was to create the LoginButton using the Interface Builder the below code works.
The following is sample code:
[Register("LoginView")]
public class LoginView : MvxView
{
UIBUtton loginButton;
public LoginView(IntPtr handler) : base(handler)
{
}
public LoginView(CGRect frame) : base(frame)
{
this.Frame = UIScreen.MainScreen.ApplicationFrame;
init();
}
init()
{
CreateView();
}
CreateView()
{
loginButton = new UIButton(new CoreGraphics.CGRect(0, 193, 300, 30))
{
Font = UIFont.SystemFontOfSize(15, UIFontWeight.Medium),
TranslatesAutoresizingMaskIntoConstraints = false,
BackgroundColor = UIColor.Red,
};
AddSubView(loginButton);
var set = this.CreateBindingSet<LoginView, LoginViewModel>();
set.Bind(loginButton).To(vm => vm.ClickCommand);
set.Apply();
}
}
ViewModel:
public class LoginViewModel : MvxViewModel
{
public AuthenticationViewModel() : base()
{
}
public IMvxCommand ClickCommand
{
get
{
return new MvxCommand(HandleClick);
}
}
public void HandleClick()
{
//This never gets called
}
}
WOW, so no joke 5 minutes after I posted this I realized I had forgotten to set the DataContext of the View to the ViewModel.
Literally, this line of code fixes the problem.
this.DataContext = new LoginViewModel();
Add that into the Init method and all is well
Hopefully, this helps someone else.
Related
Please let me re-stress, I am on MvvmCross 6.4.2. I'm currently upgrading a very old project.
I'm getting the error "Trying to show a page without a PageViewController, this is not possible!". According to the source, this is because my PageViewController is null.
I'm not sure why this is, because I've set my pager views and viewmodels up like in the playground sample (the big change in 6.4.2. appears to add attribute support for PageViewControllers).
The PageViewController I want to show (embedded in another ViewController via Container View, for clarity):
[MvxFromStoryboard("Main")]
//[MvxRootPresentation(WrapInNavigationController = true)]
public partial class MyPageViewController : MvxPageViewController<MyPagingViewModel>
And my first page:
[MvxPagePresentation(WrapInNavigationController = false)]
public partial class Page1View : MvxViewController<Page1ViewModel>
{
public Page1View() : base()
{
}
public Page1View(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
UIButton myButton = new UIButton(UIButtonType.System);
myButton.Frame = new CGRect(25, 25, 300, 150);
myButton.SetTitle("Hello, World!", UIControlState.Normal);
}
}
And my pages ViewModel:
public class Page1ViewModel : MvxNavigationViewModel
{
public Page1ViewModel(IMvxLogProvider logProvider, IMvxNavigationService navigationService) : base(logProvider, navigationService)
{
}
}
I override ShowPageViewController in my custom MvxIosViewPresenter to see what variables are passed in:
protected override Task<bool> ShowPageViewController(UIViewController viewController, MvxPagePresentationAttribute attribute, MvxViewModelRequest request)
The viewController is of type Page1View. Shouldn't it be MyPageViewController though if the method is called ShowPageViewController?
I use a command to navigate to the pages, just like in the playground:
private Task ShowInitialViewModels()
{
var tasks = new List<Task>();
tasks.Add(NavigationService.Navigate<Page1ViewModel>());
return Task.WhenAll(tasks);
}
So what could be going wrong?
I want to open a UIViewController from a content page and this code helps me in doing this
UIWindow Window = new UIWindow(UIScreen.MainScreen.Bounds);
var cvc = new ScanningPage();
var navController = new UINavigationController(cvc);
Window.RootViewController = navController;
Window.MakeKeyAndVisible();
but I also want a Back button on that Controller Page which navigate me back to that content Page.
this.NavigationController.PopViewController(true);
or
this.DismissViewController(true,null);
not working in this case.
I would recommend not creating a new Window and a UINavigationController...
Xamarin.Forms is contained in a single VC in the first Window, so you can obtain that view controller via:
UIApplication.SharedApplication.Windows[0].RootViewController;
So as an example Dependency service that presents and dismisses a VC (either from Forms or the view controller), you can do something like this.
Dependency interface:
public interface IDynamicVC
{
void Show();
void Dismiss();
}
iOS Dependency implementation
public class DynamicVC : IDynamicVC
{
UIViewController vc;
public void Show()
{
if (vc != null) throw new Exception("DynamicVC already showing");
vc = new UIViewController();
var button = new UIButton(new CGRect(100, 100, 200, 200));
button.SetTitle("Back", UIControlState.Normal);
button.BackgroundColor = UIColor.Red;
button.TouchUpInside += (object sender, EventArgs e) =>
{
Dismiss();
};
vc.Add(button);
var rootVC = UIApplication.SharedApplication.Windows[0].RootViewController;
rootVC.PresentViewController(vc, true, () => { });
}
public void Dismiss()
{
vc?.DismissViewController(true, () =>
{
vc.Dispose();
vc = null;
});
}
}
I saw this example:
Xamarin Forms - How to create custom render to give TableSection the default iOS Footer?
It does 75% of what I am looking for with this code:
using CoreGraphics;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(TableView), typeof(Japanese.iOS.TableViewCustomRenderer))]
namespace Japanese.iOS
{
public class TableViewCustomRenderer : TableViewRenderer
{
UITableView tableView;
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
var tableView = Control as UITableView;
var formsTableView = Element as TableView;
tableView.WeakDelegate = new CustomFooterTableViewModelRenderer(formsTableView);
}
void Current_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
}
private class CustomFooterTableViewModelRenderer : TableViewModelRenderer
{
public CustomFooterTableViewModelRenderer(TableView model) : base(model)
{
}
public override nfloat GetHeightForFooter(UITableView tableView, nint section)
{
return 10;
}
public override string TitleForFooter(UITableView tableView, nint section)
{
return "This is the title for this given section";
}
}
}
}
1. However what I would like is to be able to extend TableView so that I am able to put in the XAML some way to set or leave unset the footer text and height. Something like:
<ExtTableView FooterText="abc" FooterHeight="50". ..
2. From experiments with the code above I tried hardcoding in some text and realize that there is no spacing set. So I would also like to find out if there is a way to set the spacing and font so it appears just like in the iOS settings pages?
Could someone suggest how I could go about creating what I am looking for which is I guess something like an ExtTableView class that can accept additional arguments.
As hankide said , I just provide more details.
However what I would like is to be able to extend TableView so that I am able to put in the XAML some way to set or leave unset the footer text and height.
Create MyTableView that inherits from TableView
public class MyTableView : TableView
{
public static readonly BindableProperty FooterHeightProperty =
BindableProperty.Create("FooterHeight", typeof(string), typeof(MyTableView), "");
public string FooterHeight
{
get { return (string)GetValue(FooterHeightProperty); }
set { SetValue(FooterHeightProperty, value); }
}
public static readonly BindableProperty FooterTextProperty =
BindableProperty.Create("FooterText", typeof(string), typeof(MyTableView), "");
public string FooterText
{
get { return (string)GetValue(FooterTextProperty); }
set { SetValue(FooterTextProperty, value); }
}
}
Get the value that you set in XMAL and assign them to CustomFooterTableViewModelRenderer
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
var tableView = Control as UITableView;
var formsTableView = Element as MyTableView;
CustomFooterTableViewModelRenderer render = new CustomFooterTableViewModelRenderer(formsTableView);
render.height = float.Parse(formsTableView.FooterHeight);
render.text = formsTableView.FooterText;
tableView.WeakDelegate = render;
}
private class CustomFooterTableViewModelRenderer : TableViewModelRenderer
{
public float height { get; set; }
public String text { get; set; }
public CustomFooterTableViewModelRenderer(TableView model) : base(model)
{
}
public override UIView GetViewForFooter(UITableView tableView, nint section)
{
UIView view = new UIView(new CGRect(0, 0, tableView.Frame.Width, 50));
view.BackgroundColor = UIColor.Gray;
UILabel label = new UILabel();
label.Frame = new CGRect(0, 0, tableView.Frame.Width, height);
label.BackgroundColor = UIColor.Red;
label.Text = text;
label.Font = UIFont.SystemFontOfSize(15);
view.Add(label);
return view;
}
public override nfloat GetHeightForFooter(UITableView tableView, nint section)
{
return 50;
}
}
Usage:
<local:MyTableView FooterHeight="20" FooterText="ABC">
<TableRoot>
<TableSection>
<TextCell Text="22222" ></TextCell>
</TableSection>
</TableRoot>
</local:MyTableView>
From experiments with the code above I tried hardcoding in some text and realize that there is no spacing set. So I would also like to find out if there is a way to set the spacing and font so it appears just like in the iOS settings pages?
You could override the method GetViewForFooter to change the defalut style of footer,find it in the code above .
My test :
You had the right idea about creating the custom control. Here's what to do:
Create ExtTableView class that inherits from TableView
public class ExtTableView : TableView { }
Create BindableProperties for both FooterText and FooterHeight, as outlined here.
After that you can set the properties in XAML
<ExtTableView FooterText="abc" FooterHeight="50" ...
Within the renderer, you can get the values from Element (which points to our Xamarin.Forms ExtTableView).
// Modify the native control with these values
var text = Element.FooterText;
var height = Element.FooterHeight;
In my Xamarin forms application I want to show a confirmation message when user clicks the back button from Main-page. Is there any way to achieve this?
I overrided the OnBackButtonPressed method in my MainPage. But still the app is closing while back key press. Here is my code
protected override bool OnBackButtonPressed ()
{
//return base.OnBackButtonPressed ();
return false;
}
You can override OnBackButtonPressed for any Xamarin.Form Page. But it only will work for the physical button in Android and Windows Phone devices.
protected override bool OnBackButtonPressed () {
DisplayAlert("title","message","ok");
return true;
}
For the virtual one, you will need to create CustomRenderers and to intercept the click handler. In iOS it can be tricky because the user can go back doing other actions (e.g. the swipe gesture). Once you intercept it you just need to create your Confirmation Message (which I assume that you know how to do it).
For iOS you can do something like this:
[assembly: ExportRenderer (typeof (YourPage), typeof (YourPageRenderer))]
namespace YourNamespace {
public class YourPageRenderer : PageRenderer {
public override void ViewWillAppear (bool animated) {
base.ViewWillAppear (animated);
Action goBack = () => page.DisplayAlert("title","message","ok");
var backButton = new NavBackButton (goBack);
navigationItem.LeftBarButtonItem = new UIBarButtonItem (backButton);
}
}
public class NavBackButton : UIView {
public NavBackButton (Action onButtonPressed) {
SetButton (onButtonPressed);
}
UILabel text;
UIImageView arrow;
void SetButton(Action onButtonPressed){
arrow = new UIImageView(new CGRect(-25,0, 50, 50)) {
Image = new UIImage("Images/back").ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
};
arrow.TintColor = Colors.DarkGreen.ToUIColor ();
text = new UILabel(new CGRect(arrow.Frame.Width + arrow.Frame.X -15, arrow.Frame.Height /2 - 10, 40, 20)) { Text = "Back" };
text.TextColor = Colors.DarkGreen.ToUIColor ();
Frame = new CGRect(0,0,text.Frame.Size.Width + arrow.Frame.Width, arrow.Frame.Height);
AddSubviews (new UIView[] { arrow, text });
var tapGesture = new UITapGestureRecognizer (onButtonPressed);
AddGestureRecognizer (tapGesture);
}
public override void TouchesBegan (Foundation.NSSet touches, UIEvent evt) {
base.TouchesBegan (touches, evt);
text.TextColor = UIColor.YourColor;
arrow.TintColor = UIColor.YourColor;
}
public override void TouchesEnded (Foundation.NSSet touches, UIEvent evt){
base.TouchesEnded (touches, evt);
arrow.TintColor = UIColor.YourColor;
text.TextColor = UIColor.YourColor;
}
}
}
PS You will need to provide an arrow image ("Images/back")
I have looked around the web for some time looking for any resources on this topic and have come up with nothing that solves my dilemma.
I have a dialog view controller and its root is simply displaying a list of strings similar to how the iphone music song scrollable view is laid out. What I need is a subview located at the top of the screen and the scrollable DVC below it. I need to the top view to be always in view while the user can scroll through the root element because the top view will be holding statistics.
I have tried adding a subview but it simply overlaps the dvc below it, and I have not been able to figure out a way to add a dvc as a subview to a UIView.
Any help would be much appreciated.
What is needed to achieve this is a single root view controller that hosts two subview controllers. One subview contains the statistics at the top of the window. The bottom subview contains a navigation controller that holds the dialog view.
using System;
using System.Collections.Generic;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using MonoTouch.Dialog;
using System.Drawing;
namespace delete201205203
{
[Register ("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
UIWindow window;
MyUIViewController _mvc;
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
window = new UIWindow (UIScreen.MainScreen.Bounds);
_mvc = new MyUIViewController ();
window.RootViewController = _mvc;
window.MakeKeyAndVisible ();
return true;
}
}
public class MyUIViewController : UIViewController
{
MyDialogViewController _dvc;
UINavigationController _nav;
StatisticsViewController _statistics;
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
var root = new RootElement ("Root") {
new Section ("Section") {
new EntryElement ("caption", "placeholder", ""),
new RootElement ("Root 2") {
new Section ("Section") {
new EntryElement ("caption", "placeholder", ""),
new StringElement ("Back", () => {
_nav.PopViewControllerAnimated (true);
})
}
}
}
};
_dvc = new MyDialogViewController (root);
_nav = new UINavigationController (_dvc);
_nav.SetNavigationBarHidden (true, false);
_nav.View.Frame = new RectangleF (0, 70f,
this.View.Bounds.Width,
this.View.Bounds.Height -70f);
_statistics = new StatisticsViewController ();
_statistics.View.Frame = new RectangleF (0, 0,
this.View.Bounds.Width,
70f);
this.AddChildViewController (_nav);
this.View.AddSubview (_nav.View);
this.AddChildViewController (_statistics);
this.View.AddSubview (_statistics.View);
}
public override void ViewWillLayoutSubviews ()
{
base.ViewWillLayoutSubviews ();
_nav.View.Frame = new RectangleF (0, 70f,
this.View.Bounds.Width,
this.View.Bounds.Height -70f);
_statistics.View.Frame = new RectangleF (0, 0,
this.View.Bounds.Width,
70f);
}
public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)
{
return true;
}
}
public class StatisticsViewController : UIViewController
{
UILabel _label;
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
this.View.BackgroundColor = UIColor.White;
_label = new UILabel (new RectangleF (this.View.Bounds.Width * .5f - 50f,
this.View.Bounds.Height * .5f -10f,
100f, 20f));
_label.AutoresizingMask = UIViewAutoresizing.FlexibleMargins;
_label.Text = "statistics";
this.View.AddSubview (_label);
}
public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)
{
return true;
}
}
// This overrde is needed to ensure the pop view animation
// works correctly in landscape mode
public class MyDialogViewController : DialogViewController
{
public MyDialogViewController (RootElement root) : base (root) {}
public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)
{
return true;
}
}
}