Skip to content

Fix SwipeView.Open() crash on second call (iOS/MacCatalyst)#34983

Closed
StephaneDelcroix wants to merge 1 commit intomainfrom
fix/swipeview-open-crash-34917
Closed

Fix SwipeView.Open() crash on second call (iOS/MacCatalyst)#34983
StephaneDelcroix wants to merge 1 commit intomainfrom
fix/swipeview-open-crash-34917

Conversation

@StephaneDelcroix
Copy link
Copy Markdown
Contributor

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Description

Fixes #34917

Calling SwipeView.Open() a second time on iOS/MacCatalyst crashes with System.ArgumentException: An item with the same key has already been added.

Root Cause

In MauiSwipeView.cs, the Swipe() method (line 584) sets _isOpen = offset != 0, which can transition _isOpen to false without calling DisposeSwipeItems(). This leaves stale entries in the _swipeItems dictionary. When Open() is called again:

  1. _isOpen is false, so the guard in ProgrammaticallyOpenSwipeItem is skipped
  2. UpdateSwipeItems() is called
  3. _swipeItems.Add(item, swipeItem) throws ArgumentException because the dictionary already contains keys from the previous open

Fix

Clear the _swipeItems dictionary and remove the old _actionView from the superview at the start of UpdateSwipeItems(), before creating new swipe item views. This makes the method idempotent — safe to call regardless of prior state.

Testing

Added UI test (Issue34917) that opens a SwipeView, closes it, then opens it again — verifying no crash occurs on the second open.

Clear stale _swipeItems dictionary entries and remove old _actionView in
UpdateSwipeItems() before creating new ones. The Swipe() method could
set _isOpen = false without calling DisposeSwipeItems(), leaving stale
entries in _swipeItems. The next Open() call would then attempt to add
duplicate keys, causing an ArgumentException.

Fixes #34917

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 15, 2026 14:51
@github-actions
Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34983

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34983"

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes an iOS/MacCatalyst crash when calling SwipeView.Open() multiple times by making the iOS MauiSwipeView.UpdateSwipeItems() path safe to re-enter, and adds a regression UI test for the scenario.

Changes:

  • Clear stale swipe-item state in MauiSwipeView.UpdateSwipeItems() before rebuilding native swipe-item views.
  • Add HostApp repro page Issue34917 with buttons to open/close a SwipeView programmatically.
  • Add an Appium/NUnit UI test Issue34917 that opens, closes, then opens again to validate no crash.

Reviewed changes

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

File Description
src/Core/src/Platform/iOS/MauiSwipeView.cs Clears prior swipe-item cache/view before rebuilding to avoid duplicate-key crashes on repeated Open().
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34917.cs Adds UI test validating repeated programmatic open does not crash.
src/Controls/tests/TestCases.HostApp/Issues/Issue34917.cs Adds HostApp issue page used by the UI test to reproduce the scenario.

Comment on lines +23 to +26
App.WaitForElement("StatusLabel");
var status1 = App.FindElement("StatusLabel").GetText();
Assert.That(status1, Is.EqualTo("Opened 1"), "First Open should succeed");

Comment on lines +28 to +36
App.Tap("CloseButton");

// Wait for close animation
Task.Delay(500).Wait();

// Second open - this used to crash with ArgumentException
App.Tap("OpenButton");
var status2 = App.FindElement("StatusLabel").GetText();
Assert.That(status2, Is.EqualTo("Opened 2"), "Second Open should succeed without crash");
@@ -0,0 +1,87 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 34917, "SwipeView.Open crashes with ArgumentException on second call", PlatformAffected.iOS)]
@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Apr 15, 2026

/azp run maui-pr-uitests , maui-pr-devicetests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

