[Guide] Calendar with Highlighted Days

RaphaelSchindlerRaphaelSchindler USMember ✭✭✭
edited May 2016 in Xamarin.Forms

Hey Guys.

I've finally found a solution for this without a third-party component. So let's get straight into the code shall we?

Note: This is for Android only at this time. I'm gonna update this post when I get to the iOS Version. Also I'm gonna add more functionality over time that I will post here.

First of all we're gonna create a CustomControl in our main project (I use a PCL).

Let's call it CalendarView. For obvious reasons though ;) It will contain an EventHandler and a BindableProperty for now.
So here's the code:

public class CalendarView : View
{
    public static readonly BindableProperty HighlightedDaysProperty = BindableProperty.Create("HighlightedDays", typeof(List<DateTime>), typeof(CalendarView));

    public List<DateTime> HighlightedDays
    {
        get { return (List<DateTime>)GetValue(HighlightedDaysProperty);}
        set { SetValue(HighlightedDaysProperty, value); }
    }

    public void NotifyDateSelected(DateTime dateSelected)
    {
        DateSelected?.Invoke(this, dateSelected);
    }

    public event EventHandler<DateTime> DateSelected;
}

Note: We inherit from a View here, because it's getting used on a Page where there's a ListView underneath the Calendar. I will post the code that inherits from a Page if there's a request for it.

Now we're hoping to the Android project and stick around there a little time.

First you need to add this component to your Android project. It's the lovely TimesSquare component from Xamarin.

The next step is to add a View to Resource/layout and for reasons we gonna call it CalendarView which is a axml.
Then add this code to your CalendarView.axml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.squareup.timessquare.CalendarPickerView
        android:id="@+id/calendar_view"
        android:layout_width="match_parent"
        android:layout_weight="1"
        android:layout_height="0dp"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:paddingBottom="16dp"
        android:scrollbarStyle="outsideOverlay"
        android:clipToPadding="false"
        android:background="#FFFFFF" />
</LinearLayout>

Now to get this up and running we need a CustomRenderer for all of this. For the sake of this post we will call it CalendarViewRenderer ;)
Drop in this code and then we can hop to the next step:

[assembly: ExportRenderer(typeof(CalendarView), typeof(CalendarViewRenderer))]

namespace YourNameSpace.Droid
{
    public class CalendarViewRenderer : ViewRenderer<CalendarView, Android.Views.View>
    {
        private CalendarPickerView _calendarPickerView;

        private Android.Views.View _calendarView;

        public CalendarViewRenderer()
        {

        }

        protected override void OnElementChanged(ElementChangedEventArgs<CalendarView> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement == null)
            {
                var inflatorService = (LayoutInflater) Context.GetSystemService(Context.LayoutInflaterService);
                _calendarView= (LinearLayout) inflatorService.Inflate(Resource.Layout.CalendarView, null, false);

                _calendarPickerView= _view.FindViewById<CalendarPickerView>(Resource.Id.calendar_view);

                _calendarPickerView.Init(DateTime.Now.AddYears(-2), DateTime.Now.AddYears(2))
                    .WithSelectedDate(DateTime.Today)
                    .InMode(CalendarPickerView.SelectionMode.Single);

                _calendarPickerView.DateSelected += (sender, args) =>
                {
                    Element.NotifyDateSelected(args.Date);
                };

                _calendarPickerView.HighlightDates(Element.HighlightedDays);

                SetNativeControl(_calendarView);
            }
        }

        protected override void OnLayout(bool changed, int l, int t, int r, int b)
        {
            base.OnLayout(changed, l, t, r, b);

            var msw = MeasureSpec.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly);
            var msh = MeasureSpec.MakeMeasureSpec(b - t, MeasureSpecMode.Exactly);

            _calendarView.Measure(msw, msh);
            _calendarView.Layout(0, 0, r - l, b - t);
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if (e.PropertyName == CalendarView.HighlightedDaysProperty.PropertyName)
            {
                _calendarPickerView.HighlightDates(Element.HighlightedDays);
            }
        }
    }
}

