Nullreference on popmodal and custom iOS renderer - xamarin

I have a modal view, in which I have multiple Entry fields that I through an iOS customrenderer have customized to change BorderColor when Focused.
When i pop my modal view on button press:
await Navigation.PopModalAsync(true);
I get a nullreference in my iOS customrenderer, because I guess the element suddently becomes null, and i somehow haven't told it, that the view is gone.
public class BorderColorChange : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.Layer.BorderWidth = 1;
Control.Layer.CornerRadius = 4;
e.NewElement.Focused += (sender, evt) =>
{
Control.Layer.BorderColor = UIColor.FromRGB(3, 169, 244).CGColor;
};
e.NewElement.Unfocused += (sender, evt) =>
{
Control.Layer.BorderColor = UIColor.LightGray.CGColor;
};
};
}
}
I've noticed, that when i remove the await keyword from Navigation.PopModalAsync(true); it doesn't produce the error.
Any help on how to solve this error?

It is perfectly normal for OnElementChanged to be called with e.NewElement==null. This just means that the element is being removed (like when you await the PopModelAsync), so it should handle the change that the new element to associate with is null.
With custom renderers, you need to both subscribe and unsubscribe to events when changes occur in associating your custom renderer with a native control. So for example:
public class BorderColorChange : EntryRenderer
{
private void MyFocusedEventHandler(...) ...
private void MyUnfocusedEventHandler(...) ...
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.Layer.BorderWidth = 1;
Control.Layer.CornerRadius = 4;
if (e.OldElement != null) // unsubscribe from events on old element
{
e.OldElement.Focused -= MyFocusedEventHandler;
e.OldElement.Unfocused -= MyUnfocusedEventHandler;
}
if (e.NewElement != null) // subscribe to events on new element
{
e.NewElement.Focused += MyFocusedEventHandler;
e.NewElement.Unfocused += MyUnfocusedEventHandler;
}
}
}
}
The logic for what to do when the entry gets/loses focus goes into the MyFocusedEventHandler/MyUnfocusedEventHandler rather than inline to allow for both subscribing and unsubscribing.

Related

Adding Xamarin GestureRecognizers on SKCanvasView

I have added Xamarin GestureRecognizers on a SKCanvasView and overridden OnTouch method.
I have some implementations on SKTouchAction.Moved. In order to trigger it after SKTouchAction.Pressed on Android I set e.Handled = true;
Doing so, none of the Xamarin GestureRecognizers seems to be working.
Is there any way to make both of these events work together or any alternative way to achieve this requirement?
Here is my code sample.
public abstract class GestureContainer : SKCanvasView, IViewportable
{
public GestureContainer()
{
var doubleTapGesture = new TapGestureRecognizer();
doubleTapGesture.NumberOfTapsRequired = 2;
doubleTapGesture.Tapped += TapGesture_DoubleTapped;
GestureRecognizers.Add(doubleTapGesture);
var pinchGesture = new PinchGestureRecognizer();
pinchGesture.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add(pinchGesture);
}
private void TapGesture_DoubleTapped(object sender, EventArgs e)
{
//Double tap action here
}
public void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
//Pinch action here
}
protected override void OnTouch(SKTouchEventArgs e)
{
switch (e.ActionType)
{
case SKTouchAction.Moved:
{
//Move action here
}
break;
case SKTouchAction.Pressed:
{
//Move action here
}
break;
}
e.Handled = false; // Xamarin gesture works when I set this to false
InvalidateSurface();
}
}

How to change Picker Border color in xamarin forms