PureWeen added a commit that referenced this pull request Apr 15, 2026
…, security hardening (#34678)

## Description

Overhauls the `copilot-evaluate-tests` gh-aw workflow — switches to
on-demand triggers only (`/evaluate-tests` slash command + manual
`workflow_dispatch`), adds security hardening, and improves error
handling. No auto-runs on PR create/update.

### What Changed

**Triggers (on-demand only — no auto-runs)**
- Add `slash_command: evaluate-tests` — comment `/evaluate-tests` on a
PR to trigger
- Keep `workflow_dispatch` — manual trigger from Actions tab with PR
number input
- Disable `pull_request_target` — no auto-evaluation on PR create/update
- Add `bots: ["copilot-swe-agent[bot]"]` — Copilot-authored PRs can be
evaluated
- Add `labels: ["pr-review", "testing"]` — workflow runs are labeled

**Gate step (fast-fail for invalid requests)**
- Check PR is OPEN before evaluating (rejects closed/merged PRs with
clear message)
- Check for test source files in diff before spinning up agent
- Fall back to REST API for PRs with 300+ files (where `gh pr diff`
returns HTTP 406)
- All API errors surfaced with clear messages — no silent masking
- `exit 1` stops the workflow immediately — no wasted agent compute

**Access gating (`Checkout-GhAwPr.ps1`)**
- Reject fork PRs (`isCrossRepository` check)
- Verify PR author has write access (admin/write/maintain roles)
- Fix `ConvertFrom-Json` ordering — check exit code before JSON parsing
- Make infrastructure restore fatal on failure (was soft warning)
- Remove pre-delete pattern — `git checkout` overwrites in-place

**Workflow improvements**
- `hide-older-comments: true` — previous evaluations auto-collapse
- `report-as-issue: false` for noop — no issue created when nothing to
evaluate
- Timeout bumped 15 → 20 minutes
- Dry-run mode via `suppress_output` input (workflow_dispatch only)
- `Gather-TestContext.ps1` now receives `-PrNumber` parameter

**Security documentation (`gh-aw-workflows.instructions.md`)**
- Add "Before You Build" anti-patterns table — prefer built-in gh-aw
features
- Add Security Boundaries section with defense layers table
- Add Rules for gh-aw Workflow Authors (DO/DON'T list)
- Document `COPILOT_TOKEN` exposure and mitigations
- Add `slash_command` to fork behavior table
- Update `Checkout-GhAwPr.ps1` description to match current behavior

### Trigger Behavior

| Trigger | When it fires | Who can trigger |
|---------|---------------|-----------------|
| `/evaluate-tests` comment | Comment on a PR | Write-access
collaborators + copilot-swe-agent[bot] |
| `workflow_dispatch` | Actions tab → "Run workflow" → enter PR number |
Write-access collaborators |
| ~~`pull_request_target`~~ | ~~Auto on PR create/update~~ |
~~Disabled~~ |

### Security Model

Based on [GitHub Security Lab
guidance](https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/):

- PR contents treated as **passive data** (read/analyze, never built or
executed)
- Agent runs in **sandboxed container** with `GITHUB_TOKEN` and `gh` CLI
scrubbed
- Write operations in **separate `safe_outputs` job** (not the agent)
- Agent output limited to `max: 1` comment via safe-outputs
- `Checkout-GhAwPr.ps1` rejects fork PRs and verifies write access
before checkout
- Infrastructure restore is fatal on failure — prevents running with
untrusted infra

### Validation

| Test | PR | Result |
|------|-----|--------|
| Open PR with tests | #34983 | ✅ Full success (gate → checkout → agent
→ comment) |
| No-test PR | #34876 | ✅ Gate fast-fail ("no test source files") |
| Merged PR | #34932 | ✅ Gate fast-fail ("MERGED — skipping") |

### Known Limitations

- Fork PRs via `/evaluate-tests` can supply modified `.github/skills/` —
accepted residual risk (agent sandboxed, output bounded). Tracked as
[gh-aw#18481](github/gh-aw#18481)
- `exit 1` in gate step shows ❌ in GitHub checks for no-test/closed PRs
— intentional (no built-in "skip" mechanism in gh-aw steps)
- `pull_request_target` commented out — can be re-enabled later for
auto-evaluation

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Apr 15, 2026

🚦 Gate — Test Before and After Fix

👋 @StephaneDelcroix — new gate results are available. Please review the latest session below.

🚦 Gate Session1256944 · Fix SwipeView.Open() crash on second call (iOS/MacCatalyst) · 2026-04-15 19:30 UTC

Gate Result: ❌ FAILED

Platform: IOS · Base: main · Merge base: eb0b82fe

Test Without Fix (expect FAIL) With Fix (expect PASS)
🖥️ Issue34917 Issue34917 ❌ PASS — 217s ✅ PASS — 91s
🔴 Without fix — 🖥️ Issue34917: PASS ❌ · 217s
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 565 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 601 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 10.37 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 10.65 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 10.65 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 10.65 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 10.66 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 10.69 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 10.68 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/maps/src/Maps.csproj (in 10.72 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 10.72 sec).
/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-ios26.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-ios26.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-ios26.0/Microsoft.Maui.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Maps.dll
  Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Xaml.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Controls.Foldable -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Foldable.dll
  Controls.Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Maps.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-ios26.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-ios/iossimulator-arm64/Controls.TestCases.HostApp.dll
  Optimizing assemblies for size may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
  Optimizing assemblies for size. This process might take a while.

Build succeeded.

/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
    1 Warning(s)
    0 Error(s)

Time Elapsed 00:01:45.17
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 791 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 791 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 791 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 791 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 1 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 814 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 882 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 900 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 1.63 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Appium/UITest.Appium.csproj (in 1.91 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 2.54 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 3.77 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.iOS.Tests/Controls.TestCases.iOS.Tests.csproj (in 4.61 sec).
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Controls.CustomAttributes -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
  UITest.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
  VisualTestUtils -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
  UITest.NUnit -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
  VisualTestUtils.MagickNet -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
  UITest.Appium -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
  UITest.Analyzers -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
  Controls.TestCases.iOS.Tests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
Test run for /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (arm64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
/Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.05]   Discovering: Controls.TestCases.iOS.Tests
[xUnit.net 00:00:00.14]   Discovered:  Controls.TestCases.iOS.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 4/15/2026 12:28:53 PM FixtureSetup for Issue34917(iOS)
>>>>> 4/15/2026 12:28:56 PM SwipeViewOpenDoesNotCrashOnSecondCall Start
>>>>> 4/15/2026 12:28:59 PM SwipeViewOpenDoesNotCrashOnSecondCall Stop
  Passed SwipeViewOpenDoesNotCrashOnSecondCall [2 s]
NUnit Adapter 4.5.0.0: Test execution complete

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 1.0444 Minutes

🟢 With fix — 🖥️ Issue34917: PASS ✅ · 91s
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 403 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 418 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 424 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 456 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 472 ms).
  6 of 11 projects are up-to-date for restore.
/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-ios26.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-ios26.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-ios26.0/Microsoft.Maui.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Maps.dll
  Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Controls.Foldable -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Foldable.dll
  Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Xaml.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-ios26.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Controls.Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Maps.dll
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-ios/iossimulator-arm64/Controls.TestCases.HostApp.dll
  Optimizing assemblies for size may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
  Optimizing assemblies for size. This process might take a while.

Build succeeded.

/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
    1 Warning(s)
    0 Error(s)

Time Elapsed 00:00:44.46
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 396 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 394 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 398 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 413 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 427 ms).
  8 of 13 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Controls.CustomAttributes -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.70-ci+azdo.13847096
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
  UITest.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
  VisualTestUtils -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
  UITest.Appium -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
  VisualTestUtils.MagickNet -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
  UITest.NUnit -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
  UITest.Analyzers -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
  Controls.TestCases.iOS.Tests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
Test run for /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (arm64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
/Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.05]   Discovering: Controls.TestCases.iOS.Tests
[xUnit.net 00:00:00.18]   Discovered:  Controls.TestCases.iOS.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 4/15/2026 12:30:23 PM FixtureSetup for Issue34917(iOS)
>>>>> 4/15/2026 12:30:27 PM SwipeViewOpenDoesNotCrashOnSecondCall Start
>>>>> 4/15/2026 12:30:30 PM SwipeViewOpenDoesNotCrashOnSecondCall Stop
  Passed SwipeViewOpenDoesNotCrashOnSecondCall [2 s]
NUnit Adapter 4.5.0.0: Test execution complete

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 19.9009 Seconds

⚠️ Issues found
  • Issue34917 PASSED without fix (should fail) — tests don't catch the bug
📁 Fix files reverted (1 files)
  • src/Core/src/Platform/iOS/MauiSwipeView.cs

@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Apr 15, 2026

🤖 AI Summary

👋 @StephaneDelcroix — new AI review results are available. Please review the latest session below.

📊 Review Session1256944 · Fix SwipeView.Open() crash on second call (iOS/MacCatalyst) · 2026-04-15 20:54 UTC
🔍 Pre-Flight — Context & Validation

Issue: #34917 - [net 11.0][iOS,MacCatalyst] SwipeView.Open() throws ArgumentException on second programmatic call
PR: #34983 - Fix SwipeView.Open() crash on second call (iOS/MacCatalyst)
Platforms Affected: iOS, MacCatalyst
Files Changed: 1 implementation, 2 test

Key Findings

  • Bug: SwipeView.Open() crashes with ArgumentException: An item with the same key has already been added on the second call on iOS/MacCatalyst. Android is unaffected.
  • Root Cause: Swipe() (line 589) sets _isOpen = offset != 0, transitioning it to false without calling DisposeSwipeItems(). This leaves stale entries in _swipeItems. On a second Open() call, UpdateSwipeItems() tries to _swipeItems.Add(item, swipeItem) and throws because the key already exists.
  • PR Fix: Adds _swipeItems.Clear() and _actionView?.RemoveFromSuperview() at the start of UpdateSwipeItems(), making it idempotent.
  • Gate FAILED: The test PASSED without the fix (expected: FAIL). The Task.Delay(500).Wait() in the test gives sufficient time for the close animation callback to complete (DisposeSwipeItems() is called after animation), which clears the dictionary before the second Open(). This masks the bug entirely.
  • Test issue (inline review): App.WaitForElement("StatusLabel") races UI updates; Task.Delay(500).Wait() causes flakiness and thread blocking; PlatformAffected.iOS misses MacCatalyst.
  • Three unresolved inline review comments from copilot-pull-request-reviewer:
    1. Race condition on WaitForElement("StatusLabel") — should wait for text
    2. Task.Delay(500).Wait() is timing-based flakiness
    3. [Issue] attribute says PlatformAffected.iOS but issue also affects MacCatalyst

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #34983 _swipeItems.Clear() + _actionView?.RemoveFromSuperview() at start of UpdateSwipeItems() ✅ PASSED (with fix) / ❌ PASS without fix (Gate FAILED) MauiSwipeView.cs Defensive/idempotent approach

🔧 Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix (claude-opus-4.6) in + DispatchDelayed(50ms) test PASS MauiSwipeView.cs, Issue34917.cs (x2) Targeted fix at crash site; missing _actionView cleanup
2 try-fix (claude-sonnet-4.6) Root cause in : + DispatchDelayed(50ms) test PASS MauiSwipeView.cs, Issue34917.cs (x2) Prevents stale state creation; only clears dict, not _actionView
3 try-fix (gpt-5.3-codex) Replace with indexer + before rebuild PASS MauiSwipeView.cs, Issue34917.cs (x2) Avoids exception at call site; handles both dict and view leak
closed only (`wasOpen && !_ FAIL MauiSwipeView.cs, Issue34917.cs (x2) Missed back-to-back case: stale state from first Open() before animation fires isOpen`)
5 try-fix (claude-opus-4.6) only sets , never false; owns false transition PASS MauiSwipeView.cs, Issue34917.cs (x2) Elegant ownership semantics; may affect gesture timing window
6 try-fix (gpt-5.3-codex) Reentrancy guard + transactional swap in PASS MauiSwipeView.cs, Issue34917.cs (x2) Most complex; adds new field; overkill for this bug
PR PR #34983 + at start of With Gate FAILED MauiSwipeView.cs Defensive/idempotent; correct production fix; test is the problem

Cross-Pollination

Model Round New Ideas? Details
ran as Attempt 5
claude-sonnet-4.6 2 No NO NEW IDEAS
ran as Attempt 6
gpt-5.4 2 No NO NEW IDEAS
claude-opus-4.6 3 No NO NEW solution space exhausted

Exhausted: Yes
Selected Fix: PR's production fix is CORRECT + test needs to be rewritten. The PR's _swipeItems.Clear() + _actionView?.RemoveFromSuperview() at start of UpdateSwipeItems() is the cleanest: idempotent, handles both concerns, minimal change. Gate FAILED only because Task.Delay(500).Wait() masks the bug. Test must use DispatchDelayed(50ms) + WaitForElement-based assertions.


📋 Report — Final Recommendation

⚠️ Final Recommendation: REQUEST CHANGES

Phase Status

Phase Status Notes
Pre-Flight ✅ COMPLETE Issue #34917, iOS/MacCatalyst, 1 impl + 2 test files
Gate ❌ FAILED iOS — test PASSED without fix (bug masked by Task.Delay)
Try-Fix ✅ COMPLETE 6 attempts (5 passing, 1 failing); exhausted after 3 cross-pollination rounds
Report ✅ COMPLETE

Summary

PR #34983 contains a correct production fix for SwipeView.Open() crashing on second call with ArgumentException on iOS/MacCatalyst. However, the Gate FAILED because the test passes even without the fix — Task.Delay(500).Wait() allows the close animation to complete and call DisposeSwipeItems(), which clears the dictionary before the second Open(), masking the bug entirely. Three unresolved inline review comments also remain unaddressed.

Selected Fix: PR's production fix is correct. The test needs to be fixed to reliably demonstrate the bug.

Root Cause

In MauiSwipeView.cs, the Swipe() method sets _isOpen = offset != 0. During the close animation, when the content reaches offset=0, _isOpen becomes false but DisposeSwipeItems() is only called in the animation completion callback (~200ms later). During this window, if Open() is called again:

  • _isOpen == false → guard in ProgrammaticallyOpenSwipeItem is skipped
  • UpdateSwipeItems() is called
  • _swipeItems.Add(item, swipeItem) throws ArgumentException because dictionary already contains keys from the first open

Fix Quality

Production fix (MauiSwipeView.cs): ✅ CORRECT and COMPLETE

  • _swipeItems.Clear() + _actionView?.RemoveFromSuperview() at the start of UpdateSwipeItems() makes the method idempotent
  • Handles both the dictionary duplicate-key issue AND the stale _actionView view hierarchy concern
  • Minimal, defensive, and consistent with existing patterns in the codebase
  • 5 out of 6 independent try-fix attempts also passed with this general approach

Test quality: ❌ REQUIRES CHANGES — 3 issues:

  1. Task.Delay(500).Wait() masks the bug (line 36 of test): The 500ms delay allows the close animation's completion callback to run DisposeSwipeItems(), clearing the stale state before the second Open(). This means the test passes even without the fix. Replace with Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(50), () => swipeView.Open(...)) in the HostApp and use App.WaitForElement(e => e.GetText() == "Opened 2") in the test.

  2. App.WaitForElement("StatusLabel") races UI update (line 26): StatusLabel is always present; this does not wait for the text to update. Replace with a wait for the specific text value (e.g., App.WaitForTextToBePresentInElement("StatusLabel", "Opened 1")).

  3. PlatformAffected.iOS misses MacCatalyst (HostApp line 3): The issue affects both iOS and MacCatalyst per the bug report ([net 11.0][iOS,MacCatalyst] SwipeView.Open() throws ArgumentException on second programmatic call #34917) and PR title. Should be PlatformAffected.iOS | PlatformAffected.macOS or equivalent.

Required Changes

// HostApp: Issue34917.cs
// Replace the Clicked handler — trigger re-open mid-animation (50ms after close starts)
closeButton.Clicked += (s, e) =>
{
    swipeView.Close();
    statusLabel.Text = "Closed";
-   // No delay needed in HostApp — test should drive timing
+   Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(50), () =>
+   {
+       try
+       {
+           openCount++;
+           swipeView.Open(OpenSwipeItem.LeftItems);
+           statusLabel.Text = $"Opened {openCount}";
+       }
+       catch (Exception ex)
+       {
+           statusLabel.Text = $"Error: {ex.GetType().Name}";
+       }
+   });
};

// Test: Issue34917.cs  
- App.WaitForElement("StatusLabel");
- var status1 = App.FindElement("StatusLabel").GetText();
+ var status1 = App.WaitForTextToBePresentInElement("StatusLabel", "Opened 1");

- Task.Delay(500).Wait();
- App.Tap("OpenButton");
- var status2 = App.FindElement("StatusLabel").GetText();
+ App.Tap("CloseButton");
+ // Wait for re-open triggered by DispatchDelayed in HostApp
+ App.WaitForTextToBePresentInElement("StatusLabel", "Opened 2", TimeSpan.FromSeconds(5));

// HostApp: Issue34917.cs — line 3
- [Issue(IssueTracker.Github, 34917, "...", PlatformAffected.iOS)]
+ [Issue(IssueTracker.Github, 34917, "...", PlatformAffected.iOS | PlatformAffected.macOS)]

Try-Fix Winner Comparison

Approach Simplicity Completeness Risk
PR fix (UpdateSwipeItems idempotent) ✅ 2 lines ✅ Handles dict + view ✅ Low
Attempt 3 (indexer + remove) ✅ Similar ✅ Handles dict + view ✅ Low
Attempt 5 (_isOpen ownership) ✅ 1 line ⚠️ May affect gesture timing ⚠️ Medium
Attempt 6 (reentrancy guard) ❌ Complex ✅ Most defensive ⚠️ Overengineered

PR's fix is the recommended production approach. The test is the blocker.


@BagavathiPerumal
Copy link
Copy Markdown
Contributor

@StephaneDelcroix, I have already fixed this issue. Please find the below-mentioned PR for your reference.

PR link: #34982

I also reviewed the changes in this PR. It clears _swipeItems and resolves the crash. However, it adds _actionView?.RemoveFromSuperview() in UpdateSwipeItems(), which removes the swipe items view from the hierarchy before recreating it.

This introduces a regression where the SwipeView closes immediately after being opened programmatically. Please refer to the video below.

Android behavior iOS behavior after applying your fix
AndroidBehavior-SwipeView.mov
StephaneFix-iOSSwipeView.mov

@kubaflo
Copy link
Copy Markdown
Contributor

kubaflo commented Apr 16, 2026

Closing in favour of #34982

@kubaflo kubaflo closed this Apr 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[net 11.0][iOS,MacCatalyst] SwipeView.Open() throws ArgumentException on second programmatic call

5 participants