Oh boy there's a lot happening here. So let's break it down a little bit.
In the OnElementChanged we're inflating the CalendarView.axml we made earlier.
Then we're Initiating the Calendar. Here I'm only adding 4 years to it. You can change this easily on your own.

Next I set the SelectedDate to Today which is normal I guess. When the Calendar is shown in your app it will automatically scroll to the SelectedDate and highlight it.

Our last option we're setting is the SelectionMode. Currently there are 3 of them. Single, Multi and Range. They should explain themself I guess so I'm not going over every single one. I need the Single mode in my app.

Now we need to hook up the DateSelected event which is hooked up to our EventHandler in the CalendarView.cs. After that we're simply calling HighlightDates which should speak for itself.

OnLayout simply calculates the width and height of the View

Which gets us to our last but interesting function. The OnElementPropertyChanged function. So whenever you change the HighlightedDays property of the CalendarView this will be called and a new Cell in the Calendar is highlighted. I find this really really helpful because you don't need to Init the Calendar again when something changes.

Now let's hook this up in our app so we can see the calendar in action.

Add a new Page to your main project. Where gonna call this one CalendarPage.
To keep it simple I'm only put the important bits in here. Where gonna add this code to the CalendarPage.cs

public class CalendarPage : ContentPage
{
    private DateTime _selectedDate = DateTime.Now; //This will moved to the Control in the future

    public CalendarPage()
    {
        var testDates = new List<DateTime>
        {
            DateTime.Now,
            DateTime.Now.AddDays(1)
        };

        var calendarView = new CalendarView
        {
            HorizontalOptions = LayoutOptions.FillAndExpand,
            VerticalOptions = LayoutOptions.FillAndExpand,
            HighlightedDays = testDates;
        };
        calendarView.DateSelected += OnDateSelected;

        var titleLabel = new Label
        {
            Text = "Appointments"
        };

        var appointmentList = new ListView
        {
            HorizontalOptions = LayoutOptions.FillAndExpand,
            VerticalOptions = LayoutOptions.FillAndExpand,
            HasUnevenRows = true,
            ItemTemplate = new DataTemplate(typeof(AppointmentCell)), //Drop in any of your cumstom view cells
            ItemsSource = testDates
        };

        var mainStack = new StackLayout
        {
            Children =
            {
                calendarView,
                titleLabel,
                appointmentList
            }
        };

        Content = mainStack;
    }

    private async void OnDateSelected(object sender, DateTime dateTime)
    {
        _selectedDate = dateTime;
        await DisplayAlert("Date", dateTime.ToString(), "ok");
    }
}

And that's it. Now you have a Scrollable Calendar in your View with Highlighted Dates. Take a look at the Screenshot I added so you can see it in action. Note that this is how it looks with my styling. Yours will differ obviously :D

I hope you liked my post. Feel free to leave a comment if you have some ideas and thoughts :)

Kudos to all the developers who put this stuff together. You got me all inspired to do this post
References:
https://github.com/XLabs/Xamarin-Forms-Labs/blob/master/src/Forms/XLabs.Forms.Droid/Controls/Calendar/CalendarViewRenderer.cs
https://components.xamarin.com/gettingstarted/square.androidtimessquare
https://github.com/rid00z/Xamarin.Forms.Calendar

//Edit: Wording

