Why does SkiaSharp Touch SKTouchAction.Moved event not work? - xamarin

Summary
The Touch event is never raised with the ActionType of SKTouchAction.Moved, but SKTouchAction.Pressed is raised. Why does the .Moved event never get raised?
Detail
I am attempting to create a slider in SkiaSharp. This control will need to update the thumb (the little movable circle) when the user either touches on the slider or drags. This is hosted in a Xamarin Forms app, and I have created a SKCanvasView to draw the slider and to respond to touch events.
I have successfully responded to touch events, so I know the SKCanvasView is receiving some UI events, but the SKTouchAction.Moved is never raised.
Example Code
private SKCanvasView CreateSliderControl()
{
var control = new SKCanvasView();
control.PaintSurface += HandlePaintHeightControl;
control.EnableTouchEvents = true;
control.Touch += (sender, args) =>
{
var pt = args.Location;
switch (args.ActionType)
{
case SKTouchAction.Pressed:
// 1. This code gets hit whenever I touch the canvas
control.InvalidateSurface();
break;
case SKTouchAction.Moved:
// 2. This code never gets hit, even if I push, touch, slide, etc
control.InvalidateSurface();
break;
}
};
control.InputTransparent = false;
return control;
}
As shown above #1 gets hit (I can put a breakpoint there, and my control surface is invalidated). #2 never gets hit ever, but I expect it to get hit when I slide my finger over the control.
What I've Tried
Clean/rebuild to make sure my code actually was being deployed
Upgrade from SkiaSharp 1.60.3 to 1.68.0
All the crazy finger movements I could come up with
Read through https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/transforms/touch , but that uses XAML to assign an Effect (which I'm not familiar with) and I don't see a TouchEffect class available anywhere in my code to add it to the parent Grid that contains my SkiaCanvasView.

Found a similar issue on SkiaSharp's Github.
Try telling the OS that you want to "continue receiving touch events" by setting the Handled property in the event's args to true.
private SKCanvasView CreateSliderControl()
{
var control = new SKCanvasView();
control.PaintSurface += HandlePaintHeightControl;
control.EnableTouchEvents = true;
control.Touch += (sender, args) =>
{
var pt = args.Location;
switch (args.ActionType)
{
case SKTouchAction.Pressed:
control.InvalidateSurface();
break;
case SKTouchAction.Moved:
control.InvalidateSurface();
break;
}
// Let the OS know that we want to receive more touch events
args.Handled = true;
};
control.InputTransparent = false;
return control;
}

Related

Prompts on top of popup windows in Xamarin and MAUI

I have discovered the limitation of displaying prompts when they are invoked from a popup window. Specifically verified with CommunityToolkit.Maui Popups.
Here's the details:
In the Map page I have this handler for the map clicked event:
void mapClicked(object sender, MapClickedEventArgs e) {
var pin = new Pin {
Label = "Here's where it is",
Location = e.Location
};
map.Pins.Add(pin);
}
I wanted to allow the user to edit the pin label when clicking on the it, like so:
pin.InfoWindowClicked += async (s, args) => {
string pinName = ((Pin)s).Label;
await DisplayPromptAsync("Enter new label", "enter new label");
};
However, this didn't work as no DisplayPrompt was shown. I tried running it in the main thread, to no avail either.
UPDATE. I've figured it out, see answer below.
The problem arises when attempting to bring up the prompt from a popup window. Evidently, one can't have a DisplayPromptAsync (or DisplayAlert for that matter) on top of a popup.
On a platform-specific level in iOS the error message reads:
Attempt to present <UIAlertController> on <Microsoft_Maui_Controls_Platform_Compatibility_ShellFlyoutRenderer> (from <Microsoft_Maui_Controls_Platform_Compatibility_ShellFlyoutRenderer>) which is already presenting <CommunityToolkit_Maui_Core_Views_MauiPopup>.

Will it be a problem to have a TapGesture added inside of a data template in Xamarin? Should I somehow remove the event I added?

Within my DataTemplate (written in C#), I have this code:
var plusMinusGrid = new Grid
{
Children =
{
_minusFrame.Column(0).Bind(IsVisibleProperty, nameof(DeckRow.FRMIsVisible), source: this),
_plusFrame.Column(0).Bind(IsVisibleProperty, nameof(DeckRow.FRPIsVisible), source: this)
},
};
var plusMinusTapGesture = new TapGestureRecognizer();
plusMinusTapGesture.Tapped += PlusMinusTap;
plusMinusGrid.GestureRecognizers.Add(plusMinusTapGesture);
So I am adding the tap event to a part of each row.
My question is, will this be an issue as a memory leak and if that's the case is there a way that I can deal with this.
Here is what I have. Override the Disappearing method add a call to a CleanUp() that you write yourself. As I am doing the += on in the view model I add the -= there also. This is annoying as I also found that I needed to set itemsource to null, but itemsource is in the view. Since Disappearing is part of the view, I call the VM's CleanUp() method!
As my page is used modally this works fine. If your page is not modal, and you refresh the grid, make sure you do the -= on existing items before you repopulate.
View page :
public void CleanUp()
{
this.athleteDetailVM.CleanUp();
this.collectionV.ItemsSource = null;
}
VM Implementation:
public void CleanUp()
{
foreach (CheckedItem<EventResult> item in eventResults)
{
item.PropertyChanged -= null;
}
}

Xamarin app crashes on ObservableCollection.Add?

I have an Xamarin.Forms app running on iOS simulator that just stops as soon as it hits code which adds a new member to an observable collection that is the template source for a CarouselView. Oddly, it doesnt happen immediately, but only when I add more items to the collection when the user gets close to the end of the carousel. I can't trap the error in a try catch block. In debug mode, when I stop at the line in question and either step in or step over, the app just closes and goes back to the home screen. I recently updated my packages, maybe there is some issue there?
Code as follows:
private ObservableCollection<ResultsScroller> mySource;
public void PopulateResultsPages(List<NominalResult> resultList)
{
foreach (var nr in resultList)
{
var template = new ResultsScroller();
template.LoadData(nr);
try
{
mySource.Add(template); // <----app quits here
}
catch (System.Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
}
lastPosition = mySource.Count - 1;
}
initialize observablecollevtion mySource in constructor like this
mySource = new ObservableCollection<ResultsScroller>();
after that add item in it.
Currently you have to somehow programmatically make the ListView appear before adding an item. If the ListView is inside a tab that is not visible, show it by setting the CurrentPage Property. If it is in a MasterDetailPage, you could try showing it by toggling the IsPresented Property.
An upcoming Update of XF should fix this issue:
- https://github.com/xamarin/Xamarin.Forms/issues/1542
- https://github.com/xamarin/Xamarin.Forms/issues/1927

Xamarin Forms, Detecting map Scrolling, Zooming and etc event

I'm using Xamarin Forms Maps official nuget library, everything works good and I handle finish zooming and scrolling map via this code:
map.PropertyChanged += (sender, args) =>
{
var m = sender as Map;
if (m?.VisibleRegion == null) return;
SearchButton.IsVisible = true;
};
map.PropertyChanging += (sender, args) => { SearchButton.IsVisible = false; };
But I want to do some stuff when user starts scrolling or zooming map!
I didn't find it and also PropertyChanging not called when user is surfing the map, it's calling just before PropertyChanged.
I think in Xamarin.Forms your options are limited, so your best option would be to either create a custom renderer and abstract the gestures into Xamarin.Forms, or create your own custom canvas, render the map onto it and that way you would have full control.

How to handle screen rotation/orientation in Xamarin Forms?

Is there a (half) generic way to handle rotation/orientation in Xamarin Forms for different views and platforms (Android, iOS, WinPhone)?
The UI does rotate, and that is nice enough, though it wreaks havoc to my layout (absolute layout right now). I suppose with a Stacklayout I could make it a litte more flexible, but would then hit a road block somewhere else when the layout is more intricate.
Can I somehow display different views for portrait and landscape, with the same ViewModel? (I am using XLABs based MVVM.)
Possible solutions I have found:
http://blog.rthand.com/post/2014/07/24/Different-XAML-layouts-for-different-device-orienations-in-XamarinForms.aspx is lacking iOS and I wonder if it will handle MVVM too well, seems good though and I am investigating it right now
http://www.jimbobbennett.io/orientation-with-xamarin-forms/ sounds promising but the sample is iOS only, the linked GIT repository has no documentation at all, it just says "Xamarin Helpers"
http://www.sellsbrothers.com/posts/Details/13740 might also be a possibility for programmatically created views. Though in my tests I did not get a size changed event (though I listened at a different code place) for ios simulator when rotating. (The solution is based on size changed to detect rotation.)
If you are already using XLabs then you could use IXFormsApp and property 'Orientation' and event handler 'Rotation'. You would have to add the native observers per platform and set IXFormsApp's 'Orientation' there which would cause the event handler to invoke.
For example on iOS:
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
var xapp = new XFormsAppiOS();
xapp.Init(this);
var resolverContainer = new SimpleContainer();
resolverContainer.Register<IXFormsApp>(xapp);
Resolver.SetResolver(resolverContainer.GetResolver());
var a = new App();
LoadApplication(a);
UIDevice.Notifications.ObserveOrientationDidChange((s, e) =>
{
xapp.Orientation = ... // set orientation here
});
Now you can monitor orientation changes by resolving IXFormsApp:
xapp = Resolver.Resolve<IXFormsApp>();
if (xapp.Orientation == Orientation.Landscape) { ... } else { ... }
xapp.Rotation += (sender, args) =>
{
switch (args.Value)
{
case Orientation.LandscapeLeft:
break;
default:
// etc.
break;
}
};
As for layouts I would imagine RelativeLayout would be the most convenient choice as you could put the orientation inside the Constraint's. On rotation make the layout refresh itself.

Resources