MvvmCross: Cannot add more than one UIPickerView - xamarin.ios

I'm building an iOS view using Xamarin and MvvmCross and I have come across an interesting little issue. I can't seem to add more than one UIPickerView to a UIView.
Add one view and all works well. Add a second and the simulator just hangs when I try and open the page.
This seems to be related to a UITextField with an InputView as I also have an issue if I try to add a UIDatePicker as well.
Nothing strange in the debug output.
Here is the code:
[Register("EditJobViewJobView")]
public class EditJobView : MvxViewController
{
public new EditJobViewModel ViewModel
{
get { return (EditJobViewModel)base.ViewModel; }
}
private const float _leftMargin = 6;
private const float _labelHeight = 20;
private const float _pickerHeight = 28;
private readonly UIFont _labelFont = UIFont.BoldSystemFontOfSize(18f);
private readonly UIFont _controlFont = UIFont.SystemFontOfSize(18f);
private readonly UIView _paddingInsert = new UIView(new RectangleF(0, 0, 4, 0));
private int _currentTop = 0;
public override void ViewDidLoad()
{
base.ViewDidLoad();
View = new UIView() { BackgroundColor = UIColor.White };
NavigationItem.SetRightBarButtonItem(new UIBarButtonItem("Save", UIBarButtonItemStyle.Bordered,
(sender, args) => ViewModel.OkCommand.Execute(null)), true);
// ios7 layout
if (RespondsToSelector(new Selector("edgesForExtendedLayout")))
EdgesForExtendedLayout = UIRectEdge.None;
Title = "Edit Job";
AddLabel("Job Status");
MvxPickerViewModel jobStatusPickerViewModel;
var jobStatusTextView = AddPickerView(out jobStatusPickerViewModel);
AddLabel("Job Priority");
MvxPickerViewModel jobPriorityPickerViewModel;
var jobPriorityTextView = AddPickerView(out jobPriorityPickerViewModel);
var set = this.CreateBindingSet<EditJobView, EditJobViewModel>();
set.Bind(jobStatusPickerViewModel).For(p => p.SelectedItem).To(vm => vm.SelectedJobStatusType);
set.Bind(jobStatusPickerViewModel).For(p => p.ItemsSource).To(vm => vm.JobStatusTypes);
set.Bind(jobStatusTextView).To(vm => vm.SelectedJobStatusType);
set.Bind(jobPriorityPickerViewModel).For(p => p.SelectedItem).To(vm => vm.SelectedJobPriority);
set.Bind(jobPriorityPickerViewModel).For(p => p.ItemsSource).To(vm => vm.JobPriorities);
set.Bind(jobPriorityTextView).To(vm => vm.SelectedJobPriority);
set.Apply();
}
private void AddLabel(string caption)
{
_currentTop += 10;
var frame = new RectangleF(_leftMargin, _currentTop, 300, _labelHeight);
var label = new UILabel(frame);
label.Font = _labelFont;
label.Text = caption;
AddView(label);
_currentTop += 2;
}
private UITextField AddPickerView(out MvxPickerViewModel pickerViewModel)
{
var textField = AddTextField();
var pickerView = new UIPickerView();
pickerViewModel = new MvxPickerViewModel(pickerView);
pickerView.Model = pickerViewModel;
pickerView.ShowSelectionIndicator = true;
textField.InputView = pickerView;
return textField;
}
private UITextField AddTextField()
{
var frame = new RectangleF(_leftMargin, _currentTop, 300, _pickerHeight);
var textField = new UITextField(frame);
textField.Layer.BorderColor = UIColor.Black.CGColor;
textField.Layer.BorderWidth = 1f;
textField.Font = _controlFont;
textField.LeftView = _paddingInsert;
textField.LeftViewMode = UITextFieldViewMode.Always;
AddView(textField);
return textField;
}
private void AddView(UIView view)
{
View.AddSubview(view);
_currentTop += (int)view.Frame.Height;
}
}
Any ideas?

This was caused by creating a shared padding insert across a number of UITextFields.
To fix this I changed
textField.LeftView = _paddingInsert;
to
textField.LeftView = new UIView(new RectangleF(0, 0, 4, 0));

Related

How I can Add Text inside Frame TabbedRenderer in Xamarin iOS

