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

Don’t call NSAttributedString with HTML from a background thread #26153

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

kubaflo
Copy link
Contributor

@kubaflo kubaflo commented Nov 27, 2024

According to Apple's documentation: https://developer.apple.com/documentation/foundation/nsattributedstring/1524613-initwithdata

Don’t call this method from a background thread if the options dictionary includes the NSDocumentTypeDocumentAttribute attribute with a value of NSHTMLTextDocumentType. If you do, the method tries to synchronize with the main thread, fails, and times out. Calling it from the main thread works, but can still time out if the HTML contains references to external resources. The HTML import mechanism is meant for implementing something like markdown (that is, text styles, colors, and so on), not for general HTML import.

Also, it seems to be a common problem among Apple developers:
https://forums.developer.apple.com/forums/thread/115405

Issues Fixed

Fixes #25946

Before After
Simulator.Screen.Recording.-.iPhone.16.Pro.-.2024-11-27.at.12.54.42.mp4
Simulator.Screen.Recording.-.iPhone.16.Pro.-.2024-11-27.at.12.52.55.mp4

@kubaflo kubaflo requested a review from a team as a code owner November 27, 2024 12:20
@dotnet-policy-service dotnet-policy-service bot added the community ✨ Community Contribution label Nov 27, 2024
@kubaflo kubaflo added area-controls-collectionview CollectionView, CarouselView, IndicatorView area-controls-label Label, Span community ✨ Community Contribution and removed community ✨ Community Contribution labels Nov 27, 2024
@rmarinho

This comment was marked as outdated.

This comment was marked as outdated.

@kubaflo
Copy link
Contributor Author

kubaflo commented Nov 27, 2024

I now noticed that the crash happens with CarouselView1 only when swiping between more than 2 items, but in the case of CarouselView2 the app crashes instantly when CarouselView2 has more than 1 item

<CarouselView Grid.Row="1">
    <CarouselView.ItemsSource>
        <x:Array Type="{x:Type x:String}">
            <x:String>Item1</x:String>
            <x:String>Item2</x:String>
        </x:Array>
    </CarouselView.ItemsSource>
    <CarouselView.ItemTemplate>
        <DataTemplate>
            <VerticalStackLayout Background="Red">
                <Label Text="Hello"/>
                <Label Text="{Binding .}"
                        TextType="Html"/>
            </VerticalStackLayout>
        </DataTemplate>
    </CarouselView.ItemTemplate>
</CarouselView>

@kubaflo
Copy link
Contributor Author

kubaflo commented Nov 27, 2024

Ohh I just bumped into a scaling issue with ListView/CollectionView1 and CollectionView1 after changes in this PR:

For the following code:

<Grid RowDefinitions="*,*">
    <CollectionView>
        <CollectionView.ItemsSource>
            <x:Array Type="{x:Type x:String}">
                <x:String>Item1</x:String>
                <x:String>Item1</x:String>
                <x:String>Item1</x:String>
            </x:Array>
        </CollectionView.ItemsSource>
        <CollectionView.ItemTemplate>
            <DataTemplate>
                <VerticalStackLayout>
                    <Label Text="Hello"/>
                    <Label Text="{Binding .}"
                            TextType="Html"/>
                </VerticalStackLayout>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>

    <StackLayout Grid.Row="1">
        <BindableLayout.ItemsSource>
            <x:Array Type="{x:Type x:String}">
                <x:String>Item1</x:String>
                <x:String>Item1</x:String>
                <x:String>Item1</x:String>
            </x:Array>
        </BindableLayout.ItemsSource>
        <BindableLayout.ItemTemplate>
            <DataTemplate>
                <VerticalStackLayout>
                    <Label Text="Hello"/>
                    <Label Text="{Binding .}"
                            TextType="Html"/>
                </VerticalStackLayout>
            </DataTemplate>
        </BindableLayout.ItemTemplate>
    </StackLayout>
