Skip to content

Instantly share code, notes, and snippets.

@yuv4ik
Last active December 28, 2020 16:52
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save yuv4ik/c7137c4ea89ededa99dfee51bfb1de4e to your computer and use it in GitHub Desktop.
Xamarin.Forms DatePicker with hours, minutes and seconds: TimeCountdownPicker
/*
Xaml example.
*/
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:YourNameSpace"
x:Class="YourNameSpace.ContentPage">
<local:TimeCountdownPicker SelectedTime="{Binding Countdown}" WidthRequest="85" />
</ContentPage>
/*
Special thanks:
@Mathieu who shared his solution on stackoVerflow.
http://stackoverflow.com/questions/35931470/timepicker-with-seconds/35954206#35954206
http://stackoverflow.com/users/659546/mathieu
Original solution was based on @XLabs, in this example the dependency is removed.
https://github.com/XLabs/Xamarin-Forms-Labs
*/
using System;
using Xamarin.Forms;
namespace YourNameSpace
{
public class TimeCountdownPicker : Picker
{
public static readonly BindableProperty SelectedTimeProperty =
BindableProperty.Create(nameof(SelectedTime), typeof(TimeSpan), typeof(TimeCountdownPicker), defaultValue: TimeSpan.Zero, defaultBindingMode: BindingMode.TwoWay, propertyChanged: OnSelectedTimePropertyPropertyChanged);
public TimeCountdownPicker()
{
// Add only one item, later will manipulate only it's value for performance
Items.Add("00:00:00");
SelectedIndex = 0;
SelectedTime = TimeSpan.Zero;
}
public TimeSpan SelectedTime
{
get { return (TimeSpan)GetValue(SelectedTimeProperty); }
set { SetValue(SelectedTimeProperty, value); }
}
private static void OnSelectedTimePropertyPropertyChanged(BindableObject bindable, object value, object newValue)
{
var picker = (TimeCountdownPicker)bindable;
// Update value
picker.Items[0] = newValue.ToString();
picker.SelectedIndex = 0;
}
}
}
/*
Special thanks:
@Mathieu who shared his solution on stackoVerflow.
http://stackoverflow.com/questions/35931470/timepicker-with-seconds/35954206#35954206
http://stackoverflow.com/users/659546/mathieu
Original solution was based on @XLabs, in this example the dependency is removed.
https://github.com/XLabs/Xamarin-Forms-Labs
*/
using System;
using CoreGraphics;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using YourNameSpace;
using YourNameSpace.iOS;
[assembly: ExportRendererAttribute(typeof(TimeCountdownPicker), typeof(TimeCountdownPickerRenderer))]
namespace YourNameSpace.iOS
{
public class TimeCountdownPickerRenderer : PickerRenderer
{
public static double DisplayScale = UIScreen.MainScreen.Scale;
public static int DispalyHeight = (int)UIScreen.MainScreen.NativeBounds.Height;
public static int DisplayWidth = (int)UIScreen.MainScreen.NativeBounds.Width;
internal const int ComponentCount = 6;
private const int _labelSize = 30;
private TimeCountdownPicker timeCountdownPicker;
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.BorderStyle = UITextBorderStyle.None;
timeCountdownPicker = e.NewElement as TimeCountdownPicker;
var customModelPickerView = new UIPickerView
{
Model = new TimeCountdownPickerView(timeCountdownPicker)
};
SelectPickerValue(customModelPickerView, timeCountdownPicker);
CreatePickerLabels(customModelPickerView);
Control.InputView = customModelPickerView;
}
}
private void SelectPickerValue(UIPickerView customModelPickerView, TimeCountdownPicker myTimePicker)
{
if (myTimePicker == null)
return;
customModelPickerView.Select(new nint(myTimePicker.SelectedTime.Hours), 0, false);
customModelPickerView.Select(new nint(myTimePicker.SelectedTime.Minutes), 2, false);
customModelPickerView.Select(new nint(myTimePicker.SelectedTime.Seconds), 4, false);
}
private void CreatePickerLabels(UIPickerView customModelPickerView)
{
nfloat verticalPosition = (customModelPickerView.Frame.Size.Height / 2) - (_labelSize / 2);
nfloat componentWidth = new nfloat(DisplayWidth / ComponentCount /DisplayScale);
var hoursLabel = new UILabel(new CGRect(componentWidth, verticalPosition, _labelSize, _labelSize));
hoursLabel.Text = "h";
var minutesLabel = new UILabel(new CGRect((componentWidth * 3) + (componentWidth / 2), verticalPosition, _labelSize, _labelSize));
minutesLabel.Text = "m";
var secondsLabel = new UILabel(new CGRect((componentWidth * 5) + (componentWidth / 2), verticalPosition, _labelSize, _labelSize));
secondsLabel.Text = "s";
customModelPickerView.AddSubview(hoursLabel);
customModelPickerView.AddSubview(minutesLabel);
customModelPickerView.AddSubview(secondsLabel);
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Control == null)
return;
if (e.PropertyName == Picker.SelectedIndexProperty.PropertyName)
{
var customModelPickerView = (UIPickerView)Control.InputView;
SelectPickerValue(customModelPickerView, timeCountdownPicker);
}
}
public class TimeCountdownPickerView : UIPickerViewModel
{
private TimeCountdownPicker timeCountdownPicker { get; }
public TimeCountdownPickerView(TimeCountdownPicker picker)
{
timeCountdownPicker = picker;
}
public override nint GetComponentCount(UIPickerView pickerView)
{
return new nint(ComponentCount);
}
public override nint GetRowsInComponent(UIPickerView pickerView, nint component)
{
if (component == 0)
{
// Hours
return new nint(24);
}
if (component % 2 != 0)
{
// Odd components are labels for hrs, mins and secs
return new nint(1);
}
// Minutes & seconds
return new nint(60);
}
public override string GetTitle(UIPickerView pickerView, nint row, nint component)
{
if (component == 0)
{
return row.ToString();
}
else if (component == 1)
{
return null;
}
else if (component == 3)
{
return null;
}
else if (component == 5)
{
return null;
}
return row.ToString("#0");
}
public override void Selected(UIPickerView pickerView, nint row, nint component)
{
var selectedHours = pickerView.SelectedRowInComponent(0);
var selectedMinutes = pickerView.SelectedRowInComponent(2);
var selectedSeconds = pickerView.SelectedRowInComponent(4);
var time = new TimeSpan((int)selectedHours, (int)selectedMinutes, (int)selectedSeconds);
timeCountdownPicker.SelectedTime = time;
}
public override nfloat GetComponentWidth(UIPickerView pickerView, nint component)
{
var screenWidth = DisplayWidth;
var componentWidth = screenWidth /
ComponentCount /
DisplayScale;
return new nfloat(componentWidth);
}
}
}
}
@marcusts
Copy link

Hi, do you have an Android renderer for this? Thanks!

@yuv4ik
Copy link
Author

yuv4ik commented Apr 2, 2018

Hello @marcusts, unfortunately not.
I don't want to promise anything, but I will think of the Android implementation in the near future.

@cullenjohnson
Copy link

cullenjohnson commented Apr 3, 2018

I had to remove the "h", "m", and "s" labels, as they caused the time values to be displayed as "..." on tiny iPod touch screens. They weren't positioning properly for me anyway.

@yuv4ik
Copy link
Author

yuv4ik commented Apr 5, 2018

@cullenjohnson I didn't get to test on smaller screens. I guess you could tweak it in CreatePickerLabels method.
Thanks for letting me now.

@kshumon2000
Copy link

Thanks so much @yuv4ik!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment