Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix touch gestures not working on MAUI iOS once packaged #1704

Merged
merged 6 commits into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project>
<PropertyGroup>
<MauiVersion>8.0.82</MauiVersion>
<!-- TODO: Move here other global properties like Version, Author -->
</PropertyGroup>
</Project>
6 changes: 6 additions & 0 deletions LiveCharts.Maui.sln
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiSample", "samples\MauiS
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ViewModelsSamples", "samples\ViewModelsSamples\ViewModelsSamples.csproj", "{F12F0322-ED2F-48FB-99C2-61B415457443}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveChartsCore.Behaviours", "src\LiveChartsCore.Behaviours\LiveChartsCore.Behaviours.csproj", "{CCFF5DA6-2C27-48C9-BBB6-CDDDAA835116}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -41,6 +43,10 @@ Global
{F12F0322-ED2F-48FB-99C2-61B415457443}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F12F0322-ED2F-48FB-99C2-61B415457443}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F12F0322-ED2F-48FB-99C2-61B415457443}.Release|Any CPU.Build.0 = Release|Any CPU
{CCFF5DA6-2C27-48C9-BBB6-CDDDAA835116}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CCFF5DA6-2C27-48C9-BBB6-CDDDAA835116}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CCFF5DA6-2C27-48C9-BBB6-CDDDAA835116}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CCFF5DA6-2C27-48C9-BBB6-CDDDAA835116}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
4 changes: 2 additions & 2 deletions build/pack.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ param([string]$configuration = "Debug", [string]$nupkgOutputPath = "./nupkg")

[Project[]]$projects = @(
[Project]::new("./src/LiveChartsCore/LiveChartsCore.csproj")
[Project]::new("./src/LiveChartsCore.Behaviours/LiveChartsCore.Behaviours.csproj")
[Project]::new("./src/skiasharp/LiveChartsCore.SkiaSharp/LiveChartsCore.SkiaSharpView.csproj")
[Project]::new("./src/skiasharp/LiveChartsCore.SkiaSharp.Avalonia/LiveChartsCore.SkiaSharpView.Avalonia.csproj")
[Project]::new("./src/skiasharp/LiveChartsCore.SkiaSharp.WinForms/LiveChartsCore.SkiaSharpView.WinForms.csproj")
Expand All @@ -11,7 +12,6 @@ param([string]$configuration = "Debug", [string]$nupkgOutputPath = "./nupkg")
[Project]::new("./src/skiasharp/LiveChartsCore.SkiaSharpView.Eto/LiveChartsCore.SkiaSharpView.Eto.csproj")
[Project]::new("./src/skiasharp/LiveChartsCore.SkiaSharpView.Maui/LiveChartsCore.SkiaSharpView.Maui.csproj")
[Project]::new("./src/skiasharp/LiveChartsCore.SkiaSharpView.Uno.WinUI/LiveChartsCore.SkiaSharpView.Uno.WinUI.csproj")
[Project]::new("./src/LiveChartsCore.Behaviours/LiveChartsCore.Behaviours.csproj")
[Project]::new("./src/skiasharp/LiveChartsCore.SkiaSharpView.WinUI/LiveChartsCore.SkiaSharpView.WinUI.csproj")
)

Expand Down Expand Up @@ -41,6 +41,6 @@ foreach ($p in $projects) {
if (Test-Path $($folder + "/bin")) {
Remove-Item $($folder + "/bin") -Force -Recurse
}

dotnet pack $p.src -o $nupkgOutputPath -c $configuration -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg
}
6 changes: 6 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"sdk": {
"version": "8.0.401",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason to use this version?

Maybe upgrading the version to 9.x is a good idea, just want to confirm, if there is a particular reason, I can make this commit.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recently asked Microsoft what we should use as library authors regarding broad compatibility.

I think that for now you can stick with 8.0.100 with roll forward latest feature, but make sure you target net8.0-ios17.0 in the TargetFramework and not just net8.0-ios because that implicitly targets only the latest native version released with the latest workload (right now is 18.2).

This ensures compatibility with MAUI8 and MAUI9.

Take into consideration that many developers (like me) are still on .NET8 considering that MAUI9 is still a bit "unstable", or simply because they didn't have enough time to upgrade.

"rollForward": "latestFeature"
}
}
1 change: 1 addition & 0 deletions samples/MauiSample/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public static MauiApp CreateMauiApp()
var builder = MauiApp.CreateBuilder();
_ = builder
.UseSkiaSharp()
.UseLiveCharts()
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
Expand Down
2 changes: 1 addition & 1 deletion samples/MauiSample/MauiSample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
The Mac App Store will NOT accept apps with ONLY maccatalyst-arm64 indicated;
either BOTH runtimes must be indicated or ONLY macatalyst-x64. -->
<!-- For example: <RuntimeIdentifiers>maccatalyst-x64;maccatalyst-arm64</RuntimeIdentifiers> -->

<OutputType>Exe</OutputType>
<RootNamespace>MauiSample</RootNamespace>
<UseMaui>true</UseMaui>
Expand Down
3 changes: 1 addition & 2 deletions samples/MauiSample/Platforms/Android/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="31" />
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
</manifest>
233 changes: 121 additions & 112 deletions src/LiveChartsCore.Behaviours/ChartBehaviour.MacCatalyst.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,132 +39,45 @@ public partial class ChartBehaviour
#if MACCATALYST

/// <summary>
/// Builds a mac catalyst gesture recognizer.
/// Gets the hover gesture recognizer.
/// </summary>
/// <param name="view">the view.</param>
/// <returns>the recognizer.</returns>
protected UIHoverGestureRecognizer GetMacCatalystHover(UIView view)
{
return new UIHoverGestureRecognizer((UIHoverGestureRecognizer e) =>
{
switch (e.State)
{
case UIGestureRecognizerState.Changed:
var p = e.LocationInView(view);
Moved?.Invoke(view, new(new(p.X, p.Y), e));
break;
case UIGestureRecognizerState.Cancelled:
case UIGestureRecognizerState.Failed:
case UIGestureRecognizerState.Ended:
Exited?.Invoke(view, new(e));
break;
case UIGestureRecognizerState.Possible:
case UIGestureRecognizerState.Began:
default:
break;
}
});
}
protected UIHoverGestureRecognizer MacCatalystHoverGestureRecognizer { get; }

#endif

/// <summary>
/// Builds a mac catalyst gesture recognizer.
/// Gets the long press gesture recognizer.
/// </summary>
/// <param name="view">the view.</param>
/// <returns>the recognizer.</returns>
protected UILongPressGestureRecognizer GetMacCatalystLongPress(UIView view)
{
return new UILongPressGestureRecognizer((UILongPressGestureRecognizer e) =>
{
var location = e.LocationInView(view);
var p = new LvcPoint((float)location.X, (float)location.Y);
var isRightClick = (DateTime.Now - _previousPress).TotalMilliseconds < 500;
var isPinch = e.NumberOfTouches > 1;

switch (e.State)
{
case UIGestureRecognizerState.Began:
Pressed?.Invoke(view, new(p, isRightClick, e));
_previousPress = DateTime.Now;
break;
case UIGestureRecognizerState.Changed:
Moved?.Invoke(view, new(p, e));
break;
case UIGestureRecognizerState.Cancelled:
case UIGestureRecognizerState.Ended:
Released?.Invoke(view, new(p, isRightClick, e));
break;
case UIGestureRecognizerState.Possible:
case UIGestureRecognizerState.Failed:
default:
break;
}
})
{
MinimumPressDuration = 0,
ShouldRecognizeSimultaneously = (g1, g2) => true
};
}

private float _previousScale = 1;
protected UILongPressGestureRecognizer MacCatalystLongPressGestureRecognizer { get; }

/// <summary>
/// Builds a mac catalyst gesture recognizer.
/// Gets the pinch gesture recognizer.
/// </summary>
/// <param name="view">the view.</param>
/// <returns>the recognizer.</returns>
protected UIPinchGestureRecognizer GetMacCatalystPinch(UIView view)
{
return new UIPinchGestureRecognizer((UIPinchGestureRecognizer e) =>
{
var p = e.LocationInView(view);

switch (e.State)
{
case UIGestureRecognizerState.Began:
_previousScale = 1;
break;
case UIGestureRecognizerState.Changed:
var s = (float)e.Scale;
var delta = _previousScale - s;
Pinched?.Invoke(view, new(1 - delta, new(p.X, p.Y), e));
_previousScale = s;
break;
case UIGestureRecognizerState.Ended:
case UIGestureRecognizerState.Cancelled:
break;
case UIGestureRecognizerState.Possible:
case UIGestureRecognizerState.Failed:
default:
break;
}
})
{
ShouldRecognizeSimultaneously = (g1, g2) => true
};
}
protected UIPinchGestureRecognizer MacCatalystPinchGestureRecognizer { get; }

private CGPoint? _last;
/// <summary>
/// Gets the pan gesture recognizer.
/// </summary>
protected UIPanGestureRecognizer MacCatalystPanGestureRecognizer { get; }