My borderless custom renderer for picker
public class BorderlessPickerRenderer : PickerRenderer
{
public static void Init() { }
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
Control.Background = null;
}
}
}
It will change the picker list text color as white. please see the screenshot
If you check the source code of PickerRenderer, you will find that the Dialog is totally generated in the code behind.
So here to set a Transparent(border-less) background, we can re-write the Click event of this control, for example:
public class BorderlessPickerRenderer : Xamarin.Forms.Platform.Android.PickerRenderer
{
private IElementController ElementController => Element as IElementController;
private AlertDialog _dialog;
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || e.OldElement != null)
return;
Control.Click += Control_Click;
}
protected override void Dispose(bool disposing)
{
Control.Click -= Control_Click;
base.Dispose(disposing);
}
private void Control_Click(object sender, EventArgs e)
{
Picker model = Element;
var picker = new NumberPicker(Context);
if (model.Items != null && model.Items.Any())
{
picker.MaxValue = model.Items.Count - 1;
picker.MinValue = 0;
picker.SetDisplayedValues(model.Items.ToArray());
picker.WrapSelectorWheel = false;
picker.DescendantFocusability = DescendantFocusability.BlockDescendants;
picker.Value = model.SelectedIndex;
}
var layout = new LinearLayout(Context) { Orientation = Orientation.Vertical };
layout.AddView(picker);
ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
var builder = new AlertDialog.Builder(Context);
builder.SetView(layout);
builder.SetTitle(model.Title ?? "");
builder.SetNegativeButton(global::Android.Resource.String.Cancel, (s, a) =>
{
ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
// It is possible for the Content of the Page to be changed when Focus is changed.
// In this case, we'll lose our Control.
Control?.ClearFocus();
_dialog = null;
});
builder.SetPositiveButton(global::Android.Resource.String.Ok, (s, a) =>
{
ElementController.SetValueFromRenderer(Picker.SelectedIndexProperty, picker.Value);
// It is possible for the Content of the Page to be changed on SelectedIndexChanged.
// In this case, the Element & Control will no longer exist.
if (Element != null)
{
if (model.Items.Count > 0 && Element.SelectedIndex >= 0)
Control.Text = model.Items[Element.SelectedIndex];
ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
// It is also possible for the Content of the Page to be changed when Focus is changed.
// In this case, we'll lose our Control.
Control?.ClearFocus();
}
_dialog = null;
});
_dialog = builder.Create();
_dialog.DismissEvent += (ssender, args) =>
{
ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
};
_dialog.Show();
_dialog.Window.SetBackgroundDrawable(new ColorDrawable(Android.Graphics.Color.Transparent));
}
}
Rendering image of this custom picker:
The font color and button's style can be modified as you need since you created this dialog by yourself. And the style of the dialog also depends on the style of your app.

[Xamarin.Forms][Android] Change back and next color in navigation

I' have some navigation page and I want to override the color for the back button and my next button ( ToolbarItem )
I Already tried BarTextColor property but it change color for all navigation header text.
It's done in IOS, but I' not able to find a solution for android.
It works perfectly for the title but not for the Icons.
Here my code :
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var page = this.Element as NavigationPage;
if (page != null && toolbar != null)
{
toolbar.SetTitleTextColor(Color.Black.ToAndroid());
if (toolbar.NavigationIcon != null)
toolbar.NavigationIcon.SetColorFilter(Color.Green.ToAndroid(), Android.Graphics.PorterDuff.Mode.Multiply);
if (toolbar.OverflowIcon != null)
toolbar.OverflowIcon.SetColorFilter(Color.Green.ToAndroid(), Android.Graphics.PorterDuff.Mode.Multiply);
}
}
I' have some navigation page and I want to override the color for the back button and my next button ( ToolbarItem )
Your next button is a ToolbarItem, which is defined by yourself. So it won't be a problem for you to customize it. The difficult part lies in the back button, because it is offered by Xamarin.Forms. You need to override the NavigationPageRenderer to change the color:
public class MyNavigationPageRenderer : NavigationPageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
var navController = (INavigationPageController)e.NewElement;
navController.PushRequested += NavController_PushRequested;
navController.PopRequested += NavController_PopRequested;
}
}
private void NavController_PopRequested(object sender, Xamarin.Forms.Internals.NavigationRequestedEventArgs e)
{
Device.StartTimer(TimeSpan.FromMilliseconds(220), () =>
{
ChangeIconColor();
return false;
});
}
private void NavController_PushRequested(object sender, Xamarin.Forms.Internals.NavigationRequestedEventArgs e)
{
ChangeIconColor();
}
private void ChangeIconColor()
{
int count = this.ViewGroup.ChildCount;
var toolbar = GetToolbar();
if (toolbar.NavigationIcon != null)
{
var drawable = (toolbar.NavigationIcon as DrawerArrowDrawable);
drawable.Color = Resource.Color.material_grey_850;//set the navigation icon color here
}
}
private AToolbar GetToolbar()
{
for (int i = 0; i < this.ViewGroup.ChildCount; i++)
{
var child = this.ViewGroup.GetChildAt(i);
if (child is AToolbar)
{
return (AToolbar)child;
}
}
return null;
}
}
A little explanation to the codes above: PushRequest and PopRequest fires when you push and pop a new page to the navigation page and it is the perfect time for you to customize the existing Toolbar's NavigationIcon. So first find the Toolbar using GetToolbar then change the icon color by ChangeIconColor.