I am customizing Badge in TabbedPage. I have now added the red dot from inheriting UIView. However how can I Add Text inside that Frame. Xamarin iOS
MyTabbedPageRenderer.cs
public static class TabbarExtensions
{
readonly static int tabBarItemTag = 9999;
public static void addItemBadge(this UITabBar tabbar, int index)
{
if (tabbar.Items != null && tabbar.Items.Length == 0) return;
if (index >= tabbar.Items.Length) return;
removeItemBadge(tabbar, index);
var badgeView = new UIView();
badgeView.Tag = tabBarItemTag + index;
badgeView.Layer.CornerRadius = 7;
badgeView.BackgroundColor = UIColor.Red;
var tabFrame = tabbar.Frame;
var percentX = (index + 0.56) / tabbar.Items.Length;
var x = percentX * tabFrame.Width;
var y = tabFrame.Height * 0.05;
badgeView.Frame = new CoreGraphics.CGRect(x, y, 13, 13);
tabbar.AddSubview(badgeView);
//var badgeView = new UILabel();
//badgeView.Tag = tabBarItemTag + index;
//badgeView.BackgroundColor = UIColor.Red;
//var tabFrame = tabbar.Frame;
//var percentX = (index + 0.56) / tabbar.Items.Length;
//var x = percentX * tabFrame.Width;
//var y = tabFrame.Height * 0.05;
//badgeView.Text = "1";
//badgeView.TextAlignment = UITextAlignment.Center;
//badgeView.TextColor = UIColor.White;
//badgeView.Font = UIFont.SystemFontOfSize(10);
//tabbar.AddSubview(badgeView);
}
public static bool removeItemBadge(this UITabBar tabbar, int index)
{
foreach (var subView in tabbar.Subviews)
{
if (subView.Tag == tabBarItemTag + index)
{
subView.RemoveFromSuperview();
return true;
}
}
return false;
}
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
MessagingCenter.Subscribe<object, int>(this, "Add", (obj, index) => {
TabBar.addItemBadge(index);
});
MessagingCenter.Subscribe<object, int>(this, "Remove", (obj, index) => {
TabBar.removeItemBadge(index);
});
}
MainView.xaml.cs
protected override void OnAppearing()
{
base.OnAppearing();
MessagingCenter.Send<object, int>(this, "Add", 2);
}
As in my code above I tried with UILabel. However I cannot set CornerRadius for Label
Summarize how I can Add Text inside Frame. Or how can I set CornerRadius for Lable in Xamarin iOS.
Thank you!
Description picture:
If I use UILabel, I cannot set CornerRadius
If I use UIView
For UILabel, you could set the CornerRadius using the following:
badgeView.Layer.CornerRadius = 7;
badgeView.Layer.MasksToBounds = true;//add this line
For more info, you could refer to masksToBounds.
Hope it works for you.

How to add shadow to an UIView in Xamarin.iOS?

I found this link for doing it in Swift:
What's the best way to add a drop shadow to my UIView
However when I adapt the solution to Xamarin, no shadow appears.
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
UIBezierPath path = new UIBezierPath();
PreviousButton.Layer.MasksToBounds = false;
PreviousButton.Layer.ShadowColor = UIColor.Gray.CGColor;
PreviousButton.Layer.ShadowOffset = new CoreGraphics.CGSize(0f, 7f);
PreviousButton.Layer.ShadowOpacity = 1;
PreviousButton.Layer.ShadowPath = path.CGPath;
}
I am using AutoLayout.
Cause:
You seem forgot to set the Rect of the path
Solution 1:
You could directly set the shadow of the button
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
PreviousButton.Layer.MasksToBounds = false;
PreviousButton.Layer.ShadowColor = UIColor.Gray.CGColor;
PreviousButton.Layer.ShadowOffset = new CoreGraphics.CGSize(0f, 7f);
PreviousButton.Layer.ShadowOpacity = 1;
}
Solution 2:
If you do want to use BezierPath , set the rect of it .
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
UIBezierPath path = UIBezierPath.FromRect(PreviousButton.Bounds);
PreviousButton.Layer.MasksToBounds = false;
PreviousButton.Layer.ShadowColor = UIColor.Gray.CGColor;
PreviousButton.Layer.ShadowOffset = new CoreGraphics.CGSize(0f, 7f);
PreviousButton.Layer.ShadowOpacity = 1;
PreviousButton.Layer.ShadowPath = path.CGPath;
}

Radio Button in Xamarin.iOS listen for tap?

