xamarinxamarin.formsdatepicker

Month Picker In Xamarin Forms


I am having a requirement like a Month picker which is showing only month and year and not the date .How can we achieve the same for both IOS and Android platform using Xamarin forms ?


Solution

  • You could implement it by using Custom Renderer

    in Forms

    create a custom View

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Xamarin.Forms;
    
    namespace App20
    {
        public  class MonthYearPickerView :View
        {
          
            public static readonly BindableProperty FontSizeProperty = BindableProperty.Create(
                propertyName: nameof(FontSize),
                returnType: typeof(double),
                declaringType: typeof(MonthYearPickerView),
                defaultValue: (double)24,
                defaultBindingMode: BindingMode.TwoWay);
    
            [TypeConverter(typeof(FontSizeConverter))]
            public double FontSize
            {
                get => (double)GetValue(FontSizeProperty);
                set => SetValue(FontSizeProperty, value);
            }
    
           
    
            public static readonly BindableProperty TextColorProperty = BindableProperty.Create(
                propertyName: nameof(TextColor),
                returnType: typeof(Color),
                declaringType: typeof(MonthYearPickerView),
                defaultValue: Color.White,
                defaultBindingMode: BindingMode.TwoWay);
    
            public Color TextColor
            {
                get => (Color)GetValue(TextColorProperty);
                set => SetValue(TextColorProperty, value);
            }
    
         
            public static readonly BindableProperty InfiniteScrollProperty = BindableProperty.Create(
                propertyName: nameof(InfiniteScroll),
                returnType: typeof(bool),
                declaringType: typeof(MonthYearPickerView),
                defaultValue: true,
                defaultBindingMode: BindingMode.TwoWay);
    
            public bool InfiniteScroll
            {
                get => (bool)GetValue(InfiniteScrollProperty);
                set => SetValue(InfiniteScrollProperty, value);
            }
    
      
    
            public static readonly BindableProperty DateProperty = BindableProperty.Create(
                propertyName: nameof(Date),
                returnType: typeof(DateTime),
                declaringType: typeof(MonthYearPickerView),
                defaultValue: default,
                defaultBindingMode: BindingMode.TwoWay);
    
            public DateTime Date
            {
                get => (DateTime)GetValue(DateProperty);
                set => SetValue(DateProperty, value);
            }
    
    
    
            public static readonly BindableProperty MaxDateProperty = BindableProperty.Create(
                propertyName: nameof(MaxDate),
                returnType: typeof(DateTime?),
                declaringType: typeof(MonthYearPickerView),
                defaultValue: default,
                defaultBindingMode: BindingMode.TwoWay);
    
            public DateTime? MaxDate
            {
                get => (DateTime?)GetValue(MaxDateProperty);
                set => SetValue(MaxDateProperty, value);
            }
    
    
    
            public static readonly BindableProperty MinDateProperty = BindableProperty.Create(
                propertyName: nameof(MinDate),
                returnType: typeof(DateTime?),
                declaringType: typeof(MonthYearPickerView),
                defaultValue: default,
                defaultBindingMode: BindingMode.TwoWay);
    
            public DateTime? MinDate
            {
                get => (DateTime?)GetValue(MinDateProperty);
                set => SetValue(MinDateProperty, value);
            }
    
          
        }
    }
    
    

    in iOS

    using System;
    using App20;
    using App20.iOS;
    using UIKit;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.iOS;
    
    [assembly: ExportRenderer(typeof(MonthYearPickerView), typeof(MonthYearPickerRenderer))]
    namespace App20.iOS
    {
        public class MonthYearPickerRenderer : ViewRenderer<MonthYearPickerView, UITextField>
        {
            private DateTime _selectedDate;
            private UITextField _dateLabel;
            private PickerDateModel _pickerModel;
    
            protected override void OnElementChanged(ElementChangedEventArgs<MonthYearPickerView> e)
            {
                base.OnElementChanged(e);
                _dateLabel = new UITextField();
                _dateLabel.TextAlignment = UITextAlignment.Center;
    
                var dateToday = DateTime.Today;
                SetupPicker(new DateTime(dateToday.Year, dateToday.Month, 1));
                
                SetNativeControl(_dateLabel);
    
                Control.EditingChanged += ControlOnEditingChanged;
                Element.PropertyChanged += Element_PropertyChanged;
            }
    
            private void ControlOnEditingChanged(object sender, EventArgs e)
            {
                var currentDate = $"{Element.Date.Month:D2} | {Element.Date.Year}";
                if (_dateLabel.Text != currentDate)
                {
                    _dateLabel.Text = currentDate;
                }
            }
    
            protected override void Dispose(bool disposing)
            {
                Element.PropertyChanged -= Element_PropertyChanged;
                base.Dispose(disposing);
            }
    
            private void SetupPicker(DateTime date)
            {
                var datePicker = new UIPickerView();
                _pickerModel = new PickerDateModel(datePicker, date, Element.MaxDate, Element.MinDate);
                datePicker.ShowSelectionIndicator = true;
                _selectedDate = date;
                _pickerModel.PickerChanged += (sender, e) =>
                {
                    _selectedDate = e;
                };
                datePicker.Model = _pickerModel;
                _pickerModel.MaxDate = Element.MaxDate ?? DateTime.MaxValue;
                _pickerModel.MinDate = Element.MinDate ?? DateTime.MinValue;
    
                var toolbar = new UIToolbar
                {
                    BarStyle = UIBarStyle.Default, 
                    Translucent = true
                };
                toolbar.SizeToFit();
    
                var doneButton = new UIBarButtonItem("Done", UIBarButtonItemStyle.Done,
                    (s, e) =>
                    {
                        Element.Date = _selectedDate;
                        _dateLabel.Text = $"{Element.Date.Month:D2} | {Element.Date.Year}";
                        _dateLabel.ResignFirstResponder();
                    });
    
                toolbar.SetItems(new[] { new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace), doneButton }, true);
    
                _dateLabel.InputView = datePicker;
                _dateLabel.Text = $"{Element.Date.Month:D2} | {Element.Date.Year}";
                _dateLabel.InputAccessoryView = toolbar;
                _dateLabel.TextColor = Element.TextColor.ToUIColor();
            }
    
            private void Element_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
            {
                if (e.PropertyName == MonthYearPickerView.MaxDateProperty.PropertyName)
                {
                    _pickerModel.MaxDate = Element.MaxDate ?? DateTime.MinValue;
                }
                else if (e.PropertyName == MonthYearPickerView.MinDateProperty.PropertyName)
                {
                    _pickerModel.MinDate = Element.MinDate ?? DateTime.MaxValue;
                }
            }
        }
    }
    
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using UIKit;
    
    namespace App20.iOS
    {
        public class PickerDateModel : UIPickerViewModel
        {
            public event EventHandler<DateTime> PickerChanged;
    
            #region Fields
    
            private readonly List<string> _mainNamesOfMonthSource;
            private readonly List<int> _mainYearsSource;
            private readonly UIPickerView _picker;
            private readonly int _numberOfComponents;
            private readonly int _minYear;
            private readonly int _maxYear;
    
            private List<int> _years;
            private List<string> _namesOfMonth;
    
            private DateTime _selectedDate;
            private DateTime _maxDate;
            private DateTime _minDate;
    
            #endregion Fields
    
            #region Constructors
    
            public PickerDateModel(UIPickerView datePicker, DateTime selectedDate, DateTime? maxDate, DateTime? minDate)
            {
                _mainNamesOfMonthSource = DateTimeFormatInfo.CurrentInfo?.MonthNames
                    .Where(x => !string.IsNullOrWhiteSpace(x))
                    .ToList();
    
    
    
                _maxDate = maxDate ?? DateTime.MaxValue;
                _minDate = minDate ?? DateTime.MinValue;
    
                _maxYear = _maxDate.Year;
                _minYear = _minDate.Year;
    
                _years = new List<int>();
    
                
                
                _picker = datePicker;
                _namesOfMonth = _mainNamesOfMonthSource;
                
                _numberOfComponents = 2;
    
                SelectedDate = selectedDate;
            }
    
            #endregion Constructors
    
            #region Properties
    
            public DateTime SelectedDate
            {
                get => _selectedDate;
                set
                {
                    _selectedDate = value;
                    ReloadSections();
                    PickerChanged?.Invoke(this, value);
                }
            }
    
    
            public DateTime MaxDate
            {
                get => _maxDate;
                set
                {
                    _maxDate = value;
                    ReloadSections();
                }
            }
    
            public DateTime MinDate
            {
                get => _minDate;
                set
                {
                    _minDate = value;
                    ReloadSections();
                }
            }
    
            #endregion Properties
    
            #region Private Methods
    
            private void ReloadSections()
            {
                var selectedDate = SelectedDate == DateTime.MinValue
                    ? DateTime.Today
                    : SelectedDate;
    
                _years.Clear();
                for (int i = _minYear; i <= _maxYear; i++)
                {
                    _years.Add(i);
                }
    
                _namesOfMonth = _mainNamesOfMonthSource;
    
                if (SelectedDate.Year == MinDate.Year)
                {
                    _namesOfMonth = _mainNamesOfMonthSource.Skip(MinDate.Month - 1).ToList();
                }
    
                if (SelectedDate.Year == MaxDate.Year)
                {
                    _namesOfMonth = _mainNamesOfMonthSource.Take(MaxDate.Month).ToList();
                }
    
                SetCarousels(selectedDate);
            }
    
            #endregion Private Methods
    
            #region Public Methods
    
            public void SetCarousels(DateTime dateTime)
            {
                if (_picker.NumberOfComponents != _numberOfComponents) return;
    
                var y = DateTimeFormatInfo.CurrentInfo?.GetMonthName(dateTime.Month);
                var x = _namesOfMonth.IndexOf(y);
    
                _picker.Select(x, 0, false);
                _picker.Select(_years.IndexOf(dateTime.Year), 1, false);
    
                _picker.ReloadComponent(0);
                _picker.ReloadComponent(1);
            }
    
            public override nint GetComponentCount(UIPickerView pickerView)
            {
                return _numberOfComponents;
            }
    
            public override nint GetRowsInComponent(UIPickerView pickerView, nint component)
            {
    
    
    
                if (component == 0)
                {
                    return _namesOfMonth.Count;
                }
                else if (component == 1)
                {
                    return _years.Count;
                }
                else
                {
                    return 0;
                }
    
            }
    
    
            public override string GetTitle(UIPickerView pickerView, nint row, nint component)
            {
                
    
                if (component == 0)
                {
                    return _namesOfMonth.Count==0 ? _namesOfMonth.First() : _namesOfMonth[(int)row];
                }
                else if (component == 1)
                {
                    var list = _years;
                    return _years.Count==0? _years.First().ToString() : _years[(int)row].ToString();
                }
                else
                {
                    return row.ToString();
                }
               
            }
        
            public override void Selected(UIPickerView pickerView, nint row, nint component)
            {
                var month = GetMonthNumberByName(_namesOfMonth[(int)pickerView.SelectedRowInComponent(0)]);
                var year = _years[(int)pickerView.SelectedRowInComponent(1)];
    
                if (year == MinDate.Year)
                {
                    month = month >= MinDate.Month ? month : MinDate.Month;
                }
                
                if (year == MaxDate.Year)
                {
                    month = month <= MaxDate.Month ? month : MaxDate.Month;
                }
        
                SelectedDate = new DateTime(year, month, 1);
        
                 ReloadSections();
                pickerView.ReloadAllComponents();
    
                int GetMonthNumberByName(string monthName) =>
                    DateTime.ParseExact(monthName, "MMMM", CultureInfo.CurrentCulture).Month;
            }
            
            #endregion Public Methods
        }
    }
    

    in Android

    in MainActivity

        public static MainActivity Instance { get; private set; }
    
        protected override void OnCreate(Bundle savedInstanceState)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;
    
            Instance = this;
    
            base.OnCreate(savedInstanceState);
    
            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
            LoadApplication(new App());
        }
    
    using Android.Content;
    using Android.Support.V7.App;
    using Android.Widget;
    using App20;
    using App20.Droid;
    
    using System;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.Android;
    
    [assembly: ExportRenderer(typeof(MonthYearPickerView), typeof(MonthYearPickerRenderer))]
    namespace App20.Droid
    {
        public class MonthYearPickerRenderer : ViewRenderer<MonthYearPickerView, EditText>
        {
            private readonly Context _context;
            private MonthYearPickerDialog _monthYearPickerDialog;
    
            public MonthYearPickerRenderer(Context context) : base(context)
            {
                _context = context;
            }
    
            protected override void OnElementChanged(ElementChangedEventArgs<MonthYearPickerView> e)
            {
                base.OnElementChanged(e);
    
                CreateAndSetNativeControl();
    
                Control.KeyListener = null;
                Element.Focused += Element_Focused;
            }
    
            protected override void Dispose(bool disposing)
            {
                if (Control == null) return;
    
                Element.Focused -= Element_Focused;
    
                if (_monthYearPickerDialog != null)
                {
                    _monthYearPickerDialog.OnDateTimeChanged -= OnDateTimeChanged;
                    _monthYearPickerDialog.OnClosed -= OnClosed;
                    _monthYearPickerDialog.Hide();
                    _monthYearPickerDialog.Dispose();
                    _monthYearPickerDialog = null;
                }
    
                base.Dispose(disposing);
            }
    
            #region Private Methods
    
            private void ShowDatePicker()
            {
                if (_monthYearPickerDialog == null)
                {
                    _monthYearPickerDialog = new MonthYearPickerDialog();
                    _monthYearPickerDialog.OnDateTimeChanged += OnDateTimeChanged;
                    _monthYearPickerDialog.OnClosed += OnClosed;
                }
                _monthYearPickerDialog.Date = Element.Date;
                _monthYearPickerDialog.MinDate = FormatDateToMonthYear(Element.MinDate);
                _monthYearPickerDialog.MaxDate = FormatDateToMonthYear(Element.MaxDate);
                _monthYearPickerDialog.InfiniteScroll = Element.InfiniteScroll;
    
                var appcompatActivity = MainActivity.Instance;
                var mFragManager = appcompatActivity?.SupportFragmentManager;
                if (mFragManager != null)
                {
                    _monthYearPickerDialog.Show(mFragManager, nameof(MonthYearPickerDialog));
                }
            }
    
            private void ClearPickerFocus()
            {
                ((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
                Control.ClearFocus();
            }
    
            private DateTime? FormatDateToMonthYear(DateTime? dateTime) => 
                dateTime.HasValue ? (DateTime?) new DateTime(dateTime.Value.Year, dateTime.Value.Month, 1) : null;
    
            private void CreateAndSetNativeControl()
            {
                var tv = new EditText(_context);
    
                tv.SetTextColor(Element.TextColor.ToAndroid());
                tv.TextSize = (float)Element.FontSize;
                tv.Text = $"{Element.Date.Month:D2} | {Element.Date.Year}";
                tv.Gravity = Android.Views.GravityFlags.Center;
                tv.SetBackgroundColor(Element.BackgroundColor.ToAndroid());
    
                SetNativeControl(tv);
            }
    
            #endregion
    
            #region Event Handlers
    
            private void Element_Focused(object sender, FocusEventArgs e)
            {
                if (e.IsFocused)
                {
                    ShowDatePicker();
                }
            }
    
            private void OnClosed(object sender, DateTime e)
            {
                ClearPickerFocus();
            }
    
            private void OnDateTimeChanged(object sender, DateTime e)
            {
                Element.Date = e;
                Control.Text = $"{Element.Date.Month:D2} | {Element.Date.Year}";
                ClearPickerFocus();
            }
    
            #endregion
        }
    }
    
    using Android.App;
    using Android.OS;
    using Android.Views;
    using Android.Widget;
    using System;
    using System.Linq;
    
    
    namespace App20.Droid
    {
        public class MonthYearPickerDialog : Android.Support.V4.App.DialogFragment
        {
            public event EventHandler<DateTime> OnDateTimeChanged;
            public event EventHandler<DateTime> OnClosed;
    
            #region Private Fields
    
            private const int DefaultDay = 1;
            private const int MinNumberOfMonths = 1;
            private const int MaxNumberOfMonths = 12;
            private const int MinNumberOfYears = 1900;
            private const int MaxNumberOfYears = 2100;
    
            private NumberPicker _monthPicker;
            private NumberPicker _yearPicker;
    
            #endregion
    
            #region Public Properties
            
            public DateTime? MinDate { get; set; }
            public DateTime? MaxDate { get; set; }
            public DateTime? Date { get; set; }
            public bool InfiniteScroll { get; set; }
    
            #endregion
    
            public void Hide() => base.Dialog?.Hide();
    
            public override Dialog OnCreateDialog(Bundle savedInstanceState)
            {
                var builder = new AlertDialog.Builder(Activity);
                var inflater = Activity.LayoutInflater;
    
                var selectedDate = GetSelectedDate();
    
                var dialog = inflater.Inflate(Resource.Layout.date_picker_dialog, null);
                _monthPicker = (NumberPicker)dialog.FindViewById(Resource.Id.picker_month);
                _yearPicker = (NumberPicker)dialog.FindViewById(Resource.Id.picker_year);
    
                InitializeMonthPicker(selectedDate.Month);
                InitializeYearPicker(selectedDate.Year);
                SetMaxMinDate(MaxDate, MinDate);
    
                builder.SetView(dialog)
                    .SetPositiveButton("Ok", (sender, e) =>
                    {
                        selectedDate = new DateTime(_yearPicker.Value, _monthPicker.Value, DefaultDay);
                        OnDateTimeChanged?.Invoke(dialog, selectedDate);
                    })
                    .SetNegativeButton("Cancel", (sender, e) =>
                    {
                        Dialog.Cancel();
                        OnClosed?.Invoke(dialog, selectedDate);
                    });
                return builder.Create();
            }
    
            protected override void Dispose(bool disposing)
            {
                if (_yearPicker != null)
                {
                    _yearPicker.ScrollChange -= YearPicker_ScrollChange;
                    _yearPicker.Dispose();
                    _yearPicker = null;
                }
    
                _monthPicker?.Dispose();
                _monthPicker = null;
    
    
                base.Dispose(disposing);
            }
    
            #region Private Methods
    
            private DateTime GetSelectedDate() => Date ?? DateTime.Now;
    
            private void InitializeYearPicker(int year)
            {
                _yearPicker.MinValue = MinNumberOfYears;
                _yearPicker.MaxValue = MaxNumberOfYears;
                _yearPicker.Value = year;
                _yearPicker.ScrollChange += YearPicker_ScrollChange;
                if (!InfiniteScroll)
                {
                    _yearPicker.WrapSelectorWheel = false;
                    _yearPicker.DescendantFocusability = DescendantFocusability.BlockDescendants;
                }
            }
    
            private void InitializeMonthPicker(int month)
            {
                _monthPicker.MinValue = MinNumberOfMonths;
                _monthPicker.MaxValue = MaxNumberOfMonths;
                _monthPicker.SetDisplayedValues(GetMonthNames());
                _monthPicker.Value = month;
                if (!InfiniteScroll)
                {
                    _monthPicker.WrapSelectorWheel = false;
                    _monthPicker.DescendantFocusability = DescendantFocusability.BlockDescendants;
                }
            }
    
            private void YearPicker_ScrollChange(object sender, View.ScrollChangeEventArgs e)
            {
                SetMaxMinDate(MaxDate, MinDate);
            }
    
            private void SetMaxMinDate(DateTime? maxDate, DateTime? minDate)
            {
                try
                {
                    if (maxDate.HasValue)
                    {
                        var maxYear = maxDate.Value.Year;
                        var maxMonth = maxDate.Value.Month;
    
                        if (_yearPicker.Value == maxYear)
                        {
                            _monthPicker.MaxValue = maxMonth;
                        }
                        else if (_monthPicker.MaxValue != MaxNumberOfMonths)
                        {
                            _monthPicker.MaxValue = MaxNumberOfMonths;
                        }
    
                        _yearPicker.MaxValue = maxYear;
                    }
    
                    if (minDate.HasValue)
                    {
                        var minYear = minDate.Value.Year;
                        var minMonth = minDate.Value.Month;
    
                        if (_yearPicker.Value == minYear)
                        {
                            _monthPicker.MinValue = minMonth;
                        }
                        else if (_monthPicker.MinValue != MinNumberOfMonths)
                        {
                            _monthPicker.MinValue = MinNumberOfMonths;
                        }
    
                        _yearPicker.MinValue = minYear;
                    }
                    _monthPicker.SetDisplayedValues(GetMonthNames(_monthPicker.MinValue));
                }
                catch (Exception e)
                {
                  
                }
            }
    
            private string[] GetMonthNames(int start = 1) => 
                System.Globalization.DateTimeFormatInfo.CurrentInfo?.MonthNames.Skip(start - 1).ToArray();
    
            #endregion
    
        }
    }
    
    

    create date_picker_dialog.xml in Resource ->layout

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">
    
      <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:orientation="horizontal">
    
        <NumberPicker
                android:id="@+id/picker_month"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="20dp"
                android:layout_marginRight="20dp">
        </NumberPicker>
    
        <NumberPicker
                android:id="@+id/picker_year"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">
        </NumberPicker>
      </LinearLayout>
    </LinearLayout>
    

    Now you can reference it in Xaml

    <StackLayout VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
            <local:MonthYearPickerView
                Date="06.15.2020"
                BackgroundColor="LightBlue"
               WidthRequest="150"
                MinDate="01.01.2020"
                MaxDate="12.31.2050"
                HorizontalOptions="CenterAndExpand"
                VerticalOptions="Center" />
        </StackLayout>
    

    enter image description here