</Grid>

I have a cell scaling issue

It gets automatically fixed after updating any of the Label's properties
@rmarinho do you maybe have an idea why it happens?

Copy link
Member

@rmarinho rmarinho left a comment

Choose a reason for hiding this comment

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

Nop, not from the top of my mind, can we rebase, see if it's still a issue and also add a test case for this ?

@kubaflo
Copy link
Contributor Author

kubaflo commented Dec 10, 2024

Nop, not from the top of my mind, can we rebase, see if it's still a issue and also add a test case for this ?

I've rebased, but the issue is still there without using MainQueue.DispatchAsync
However, I'm not sure if it is a correct fix, because it still regresses the height of HTML text in listView and cv

@rmarinho

This comment was marked as outdated.

This comment was marked as outdated.

rmarinho
rmarinho previously approved these changes Dec 13, 2024
Copy link
Member

@rmarinho rmarinho left a comment

Choose a reason for hiding this comment

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

Failing tests on iOS not related

@rmarinho rmarinho added this to the .NET 9 SR3 milestone Dec 13, 2024
#pragma warning disable CS8601
platformLabel.AttributedText = new NSAttributedString(text, attr, ref nsError);
#pragma warning restore CS8601
CoreFoundation.DispatchQueue.MainQueue.DispatchAsync(() =>
Copy link
Member

Choose a reason for hiding this comment

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

I might not be following your description quite right but should we check if we're on a background thread before dispatching?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

With something like this:

NSError nsError = new();
if (NSThread.IsMain)
{
	platformLabel.AttributedText = new NSAttributedString(text, attr, ref nsError);
	if (label.Handler is ILabelHandler labelHandler)
	{
		labelHandler.UpdateValue(nameof(ILabel.TextColor));
		labelHandler.UpdateValue(nameof(ILabel.Font));
	}
	label.InvalidateMeasure();
}
else
{
	CoreFoundation.DispatchQueue.MainQueue.DispatchAsync(() =>
	{
		platformLabel.AttributedText = new NSAttributedString(text, attr, ref nsError);
		if (label.Handler is ILabelHandler labelHandler)
		{
			labelHandler.UpdateValue(nameof(ILabel.TextColor));
			labelHandler.UpdateValue(nameof(ILabel.Font));
		}
		label.InvalidateMeasure();
	});
}

The list view/collection view with the HTML label looks right, but the app crashes when using the collection view
Screenshot 2024-12-14 at 00 59 02

Copy link
Member

Choose a reason for hiding this comment

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

Maybe use a local function to avoid code duplication.

Copy link
Member

Choose a reason for hiding this comment

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

Will the async cause issues because that would be unexpected? Will DispatchSync work/have issues/locking?

src/Core/src/Platform/iOS/LabelExtensions.cs Show resolved Hide resolved
@samhouts samhouts requested a review from Copilot December 13, 2024 21:41

Choose a reason for hiding this comment

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

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

@samhouts samhouts requested a review from Copilot December 13, 2024 21:50

Choose a reason for hiding this comment

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

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

Comments suppressed due to low confidence (1)

src/Core/src/Platform/iOS/LabelExtensions.cs:93

  • The NSError variable nsError is not checked after the NSAttributedString initialization. Add a check for nsError and handle any errors appropriately.
platformLabel.AttributedText = new NSAttributedString(text, attr, ref nsError);
@samhouts samhouts requested a review from Copilot December 13, 2024 21:51

Choose a reason for hiding this comment

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

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

Comments suppressed due to low confidence (1)

src/Core/src/Platform/iOS/LabelExtensions.cs:93

  • Check and handle nsError after assigning NSAttributedString to ensure any errors are properly managed.
platformLabel.AttributedText = new NSAttributedString(text, attr, ref nsError);
@kubaflo
Copy link
Contributor Author

kubaflo commented Dec 17, 2024

Item1 Item1 Item1
<StackLayout Grid.Row="1">
    <BindableLayout.ItemsSource>
        <x:Array Type="{x:Type x:String}">
            <x:String>Item1</x:String>
            <x:String>Item1</x:String>
            <x:String>Item1</x:String>
        </x:Array>
    </BindableLayout.ItemsSource>
    <BindableLayout.ItemTemplate>
        <DataTemplate>
            <VerticalStackLayout>
                <Label Text="Hello"/>
                <Label Text="{Binding .}"
                        TextType="Html"/>
            </VerticalStackLayout>
        </DataTemplate>
    </BindableLayout.ItemTemplate>
</StackLayout>

I tested and this is fixed with this PR: #25664 But, I believe @albyrock87 divided it into smaller ones

@jfversluis
Copy link
Member

jfversluis commented Dec 18, 2024

/azp run

This comment was marked as off-topic.

@PureWeen PureWeen modified the milestones: .NET 9 SR3, .NET 9 SR4 Jan 5, 2025
@jfversluis
Copy link
Member

/rebase

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

Copy link
Member

@mattleibow mattleibow left a comment

Choose a reason for hiding this comment

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

Added some comment, but I just want to check. How do we know this is on a background thread? All UIKit operations are on a main thread - in fact, all of our handlers are on a main thread.

Something has gone horribly wrong if a handler mapper is updating the UI from a background thread. Where did it switch and why?

I assume this only happens in CV, so this may be some weird case where the CV is moving to a background thread... to update the UI? Feels very sus.

#pragma warning disable CS8601
platformLabel.AttributedText = new NSAttributedString(text, attr, ref nsError);
#pragma warning restore CS8601
CoreFoundation.DispatchQueue.MainQueue.DispatchAsync(() =>
Copy link
Member

Choose a reason for hiding this comment

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

Maybe use a local function to avoid code duplication.

@@ -38,13 +38,6 @@ public static void MapMaxLines(ILabelHandler handler, Label label)

static void MapFormatting(ILabelHandler handler, Label label)
Copy link
Member

Choose a reason for hiding this comment

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

I feel like I wrote this code and overengineered this. It appears there is just 1 usage of the MapFormatting method, and it was already weird. Maybe this should just be merged with the MapText or UpdateText?

#pragma warning disable CS8601
platformLabel.AttributedText = new NSAttributedString(text, attr, ref nsError);
#pragma warning restore CS8601
CoreFoundation.DispatchQueue.MainQueue.DispatchAsync(() =>
Copy link
Member

Choose a reason for hiding this comment

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

Will the async cause issues because that would be unexpected? Will DispatchSync work/have issues/locking?

Comment on lines 94 to 98
if (label.Handler is ILabelHandler labelHandler)
{
labelHandler.UpdateValue(nameof(ILabel.TextColor));
labelHandler.UpdateValue(nameof(ILabel.Font));
}
Copy link
Member

Choose a reason for hiding this comment

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

Not sure if using handlers here is expected... Not sure if that is bad or good, but in my mind all the handler things happen in the handler or close to it. This is a few methods away now so may be surprising.

@jfversluis
Copy link
Member

@kubaflo some comments!

@kubaflo
Copy link
Contributor Author

kubaflo commented Jan 14, 2025

@mattleibow I've refactored a bit

@rmarinho
Copy link
Member

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@PureWeen PureWeen added the do-not-merge Don't merge this PR label Jan 14, 2025
@PureWeen PureWeen modified the milestones: .NET 9 SR4, .NET 9 SR5 Jan 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-controls-collectionview CollectionView, CarouselView, IndicatorView area-controls-label Label, Span community ✨ Community Contribution do-not-merge Don't merge this PR partner/syncfusion/review
Projects
Status: Changes Requested
Development

Successfully merging this pull request may close these issues.

iOS App crashes on iOS 18 when placing html label in carousel view with > 2 elements
5 participants