Bindable Property is not updating view on iOS - ios

I am creating a cross platform app on xamarin.forms. I needed a custom renderer with gradient background and i've created it and it works well on both android and ios.
However, when i want to change the colors of the gradient background, it does not work on iOS.
Test.xaml.cs
namespace KiaiDay
{
public class TesteViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Color _startColor = Color.Green;
public Color SStartColor
{
get => _startColor;
set { _startColor = value; OnPropertyChanged(nameof(SStartColor)); }
}
private Color _endColor = Color.Blue;
public Color EEndColor
{
get => _endColor;
set { _endColor = value; OnPropertyChanged(nameof(EEndColor)); }
}
public ICommand Select
{
get => new Command(() =>
{
SStartColor = Color.Red;
EEndColor = Color.Brown;
});
}
#region INotifyPropertyChanged Implementation
void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged == null)
return;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class teste : ContentPage
{
public teste()
{
BindingContext = new TesteViewModel();
InitializeComponent();
}
}
}
Test.xaml
<ContentPage.Content>
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<local:ShadowFrame StartColor="{Binding SStartColor}" EndColor="{Binding EEndColor}" HorizontalOptions="Center" VerticalOptions="Center">
<Label Text="Teste"/>
<local:ShadowFrame.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Command="{Binding Select}"/>
</local:ShadowFrame.GestureRecognizers>
</local:ShadowFrame>
</StackLayout>
</ContentPage.Content>
Custom Renderer in PCL
public class ShadowFrame : Frame
{
public static BindableProperty ElevationProperty = BindableProperty.Create(nameof(Elevation), typeof(float), typeof(ShadowFrame), default(float));
public static readonly BindableProperty StartColorProperty = BindableProperty.Create(nameof(StartColor), typeof(Color), typeof(ShadowFrame), default(Color));
public static readonly BindableProperty EndColorProperty = BindableProperty.Create(nameof(EndColor), typeof(Color), typeof(ShadowFrame), default(Color));
public float Elevation
{
get { return (float)GetValue(ElevationProperty); }
set { SetValue(ElevationProperty, value); }
}
public Color StartColor
{
get { return (Color)GetValue(StartColorProperty); }
set { SetValue(StartColorProperty, value); }
}
public Color EndColor
{
get { return (Color)GetValue(EndColorProperty); }
set { SetValue(EndColorProperty, value); }
}
}
iOS Renderer(i think the problem is here)
[assembly: ExportRenderer(typeof(ShadowFrame), typeof(ShadowFrameRenderer))]
namespace KiaiDay.iOS.Renderers
{
public class ShadowFrameRenderer : FrameRenderer
{
CAGradientLayer gradientLayer;
private Color StartColor { get; set; }
private Color EndColor { get; set; }
CGRect rect;
public static void Initialize()
{
// empty, but used for beating the linker
}
public ShadowFrameRenderer() => gradientLayer = new CAGradientLayer();
public override void Draw(CGRect rect)
{
this.rect = rect;
var stack = (ShadowFrame)this.Element;
StartColor = stack.StartColor;
EndColor = stack.EndColor;
CGColor startColor = this.StartColor.ToCGColor();
CGColor endColor = this.EndColor.ToCGColor();
#region for Vertical Gradient
//var gradientLayer = new CAGradientLayer();
#endregion
#region for Horizontal Gradient
//var gradientLayer = new CAGradientLayer()
//{
// StartPoint = new CGPoint(0, 0.5),
// EndPoint = new CGPoint(1, 0.5)
//};
#endregion
gradientLayer.Frame = rect;
gradientLayer.Colors = new CGColor[] {
startColor,
endColor
};
NativeView.Layer.InsertSublayer(gradientLayer, 0);
base.Draw(rect);
}
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
var stack = (ShadowFrame)e.NewElement;
if (e.OldElement != null || Element == null)
{
return;
}
try
{
this.StartColor = stack.StartColor;
this.EndColor = stack.EndColor;
UpdateShadow();
Draw(rect);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("ERROR:", ex.Message);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var stack = (ShadowFrame) sender;
if (e.PropertyName == ShadowFrame.ElevationProperty.PropertyName)
{
UpdateShadow();
}
if (e.PropertyName == ShadowFrame.StartColorProperty.PropertyName)
{
this.StartColor = stack.StartColor;
}
if (e.PropertyName == ShadowFrame.EndColorProperty.PropertyName)
{
this.EndColor = stack.EndColor;
}
}
private void UpdateShadow()
{
var shadowFrame = (ShadowFrame)Element;
// Update shadow to match better material design standards of elevation
Layer.ShadowRadius = shadowFrame.Elevation;
Layer.ShadowColor = UIColor.Gray.CGColor;
Layer.ShadowOffset = new CGSize(2, 2);
Layer.ShadowOpacity = 0.80f;
Layer.ShadowPath = UIBezierPath.FromRect(Layer.Bounds).CGPath;
Layer.MasksToBounds = false;
}
}
}
I would like to update iOS view immediatly after i change the color through the command.

If you change a property and the view doesn't update the issue has to be in OnElementPropertyChanged.
You need to call UpdateShadow() and Draw().

After digging a lot i think i have found a solution:
iOS Custom Renderer :
public class GradientStackColorRenderer : VisualElementRenderer<Frame>
{
private Color StartColor { get; set; }
private Color EndColor { get; set; }
public override CGRect Frame
{
get
{
return base.Frame;
}
set
{
if (value.Width > 0 && value.Height > 0)
{
foreach (var layer in NativeView?.Layer.Sublayers.Where(layer => layer is CAGradientLayer))
layer.Frame = new CGRect(0, 0, value.Width, value.Height);
}
base.Frame = value;
}
}
protected override void OnElementChanged(ElementChangedEventArgs<StackLayout> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
try
{
var stack = e.NewElement as GradientColorStack;
this.StartColor = stack.StartColor;
this.EndColor = stack.EndColor;
AdicionarGradiente();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(#"ERROR:", ex.Message);
}
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var stack = this.Element as GradientColorStack;
if(e.PropertyName == GradientColorStack.StartColorProperty.PropertyName)
{
this.StartColor = stack.StartColor;
AdicionarGradiente();
}
if (e.PropertyName == GradientColorStack.EndColorProperty.PropertyName)
{
this.EndColor = stack.EndColor;
AdicionarGradiente();
}
}
public void AdicionarGradiente()
{
var gradient = new CAGradientLayer();
gradient.CornerRadius = NativeView.Layer.CornerRadius = 5;
gradient.Colors = new CGColor[] { StartColor.ToCGColor(), EndColor.ToCGColor() };
var layer = NativeView?.Layer.Sublayers.LastOrDefault();
NativeView?.Layer.InsertSublayerBelow(gradient, layer);
}
public static CGColor ToCGColor(Color color)
{
return new CGColor(CGColorSpace.CreateSrgb(), new nfloat[] { (float)color.R, (float)color.G, (float)color.B, (float)color.A });
}
}

Related

how to expand customPinWinod in xamarin forms maps

I have this custom renderer for iOS and Im uisng xamarin forms maps:
public class CustomMapRenderer : MapRenderer
{
UIView customPinView;
ObservableCollection<CustomPin> customPins;
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
var nativeMap = Control as MKMapView;
nativeMap.GetViewForAnnotation = null;
nativeMap.CalloutAccessoryControlTapped -= OnCalloutAccessoryControlTapped;
nativeMap.DidSelectAnnotationView -= OnDidSelectAnnotationView;
nativeMap.DidDeselectAnnotationView -= OnDidDeselectAnnotationView;
}
if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
var nativeMap = Control as MKMapView;
customPins = formsMap.CustomPins;
nativeMap.GetViewForAnnotation = GetViewForAnnotation;
nativeMap.CalloutAccessoryControlTapped += OnCalloutAccessoryControlTapped;
nativeMap.DidSelectAnnotationView += OnDidSelectAnnotationView;
nativeMap.DidDeselectAnnotationView += OnDidDeselectAnnotationView;
}
}
protected override MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation annotation)
{
MKAnnotationView annotationView = null;
if (annotation is MKUserLocation)
return null;
var customPin = GetCustomPin(annotation as MKPointAnnotation);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
annotationView = mapView.DequeueReusableAnnotation(customPin.Label);
if (annotationView == null)
{
annotationView = new CustomMKAnnotationView(annotation, customPin.Label);
annotationView.Image = UIImage.FromFile("pin.png");
annotationView.CalloutOffset = new CGPoint(0, 0);
annotationView.LeftCalloutAccessoryView = new UIImageView(UIImage.FromFile("monkey.png"));
annotationView.RightCalloutAccessoryView = UIButton.FromType(UIButtonType.DetailDisclosure);
((CustomMKAnnotationView)annotationView).Name = customPin.Label;
}
annotationView.CanShowCallout = true;
return annotationView;
}
void OnCalloutAccessoryControlTapped(object sender, MKMapViewAccessoryTappedEventArgs e)
{
CustomMKAnnotationView customView = e.View as CustomMKAnnotationView;
if (!string.IsNullOrWhiteSpace(customView.Url))
{
UIApplication.SharedApplication.OpenUrl(new Foundation.NSUrl(customView.Url));
}
}
void OnDidSelectAnnotationView(object sender, MKAnnotationViewEventArgs e)
{
CustomMKAnnotationView customView = e.View as CustomMKAnnotationView;
customPinView = new UIView();
var label = new UILabel();
label.Text = "Tutorial";
label.Font.WithSize(36);
customPinView.AddSubview(label);
customPinView.Frame = new CGRect(0, 0, 200, 84);
var image = new UIImageView(new CGRect(0, 0, 200, 84));
image.Image = UIImage.FromFile("xamarin.png");
customPinView.AddSubview(image);
customPinView.Center = new CGPoint(0, -(e.View.Frame.Height + 75));
e.View.AddSubview(customPinView);
}
void OnDidDeselectAnnotationView(object sender, MKAnnotationViewEventArgs e)
{
if (!e.View.Selected)
{
customPinView.RemoveFromSuperview();
customPinView.Dispose();
customPinView = null;
}
}
CustomPin GetCustomPin(MKPointAnnotation annotation)
{
var position = new Position(annotation.Coordinate.Latitude, annotation.Coordinate.Longitude);
foreach (var pin in customPins)
{
if (pin.Position == position)
{
return pin;
}
}
return null;
}
}
And I want to add some more things below, and I dont know why it doesnt resize this pin window?
Here is the outcome:
UPDATE:
Here is Android renderer which works fine. From my CUstomPinModel, I need dynamically create labels:
public class CustomMapRenderer : MapRenderer, GoogleMap.IInfoWindowAdapter
{
private LatLngBounds.Builder _builder;
private GoogleMap _map;
ObservableCollection<CustomPin> customPins { get; set; }
Context context;
public CustomMapRenderer(Context context) : base(context)
{
this.context = context;
customPins = new ObservableCollection<CustomPin>();
_builder = new LatLngBounds.Builder();
}
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
NativeMap.InfoWindowClick -= OnInfoWindowClick;
}
if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
customPins = formsMap.CustomPins;
}
}
protected override void OnMapReady(GoogleMap map)
{
base.OnMapReady(map);
NativeMap.InfoWindowClick += OnInfoWindowClick;
NativeMap.SetInfoWindowAdapter(this);
if (_map == null)
{
_map = map;
}
}
protected override MarkerOptions CreateMarker(Pin pin)
{
CustomPin customPin = (CustomPin)pin;
var marker = new MarkerOptions();
LatLng position = new LatLng(pin.Position.Latitude, pin.Position.Longitude);
marker.SetPosition(new LatLng(pin.Position.Latitude, pin.Position.Longitude));
marker.SetTitle(pin.Label);
marker.SetSnippet(pin.Address);
marker.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.pin));
_builder.Include(position);
LatLngBounds bounds = _builder.Build();
CameraUpdate cu = CameraUpdateFactory.NewLatLngBounds(bounds, 20);
_map.MoveCamera(cu);
return marker;
}
void OnInfoWindowClick(object sender, GoogleMap.InfoWindowClickEventArgs e)
{
var customPin = GetCustomPin(e.Marker);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
}
public Android.Views.View GetInfoContents(Marker marker)
{
var inflater = Android.App.Application.Context.GetSystemService(Context.LayoutInflaterService) as Android.Views.LayoutInflater;
if (inflater != null)
{
Android.Views.View view;
var customPin = GetCustomPin(marker);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
view = inflater.Inflate(Resource.Layout.MapInfoWindow, null);
LinearLayout linearLayout = (LinearLayout)view.FindViewById(Resource.Id.InfoWindowProps);
if(customPin != null && customPin.InfoBox != null && customPin.InfoBox.DetailsObjectInfos.Count > 0)
{
for (int i = 0; i < customPin.InfoBox.DetailsObjectInfos.Count; i++)
{
TextView t1 = new TextView(context);
t1.Text = customPin.InfoBox.DetailsObjectInfos[i].BoldLabelTitle + customPin.InfoBox.DetailsObjectInfos[i].LabelValue;
linearLayout.AddView(t1);
}
}
return view;
}
return null;
}
public Android.Views.View GetInfoWindow(Marker marker)
{
return null;
}
CustomPin GetCustomPin(Marker annotation)
{
var position = new Position(annotation.Position.Latitude, annotation.Position.Longitude);
foreach (var pin in customPins)
{
if (pin.Position == position)
{
return pin;
}
}
return null;
}
}
Also, I set in my layout correct naming in order to be able to find resource by id:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="#+id/InfoWindowImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:src="#drawable/monkey" />
<LinearLayout
android:id="#+id/InfoWindowProps"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="10dp"
android:layout_marginRight="261.5dp" >
</LinearLayout>
</LinearLayout>
UPDATE #2:
My model for CustomPin:
public class CustomPin : Pin, INotifyPropertyChanged
{
public string PinIcon { get; set; }
public Color Color { get; set; }
public InfoBoxMapModel InfoBox { get; set; } = new InfoBoxMapModel();
public int? Id { get; set; }
public int? ClassId { get; set; }
public string FavoriteLabel { get; set; }
public bool? _isFavorite { get; set; }
public bool? IsFavorite
{
get { return _isFavorite; }
set { _isFavorite = value; OnPropertyChanged(); }
}
}
and InfoBoxModel which is containing my string labels:
public class InfoBoxMapModel
{
//base64
public string ImageString { get; set; }
public ImageSource PinImageSource { get; set; }
public List<DetailsObjectInfo> DetailsObjectInfos { get; set; } = new List<DetailsObjectInfo>();
public int CountDetailsItemsRows { get; set; }
}
public class DetailsObjectInfo
{
public string BoldLabelTitle { get; set; }
public string LabelValue { get; set; }
}
UPDATE #3: Here is the example how it works in android:
All these rows of strings are concatenation of DetailsObjectInfo => BoldLabelTitle + ": " + LabelValue
So, I want to render DetailsObjectyInfos List in white callout pin cloud.
If need to add subviews dynamically , the one way that you need to calculate the Frame of each Control (Label).Such as follow:
for (int i = 0; i < 3; i++)
{
var label = new UILabel();
label.Frame = new CGRect(50*i, 0, 45, 84);
label.Text = "Tutorial:"+i;
label.Font.WithSize(36);
customPinView.AddSubview(label);
}
The another way is using UIStackView as RootView as follow:
void OnDidSelectAnnotationView(object sender, MKAnnotationViewEventArgs e)
{
CustomMKAnnotationView customView = e.View as CustomMKAnnotationView;
//customPinView = new UIView();
UIStackView customPinView = new UIStackView();
if (customView.Name.Equals("Xamarin"))
{
//var label = new UILabel(new CGRect(-50, 0, 50, 84));
for (int i = 0; i < 3; i++)
{
var label = new UILabel();
label.Text = "Tutorial:"+i;
label.Font.WithSize(36);
customPinView.AddArrangedSubview(label);
}
customPinView.Frame = new CGRect(0, 0, 300, 84);
customPinView.Axis = UILayoutConstraintAxis.Horizontal;
customPinView.Distribution = UIStackViewDistribution.EqualSpacing;
customPinView.Spacing = 10;
customPinView.Alignment = UIStackViewAlignment.Fill;
customPinView.Center = new CGPoint(0, -(e.View.Frame.Height + 75));
//customPinView.BackgroundColor = UIColor.Yellow;
e.View.AddSubview(customPinView);
}
}
The effect:
=============================Update #1===============================
MainPage declare the Lable text of Pin:
public partial class MapPage : ContentPage
{
public MapPage()
{
InitializeComponent();
CustomPin pin = new CustomPin
{
Type = PinType.Place,
Position = new Position(37.79752, -122.40183),
Label = "Xamarin San Francisco Office",
Address = "394 Pacific Ave, San Francisco CA",
Name = "Xamarin",
Url = "http://xamarin.com/about/"
};
customMap.CustomPins = new List<CustomPin> { pin };
customMap.Pins.Add(pin);
customMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(37.79752, -122.40183), Distance.FromMiles(1.0)));
}
}
=============================Update #2#3===============================
Your idea is right . I have found the solution that replace the adress string wtih my custom Labels .
There is a DetailCalloutAccessoryView of MKAnnotationView , if we can use custome View for it , it will show the wants.
Modify it inside the GetViewForAnnotation method :
protected override MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation annotation)
{
MKAnnotationView annotationView = null;
if (annotation is MKUserLocation)
return null;
var customPin = GetCustomPin(annotation as MKPointAnnotation);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
annotationView = mapView.DequeueReusableAnnotation(customPin.Name);
if (annotationView == null)
{
annotationView = new CustomMKAnnotationView(annotation, customPin.Name);
annotationView.Image = UIImage.FromFile("pin.png");
annotationView.CalloutOffset = new CGPoint(0, 0);
//annotationView.LeftCalloutAccessoryView = new UIImageView(UIImage.FromFile("monkey.png"));
UIImageView uIImageView = new UIImageView(UIImage.FromFile("monkey.png"));
uIImageView.Frame = new CGRect(0, 0, 75, 100);
annotationView.LeftCalloutAccessoryView = uIImageView;
annotationView.RightCalloutAccessoryView = UIButton.FromType(UIButtonType.DetailDisclosure);
((CustomMKAnnotationView)annotationView).Name = customPin.Name;
((CustomMKAnnotationView)annotationView).Url = customPin.Url;
customPinView = new UIStackView();
for (int i = 0; i < 3; i++)
{
var label = new UILabel();
label.Text = "Tutorial: " + i;
label.BackgroundColor = UIColor.White;
label.Font.WithSize(36);
customPinView.AddArrangedSubview(label);
}
customPinView.Frame = new CGRect(0, 0, 300, 84);
customPinView.Axis = UILayoutConstraintAxis.Vertical;
customPinView.Distribution = UIStackViewDistribution.EqualSpacing;
customPinView.Spacing = 1;
customPinView.Alignment = UIStackViewAlignment.Fill;
annotationView.DetailCalloutAccessoryView = customPinView;
}
annotationView.CanShowCallout = true;
return annotationView;
}
This is the full renderer code :
public class CustomMapRenderer : MapRenderer
{
UIStackView customPinView;
List<CustomPin> customPins;
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
var nativeMap = Control as MKMapView;
nativeMap.GetViewForAnnotation = null;
nativeMap.CalloutAccessoryControlTapped -= OnCalloutAccessoryControlTapped;
nativeMap.DidSelectAnnotationView -= OnDidSelectAnnotationView;
nativeMap.DidDeselectAnnotationView -= OnDidDeselectAnnotationView;
}
if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
var nativeMap = Control as MKMapView;
customPins = formsMap.CustomPins;
nativeMap.GetViewForAnnotation = GetViewForAnnotation;
nativeMap.CalloutAccessoryControlTapped += OnCalloutAccessoryControlTapped;
nativeMap.DidSelectAnnotationView += OnDidSelectAnnotationView;
nativeMap.DidDeselectAnnotationView += OnDidDeselectAnnotationView;
}
}
protected override MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation annotation)
{
MKAnnotationView annotationView = null;
if (annotation is MKUserLocation)
return null;
var customPin = GetCustomPin(annotation as MKPointAnnotation);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
annotationView = mapView.DequeueReusableAnnotation(customPin.Name);
if (annotationView == null)
{
annotationView = new CustomMKAnnotationView(annotation, customPin.Name);
annotationView.Image = UIImage.FromFile("pin.png");
annotationView.CalloutOffset = new CGPoint(0, 0);
//annotationView.LeftCalloutAccessoryView = new UIImageView(UIImage.FromFile("monkey.png"));
UIImageView uIImageView = new UIImageView(UIImage.FromFile("monkey.png"));
uIImageView.Frame = new CGRect(0, 0, 75, 100);
annotationView.LeftCalloutAccessoryView = uIImageView;
annotationView.RightCalloutAccessoryView = UIButton.FromType(UIButtonType.DetailDisclosure);
((CustomMKAnnotationView)annotationView).Name = customPin.Name;
((CustomMKAnnotationView)annotationView).Url = customPin.Url;
customPinView = new UIStackView();
for (int i = 0; i < 3; i++)
{
var label = new UILabel();
label.Text = "Tutorial: " + i;
label.BackgroundColor = UIColor.White;
label.Font.WithSize(36);
customPinView.AddArrangedSubview(label);
}
customPinView.Frame = new CGRect(0, 0, 300, 84);
customPinView.Axis = UILayoutConstraintAxis.Vertical;
customPinView.Distribution = UIStackViewDistribution.EqualSpacing;
customPinView.Spacing = 1;
customPinView.Alignment = UIStackViewAlignment.Fill;
annotationView.DetailCalloutAccessoryView = customPinView;
}
annotationView.CanShowCallout = true;
return annotationView;
}
void OnCalloutAccessoryControlTapped(object sender, MKMapViewAccessoryTappedEventArgs e)
{
CustomMKAnnotationView customView = e.View as CustomMKAnnotationView;
if (!string.IsNullOrWhiteSpace(customView.Url))
{
UIApplication.SharedApplication.OpenUrl(new Foundation.NSUrl(customView.Url));
}
}
void OnDidSelectAnnotationView(object sender, MKAnnotationViewEventArgs e)
{
CustomMKAnnotationView customView = e.View as CustomMKAnnotationView;
//customPinView = new UIView();
if (customView.Name.Equals("Xamarin"))
{
//customPinView.Frame = new CGRect(0, 0, 200, 84);
//var image = new UIImageView(new CGRect(0, 0, 200, 84));
//image.Image = UIImage.FromFile("xamarin.png");
//customPinView.AddSubview(image);
//customPinView.Center = new CGPoint(0, -(e.View.Frame.Height + 75));
//e.View.AddSubview(customPinView);
}
}
void OnDidDeselectAnnotationView(object sender, MKAnnotationViewEventArgs e)
{
if (!e.View.Selected)
{
customPinView.RemoveFromSuperview();
customPinView.Dispose();
customPinView = null;
}
}
CustomPin GetCustomPin(MKPointAnnotation annotation)
{
var position = new Position(annotation.Coordinate.Latitude, annotation.Coordinate.Longitude);
foreach (var pin in customPins)
{
if (pin.Position == position)
{
return pin;
}
}
return null;
}
}
The effect:

Multi Column Picker for Xamarin iOS

I am building a multi-column picker control, and I have some issues with the xamarin.ios renderer.
I have two columns in the picker. One for the month and another for the year. I have got the values binded to the picker control properly. However when I make a selection the app crashes.
The error is Specified cast is not valid.
Stacktrace is
at Xamarin.Forms.Platform.iOS.PickerRendererBase`1[TControl].OnEnded
(System.Object sender, System.EventArgs eventArgs) [0x0000b] in
<0648e2dffe9e4201b8c6e274ced6579f>:0 at
UIKit.UIControlEventProxy.Activated () [0x00004] in /Library/Frameworks/Xamarin.iOS.framework/Versions/12.16.0.5/src/Xamarin.iOS/UIKit/UIControl.cs:38
My ios Custom Renderer code is as follows:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using CoreGraphics;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(YearMonthPicker), typeof(YearMonthPickerRenderer))]
namespace TestApp.iOS.Renderers
{
public class YearMonthPickerRenderer : PickerRenderer
{
private MonthYear _monthYear = new MonthYear();
private UILabel _monthLabel;
private UILabel _yearLabel;
public YearMonthPickerRenderer()
{
_monthYear.Months = new List<string>();
_monthYear.Years = new List<int>();
InitValues();
}
private void InitValues()
{
_monthYear.Months = new List<string>{
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"Sepetember",
"October",
"November",
"December"
};
}
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
var customPicker = e.NewElement as YearMonthPicker;
var startYear = customPicker.StartYear;
var endYear = customPicker.EndYear;
do
{
_monthYear.Years.Add(startYear);
startYear++;
} while (startYear <= endYear);
if (_monthLabel == null)
{
_monthLabel = new UILabel();
_monthLabel.Text = customPicker.MonthLabel;
_monthLabel.TextColor = customPicker.LabelColor.ToUIColor();
_monthLabel.Font = _monthLabel.Font.WithSize(14.0f);
}
if (_yearLabel == null)
{
_yearLabel = new UILabel();
_yearLabel.Text = customPicker.YearLabel;
_yearLabel.TextColor = customPicker.LabelColor.ToUIColor();
_yearLabel.Font = _yearLabel.Font.WithSize(14.0f);
}
if (Control is UITextField textField)
{
var pickerView = textField.InputView as UIPickerView;
var yearMonthModel = new YearMonthModel(textField, _monthYear);
var yearMonthDelegate = new YearMonthPickerDelegate(textField, _monthYear);
pickerView.Model = yearMonthModel;
pickerView.Delegate = yearMonthDelegate;
textField.BorderStyle = UITextBorderStyle.None;
textField.AddSubview(_monthLabel);
textField.AddSubview(_yearLabel);
}
}
var toolbar = new UIToolbar(new CGRect(0.0f, 0.0f, Control.Frame.Size.Width, 44.0f));
toolbar.Items = new[]
{
new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace),
new UIBarButtonItem("Done",
UIBarButtonItemStyle.Done,
delegate {
Control.ResignFirstResponder();
})
};
if (this.Control != null)
{
Control.InputAccessoryView = toolbar;
}
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
_monthLabel.Frame = new CGRect(0, -10,
Control.Frame.Width / 2,
Control.Frame.Height - 5);
_yearLabel.Frame = new CGRect(Control.Frame.Width / 2, -10,
Control.Frame.Width / 2,
Control.Frame.Height - 5);
}
protected override void Dispose(bool disposing)
{
_monthLabel?.Dispose();
_yearLabel?.Dispose();
_monthLabel = null;
_yearLabel = null;
base.Dispose(disposing);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
}
}
public class YearMonthModel : UIPickerViewModel
{
private UITextField _uITextField;
private string _selectedMonth = string.Empty;
private string _selectedYear = string.Empty;
private MonthYear _monthYear;
public YearMonthModel(UITextField uITextField, MonthYear monthYear)
{
_uITextField = uITextField;
_monthYear = monthYear;
}
public override nint GetComponentCount(UIPickerView pickerView)
{
return 2;
}
public override nint GetRowsInComponent(UIPickerView pickerView, nint component)
{
if (component == 0)
return _monthYear.Months.Count;
else
return _monthYear.Years.Count;
}
public override string GetTitle(UIPickerView pickerView, nint row, nint component)
{
if (component == 0)
return _monthYear.Months[(int)row];
else
return _monthYear.Years[(int)row].ToString();
}
public override void Selected(UIPickerView pickerView, nint row, nint component)
{
if (component == 0)
_selectedMonth = _monthYear.Months[(int)row];
if (component == 1)
_selectedYear = _monthYear.Years[(int)row].ToString();
_uITextField.Text = $"{_selectedMonth} {_selectedYear}";
}
public override nfloat GetComponentWidth(UIPickerView picker, nint component)
{
if (component == 0)
return 140f;
else
return 100f;
}
public override nfloat GetRowHeight(UIPickerView picker, nint component)
{
return 40f;
}
}
public class YearMonthPickerDelegate : UIPickerViewDelegate
{
private UITextField _uITextField;
private string _selectedMonth = string.Empty;
private string _selectedYear = string.Empty;
private MonthYear _monthYear;
public YearMonthPickerDelegate(UITextField uITextField, MonthYear monthYear)
{
_uITextField = uITextField;
_monthYear = monthYear;
}
public override string GetTitle(UIPickerView pickerView, nint row, nint component)
{
if (component == 0)
return _monthYear.Months[(int)row];
else
return _monthYear.Years[(int)row].ToString();
}
public override void Selected(UIPickerView pickerView, nint row, nint component)
{
if (component == 0)
_selectedMonth = _monthYear.Months[(int)row];
if (component == 1)
_selectedYear = _monthYear.Years[(int)row].ToString();
_uITextField.Text = $"{_selectedMonth} {_selectedYear}";
}
}
public class MonthYear
{
public List<string> Months { get; set; }
public List<int> Years { get; set; }
}
}
Edit 1
I am adding the YearMonthPicker class
public class YearMonthPicker : Picker
{
public YearMonthPicker()
{
}
public static readonly BindableProperty StartYearProperty =
BindableProperty.Create(nameof(StartYear), typeof(int), typeof(YearMonthPicker), 1900);
public int StartYear
{
get { return (int)GetValue(StartYearProperty); }
set { SetValue(StartYearProperty, value); }
}
public static readonly BindableProperty EndYearProperty =
BindableProperty.Create(nameof(EndYear), typeof(int), typeof(YearMonthPicker), 9999);
public int EndYear
{
get { return (int)GetValue(EndYearProperty); }
set { SetValue(EndYearProperty, value); }
}
public static readonly BindableProperty YearLabelProperty =
BindableProperty.Create(nameof(YearLabel), typeof(string), typeof(YearMonthPicker), string.Empty);
public string YearLabel
{
get { return (string)GetValue(YearLabelProperty); }
set { SetValue(YearLabelProperty, value); }
}
public static readonly BindableProperty MonthLabelProperty =
BindableProperty.Create(nameof(MonthLabel), typeof(string), typeof(YearMonthPicker), string.Empty);
public string MonthLabel
{
get { return (string)GetValue(MonthLabelProperty); }
set { SetValue(MonthLabelProperty, value); }
}
public static readonly BindableProperty LabelColorProperty =
BindableProperty.Create(nameof(LabelColor), typeof(Color), typeof(YearMonthPicker), default(Color));
public Color LabelColor
{
get { return (Color)GetValue(LabelColorProperty); }
set { SetValue(LabelColorProperty, value); }
}
}
I appreciate if someone can help me identify what causes the crash.
Finally, I found your problem is you did not assign the pickerView to the textField's inputView.
To assign the picker to the textField's inputView, use below code:
if (Control is UITextField textField)
{
//Change here
var pickerView = new UIPickerView();
Control.InputView = pickerView;
var yearMonthModel = new YearMonthModel(textField, _monthYear);
var yearMonthDelegate = new YearMonthPickerDelegate(textField, _monthYear);
pickerView.Model = yearMonthModel;
pickerView.Delegate = yearMonthDelegate;
textField.BorderStyle = UITextBorderStyle.None;
textField.AddSubview(_monthLabel);
textField.AddSubview(_yearLabel);
}

exception `[NSURL isAdtechEvent]: unrecognized selector sent to instance` when removing HockeySDK

I want to replace HockeyApp SDK with App Center SDK. But when I remove the following line of code var manager = BITHockeyManager.SharedHockeyManager; the following unhandled exception occurs on startup: [NSURL isAdtechEvent]: unrecognized selector sent to instance. I have no clue how Adtech is related to HockeySDK. And there is no method or event isAdtechEvent or similar in my code. What can I do to narrow down this error?
Here you'll find the AppDelegate class:
using System;
using System.Threading;
using MvvmCross.Platform;
using MvvmCross.iOS.Platform;
using MvvmCross.iOS.Views.Presenters;
using MvvmCross.Core.ViewModels;
using Foundation;
using GoogleConversionTracking.Unified;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
using MTiRate;
using PCLStorage;
using PushNotification.Plugin;
using UIKit;
namespace MyApp
{
public static class ShortcutIdentifier
{
public const string Parkspace = "parkingspace";
}
// The UIApplicationDelegate for the application. This class is responsible for launching the
// User Interface of the application, as well as listening (and optionally responding) to
// application events from iOS.
[Register("AppDelegate")]
public class AppDelegate : MvxApplicationDelegate
{
private const string ConversionId = "1054453082";
private const string ConversionLabel = "VaLvCNaO018Q2trm9gM";
private const string ConversionValue = "0.00";
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
private static Action _afterPushRegistrationAction;
private UIWindow _window;
// Must override the Window property for iRate to work properly
public override UIWindow Window
{
get => _window;
set => _window = value;
}
static AppDelegate()
{
// rating window configuration
iRate.SharedInstance.DaysUntilPrompt = 0.5f; // default is 10!
iRate.SharedInstance.UsesUntilPrompt = 10;
iRate.SharedInstance.RemindPeriod = 30; // 30 days
iRate.SharedInstance.PromptForNewVersionIfUserRated = false;
iRate.SharedInstance.PromptAtLaunch = false; // trigger prompt manually for it doesn't show on splash screen
// texts
iRate.SharedInstance.MessageTitle = $"{AppResources.RatingMessageTitle} {iRate.SharedInstance.ApplicationName}";
iRate.SharedInstance.Message = AppResources.RatingMessage;
iRate.SharedInstance.RateButtonLabel = AppResources.RatingRateButton;
iRate.SharedInstance.RemindButtonLabel = AppResources.RatingRemindButton;
iRate.SharedInstance.CancelButtonLabel = AppResources.RatingCancelButton;
}
public UIApplicationShortcutItem LaunchedShortcutItem { get; set; }
private UIButton _btn;
public static MvxIosViewPresenter IosViewPresenter { get; set; }
public override bool FinishedLaunching(UIApplication app, NSDictionary launchOptions)
{
var shouldPerformAdditionalDelegateHandling = true;
// Get possible shortcut item
if (launchOptions != null)
{
LaunchedShortcutItem = launchOptions[UIApplication.LaunchOptionsShortcutItemKey] as UIApplicationShortcutItem;
shouldPerformAdditionalDelegateHandling = (LaunchedShortcutItem == null);
}
AppCenter.Start(Settings.Default.AppCenterSecretiOS, typeof(Analytics), typeof(Crashes));
_window = new UIWindow(UIScreen.MainScreen.Bounds);
if (_btn == null)
{
var viewController = new UIViewController();
_window.RootViewController = viewController;
var super = viewController.View;
_btn = new UIButton(UIButtonType.Custom)
{
AccessibilityIdentifier = "StartTrigger",
BackgroundColor = UIColor.Red,
TranslatesAutoresizingMaskIntoConstraints = false
};
_btn.SetTitle("StartTrigger", UIControlState.Normal);
super.AddSubview(_btn);
super.AddConstraint(NSLayoutConstraint.Create(_btn, NSLayoutAttribute.CenterX, NSLayoutRelation.Equal,
super, NSLayoutAttribute.CenterX, 1.0f, 1.0f));
super.AddConstraint(NSLayoutConstraint.Create(_btn, NSLayoutAttribute.CenterY, NSLayoutRelation.Equal,
super, NSLayoutAttribute.CenterY, 1.0f, 1.0f));
_btn.TouchDown += (object sender, EventArgs e) =>
{
StartMvvmCross();
_btn.RemoveFromSuperview();
_btn = null;
};
super.BringSubviewToFront(_btn);
}
StartMvvmCross();
_window.MakeKeyAndVisible();
_window.BackgroundColor = UIColor.White;
return shouldPerformAdditionalDelegateHandling;
}
private void StartMvvmCross()
{
CrossPushNotification.Initialize<AppleCrossPushNotificationListenerService>();
//Initialize Google Conversion Tracking with respective parameters
ACTReporter reporter = new ACTConversionReporter(ConversionId, ConversionLabel, ConversionValue, "USD", false);
reporter.Report();
IosViewPresenter = new MvxSlidingPanelsTouchViewPresenter(this, _window);
var setup = new Setup(this, IosViewPresenter);
setup.Initialize();
var startup = Mvx.Resolve<IMvxAppStart>();
startup.Start();
_window.MakeKeyAndVisible();
_window.BackgroundColor = UIColor.White;
UIApplication.SharedApplication.SetMinimumBackgroundFetchInterval(3600);
}
public static void AskForPushPermissionsAndRegister(Action continueWith = null)
{
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
if (!UIApplication.SharedApplication.IsRegisteredForRemoteNotifications ||
string.IsNullOrEmpty(CrossPushNotification.Current.Token))
{
CrossPushNotification.Current.Register();
_afterPushRegistrationAction = continueWith;
}
else
{
continueWith?.Invoke();
}
}
else
{
if (UIApplication.SharedApplication.EnabledRemoteNotificationTypes == UIRemoteNotificationType.None ||
string.IsNullOrEmpty(CrossPushNotification.Current.Token))
{
CrossPushNotification.Current.Register();
_afterPushRegistrationAction = continueWith;
}
else
{
continueWith?.Invoke();
}
}
}
public bool HandleShortcutItem(UIApplicationShortcutItem shortcutItem)
{
var handled = false;
if (shortcutItem == null) return false;
var routing = Mvx.Resolve<IRoutingService>();
switch (shortcutItem.Type)
{
case ShortcutIdentifier.Parkspace:
routing.Route("fzag://shortcut/parking?id=scan");
handled = true;
break;
}
return handled;
}
public override void PerformActionForShortcutItem(UIApplication application, UIApplicationShortcutItem shortcutItem,
UIOperationHandler completionHandler)
{
completionHandler(HandleShortcutItem(shortcutItem));
}
public override void OnActivated(UIApplication application)
{
// Handle any shortcut item being selected
HandleShortcutItem(LaunchedShortcutItem);
// Clear shortcut after it's been handled
LaunchedShortcutItem = null;
}
public override void ReceivedLocalNotification(UIApplication application, UILocalNotification notification)
{
// will be called if was clicked
if (notification.UserInfo == null || !notification.UserInfo.ContainsKey(FromObject("url")))
return;
var url = notification.UserInfo["url"].ToString();
var normalized = Uri.UnescapeDataString(url);
var routing = new RoutingService();
if (routing.CanRoute(normalized))
routing.Route(normalized);
}
public override bool OpenUrl(UIApplication app, NSUrl url, string srcApp, NSObject annotation)
{
var normalized = Uri.UnescapeDataString(url.ToString());
var routing = new RoutingService();
if (routing.CanRoute(normalized))
routing.Route(normalized);
return true;
}
public override void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
{
var settings = Mvx.Resolve<IAppSettingsService>();
settings.PushNotifications = false;
if (CrossPushNotification.Current is IPushNotificationHandler handler)
handler.OnErrorReceived(error);
}
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
var appSettings = Mvx.Resolve<IAppSettingsService>();
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
if (!application.IsRegisteredForRemoteNotifications)
{
appSettings.PushNotifications = false;
return;
}
}
else
{
if (application.EnabledRemoteNotificationTypes == UIRemoteNotificationType.None)
{
appSettings.PushNotifications = false;
return;
}
}
var handler = CrossPushNotification.Current as IPushNotificationHandler;
if (handler == null) return;
handler.OnRegisteredSuccess(deviceToken);
if (App.IsInitialized && Mvx.CanResolve<ILoginService>())
{
var loginService = Mvx.Resolve<ILoginService>();
try
{
AsyncHelper.RunSync(() => loginService.UpdateDeviceIdentificationAsync());
}
catch (Exception ex)
{
Log.Error(ex);
}
}
_afterPushRegistrationAction?.Invoke();
}
public override void DidRegisterUserNotificationSettings(UIApplication application,
UIUserNotificationSettings notificationSettings)
{
application.RegisterForRemoteNotifications();
}
public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo,
Action<UIBackgroundFetchResult> completionHandler)
{
if (CrossPushNotification.Current is IPushNotificationHandler handler)
handler.OnMessageReceived(userInfo);
}
public override void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
{
if (CrossPushNotification.Current is IPushNotificationHandler handler)
handler.OnMessageReceived(userInfo);
}
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations(UIApplication application,
UIWindow forWindow)
{
try
{
if (App.IsInitialized && Mvx.CanResolve<IMvxIosViewPresenter>())
{
if (Mvx.Resolve<IMvxIosViewPresenter>() is MvxIosViewPresenter mvxIosViewPresenter)
{
var viewController = mvxIosViewPresenter.MasterNavigationController.TopViewController;
return viewController.GetSupportedInterfaceOrientations();
}
}
}
catch
{
// can be called before Mvx is setup
}
return UIInterfaceOrientationMask.Portrait;
}
public override void PerformFetch(UIApplication application, Action<UIBackgroundFetchResult> completionHandler)
{
var accountStorage = new AppleAccountStorage();
if (!accountStorage.HasAccount)
{
completionHandler(UIBackgroundFetchResult.NoData);
return;
}
var voidMessenger = new VoidMessenger();
var restService = new RestService(AppleCultureService.Instance);
var authenticationService = new AuthenticationService(restService, voidMessenger, accountStorage);
var plannerService = new PlannerService(restService, authenticationService, FileSystem.Current,
new ZipService());
var tripService = new RealmTravelPlannerBookmarkService(new Lazy<IPlannerService>(() => plannerService), voidMessenger, accountStorage);
var command = new ProfileDataUpdateCommand(tripService, accountStorage, authenticationService,
plannerService);
try
{
AsyncHelper.RunSync(() => command.UpdateAsync(CancellationToken.None));
}
catch (Exception)
{
completionHandler (UIBackgroundFetchResult.Failed);
return;
}
completionHandler(UIBackgroundFetchResult.NewData);
}
}
}

Dynamically created Xamarin iOS UI buttons not clickable

I have a Tag class inflated from a nib file:
using Foundation;
using System;
using UIKit;
using ObjCRuntime;
namespace TagTest
{
public partial class Tag : UIView
{
public Tag (IntPtr handle) : base (handle)
{
}
public static Tag Create()
{
var arr = NSBundle.MainBundle.LoadNib("Tag", null, null);
var v = Runtime.GetNSObject<Tag>(arr.ValueAt(0));
return v;
}
public override void AwakeFromNib()
{
}
public UIButton Hashtag
{
get
{
return HashtagBtn;
}
}
public UILabel HashtagCount
{
get
{
return HashtagCountLbl;
}
}
}
}
which is used by the following viewmodel
using System.Collections.Generic;
using MvvmCross.Binding.BindingContext;
using MvvmCross.iOS.Views;
using TagTest.Core.ViewModels;
using UIKit;
using Cirrious.FluentLayouts.Touch;
using System;
using System.Drawing;
namespace TagTest
{
public partial class SearchView : MvxViewController
{
List<Tag> _tags;
public SearchView () : base ("SearchView", null)
{
}
new SearchViewModel ViewModel
{
get
{
return (SearchViewModel)base.ViewModel;
}
set
{
base.ViewModel = value;
}
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
DisplayTags();
View.UserInteractionEnabled = false;
View.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
View.TranslatesAutoresizingMaskIntoConstraints = false;
EdgesForExtendedLayout = UIRectEdge.None;
ExtendedLayoutIncludesOpaqueBars = false;
AutomaticallyAdjustsScrollViewInsets = false;
View.ClipsToBounds = true;
View.BackgroundColor = UIColor.Red;
CreateBindings ();
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
ViewModel.Init();
}
void CreateBindings()
{
var set = this.CreateBindingSet<SearchView, SearchViewModel> ();
set.Bind (this).For(x => x.Title).To (vm => vm.Title);
set.Apply ();
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
View.LayoutIfNeeded();
}
void DisplayTags()
{
_tags = new List<Tag>();
if (ViewModel.Tags != null)
{
foreach (var item in ViewModel.Tags)
{
_tags.Add(Tag.Create());
}
UIView lastTemplateAdded = View;
for (int i = 0; i < _tags.Count; i++)
{
var tag = _tags[i];
tag.TranslatesAutoresizingMaskIntoConstraints = false;
tag.Hashtag.SetTitle(ViewModel.Tags[i].Tagname, UIControlState.Normal);
tag.HashtagCount.Text = ViewModel.Tags[i].Count.ToString();
tag.Frame = new RectangleF(100f, 300f, 100f, 50f);
tag.Hashtag.Enabled = true;
tag.Hashtag.UserInteractionEnabled = true;
tag.UserInteractionEnabled = true;
tag.Hashtag.TouchUpInside += (sender, e) =>
{
ViewModel.TagSelectedCommand.Execute(tag);
};
View.AddSubview(tag);
if (i == 0)
{
View.AddConstraints(
tag.AtTopOf(View),
tag.AtLeftOf(View),
tag.Height().EqualTo(20)
);
}
else
{
View.AddConstraints(
tag.Below(lastTemplateAdded, 20),
tag.AtLeftOf(View),
tag.Height().EqualTo(20)
);
}
lastTemplateAdded = tag;
}
}
}
public override void DidReceiveMemoryWarning ()
{
base.DidReceiveMemoryWarning ();
// Release any cached data, images, etc that aren't in use.
}
}
}
but the Hashtag button is not clickable, the TouchupInside doesn't appear to be fired. If I add a single button to the view it is clickable. What could be going wrong?
Found the problem. It was a combination of setting View.UserInteractionEnabled = false; and using the fluent constraints. I've now set UserInteractioEnabled to true and removed the fluent constraints. All working now.

CALayer Custom Property Animation with Xamarin

i am really frustrated, because I am trying to animate a Pie Chart (Arc Segment with a transparent hole) on iOS with CoreAnimation since the last week.
At the Moment, I am drawing the ArcSegment with a CAShapeLayer with its Path-Property. It looks great, but I can't animate this property.
I want to animate the Layer-Property like Radius, Segments etc, with a CABasicAnimation.
Is there anyone here, who can tell me how to solve this?
Thank you.
Regards
Ronny
public class ArcSegmentLayer : CAShapeLayer {
private const string StartAngleProperty = "StartAngle";
private const string EndAngleProperty = "EndAngle";
public static void RegisterProperties() {
ObjCProperties.RegisterDynamicProperty(typeof(ArcSegmentLayer), StartAngleProperty, typeof(float));
ObjCProperties.RegisterDynamicProperty(typeof(ArcSegmentLayer), EndAngleProperty, typeof(float));
}
public ArcSegmentLayer() { }
[Export("initWithLayer:")]
public ArcSegmentLayer(ArcSegmentLayer layer) {
this.LineWidth = layer.LineWidth;
this.Frame = layer.Frame;
this.FillColor = layer.FillColor;
this.StrokeColor = layer.StrokeColor;
this.Segments = layer.Segments;
this.Margin = layer.Margin;
}
#region Properties
public float StartAngle {
get { return ObjCProperties.GetFloatProperty(Handle, StartAngleProperty); }
set {
ObjCProperties.SetFloatProperty(Handle, StartAngleProperty, value);
}
}
public float EndAngle {
get { return ObjCProperties.GetFloatProperty(Handle, EndAngleProperty); }
set {
ObjCProperties.SetFloatProperty(Handle, EndAngleProperty, value);
}
}
public nint Segments {
get { return segments; }
set {
if (segments != value) {
segments = value;
this.SetNeedsDisplay();
}
}
}
public nfloat Margin {
get {
return margin;
}
set {
if (margin != value) {
margin = value;
this.SetNeedsDisplay();
}
}
}
#endregion
[Export("needsDisplayForKey:")]
public static bool NeedsDisplayForKey(NSString key) {
return key == StartAngleProperty
|| key == EndAngleProperty
|| key == "Margin"
|| key == "Segments"
|| key == "LineWidth"
|| key == "StrokeColor"
|| CALayer.NeedsDisplayForKey(key);
}
[Export("display")]
public override void Display() {
base.Display();
Console.WriteLine(this.EndAngle);
this.Path = CreateSegments().CGPath;
}
[Export("actionForKey:")]
public override NSObject ActionForKey(string eventKey) {
/*
if (eventKey == EndAngleProperty) {
CABasicAnimation animation = CABasicAnimation.FromKeyPath(eventKey);
animation.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.Linear);
animation.From = new NSNumber(this.EndAngle); //PresentationLayer.ValueForKey(new NSString(eventKey));
//animation.Duration = CATransition. 1;
animation.Duration = 0;
return animation;
} else if (eventKey == StartAngleProperty) {
CABasicAnimation animation = CABasicAnimation.FromKeyPath(eventKey);
animation.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.Linear);
animation.From = new NSNumber(this.StartAngle);
animation.Duration = 0;
return animation;
}*/
return base.ActionForKey(eventKey);
}
private UIBezierPath CreateSegments() {
var path = new UIBezierPath();
nfloat segmentSize = (nfloat)(360.0 / (nfloat)this.Segments);
nfloat startSegAngle = 0;
nfloat endSegAngle = startSegAngle + segmentSize;
if (this.Segments > 1) {
var fromSeg = (nint)((((double)this.Segments) * this.StartAngle) / 360.0);
var toSeg = (nint)((((double)this.Segments) * this.EndAngle) / 360.0);
for (var seg = 0; seg < this.Segments; seg++) {
var hiddenLayer = !(seg >= fromSeg && seg < toSeg);
if (!hiddenLayer) {
path.AppendPath(
this.CreateSegmentPath(
startSegAngle, endSegAngle - this.Margin));
}
startSegAngle += segmentSize;
endSegAngle += segmentSize;
}
} else if (this.Segments == 1) {
path.AppendPath(this.CreateSegmentPath(this.StartAngle, this.EndAngle));
}
return path;
}
private UIBezierPath CreateSegmentPath(nfloat startSegAngle, nfloat endSegAngle) {
var center = new CGPoint(x: this.Bounds.Width / 2f, y: this.Bounds.Height / 2f);
var radius = (nfloat)Math.Max(this.Bounds.Width, this.Bounds.Height) / 2f - this.LineWidth / 2f;
var path = UIBezierPath.FromArc(
center,
radius,
Deg2Rad(startSegAngle - 90f),
Deg2Rad(endSegAngle - 90f),
true);
path.MoveTo(center);
path.ClosePath();
path.Stroke();
return path;
}
private static nfloat Deg2Rad(nfloat value) {
return (nfloat)(floatPI / 180.0 * value);
}
private static readonly nfloat floatPI = (nfloat)Math.PI;
private nint segments;
private nfloat margin;
}
[DesignTimeVisible(true)]
public partial class ArcSegmentView : UIView {
public ArcSegmentView(IntPtr handle) : base(handle) {
this.strokeColor = UIColor.Black.CGColor;
}
#region Properties
[Export("StartAngle"), Browsable(true)]
public nfloat StartAngle {
get { return startAngle; }
set {
if (startAngle != value) {
startAngle = value;
((ArcSegmentLayer)this.Layer).StartAngle = (float)value;
this.SetNeedsDisplay();
}
}
}
[Export("EndAngle"), Browsable(true)]
public nfloat EndAngle {
get { return endAngle; }
set {
if (endAngle != value) {
endAngle = value;
((ArcSegmentLayer)this.Layer).EndAngle = (float)value;
this.SetNeedsDisplay();
}
}
}
[Export("Segments"), Browsable(true)]
public nint Segments {
get { return segments; }
set {
if (segments != value) {
segments = value;
((ArcSegmentLayer)this.Layer).Segments = value;
this.SetNeedsDisplay();
}
}
}
[Export("Margin"), Browsable(true)]
public nfloat Margin {
get { return margin; }
set {
if (margin != value) {
margin = value;
((ArcSegmentLayer)this.Layer).Margin = value;
this.SetNeedsDisplay();
}
}
}
[Export("LineWidth"), Browsable(true)]
public nfloat LineWidth {
get { return lineWidth; }
set {
if (lineWidth != value) {
lineWidth = value;
((ArcSegmentLayer)this.Layer).LineWidth = value;
this.SetNeedsDisplay();
}
}
}
[Export("StrokeColor"), Browsable(true)]
public CGColor StrokeColor {
get { return strokeColor; }
set {
if (StrokeColor != value) {
strokeColor = value;
((ArcSegmentLayer)this.Layer).StrokeColor = value;
//this.SetNeedsDisplay();
}
}
}
#endregion
[Export("layerClass")]
static Class LayerClass() {
return new Class(typeof(ArcSegmentLayer));
}
private nfloat lineWidth;
private nfloat margin;
private nint segments;
private nfloat startAngle;
private nfloat endAngle;
private CGColor strokeColor;
}
public partial class ViewController : UIViewController {
protected ViewController(IntPtr handle) : base(handle) { }
public override void ViewDidLoad() {
base.ViewDidLoad();
arcSegment.StartAngle = 45;
arcSegment.EndAngle = 90;
arcSegment.Margin = 2;
arcSegment.StrokeColor = UIColor.Red.CGColor;
arcSegment.Segments = 70;
arcSegment.LineWidth = 10;
CABasicAnimation animation = CABasicAnimation.FromKeyPath("EndAngle");
animation.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.Linear);
animation.From = new NSNumber(45);
animation.To = new NSNumber(360);
animation.Duration = 10;
arcSegment.Layer.AddAnimation(animation, "EndAngle");
}
}
We have a sample that shows how to do this:
https://github.com/xamarin/ios-samples/tree/master/CustomPropertyAnimation
In particular:
https://github.com/xamarin/ios-samples/blob/master/CustomPropertyAnimation/AppDelegate.cs

Resources