Posts

  • mornabossabluesmornabossablues USMember ✭✭

    nice job. what about iOS?

  • QuentinDujardinQuentinDujardin BEMember

    Hello @RaphaelSchindler, Very nice job for Android, do you have any clue / tips for the iOS part ?
    I have to make the same behaviour than yours for the two platforms so I would be pleased to have some of your advices

  • JohnHardmanJohnHardman GBUniversity mod

    @RaphaelSchindler - Whilst working with calendars, have you worked out on Android or iOS how to get System.Globalization.CultureInfo.OptionalCalendars to contain more than one calendar? I need to be able to switch between calendars (such as Hijri and Hebrew) and sometimes have more than one calendar type in use at the same time. The attached image shows a screenshot from UWP (I have this working on UWP, WinRT 8.1 and WinPhone 8.1 RT), but I am finding on Android and iOS, only one entry appears in OptionalCalendars, limiting some of the functionality on those platforms (or, if not limiting, at least making me have to code stuff myself that I would expect to be able to access from System.Globalization).

  • RaphaelSchindlerRaphaelSchindler USMember ✭✭✭

    @mornabossablues @QuentinDujardin @JohnHardman Sorry that I answer so late. Till this day I couldn't find a satisfatory way for iOS. I was able to display the calendar but not to highlight the dates correctly. I have no idea why but when I told the calendar to highlight a date it was always wrong. But I will look further into it.

    As to your question @JohnHardman I'm sorry but I can't help you on that. Forms is really bitchy when it comes to calendars, even though I don't know why. What comes to my mind is that you could tell the calendar to switch from, lets say, Hebrew and then redraw it. But I have no idea if this works :/

  • JohnHardmanJohnHardman GBUniversity mod

    @RaphaelSchindler - It turns out that the OptionalCalendars problem I encountered is a bug across all Mono platforms. Xamarin Support have raised it in Bugzilla now.

  • RaphaelSchindlerRaphaelSchindler USMember ✭✭✭

    @mornabossablues @QuentinDujardin @JohnHardman I'm glad to inform you guys, that I was finally able to create a presentable version for the Calendar on iOS and I'm finished it for UWP also. Setting up the UWP version was the easiest one, when I finally understood how things work, since the documentation and examples are, err, not so on point^^ This builds up on my previous post for the Android version. You have to add the first steps from there in order to get this to work.

    Please keep in mind that this is not finished, since it's lacking functionality for multiple days and some quirks that I'm gonna add later.

    I wanted to give an update here, so everyone can hack around and flesh this more out. Maybe we could create a Plugin from the stuff we're gathering together.

    So here's the code for iOS:

    First you need to install this component. It's the XCalendar. Make sure it's installed correctly and you see the XCalendar in your References.

    Now here's the Renderer you have to drop in your iOS Project.

    [assembly: ExportRenderer(typeof(CalendarView), typeof(CalendarViewRenderer))]
    YourNameSpace.iOS
    {
        public class CalendarViewRenderer : ViewRenderer<CalendarView, CalendarContentView>
        {
            private Calendar _calendar;
    
            protected override void OnElementChanged(ElementChangedEventArgs<CalendarView> e)
            {
                base.OnElementChanged(e);
    
                if(e.OldElement == null)
                {
                    _calendar = new Calendar();
                    //Adding a nice padding of 10
                    var menuView = new CalendarMenuView { Frame = new CoreGraphics.CGRect(0f, 0f, UIScreen.MainScreen.Bounds.Width - 10, 42f) };
                    var contentView = new CalendarContentView
                    {
                        Frame = new CoreGraphics.CGRect(0f, 0f, UIScreen.MainScreen.Bounds.Width - 10, (UIScreen.MainScreen.Bounds.Height / 2) - 42f),
                        BackgroundColor = UIColor.White;
                    }
    
                    var appearance = _calendar.CalendarAppearance;
                    appearance.DayCircleColorSelected = UIColor.Orange;
                    appearance.DayCircleRatio = (9f / 10f);
                    appearance.WeekDayFormat = CalendarWeekDayFormat.Single;
                   //Setting first day of week to Monday 
                    appearance.GetNSCalendar().FirstWeekDay = 2;
                    appearance.SetMonthLabelTextCallback((NSDate date, Calendar cal) => new Foundation.NSString(((DateTime)date).ToString("MMMM yyyy")));
    
                    _calendar.ContentView = contentView;
                    _calendar.DateSelected += (sender, args) => 
                    {
                        Element.SelectedDate = (DateTime)args.Date;
                        Element.NotifyDateSelected((DateTime)args.Date);
                    }
    
                    var view = new UIView
                    {
                        //Set the calendar to half height of the screen
                        Frame = new CoreGraphics.CGRect(0f, 0f, UIScreen.MainScreen.Bound.Width - 10, UIScreen.MainScreen.Bounds.Height / 2)
                    };
    
                    HighlightDays();
                    view.Add(menuView);
                    view.Add(contentView);
                    SetNativeControl(_calendar.ContentView);
                }
    
                protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
                {
                    base.OnElementPropertyChanged(sender, e);
    
                    if(e.PropertyName == CalendarView.HighlightedDaysProperty.PropertyName)
                    {
                        HighlightDays();
                    }
                }
    
                private void HighlightDays()
                {
                    var eventSchedule = new List<EventDetails>();
                    foreach(var day in Element.HighlightedDays)
                    {
                        eventSchedule.Add(new EventDetails
                        {
                            StartDate = day.Date.ToNSDate(),
                            EndDate = day.Date.ToNSDate()
                        });
                        _calendar.EventSchedule = eventSchedule.ToArray();
                    }
                }
        }
    }
    

    So let's get the UWP version:

    There's no component or anything for UWP since the calendar is build into Windows.UI.Xaml.Controls. But let's get a few words out for this calendar. First of all: It is really fucking fast. Like insane fast. MS did some really, really great job on the calendar. But you have to understand how it's working internally, which was a little bit hard. I'm still not sure if redrawing works properly, but I will surely update this Thread if I need to do more work on it.

    [assembly: ExportRenderer(typeof(CalendarView), typeof(CalendarViewRenderer))]
    YourNameSpace.UWP
    {
        public class CalendarViewRenderer : ViewRenderer<CalendarView, Windows.UI.Xaml.Controls.CalendarView>
        {
            private Windows.UI.Xaml.Controls.CalendarView _calendar;
    
            protected override void OnElementChanged(ElementChangedEventArgs<CalendarView> e)
            {
                base.OnElementChanged(e);
    
                if(e.OldElement == null)
                {
                    _calendar = new Windows.UI.Xaml.Controls.CalendarView
                    {
                        VerticalFirstOfMonthLabelAlignment = Windows.UI.Xaml.VerticalAlignment.Top,
                        IsTodayHighlighted = true,
                        //You can change it easily with the standard language namings of c# like en-US, en-EN
                        Language = "de-DE"
                    };
    
                    _calendar.SelectedDatesChanged += (sender, args) => 
                    {
                        if(args.AddedDates == null) return;
                        //UWP is a little bit strange here. There is always a list of dates even though the standard selection mode is single. This should make things very easy if you need to let the user select multiple dates!
                        Element.SelectedDate = args.AddedDates.First().Date;
                        Element.NotifyDateSelected(args.AddedDates.First().Date);
                    };
                    _calendar.CalendarViewDayItemChanging += (sender, args) => 
                    {
                        if(args.Item != null && Element.HighlightedDays.Contains(args.Item.Date.Date))
                        {
                            args.Item.SetDensityColors(new List<Color> {Colors.Black});
                        }
                    }
                    SetNativeControl(_calendar);
                }
    
                protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
                {
                    base.OnElementPropertyChanged(sender, e);
    
                    if(e.PropertyName == CalendarView.HighlightedDaysProperty.PropertyName)
                    {
                        _calendar.DataContext = Element.HighlightedDays;
                    }
                }
            }
        }
    }
    
  • SivaiOSSivaiOS INMember ✭✭

    Hi ,

    I Am new to the Mobile Cross Platform Development......

    How to Access Default Calender(like Google Calendar for Android and default iOS Calendar) for CrossPlatForm.

    Am Using MasterList view user click on calender open the default calender based on Android or iOS.How to implement these feature in CrossPlatform.

            Or
    

    How to implement negative Platform specific calendar .

    Please share any links or examples or tutorials

    Please Help us....Thanks in Advance.

  • IvayloKisyovIvayloKisyov USMember ✭✭

    Hi, @RaphaelSchindler can you please share your Calendar with Highlighted Days project? Clone link from Git or any other way. Thank you in advance!

  • RaphaelSchindlerRaphaelSchindler USMember ✭✭✭
  • IvayloKisyovIvayloKisyov USMember ✭✭
    edited April 2017

    @RaphaelSchindler Thank you so much !! I have a question, is it possible to highlight different days in different colors not only in green?

  • RaphaelSchindlerRaphaelSchindler USMember ✭✭✭

    @IvayloKisyov It seems that in version 1.6.5 they added support for custom calendar cells.

    New: Support for completely custom cell views

    But I haven't played with them yet. You can always play around and make a PR :)

    I'm not sure when I find the time to add more to the repo. But I will update here if I make some substantial changes

  • LigaLiga DKMember

    Hi!
    I want to highligt contacs birthdays in this calendar. I have a listview with contacts and their birthdays. How can I bind them to calendar?

  • RaphaelSchindlerRaphaelSchindler USMember ✭✭✭

    Create a new List<DateTime> and put the dates of the birthdates in it. Then bind it to CalendarView.HighlightedDays

  • LigaLiga DKMember

    var BirthdayDates = new List
    {
    DateTime.Today ? or what?
    };

  • RaphaelSchindlerRaphaelSchindler USMember ✭✭✭

    I just asume that you have a List with contacts...

    var birthDates = new List<DateTime>();
    
    foreach(var birthday in Contacts)
    {
        birthDates.Add(Contacts.Birthday);
    }
    
    calendar.HighlightedDates = birthDates;
    
  • LigaLiga DKMember

    I get this issue with Contacts .
    CS0119 C# is a type, which is not valid in the given context

  • RaphaelSchindlerRaphaelSchindler USMember ✭✭✭

    Well, since I don't know how you've named your collection of contacts I just used that name...

  • LigaLiga DKMember

    This is a part of code I am using to highlight dates.

     public CalendarTestPage()
        {
    
            var testDates = new List<DateTime>
            {
                DateTime.Today,
    
            };
            var fødselsdageDates = new List<DateTime>();
    
            foreach (var kontakter in Kontakter)
            {
                fødselsdageDates.Add(Kontakter.Fødselsdage);
            }
    
            var calendarView = new CalendarView
            {
                HorizontalOptions = LayoutOptions.FillAndExpand,
                VerticalOptions = LayoutOptions.FillAndExpand,
                HighlightedDays = fødselsdageDates
            };
    

    I have contacts in cs file, which looks like

    public class Kontakter
    {

        public static List<Kontakter> ItemsSource { get; internal set; }
    
        public string Fuldenavn { get; set; }
        public int Tlfnr { get; set; }
        public string Email { get; set; }
        public string Adresse { get; set; }
        public string Billed { get; set; }
        public string Fødselsdage { get; set; }
    
        public List<Kontakter> GetKontakter()
        {
            List<Kontakter> kontakter = new List<Kontakter>()
            {
                new Kontakter ()
                {
                    Fuldenavn = 
                    Tlfnr = 
                    Email = 
                    Adresse =
                    Billed = 
                    Fødselsdage=
              };
         retur kontakter;
    

    }}}

  • RaphaelSchindlerRaphaelSchindler USMember ✭✭✭

    Ok now I actually can help you :smile:

    You can delete this. Just for testing purposes.

    var testDates = new List<DateTime>
        {
            DateTime.Today,
    
        };
    

    Now your actual problem is that Fødselsdage is a string and not a DateTime.

    Change public string Fødselsdage { get; set; } to public DateTime Fødselsdage { get; set; }.

  • LigaLiga DKMember
    foreach (var fødselsdage in Kontakter)
    

    Error CS0119 'Kontakter' is a type, which is not valid in the given context .

    I have actually problem with foreach. I have .cs file with name kontakter.cs

  • AliasAlias LVMember

    I want to highlight some specific dates in calendar,like 1th of january. How can I do it?

  • ScItM8ScItM8 Member

    I tried using the code as described. I added the first part in a file called CalendarView.xaml and second part in a file that was generated for CalendarView.xaml -> CalendarView.xaml.cs. Now for the third part I added to a file CalendarViewRenderer.cs. I added these three to my View.

    Now for the last mentioned CalendarPage.cs, I added this in the main project as instructed. After building I got 7 errors.. Maybe something went wrong when adding the package.
    What I did was (on VS mac): package --> add Nuget packages --> I searched for TimesSquare --> I got two results: (1 - AndroidTimesSquare) and (2 - Square.AndroidTimesSquare), which both seems to give me the message "Package restore failed. Rolling back package changes for ...."

    All of the errors I got was in the CalendarPage.cs. It seems like the class is missing a lot of types/namespaces.. Is this because of the packages missing?

  • I wanted to fetch list of events of selected date. Any Suggestions..??

Sign In or Register to comment.