Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.

Commit

Permalink
[iOS, UWP] fixes #2894 - Gestures collection changes weren't correctl…
Browse files Browse the repository at this point in the history
…y propagating (#3643)

* [iOS, UWP] fixes #2894 - Gestures weren't wiring up to spans in all cases

* [Core] remove ChildGestureRecognizers when spans get removed

* [iOS] removed incorrect return from null
  • Loading branch information
PureWeen authored and rmarinho committed Aug 31, 2018
1 parent 0a34ef1 commit 8f03f5e
Show file tree
Hide file tree
Showing 10 changed files with 387 additions and 103 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
using System.Linq;


#if UITEST
using Xamarin.UITest;
using NUnit.Framework;
using Xamarin.Forms.Core.UITests;
#endif

namespace Xamarin.Forms.Controls.Issues
{
[Preserve(AllMembers = true)]
[Issue(IssueTracker.Github, 2894, "Gesture Recognizers added to Span after it's been set to FormattedText don't work and can cause an NRE")]
#if UITEST
[NUnit.Framework.Category(UITestCategories.Gestures)]
#endif
public class Issue2894 : TestContentPage
{
Label label = null;
Label gestureLabel1 = null;
Label gestureLabel2 = null;
int i1 = 0;
int i2 = 0;
const string kGesture1 = "Sentence 1: ";
const string kGesture2 = "Sentence 2: ";

const string kClickSentence1 = "I will fire when clicked. ";
const string kClickSentence2 = "I should also fire when clicked.";

const string kClickSentenceAutomationId1 = "Spanning1";
const string kClickSentenceAutomationId2 = "Spanning2";

const string kLabelAutomationId = "kLabelAutomationId";

GestureRecognizer CreateRecognizer1() => new TapGestureRecognizer()
{
Command = new Command(() =>
{
i1++;
gestureLabel1.Text = $"{kGesture1}{i1}";
})
};

GestureRecognizer CreateRecognizer2() => new TapGestureRecognizer()
{
Command = new Command(() =>
{
i2++;
gestureLabel2.Text = $"{kGesture2}{i2}";
})
};

void AddRemoveSpan(bool includeRecognizers = true)
{
if (label.FormattedText != null)
{
label.FormattedText = null;
return;
}

FormattedString s = new FormattedString();

var span = new Span
{
Text = kClickSentence1,
FontAttributes = FontAttributes.Bold,
AutomationId = kClickSentenceAutomationId1
};

var span2 = new Span
{
Text = kClickSentence2,
FontAttributes = FontAttributes.Bold,
AutomationId = kClickSentenceAutomationId2
};

if (includeRecognizers)
span.GestureRecognizers.Add(CreateRecognizer1());

s.Spans.Add(span);
s.Spans.Add(span2);

label.FormattedText = s;

if (includeRecognizers)
span2.GestureRecognizers.Add(CreateRecognizer2());
}


Label GetLabel() =>
new Label()
{
HorizontalOptions = LayoutOptions.Center,
AutomationId = kLabelAutomationId
};

protected override void Init()
{
BindingContext = this;

label = GetLabel();
gestureLabel1 = new Label() { HorizontalOptions = LayoutOptions.Center };
gestureLabel2 = new Label() { HorizontalOptions = LayoutOptions.Center };

gestureLabel1.Text = $"{kGesture1}{i1}";
gestureLabel2.Text = $"{kGesture2}{i2}";

AddRemoveSpan();
StackLayout stackLayout = null;
stackLayout = new StackLayout()
{
Children =
{
label,
gestureLabel1,
gestureLabel2,
new Label(){Text = "Each sentence above has a separate Gesture Recognizer. Click each button below once then test that each Gesture Recognizer fires separately. If the sentence wraps make sure to click on the wrapped text as well."},
// test removing then adding span back
new Button()
{
Text = "Add and Remove Spans",
AutomationId = "TestSpan1",
Command = new Command(async () =>
{
if(label.FormattedText != null)
AddRemoveSpan();

await Task.Delay(100);
AddRemoveSpan();
})
},
// test removing and adding same span back
new Button()
{
Text = "Null FormattedText then set again",
AutomationId = "TestSpan2",
Command = new Command(async () =>
{
if(label.FormattedText == null)
AddRemoveSpan();

var span = label.FormattedText;
await Task.Delay(100);
label.FormattedText = null;
await Task.Delay(100);
label.FormattedText = span;
})
},
new Button()
{
Text = "Remove Gestures then add again",
AutomationId = "TestSpan3",
Command = new Command(async () =>
{
if(label.FormattedText == null)
AddRemoveSpan();

if(label.FormattedText.Spans[0].GestureRecognizers.Count > 0)
{
label.FormattedText.Spans[0].GestureRecognizers.Clear();
label.FormattedText.Spans[1].GestureRecognizers.Clear();
}

await Task.Delay(100);

label.FormattedText.Spans[0].GestureRecognizers.Add(CreateRecognizer1());
label.FormattedText.Spans[1].GestureRecognizers.Add(CreateRecognizer2());
})
},
new Button()
{
Text = "Add Gestures after rendering",
AutomationId = "TestSpan4",
Command = new Command(async () =>
{
stackLayout.Children.Remove(label);
await Task.Delay(50);
label = GetLabel();
stackLayout.Children.Insert(0, label);
await Task.Delay(50);
AddRemoveSpan(false);
await Task.Delay(50);
label.FormattedText.Spans[0].GestureRecognizers.Add(CreateRecognizer1());
label.FormattedText.Spans[1].GestureRecognizers.Add(CreateRecognizer2());
})
},
new Label()
{
Text = "This Button should remove all gestures"
},
new Button()
{
Text = "Remove All Gestures",
AutomationId = "TestSpan5",
Command = new Command(() =>
{
if(label.FormattedText == null)
return;

label.FormattedText.Spans[0].GestureRecognizers.Clear();
label.FormattedText.Spans[1].GestureRecognizers.Clear();
})
}
},
Padding = 40
};

Content = new ContentView()
{
Content = stackLayout
};
}

#if UITEST
[Test]
public void VariousSpanGesturePermutation()
{
RunningApp.WaitForElement($"{kGesture1}0");
RunningApp.WaitForElement($"{kGesture2}0");
var labelId = RunningApp.WaitForElement(kLabelAutomationId);
var target = labelId.First().Rect;


for (int i = 1; i < 5; i++)
{
RunningApp.Tap($"TestSpan{i}");
RunningApp.TapCoordinates(target.X + 5, target.Y + 5);
RunningApp.TapCoordinates(target.X + target.CenterX, target.Y + 2);


RunningApp.WaitForElement($"{kGesture1}{i}");
RunningApp.WaitForElement($"{kGesture2}{i}");
}


RunningApp.Tap($"TestSpan5");
RunningApp.TapCoordinates(target.X + 5, target.Y + 5);
RunningApp.TapCoordinates(target.X + target.CenterX, target.Y + 2);

RunningApp.WaitForElement($"{kGesture1}4");
RunningApp.WaitForElement($"{kGesture2}4");
}
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
<PropertyGroup Label="Configuration">
<Import_RootNamespace>Xamarin.Forms.Controls.Issues</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Issue2894.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue3524.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue2004.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue3333.cs" />
Expand Down
2 changes: 2 additions & 0 deletions Xamarin.Forms.Core/FormattedString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Xamarin.Forms
public class FormattedString : Element
{
readonly SpanCollection _spans = new SpanCollection();
internal event NotifyCollectionChangedEventHandler SpansCollectionChanged;

public FormattedString()
{
Expand Down Expand Up @@ -69,6 +70,7 @@ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
}

OnPropertyChanged(nameof(Spans));
SpansCollectionChanged?.Invoke(sender, e);
}

void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
Expand Down
3 changes: 3 additions & 0 deletions Xamarin.Forms.Core/GestureElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Xamarin.Forms
public class GestureElement : Element, ISpatialElement
{
readonly GestureRecognizerCollection _gestureRecognizers = new GestureRecognizerCollection();
internal event NotifyCollectionChangedEventHandler GestureRecognizersCollectionChanged;

public GestureElement()
{
Expand Down Expand Up @@ -46,6 +47,8 @@ void RemoveItems()
item.Parent = this;
break;
}

GestureRecognizersCollectionChanged?.Invoke(sender, args);
};
}

Expand Down
Loading

0 comments on commit 8f03f5e

Please sign in to comment.