/// <summary>
/// Builds a mac catalyst gesture recognizer.
/// Initializes a new instance of the <see cref="ChartBehaviour"/> class.
/// </summary>
/// <param name="view">The view.</param>
/// <returns>The recognizer.</returns>
protected UIPanGestureRecognizer GetMacCatalystOnPan(UIView view)
public ChartBehaviour()
{
return new UIPanGestureRecognizer((UIPanGestureRecognizer e) =>
#if MACCATALYST
MacCatalystHoverGestureRecognizer = new UIHoverGestureRecognizer(OnHover);
#endif
MacCatalystLongPressGestureRecognizer = new UILongPressGestureRecognizer(OnLongPress)
{
var l = e.LocationInView(view);
_last ??= l;
var delta = _last.Value.Y - l.Y;
var isZoom = e.NumberOfTouches == 0;
var tolerance = 5; // just a factor to avoid multiple calls.

if (e.State == UIGestureRecognizerState.Ended || !isZoom || Math.Abs(delta) < tolerance) return;
Scrolled?.Invoke(view, new(new(l.X, l.Y), delta, e));
_last = l;
})
MinimumPressDuration = 0,
ShouldRecognizeSimultaneously = (g1, g2) => true
};
MacCatalystPinchGestureRecognizer = new UIPinchGestureRecognizer(OnPinch)
{
ShouldRecognizeSimultaneously = (g1, g2) => true
};
MacCatalystPanGestureRecognizer = new UIPanGestureRecognizer(OnPan)
{
#if MACCATALYST
AllowedScrollTypesMask = UIScrollTypeMask.Discrete | UIScrollTypeMask.Continuous,
Expand All @@ -173,6 +86,102 @@ protected UIPanGestureRecognizer GetMacCatalystOnPan(UIView view)
ShouldRecognizeSimultaneously = (g1, g2) => true
};
}

#if MACCATALYST

private void OnHover(UIHoverGestureRecognizer e)
{
var view = e.View;
switch (e.State)
{
case UIGestureRecognizerState.Changed:
var p = e.LocationInView(view);
Moved?.Invoke(view, new(new(p.X, p.Y), e));
break;
case UIGestureRecognizerState.Cancelled:
case UIGestureRecognizerState.Failed:
case UIGestureRecognizerState.Ended:
Exited?.Invoke(view, new(e));
break;
case UIGestureRecognizerState.Possible:
case UIGestureRecognizerState.Began:
default:
break;
}
}

#endif

private void OnLongPress(UILongPressGestureRecognizer e)
{
var view = e.View;
var location = e.LocationInView(view);
var p = new LvcPoint((float)location.X, (float)location.Y);
var isRightClick = (DateTime.Now - _previousPress).TotalMilliseconds < 500;
var isPinch = e.NumberOfTouches > 1;

switch (e.State)
{
case UIGestureRecognizerState.Began:
Pressed?.Invoke(view, new(p, isRightClick, e));
_previousPress = DateTime.Now;
break;
case UIGestureRecognizerState.Changed:
Moved?.Invoke(view, new(p, e));
break;
case UIGestureRecognizerState.Cancelled:
case UIGestureRecognizerState.Ended:
Released?.Invoke(view, new(p, isRightClick, e));
break;
case UIGestureRecognizerState.Possible:
case UIGestureRecognizerState.Failed:
default:
break;
}
}

private float _previousScale = 1;

private void OnPinch(UIPinchGestureRecognizer e)
{
var view = e.View;
var p = e.LocationInView(view);

switch (e.State)
{
case UIGestureRecognizerState.Began:
_previousScale = 1;
break;
case UIGestureRecognizerState.Changed:
var s = (float)e.Scale;
var delta = _previousScale - s;
Pinched?.Invoke(view, new(1 - delta, new(p.X, p.Y), e));
_previousScale = s;
break;
case UIGestureRecognizerState.Ended:
case UIGestureRecognizerState.Cancelled:
break;
case UIGestureRecognizerState.Possible:
case UIGestureRecognizerState.Failed:
default:
break;
}
}

private CGPoint? _last;
private void OnPan(UIPanGestureRecognizer e)
{
var view = e.View;
var l = e.LocationInView(view);
_last ??= l;
var delta = _last.Value.Y - l.Y;
var isZoom = e.NumberOfTouches == 0;
var tolerance = 5; // just a factor to avoid multiple calls.

if (e.State == UIGestureRecognizerState.Ended || !isZoom || Math.Abs(delta) < tolerance) return;
Scrolled?.Invoke(view, new(new(l.X, l.Y), delta, e));
_last = l;
}
}

#endif
4 changes: 2 additions & 2 deletions src/LiveChartsCore.Behaviours/ChartBehaviour._shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ public abstract partial class ChartBehaviour
/// Gets or sets the screen size, only used internally by the Android handler, to implement a
/// workaround for https://github.com/dotnet/maui/issues/18547.
/// </summary>
public LvcSize ScreenSize { get; set; }
public virtual LvcSize ScreenSize { get; } = new(320, 480);

/// <summary>
/// Gets or sets the screen density.
/// </summary>
public double Density { get; set; }
public virtual double Density { get; } = 1.0;

/// <summary>
/// Called when the pointer/tap is pressed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<DebugType>embedded</DebugType>
</PropertyGroup>

<PropertyGroup>
Expand Down
1 change: 0 additions & 1 deletion src/LiveChartsCore/LiveChartsCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<DebugType>embedded</DebugType>
</PropertyGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<DebugType>embedded</DebugType>
</PropertyGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentView x:Class="LiveChartsCore.SkiaSharpView.Maui.CartesianChart"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.Maui">
<lvc:ChartView x:Class="LiveChartsCore.SkiaSharpView.Maui.CartesianChart"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.Maui">
<ContentView.Content>
<lvc:MotionCanvas x:Name="canvas"/>
</ContentView.Content>
</ContentView>
</lvc:ChartView>
Loading
Loading