I need to get a view with two radio buttons working, where only one can be clicked at a time. Using the answer posted here by user Alanc Liu: Radio button in xamarin.ios I've got my View Controller looking correct, but I can't figure out how to listen for the tap to set the other radio button to false.
I've tried playing around with adding a gesture recognizer to the ViewDidLoad method, but haven't gotten anything to work yet (I've mostly just used the storyboard previously to add methods to button clicks).
My View Controller:
public partial class VerifyViewController : UIViewController
{
public VerifyViewController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
MyRadioButton tBtn = new MyRadioButton(new CGPoint(100, 300), "TEXT PHONE");
MyRadioButton eBtn = new MyRadioButton(new CGPoint(100, 375), "EMAIL");
this.Add(tBtn);
this.Add(eBtn);
}
}
And his Radio Button Classes:
public class MyRadioButton : UIView
{
private CircleView circleView;
private UILabel lbTitle;
public bool State {
get {
return circleView.State;
}
set {
circleView.State = value;
}
}
public MyRadioButton (CGPoint pt,string title)
{
this.Frame = new CGRect (pt, new CGSize (150, 30));
circleView = new CircleView (new CGRect(0, 0, 30, 30));
lbTitle = new UILabel (new CGRect (30, 0, 120, 30));
lbTitle.Text = title;
lbTitle.TextAlignment = UITextAlignment.Center;
this.AddSubview (circleView);
this.AddSubview (lbTitle);
this.BackgroundColor = UIColor.FromRGBA(1,0,0,0.3f);
UITapGestureRecognizer tapGR = new UITapGestureRecognizer (() => {
State = !State;
});
this.AddGestureRecognizer (tapGR);
}
}
class CircleView : UIView
{
private bool state = false;
public bool State {
get {
return state;
}
set {
state = value;
this.SetNeedsDisplay ();
}
}
public CircleView (CGRect frame)
{
this.BackgroundColor = UIColor.Clear;
this.Frame = frame;
}
public override void Draw (CoreGraphics.CGRect rect)
{
CGContext con = UIGraphics.GetCurrentContext ();
float padding = 5;
con.AddEllipseInRect (new CGRect (padding, padding, rect.Width - 2 * padding, rect.Height - 2 * padding));
con.StrokePath ();
if (state) {
float insidePadding = 8;
con.AddEllipseInRect (new CGRect (insidePadding, insidePadding, rect.Width - 2 * insidePadding, rect.Height - 2 * insidePadding));
con.FillPath ();
}
}
}
Expose a public event in MyRadioButton ,call it when we tap the radio button.
Code in MyRadioButton:
//define the event inside MyRadioButton
public delegate void TapHandler(MyRadioButton sender);
public event TapHandler Tap;
//call it in MyRadioButton(CGPoint pt, string title)
UITapGestureRecognizer tapGR = new UITapGestureRecognizer(() => {
State = !State;
Tap(this);
});
Handle the event inside your viewController
Code in ViewController
MyRadioButton tBtn = new MyRadioButton(new CGPoint(100, 300), "TEXT PHONE");
MyRadioButton eBtn = new MyRadioButton(new CGPoint(100, 375), "EMAIL");
this.Add(tBtn);
this.Add(eBtn);
tBtn.Tap += Btn_Tap;
eBtn.Tap += Btn_Tap;
// set the default selection
Btn_Tap(tBtn);
MyRadioButton PreviousButton;
private void Btn_Tap(MyRadioButton sender)
{
if(PreviousButton != null)
{
//set previous to false
PreviousButton.State = false;
}
//set current to true
sender.State = true;
//assign current to previous
PreviousButton = sender;
}
Result:

MvvmCross - MvxTabBarViewController does not show the "More" button when using a Storyboard