Why is my Event not firing in Xamarin UWP Map CustomRenderer?

I am trying to write a custom renderer for a Xamarin Map on UWP and a collection change event in the PCL is not triggering the appropriate event in the UWP custom renderer. It works just fine on iOS and Android.
With the following code the event ItemsCollectionChanged never gets called in the CustomMapRenderer even through the OnItemsSourcePropertyChanged is being called every 5 seconds.
public class CustomMap : Map
{
#region << Events >>
public event EventHandler ItemsCollectionChanged;
#endregion
private static void OnItemsSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var map = bindable as CustomMap;
if (map == null)
return;
map.ItemsSource.CollectionChanged += (s, e) =>
{
SetPin(bindable);
if (map.ItemsCollectionChanged != null)
{
map.ItemsCollectionChanged(bindable, new EventArgs());
}
};
}
}
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
Namespace MyNamespace.Renderers
{
public class CustomMapRenderer : MapRenderer
{
MapControl _nativeMap;
protected override void OnElementChanged(ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
_nativeMap.MapElementClick -= OnMapElementClick;
_nativeMap.Children.Clear();
_nativeMap = null;
}
if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
formsMap.ItemsCollectionChanged += ItemsCollectionChanged;
_pinClickedCommand = formsMap.PinClickedCommand;
_routeCoordinates = formsMap.ItemsSource;
_nativeMap = Control as MapControl;
_nativeMap.Children.Clear();
_nativeMap.MapElementClick += OnMapElementClick;
var snPosition = new BasicGeoposition { Latitude = 45, Longitude = -88 };
Geopoint snPoint = new Geopoint(snPosition);
var mapIcon = new MapIcon();
if (mapIcon != null)
{
_nativeMap.MapElements.Remove(mapIcon);
}
mapIcon.CollisionBehaviorDesired = MapElementCollisionBehavior.RemainVisible;
mapIcon.Location = snPoint;
mapIcon.NormalizedAnchorPoint = new Windows.Foundation.Point(0.5, 1.0);
_nativeMap.MapElements.Add(mapIcon);
_nativeMap.Center = snPoint;
}
}
void ItemsCollectionChanged(object sender, EventArgs e)
{
;
}
}
}
I used a singleton to get the observable collection that I needed

How to display Pin's Label (on a Map) without clicking on Xamarin.Forms

How can I make the pin to display label by default (without clicking it) when it is added to the map in Xamarin.Forms.
map.MoveToRegion(MapSpan.FromCenterAndRadius(position, Distance.FromMiles(0.4)));
var pin = new Pin
{
Type = PinType.Place,
Position = position,
Label = "Some Text",
};
map.Pins.Add(pin);
You can do it via a custom map render.
As an example, on iOS you can add two delegates to the MKMapView control:
DidAddAnnotationViews: Any time a MKAnnotation is added, pre-select them all..
DidDeselectAnnotationView: If someone/something tries to deselect the MKAnnotation, just re-select them all...
Working Example as a starting point:
[assembly: ExportRenderer(typeof(PinViewMap), typeof(PinViewMapRenderer))]
namespace WorkingWithMaps.iOS
{
public class PinViewMapRenderer : MapRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
{
base.OnElementChanged(e);
if (Control != null)
{
var map = Control as MKMapView;
map.DidDeselectAnnotationView += (object sender, MKAnnotationViewEventArgs eventArgs) =>
{
foreach (var anno in ((MKMapView)sender).Annotations)
{
((MKMapView)sender).SelectAnnotation(anno, true);
}
};
map.DidAddAnnotationViews += (object sender, MKMapViewAnnotationEventArgs eventArgs) =>
{
foreach (var anno in ((MKMapView)sender).Annotations)
{
((MKMapView)sender).SelectAnnotation(anno, true);
}
};
}
}
}
}

Resources