Over in the Custom Renders Feedback thread I floated the idea of being able to have a simple method to set platform specific properties that are not currently implemented in XForms (X.F?). So I started playing around with what it would take to make something like that work and I came up with the following proof-of-concept example:
In the shared/portable view or layout I wanted to be able to write something like this:
var customSearchBar = new PlatformSpecificSearchBar
{
Placeholder = "Search",
WidthRequest = 300,
PlatformSpecificProperties =
{
new PlatformSpecificProperty
{
Name = "BarTintColor",
Platform = TargetPlatform.iOS,
Value = Color.Red
}
}
};
To make this work you would need a custom class to implement the IPlatformSpecificPropertiesElement interface (a custom SearchBar is used as example below):
public interface IPlatformSpecificPropertiesElement
{
List<PlatformSpecificProperty> PlatformSpecificProperties { get; set; }
}
public class PlatformSpecificSearchBar : SearchBar, IPlatformSpecificPropertiesElement
{
public PlatformSpecificSearchBar()
{
PlatformSpecificProperties = new List<PlatformSpecificProperty>();
}
public List<PlatformSpecificProperty> PlatformSpecificProperties { get; set; }
}
And to implement the properties you will need a custom renderer in each of your supported platforms. The custom render will call a PropertiesRender (in this case a custom SearchBarRender is shown):
public class CustomSearchBarRenderer : SearchBarRenderer
{
private readonly PropertiesRenderer<SearchBar, UISearchBar> _propertiesRenderer;
public CustomSearchBarRenderer()
{
_propertiesRenderer = new PropertiesRenderer<SearchBar, UISearchBar>(this);
}
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
{
base.OnElementChanged(e);
_propertiesRenderer.SetCustomProperties();
}
}
The PropertiesRender does all of the heavy lifting for mapping the properties to the base type (iOS example seen below):
public class PropertiesRenderer<TSource, TArget>
where TSource : View
where TArget : UIView
{
private readonly ViewRenderer<TSource, TArget> _renderer;
public PropertiesRenderer(ViewRenderer<TSource, TArget> renderer)
{
_renderer = renderer;
}
public void SetCustomProperties()
{
var element = (IPlatformSpecificPropertiesElement) _renderer.Element;
var iOSProperties = element.PlatformSpecificProperties.Where(x => x.Platform == TargetPlatform.iOS).ToList();
var props = typeof (UISearchBar).GetProperties();
foreach (var p in iOSProperties)
{
var prop = props.FirstOrDefault(x => x.Name == p.Name);
if (prop == null) continue;
if (prop.PropertyType == typeof (UIImage))
{
SetImage((ImageSource) p.Value, prop);
continue;
}
if (prop.PropertyType == typeof(UIFont))
{
SetFont((Font)p.Value, prop);
continue;
}
if (prop.PropertyType == typeof (UIColor))
{
var typedValue = (Color) p.Value;
prop.SetValue(_renderer.Control, typedValue.ToUIColor());
continue;
}
if (prop.PropertyType.IsEnum)
{
object typedValue = Enum.Parse(prop.PropertyType, p.Value as string);
prop.SetValue(_renderer.Control, typedValue);
continue;
}
prop.SetValue(_renderer.Control, p.Value);
}
}
private void SetFont(Font value, PropertyInfo property)
{
property.SetValue(_renderer.Control, value.ToUIFont());
}
private async void SetImage(ImageSource source, PropertyInfo property)
{
if (source == null) return;
var type = source.GetType();
var handler = GetFileHandler(type);
if (handler == null) return;
UIImage uiimage;
try
{
uiimage =
await handler.LoadImageAsync(source, new CancellationToken(), UIScreen.MainScreen.Scale);
property.SetValue(_renderer.Control, uiimage);
}
finally
{
uiimage = null;
}
}
private static IImageSourceHandler GetFileHandler(Type type)
{
if (type == typeof(FileImageSource))
{
return new FileImageSourceHandler();
}
if (type == typeof(StreamImageSource))
{
return new StreamImagesourceHandler();
}
return null;
}
}
It can currently handle Colors, Fonts, Images, Enums and basic types. A few more usage examples:
// Set SearchBarStyle which is an enum
customSearchBar.PlatformSpecificProperties.Add(
new PlatformSpecificProperty
{
Name = "SearchBarStyle",
Platform = TargetPlatform.iOS,
Value = "Minimal"
});
// Set an image
customSearchBar.PlatformSpecificProperties.Add(new PlatformSpecificProperty
{
Name = "BackgroundImage",
Platform = TargetPlatform.iOS,
Value = ImageSource.FromFile("logo.png")
});
This is all obviously very rough around the edges and I haven't worked on an Android property renderer class, though it shouldn't be much different than the iOS version. It also is non-bindable for XAML folks (and unless someone who is more comfortable with XAML can come up with a solution). But it has already helped me be more productive with my project, so I thought I'd put it out there.