In my application, I'm using the MvxTabBarViewController to display some tabs (the MvxTabBarViewController is loaded using a Storyboard):
public partial class RootView : MvxTabBarViewController
{
private int _tabIndex = 0;
private Dictionary<int, SectionBase> _sectionBaseForConfig;
public new RootViewModel ViewModel
{
get { return (RootViewModel)base.ViewModel; }
set { base.ViewModel = value; }
}
public RootView(IntPtr handle)
: base(handle)
{
}
UIViewController tab1, tab2, tab3, tab4, tab5, tab6, tab7;
public override async void ViewDidLoad()
{
base.ViewDidLoad();
if (ViewModel == null)
return;
tab1 = new UIViewController();
tab1.Title = "1";
tab1.View.BackgroundColor = UIColor.Green;
tab2 = new UIViewController();
tab2.Title = "2";
tab2.View.BackgroundColor = UIColor.Orange;
tab3 = new UIViewController();
tab3.Title = "3";
tab3.View.BackgroundColor = UIColor.Red;
tab4 = new UIViewController();
tab4.Title = "4";
tab4.View.BackgroundColor = UIColor.Blue;
tab5 = new UIViewController();
tab5.Title = "5";
tab5.View.BackgroundColor = UIColor.Brown;
tab6 = new UIViewController();
tab6.Title = "6";
tab6.View.BackgroundColor = UIColor.Brown;
tab7 = new UIViewController();
tab7.Title = "7";
tab7.View.BackgroundColor = UIColor.Brown;
var tabs = new UIViewController[] {
tab1, tab2, tab3, tab4, tab5, tab6, tab7
};
ViewControllers = tabs;
CustomizableViewControllers = null;
SelectedViewController = ViewControllers[0];
}
}
Unfortunately, even if there are 7 tabs, only 5 are displayed and the "More" button does not show. Anyone got an idea ? I stuck on this problem since 2 hrs now, without any clues :(
Thanks!

MvvmCross bind to progress indicator in MvxDialogViewController

I have a MvxDialogViewController and I'm trying to use the progress indicator shown in the Xamarin example by adding bindable properties.
I can get the indicator to appear when I set Visble to true programatically but not when I bind to a vm property.
Here is the view code:
var bindings = this.CreateInlineBindingTarget<LoginViewModel>();
Root = new RootElement("Login")
{
new Section("Login Credentials")
{
new EntryElement("Username", "Enter user name").Bind(bindings, vm => vm.UserName),
new EntryElement("Password", "Enter password", "", true).Bind(bindings, vm => vm.Password)
}
};
_bindableProgress = new BindableProgress(UIScreen.MainScreen.Bounds).Bind(bindings, b => b.Visible, vm => vm.IsBusy);
_bindableProgress.Title = "Logging in...";
View.Add(_bindableProgress);
I also tried to bind like this:
var set = this.CreateBindingSet<LoginView, LoginViewModel>();
set.Bind(_bindableProgress).For(b => b.Title).To(vm => vm.ProgressTitle);
set.Bind(_bindableProgress).For(b => b.Visible).To(vm => vm.IsBusy);
set.Apply();
But neither way worked.
Here is by BindableProgress class:
public class BindableProgress : UIView
{
private UIActivityIndicatorView _activitySpinner;
private UILabel _loadingLabel;
public string Title { get; set; }
private bool _visible;
public bool Visible
{
get { return _visible; }
set
{
_visible = value;
if (_visible)
{
Show();
}
else
{
Hide();
}
}
}
public BindableProgress(RectangleF frame) : base(frame)
{
// configurable bits
BackgroundColor = UIColor.Black;
Alpha = 0;
AutoresizingMask = UIViewAutoresizing.FlexibleDimensions;
float labelHeight = 22;
float labelWidth = Frame.Width - 20;
// derive the center x and y
float centerX = Frame.Width/2;
float centerY = Frame.Height/2;
// create the activity spinner, center it horizontally and put it 5 points above center x
_activitySpinner = new UIActivityIndicatorView(UIActivityIndicatorViewStyle.WhiteLarge);
_activitySpinner.Frame = new RectangleF(
centerX - (_activitySpinner.Frame.Width / 2),
centerY - _activitySpinner.Frame.Height - 20,
_activitySpinner.Frame.Width,
_activitySpinner.Frame.Height);
_activitySpinner.AutoresizingMask = UIViewAutoresizing.FlexibleMargins;
AddSubview(_activitySpinner);
// create and configure the label
_loadingLabel = new UILabel(new RectangleF(
centerX - (labelWidth/2),
centerY + 20,
labelWidth,
labelHeight
));
_loadingLabel.BackgroundColor = UIColor.Clear;
_loadingLabel.TextColor = UIColor.White;
_loadingLabel.TextAlignment = UITextAlignment.Center;
_loadingLabel.AutoresizingMask = UIViewAutoresizing.FlexibleMargins;
AddSubview(_loadingLabel);
}
private void Show()
{
_loadingLabel.Text = Title;
Alpha = 0.75f;
_activitySpinner.StartAnimating();
}
/// <summary>
/// Fades out the control and then removes it from the super view
/// </summary>
private void Hide()
{
_activitySpinner.StopAnimating();
Animate(
0.5, // duration
() => { Alpha = 0; },
() => { RemoveFromSuperview(); }
);
}
}
Any ideas?
UPDATE
My vm property looks like this
private bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set { _isBusy = value; RaisePropertyChanged(() => IsBusy); }
}
It works fine in Android so I'm guessing the problem in not with that.
IsBusy is probably false at binding time. So _visible is set to false and Hide() is called. Now the view is removed from the superview and you can't show it anymore, because Show() doesn't add it to the superview again. Try to omit the RemoveFromSuperview();. Or modify the Visible property like this:
public bool Visible
{
get { return _visible; }
set
{
if(_visible == value)
return;
_visible = value;
if (_visible)
{
Show();
}
else
{
Hide();
}
}
}

Resources