Skip to content

[automated] Merge branch 'main' => 'net11.0'#35001

Closed
github-actions[bot] wants to merge 220 commits intonet11.0from
merge/main-to-net11.0
Closed

[automated] Merge branch 'main' => 'net11.0'#35001
github-actions[bot] wants to merge 220 commits intonet11.0from
merge/main-to-net11.0

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

I detected changes in the main branch which have not been merged yet to net11.0. I'm a robot and am configured to help you automatically keep net11.0 up to date, so I've opened this PR.

This PR merges commits made on main by the following committers:

  • kubaflo
  • PureWeen
  • mattleibow
  • Copilot
  • CathyZhu0110
  • Vignesh-SF3580
  • StephaneDelcroix
  • Oxymoron290
  • mmitche
  • akoeplinger
  • sheiksyedm

Instructions for merging from UI

This PR will not be auto-merged. When pull request checks pass, complete this PR by creating a merge commit, not a squash or rebase commit.

merge button instructions

If this repo does not allow creating merge commits from the GitHub UI, use command line instructions.

Instructions for merging via command line

Run these commands to merge this pull request from the command line.

git fetch
git checkout main
git pull --ff-only
git checkout net11.0
git pull --ff-only
git merge --no-ff main

# If there are merge conflicts, resolve them and then run git merge --continue to complete the merge
# Pushing the changes to the PR branch will re-trigger PR validation.
git push https://github.com/dotnet/maui HEAD:merge/main-to-net11.0
or if you are using SSH
git push git@github.com:dotnet/maui HEAD:merge/main-to-net11.0

After PR checks are complete push the branch

git push

Instructions for resolving conflicts

⚠️ If there are merge conflicts, you will need to resolve them manually before merging. You can do this using GitHub or using the command line.

Instructions for updating this pull request

Contributors to this repo have permission update this pull request by pushing to the branch 'merge/main-to-net11.0'. This can be done to resolve conflicts or make other changes to this pull request before it is merged.
The provided examples assume that the remote is named 'origin'. If you have a different remote name, please replace 'origin' with the name of your remote.

git fetch
git checkout -b merge/main-to-net11.0 origin/net11.0
git pull https://github.com/dotnet/maui merge/main-to-net11.0
(make changes)
git commit -m "Updated PR with my changes"
git push https://github.com/dotnet/maui HEAD:merge/main-to-net11.0
or if you are using SSH
git fetch
git checkout -b merge/main-to-net11.0 origin/net11.0
git pull git@github.com:dotnet/maui merge/main-to-net11.0
(make changes)
git commit -m "Updated PR with my changes"
git push git@github.com:dotnet/maui HEAD:merge/main-to-net11.0

Contact .NET Core Engineering (dotnet/dnceng) if you have questions or issues.
Also, if this PR was generated incorrectly, help us fix it. See https://github.com/dotnet/arcade/blob/main/.github/workflows/scripts/inter-branch-merge.ps1.

mmitche and others added 30 commits March 18, 2026 08:58
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…#34265)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

## Summary

Adds a standalone `code-review` skill for reviewing PR code changes with
MAUI-specific domain knowledge, modeled after [dotnet/android's
android-reviewer
skill](https://github.com/dotnet/android/tree/main/.github/skills/android-reviewer).

### What's included

**`.github/skills/code-review/SKILL.md`** (196 lines) — Review workflow:
- Independence-first methodology (read code before PR description to
avoid anchoring bias)
- 6-step workflow: gather context → load rules → independent assessment
→ reconcile with PR narrative → check CI → devil's advocate verdict
- Three verdicts: `LGTM`, `NEEDS_CHANGES`, `NEEDS_DISCUSSION`
- Optional multi-model review for diverse perspectives
- Output constraints: don't pile on, don't flag what CI catches

**`.github/skills/code-review/references/review-rules.md`** (345 lines)
— Domain knowledge:
- **22 sections** of review rules distilled from real FTE code reviews
- **138 PR citations** linking each rule to the PR where the pattern was
identified
- Sourced from **366 PRs**: top 142 most-discussed PRs (2,883 review
comments), 30 reverted PRs, 50 candidate-branch failures, 64 regression
fixes
- Covers: handler lifecycle, memory leaks, safe area, layout, platform
code, Android/iOS/Windows specifics, navigation, CollectionView,
threading, XAML, testing, performance, error handling, API design,
images, gestures, build, accessibility
- §21 Regression Prevention — lessons from reverts and candidate
failures
- §22 Component Risk Table — ranking the most regression-prone
components

### Design decisions

- **Separation of concerns**: SKILL.md = workflow, review-rules.md =
knowledge (follows Android's pattern)
- **Standalone**: Can be invoked directly by users or by other agents.
No coupling to the PR agent pipeline.
- **Data-driven**: Every rule traces to a real PR where a senior
maintainer flagged the pattern. No generic advice.

### Changes to copilot-instructions.md

- Added "Opening PRs" section with required NOTE block template
- Added code-review skill to the skills listing
- Minor reformatting of existing documentation sections

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jakub Florkowski <kubaflo123@gmail.com>
… a footer template is present or crashes when removing data. (#24867)

### Root Cause

- **Add** - When a CollectionView contains grouping and a footer
template, we create a new List<object> and add the footer to the list if
the footer's item template type is not an IList. This prevents events
such as CollectionChanged or INotifyCollectionChanged from being
triggered on the original collection. As a result, when a new item is
added to the collection, it is not reflected in the view.

- **Clear** - When clearing the collection and adding items at runtime,
a COM exception occurs when items are grouped. This happens because the
OnItemsVectorChanged event is triggered whenever there is a change in
the item collection, causing ListViewBase.ScrollIntoView to be called
from a background thread, which leads to the COM exception.
 
### Description of Change
 
#### Windows 

- **Add** - Reverting the changes made in PR #24205 caused the exception
. This exception occurred due to the triggering of the
TemplateCollectionChanged event, which was introduced during the
implementation of drag-and-drop functionality for the CollectionView in
PR #3768. Adding the footer to the items triggers the
TemplateCollectionChanged event, which incorrectly adds the footer as a
new item in the collection. Since the footer template is not the same
type as the items in the original collection, this results in the
exception. To resolve this, we can ignore the addition of the footer
template to the ItemsSource.
 
- **Clear** - Calling ListViewBase.ScrollIntoView in the DispatcherQueue
helps avoid exceptions and ensures synchronized updates.
Reference -
https://github.com/dotnet/maui/blob/5ceff42f4713a18516a0bffa209b0ed272e915b7/src/Controls/src/Core/Compatibility/Handlers/ListView/Windows/ListViewRenderer.cs#L628
 
Validated the behaviour in the following platforms
 
- [x] Android
- [x] Windows
- [ ] iOS
- [ ] Mac
 
iOS and Mac has issues - #17969 
 
### Issues Fixed
  
Fixes #24866 
Fixes #27302
Fixes #18481
Fixes #21791 

### Output 

#### Windows
|**Before**|**After**|
|--|--|
|<video
src="https://github.com/user-attachments/assets/2813d618-2c9d-4700-af20-8402b0779597">
| <video
src="https://github.com/user-attachments/assets/bb96519e-5a70-47a8-a872-d61ac2cf8f4f">
|
… value on iOS16+ - fix (#30733)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issues Fixed

Fixes #18669

|Before|After|
|--|--|
|<video
src="https://github.com/user-attachments/assets/1c43b0dc-a432-4107-81b5-d418e0a70a7b"
width="300px"></video>|<video
src="https://github.com/user-attachments/assets/fff7b26c-e703-4761-baa1-bf3e60f01005"
width="300px"></video>|
… fix (#29339)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issues Fixed

Fixes #20596

|Before|After|
|--|--|
|<video
src="https://github.com/user-attachments/assets/dd0ce289-bbff-453c-81ff-5306c891b14e"
width="300px"/>|<video
src="https://github.com/user-attachments/assets/8b07e943-4d38-4575-a216-e1252d12785c"
width="300px"/>|
…ft Text Selection on Editor and Entry (#30906)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
When selecting text from right to left, the SelectionLength property in
Entry and Editor is incorrectly reported as 0 on Android.
 
### Root Cause
On Android, when selecting text from right to left,
GetSelectedTextLength returned 0 due to incorrect handling of reverse
selection. In GetSelectionEnd, the selection length was also calculated
as 0, resulting in the selection length remaining zero even though text
was selected.
 
### Description of Change
 
Updated GetSelectedTextLength() to return the absolute difference
between SelectionStart and SelectionEnd using Math.Abs. Modified
GetSelectionEnd() to handle right-to-left selection by adjusting the end
position and correctly updating SelectionLength. This ensures consistent
behavior across selection directions and platforms.
 
### Validated the behaviour in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed:
Fixes #30782 
 
### Screenshots
| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/9e6aff76-aa74-41f5-9047-e8449262b313">
|   <video
src="https://github.com/user-attachments/assets/e744b5fd-0b98-40d1-8875-e36e64021ccc"> 
|
…PopToRootAsync (#29779)

### Issue Detail
When the user inserts a page before the root and then pops the current
page using PopAsync (or pops to root using PopToRootAsync), the flyout
hamburger icon is not visible on iOS after the navigation completes.

### Root Cause
UpdateLeftBarButtonItem (which sets the flyout hamburger icon on the
ParentingViewController) was only called in the RemovePage method. The
programmatic pop paths — OnPopViewAsync, OnPopToRoot, and the
native-swipe-back path via UpdateFormsInnerNavigation — did not call it.
After a pop that results in a new root page, the flyout button was never
refreshed.

### Description of Change
Extracted the flyout-button-refresh logic from RemovePage into a new
private helper:

```
void UpdateFlyoutMenuButton(Page pageBeingRemoved = null)
{
    var parentingViewController = GetParentingViewController();
    parentingViewController?.UpdateLeftBarButtonItem(pageBeingRemoved);
}
```
Then called UpdateFlyoutMenuButton() at the end of:

- OnPopViewAsync — programmatic PopAsync()
- OnPopToRoot — programmatic PopToRootAsync()
- UpdateFormsInnerNavigation — native back-button (swipe) pop

RemovePage was refactored to use the same helper (no behavior change).

**File changed:**
src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs

**Reference:**
https://github.com/dotnet/maui/blob/main/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs#L693

### Tested the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

### Issues Fixed
 
Fixes #21828

### Screenshots

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/7afc9d80-3d98-4f73-a124-dcc06c5e6d1c">
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/7d59f508-9760-44bb-9889-4bd4fcca9353">)
|
…ource loads with delay (#34424)

> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Root Cause

The bug is a **circular sizing dependency** specific to iOS horizontal
`CollectionView` with delayed data loading:

1. **UIKit assigns a large initial frame** (~812–1013px) to the
`CollectionView` before items load.
2. **`LayoutFactory2.CreateHorizontalList`** configures item and group
heights as `NSCollectionLayoutDimension.CreateFractionalHeight(1f)` —
items always fill 100% of the container height. This is intentional for
horizontal scroll UX.
3. Before items are realized, `CollectionViewContentSize.Width == 0` but
**`CollectionViewContentSize.Height` equals the current (large) frame
height** — a side effect of `FractionalHeight(1f)`.
4. In **`EnsureContentSizeForScrollDirection`** (CV2 /
`ItemsViewHandler2.iOS.cs`), the `Width == 0` branch correctly fetches a
desired width from `base.GetDesiredSize()` — but previously did NOT
reset `Height`, letting the large frame-derived height pass through.
5. MAUI reports this large height as the desired size → sets the frame
to that height → when items finally load, `FractionalHeight(1f)` × large
frame = items are large → height is never corrected. **The lock is
permanent.**

The same bug exists in the legacy CV1 handler (`ItemsViewController.cs`)
where `GetSize()` returned the raw `CollectionViewContentSize` without
resetting height for an empty horizontal layout.

### Description of Change
In both the CV2 (`Items2/`) and CV1 (`Items/`) iOS handler pipelines,
when a **horizontal CollectionView has `Width == 0`** (meaning no items
have been realized yet), also reset `Height = 0`. This breaks the
circular dependency by letting `MinimumHeightRequest` / `HeightRequest`
govern the initial size rather than an arbitrary frame-derived value.

**The condition `Width == 0`** is a reliable proxy for "no items
realized" because:
- A horizontal CV with any visible items will always have non-zero
content width.
- `Width == 0` uniquely occurs in the pre-items-loaded state that causes
the bug.

### Issues Fixed

Fixes #34336

### Technical Details

- **CV2 (`ItemsViewHandler2.iOS.cs`)** — When a horizontal
`CollectionView` has no items loaded yet (`Width == 0`),
`contentSize.Height` was incorrectly carrying the large UIKit-assigned
frame height. The fix also resets `Height` to zero in that case, so
`MinimumHeightRequest` / `HeightRequest` governs the initial size.

- **CV1 (`ItemsViewController.cs`)** — Same issue in the legacy handler.
A guard was added: if the layout is horizontal and `Width == 0`,
`Height` is reset to zero before the size is returned.

### Files Changed

| File | Change | Purpose |
|------|--------|---------|
| `src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs` | +8
lines | Fix CV2 (new iOS handler) circular height lock |
| `src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs` |
+15/-1 lines | Fix CV1 (legacy iOS handler) circular height lock |
| `src/Controls/tests/TestCases.HostApp/Issues/Issue34336.cs` | New file
(104 lines) | HostApp repro page: horizontal CV with 1s delayed
`ObservableCollection` load |
| `src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34336.cs`
| New file (31 lines) | NUnit UI test using `VerifyScreenshot(tolerance:
0.5)` to verify correct layout after delayed load |

### Screenshots

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/ce13c0b9-cd35-40fe-a341-bb3c634cd441">
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/49f720d6-b8ca-4520-bbe7-d579a9b43d18">)
|
<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change
This pull request introduces a new Material 3-based `SearchBarHandler2`
for Android, and conditionally uses it when Material 3 is enabled. The
new handler and its supporting platform classes provide improved
integration with Material 3 design, including custom appearance, clear
button, and color handling. Several internal extension methods and
mappings are updated to support this new handler, while maintaining
compatibility with the existing handler for non-Material 3 scenarios.

**Material 3 SearchBar support for Android:**

* Added a new internal `SearchBarHandler2` class for Material 3, with a
complete set of property and command mappers, event handlers, and
platform view integration. This handler is used only when Material 3 is
enabled.
* **SearchView limitation:** Android’s SearchView is designed to open as
an expanded overlay when tapped, covering other content. It's meant to
work in combination with a collapsed search bar trigger.
* The overlay’s height and behavior cannot be customized to behave like
a persistent inline search field. This limitation is inherent to the
SearchView design.
* **Solution**: So Introduced `MauiMaterialTextInputLayout` and
`MauiMaterialTextInputEditText` platform classes to provide Material 3
styling, clear button, and selection change support for the new
SearchBar handler.
* Updated `AppHostBuilderExtensions.cs` to register `SearchBarHandler2`
conditionally for Android when Material 3 is enabled, otherwise falling
back to the original handler.

**Mapping and platform extension updates:**

* Modified SearchBar mapping logic in `SearchBar.Mapper.cs` to use the
correct handler and mapping methods based on whether Material 3 is
enabled on Android.
* Added new extension methods in `SearchViewExtensions.cs` for Material
3 SearchBar support, including updating text, background, icon colors,
and return type for the new handler.
* Added a Material 3-specific overload for `MapText` in
`SearchBar.Android.cs` for use with `SearchBarHandler2`.

**Internal API and behavior improvements:**

* Made `UpdateIsTextPredictionEnabled` and `UpdateIsSpellCheckEnabled`
extension methods internal to allow usage by the new handler.
* Improved `UpdateCursorSelection` in `EditTextExtensions.cs` to post
selection changes when the EditText is focused, preventing potential
issues with selection updates.

### Issues Fixed

Fixes #33947 

### Screenshot
| Material 2  | Material 3 |
|---------|--------|
| <img height=600 width=300
src="https://github.com/user-attachments/assets/75269b67-1b8c-4e31-a880-c86d4102c76c">
|  <img height=600 width=300
src="https://github.com/user-attachments/assets/ba76f61d-587f-4421-b782-24bfb7a74155"> 
|

---------

Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
The sizing of the RefreshView no longer works when the content is
wrapped inside a RefreshView. As a result, the RefreshView height is
calculated as 0.

### Root Cause
MauiSwipeRefreshLayout.OnMeasure called CrossPlatformMeasure() but
discarded the return value, never calling SetMeasuredDimension with the
correct size. Android kept height=0 from base.OnMeasure().

### Description of Change
Updated MauiSwipeRefreshLayout.OnMeasure() to correctly use the result
from CrossPlatformMeasure()
and call SetMeasuredDimension() with the resolved size. Previously, the
return value was discarded
and Android retained height=0 from base.OnMeasure().

The implementation respects MeasureSpecMode as follows:

- EXACTLY — uses the size from the measure spec (parent dictates size).
- AT_MOST — uses the full spec constraint (not the cross-platform
measure), matching Android's View.getDefaultSize() semantics. This is
intentional: SwipeRefreshLayout internally centers its spinner indicator
using getMeasuredWidth() / 2, so clamping to content size would
misposition the spinner.
- UNSPECIFIED — uses the size returned from CrossPlatformMeasure()
(content wraps naturally).

### Validated the behaviour in the following platforms
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac


### Issues Fixed:
Fixes #12131

### Screenshots
| Before  | After |
|---------|--------|
| <img height=600 width=300
src="https://github.com/user-attachments/assets/6ffdfc04-fdc0-4c1a-b0fd-3f67c4744571">
|  <img height=600 width=300
src="https://github.com/user-attachments/assets/7143297a-94f2-48a8-9eca-3516ddf70491"> 
|
<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Root Cause : 
`SendNavigatingFrom()` was called too early in `SendNavigating()`—before
the navigation stack updated. At that point, `CurrentPage` still pointed
to the source page, so the event incorrectly reported the source page as
the destination.
### Description of Change
Bug fix for navigation event:

* Updated `IShellController.UpdateCurrentState` in `Shell.cs` to fire
`SendNavigatingFrom` after the shell state is updated, ensuring
`CurrentPage` correctly reflects the destination page in the
`NavigatingFromEventArgs`. The navigation type is determined based on
the navigation source.
* Modified `SendNavigating` in `Shell.cs` to capture the current page
for later use, rather than immediately firing `SendNavigatingFrom`, so
the destination is correct when the event is triggered.

Test coverage for the fix:

* Added a new test case in `Issue34073.cs` under `TestCases.HostApp` to
demonstrate and validate the correct behavior of `OnNavigatingFrom`,
showing the destination page is now accurate.
* Added a UI test in `TestCases.Shared.Tests/Tests/Issues/Issue34073.cs`
that navigates between pages and asserts that the destination page
reported by `OnNavigatingFrom` is correct.


<!-- Enter description of the fix in this section -->

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #34073 
### Tested the behavior in the following platforms

- [x] Windows
- [x] Android
- [x] iOS
- [x] Mac

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <img width="1080" height="1920" alt="BeforeFix34073"
src="https://github.com/user-attachments/assets/763e1c20-9f41-4fc2-b7d6-21261280b42a"
/> | <img width="1080" height="1920" alt="AfterFix34073"
src="https://github.com/user-attachments/assets/bc4f998e-52b7-492f-8fc5-61e2f56881a4">
|
| <img width="1125" height="2436" alt="BeforeFixiOS34073"
src="https://github.com/user-attachments/assets/fadc8294-77a8-4db2-b48f-787eb49e0f8b"
/> | <img width="1125" height="2436" alt="AfterFixiOS34073"
src="https://github.com/user-attachments/assets/237f1fbf-649a-48d2-9930-a182695cf9e9"
/> |
<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
…anges (#34416)

### Root Cause
On Android, MAUI declares `configChanges="density"` in the manifest. As
a result, when the user changes the system display size (display
density), the `Activity` is not recreated. Instead, the existing view
hierarchy remains active and `OnSizeChanged` is triggered to update the
view’s pixel dimensions.

However, `PlatformGraphicsView` initializes its internal `_scale` field
only once during construction, and the field is marked as `readonly`.
Because the `Activity` is not recreated, this value is never refreshed
after a density change.

This leads to a mismatch between two density sources:

* `GraphicsView.Width` and `GraphicsView.Height`, which rely on MAUI’s
cached display density (`s_displayDensity`) set at application startup.
* The `dirtyRect` passed into `IDrawable.Draw()`, which is calculated
using the stale `_scale` value from `PlatformGraphicsView`.

After a system display size change, these two values diverge. As a
result, the drawable’s coordinate space no longer aligns with the view’s
logical dimensions, causing rendering inconsistencies and incorrect
drawing behavior.

### Description of Change
Removed the `readonly` modifier from the `_scale` field in
`PlatformGraphicsView` and introduced an internal virtual
`GetDisplayDensity()` method. The `_scale` value is now refreshed on
every `OnSizeChanged` call using the value returned by this method,
ensuring it reflects the current display density.

`PlatformTouchGraphicsView` (the MAUI-integrated subclass in the Core
project) overrides `GetDisplayDensity()` to return MAUI’s cached display
density — the same value used by `GraphicsView.Width` and
`GraphicsView.Height`.

This ensures that both logical sizing and `dirtyRect` calculations rely
on a consistent density source, keeping them fully synchronized even
after a system display density change.

### Issues Fixed
Fixes #34211 
 
Tested the behaviour in the following platforms
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

### Note
display density can be forced on Android using `adb shell wm density`.
Other platforms do not provide a comparable runtime command or
automation method to override display density, so this scenario cannot
be triggered or validated the same way outside Android.

### Output Video
Before Issue Fix | After Issue Fix |
|----------|----------|
|<video width="40" height="60" alt="Before Fix"
src="https://github.com/user-attachments/assets/6c6af647-c758-40b3-802c-000d5021a936">|<video
width="50" height="40" alt="After Fix"
src="https://github.com/user-attachments/assets/85f7b299-2349-4fef-a2c1-b68b485dbd34">|
…WebView and HybridWebView (#30653)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
FlowDirection was only applied to the WebView and HybridWebview, not its
internal ScrollView, which handles the actual content layout and
scrolling.

### Description of Change

<!-- Enter description of the fix in this section -->
FlowDirection is now applied to the internal ScrollView of WKWebView to
ensure correct layout direction.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #30605 

### Regarding Test case
For this case, it's not possible to write a test because the scrollbar
does not appear during initial loading. It only becomes visible when a
scroll action is performed, and even then, it remains visible only for a
few seconds.

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

**Tested the behavior in the following platforms.**
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac


| Before  | After  |
|---------|--------|
| **iOS**<br> <video
src="https://github.com/user-attachments/assets/eaf6620b-5f00-402c-b191-2ba881cacac2"
width="300" height="600"> | **iOS**<br> <video
src="https://github.com/user-attachments/assets/b038125e-5dbf-483b-aa7d-640f2c72555e"
width="300" height="600"> |
#33439)

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Issue Details
MediaPicker on Android was appending _rotated and _processed suffixes to
filenames when rotating or compressing images, causing the original
filename to be lost.

### Description of Change

<!-- Enter description of the fix in this section -->
The updated implementation closes the input stream before performing any
file operations, deletes the original file, and writes the processed
(rotated) image back to the same file path. This ensures that the
original filename is preserved, avoiding the addition of any _rotated or
_processed suffixes.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #33258 

### Regarding test case
Picking an image is not possible in a test.

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

**Tested the behavior in the following platforms.**
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac

| Before  | After  |
|---------|--------|
| **Android**<br> <video
src="https://github.com/user-attachments/assets/35cb8242-9d10-4926-81b8-d28e291a56fe"
width="300" height="600"> | **Android**<br> <video
src="https://github.com/user-attachments/assets/92249ba3-76af-4d05-a789-8f63d02de86b"
width="300" height="600"> |
…iew (#34382)

### Root Cause
When `RefreshView` initializes on iOS, it attaches the native
`UIRefreshControl` to the CollectionView’s scroll layer. This attachment
triggers an internal layout pass. During that layout pass, iOS
temporarily reports an incorrect container width to the layout engine,
typically double the actual screen width.
`UICollectionViewCompositionalLayout`, which is used by the CV2 handler,
uses this incorrect width to calculate the total content size and caches
it. After the animation settles and the correct screen width is
restored, the layout engine does not recalculate the content size and
continues using the cached double-width value.
As a result, the scroll system believes the content is wider than the
screen and enables horizontal scrolling.
 
### Description of Change
The fix was implemented by overriding the `CollectionViewContentSize`
property inside `CustomUICollectionViewCompositionalLayout`. This
property is where the scroll system queries the layout for the total
content size.
The override intercepts the content size before it is returned to the
scroll system. If the layout is vertical and the reported content width
is greater than the actual screen width, the width is clamped to the
real screen width before being returned.
By ensuring that a vertical layout never reports a content width larger
than the screen, horizontal scrolling is prevented.
The change is minimal, localized to a single file, and safe because a
vertical layout can never legitimately have content wider than the
screen.

### Issues Fixed
Fixes #34165 
 
Tested the behaviour in the following platforms
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

### Output Video
Before Issue Fix | After Issue Fix |
|----------|----------|
|<video width="40" height="60" alt="Before Fix"
src="https://github.com/user-attachments/assets/0acc28a6-a526-4799-8de7-99c4b0dbf4fe">|<video
width="50" height="40" alt="After Fix"
src="https://github.com/user-attachments/assets/61100874-a442-4a50-96ee-0539a2544ac3">|
…dices with grouped items (#34240)

> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details

- When a CollectionView's IsGrouped property is set to true, the values
of FirstVisibleItemIndex, CenterItemIndex, and LastVisibleItemIndex in
the ItemsViewScrolledEventArgs passed to the Scrolled event handler are
incorrect.

### Root Cause

- Visible items were sorted only by Row, which produced incorrect
ordering when items from multiple sections were visible simultaneously.

### Description of Change

**iOS (ItemsViewDelegator.cs and ItemsViewDelegator2.cs)**

- Changed the sort from .OrderBy(x => x.Row) to .OrderBy(x =>
x.Section).ThenBy(x => x.Row) so that IndexPathsForVisibleItems is
ordered correctly across section boundaries. The fix is applied to both
the legacy handler (Items/) and the current handler (Items2/).

### Issues Fixed
Fixes #17664 


### Validated the behaviour in the following platforms

- [x] Windows
- [x] Android
- [x] iOS
- [x] Mac

Android fix PR: #31437

### Output
| Before | After |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/3612815d-3a43-4dd5-8d2c-3832e3d4a077">
| <video
src="https://github.com/user-attachments/assets/f6cd63b8-1ed9-465a-99d9-11e38b004dac">
|
)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
Label.Clip did not clip the background on iOS and Windows when
`Background` was also set. The background rendered outside the clipping
geometry.

### Root Cause
When a Label has a non-null Background, LabelHandler.NeedsContainer
returns true, wrapping the label in a WrapperView.
 
**iOS**: MapBackground called handler.ToPlatform(), which returns the
WrapperView when a container is present. The background was applied to
the wrapper, but WrapperView.SetClip() only masks sublayers named
MauiBackgroundLayer — solid colors set via BackgroundColor are not
sublayers, so they were never masked.

### Description of Change

**iOS**: Changed MapBackground to use
handler.PlatformView?.UpdateBackground(label) (routes to MauiLabel)
instead of handler.ToPlatform() (routes to WrapperView). The WrapperView
clip then correctly clips the label and its background together.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
 
### Issues Fixed
  
Fixes #34114 

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/415fa851-7a19-4874-8afb-f240d3962067"
>| <video
src="https://github.com/user-attachments/assets/59c24428-0631-43a9-9720-f22649a20f82">|
…ting 4+ images with CompressionQuality set (#34281)

> [!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!

### Issue Details
On iOS, when using MediaPicker.PickPhotosAsync() with CompressionQuality
enabled and SelectionLimit = 0 (unlimited), selecting several photos
returns an empty list instead of the selected photos.

### Root Cause
When compression is enabled, the original implementation eagerly loads
and processes all selected photos immediately after the picker is
dismissed by calling NSItemProvider.LoadDataRepresentationAsync()
sequentially in a tight loop.
When four or more items are processed in rapid succession, these data
loads fail or time out on iOS. As a result, the operation fails and
returns an empty list. This occurs because multiple instances cannot
reliably load data simultaneously or immediately after the picker view
controller is dismissed.
 
### Description of Change
The fix introduces a lazy compression approach using a new
PHPickerProcessedFileResult wrapper. Image processing is deferred until
the stream is actually opened, avoiding iOS resource constraints caused
by eager loading of multiple instances in quick succession.
The changes now aligns with MAUI Graphics limitations: image.SaveAsync
supports only JPEG and PNG output formats. Therefore, when a non-PNG
image is compressed, JPEG is the only valid output format.

### Issues Fixed
Fixes #33954

### Tested platforms

- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

**Regarding the test case:** MediaPicker invokes the native iOS photo
picker, which is a system-level UI running outside the app process.
Because of this, it cannot be controlled or captured by Appium or other
automated test frameworks. Therefore, the test case is ignored.

**Key changes in `MediaPicker.ios.cs`:**
- Added `PHPickerProcessedFileResult` class (~67 lines) — wraps a
`FileResult` and defers all NSItemProvider I/O to stream-open time
- Modified `PickerResultsToMediaFiles()` — wraps results in
`PHPickerProcessedFileResult` instead of eagerly compressing

### Screenshots

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/18e6f361-0ba4-4176-9f25-5c0e07a32b3c">
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/435083d5-301b-4e8f-9daf-94b324f63356">)
|
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Detail
SearchBar.CancelButtonColor has no effect on iOS 26 (Liquid Glass
design). On iOS 18.x, the cancel button shows "Cancel" text and
CancelButtonColor is correctly applied via SetTitleColor. On iOS 26,
Apple redesigned the cancel button — it now shows an "xmark" icon (a
UIButton with accessibility label "Close") instead of text. All standard
color APIs (SetTitleColor, TintColor,
UIButtonConfiguration.BaseForegroundColor) are silently overridden by
UIButton's Liquid Glass compositor on every layout pass, so the X icon
always renders in the system default color regardless of
CancelButtonColor.

### Root Cause
UIButton on iOS 26 renders the X icon through its own Liquid Glass
compositor — a system-level final rendering pass that runs after all
UIKit APIs, CALayer, and CALayer.Filters. The compositor is scoped to
UIButton's own view subtree, meaning any color applied inside the button
(text color, tint, image, configuration, sublayer) is overridden before
it reaches the screen.

### Description of Change
On iOS 26, UIButton renders the X icon through the Liquid Glass
compositor, which runs after all UIKit APIs and overrides any color set
on the button or its subviews. To work around this, instead of coloring
the button itself, the fix renders a colored "xmark" icon image using
UIGraphicsImageRenderer with CGBlendMode.SourceIn (baking the color into
the pixels so UIKit cannot override it) and places it in a separate
UIImageView next to the cancel button in the view hierarchy — completely
outside UIButton's rendering scope. The overlay is positioned to sit
exactly on top of the X icon and has UserInteractionEnabled = false so
taps still reach the real cancel button underneath.
The cancel button lookup uses FindDescendantView<UIButton> with a
predicate btn.FindParent(v => v is UITextField) == null to ensure only
the cancel button is matched, excluding the clear button inside the text
field. The overlay is deferred via DispatchAsync so the cancel button
frame is valid after layout, and a WeakReference<UISearchBar> is used to
avoid retain cycles and always look up the current cancel button
instance across theme transitions.

### Issues Fixed
Fixes #33964

**Regarding the test case:** The [AppTheme feature
matrix](https://github.com/dotnet/maui/tree/main/src/Controls/tests/TestCases.HostApp/FeatureMatrix/AppTheme)
already covers the SearchBar cancel button color in both light and dark
themes through LightTheme_SearchBar_VerifyVisualState and
DarkTheme_SearchBar_VerifyVisualState. Therefore, no additional tests
are added.

### Files Changed

- `src/Core/src/Platform/iOS/SearchBarExtensions.cs` — iOS 26 overlay
fix

### Key Technical Details
- `CGBlendMode.SourceIn` recolors only the xmark pixels while preserving
the icon shape and transparency
- `UIImageRenderingMode.AlwaysOriginal` prevents UIKit from re-tinting
the baked image after placement
- `UserInteractionEnabled = false` on the overlay ensures taps fall
through to the real cancel button
- `CancelButtonColorOverlayTag = 0x53424343` (encodes "SBCC") identifies
the overlay for cleanup on color change or cancel button removal
- `WeakReference<UISearchBar>` avoids retain cycles; the cancel button
itself is looked up fresh on each dispatch since iOS 26 may recreate it
during theme transitions
- This change only applies to `OperatingSystem.IsIOSVersionAtLeast(26)`
— pre-iOS 26 behavior is unchanged

### Screenshots

Light Theme:
| Before Issue Fix | After Issue Fix |
|----------|----------|
|<img width="762" height="1682" alt="Image"
src="https://github.com/user-attachments/assets/24e30082-096a-4fc3-b75d-51ca8994e8f5"
/>|<img width="762" height="1682" alt="Image"
src="https://github.com/user-attachments/assets/66c83175-a4a8-40d1-9a9f-6bddb21857e2"
/>|

Dark Theme:
| Before Issue Fix | After Issue Fix |
|----------|----------|
|<img width="762" height="1682" alt="Image"
src="https://github.com/user-attachments/assets/1aeb1bac-9c51-4df0-8afe-d189f7fec46c"
/>|<img width="762" height="1682" alt="Image"
src="https://github.com/user-attachments/assets/584b1cd2-ab53-4631-bb1b-626554000591"
/>|
)

This pull request adds a new Shell feature matrix to the test cases host
app, providing a dedicated UI for testing and exploring Shell navigation
features in .NET MAUI. The main changes introduce new pages for the
Shell feature matrix, including a main page and a comprehensive options
page to test navigation events and stack manipulation.

**Shell Feature Matrix Integration**

* Added a new `ShellFeaturePage` to the feature matrix in
`CorePageView.cs`, making Shell navigation features accessible from the
gallery of test pages.
* Implemented `ShellFeaturePage.xaml` and its code-behind, providing a
main entry point for Shell navigation testing, including a button to
launch the navigation control page.
[[1]](diffhunk://#diff-429088ce96d697ab4ebcb64f4f34eab95990318df0e699a206770e487cc5f99cR1-R17)
[[2]](diffhunk://#diff-d9fe6832827db8c2b917b1667eb42de532a901608d7f118f002848d9a7fc5018R1-R25)

**Shell Navigation Options UI**

* Added `ShellNavigationOptionsPage.xaml`, a comprehensive UI for
testing Shell navigation options, including navigation events, stack
inspection, and tab navigation methods. This page uses data binding to
display current navigation state and exposes buttons for navigation
stack manipulation.

**New Issue Identified**

-  #34318

**Existing Issue Identified**

- [On Windows TextOverride and IconOverride is not
working](#1625)

https://github.com/user-attachments/assets/9ab48004-334a-45d9-95db-aaf42de2b083

---------
Co-authored-by: Jakub Florkowski <kubaflo123@gmail.com>
# Conflicts:
#	.github/scripts/Review-PR.ps1
#	eng/pipelines/ci-copilot.yml
…n called before the view is mounted (#34331)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Root Cause:

The issue occurs because CollectionView (Items2 /
CollectionViewHandler2) depends on the native UIKit layout pass to
compute its content size. When Measure() is called before the view is
mounted, the native collection view has not performed layout yet, so
ContentSize remains {0,0}. MAUI then falls back to
base.GetDesiredSize(), which returns the full constraint height,
producing an incorrect measured value.

### Fix Description:

The fix involves performing a temporary in-place layout pass during
pre-mount measurement. If Window == null, the CollectionView frame is
set directly to the given constraints (clamping ∞ to 10000 to match
UIKit’s UILayoutFittingExpandedSize). Then SetNeedsLayout() and
LayoutIfNeeded() are called so UIKit computes the real content size. The
original frame is always restored in a finally block. No AddSubview,
RemoveFromSuperview, or ReloadData() is used. This allows Measure() to
return the correct height even before the control is mounted.
 
**Reason for Mac behavior:**
 
On macOS via MacCatalyst, UISheetPresentationController always presents
sheets as full-height modal panels. Custom detents (custom heights) are
ignored by the platform.

### Issues Fixed
Fixes #32983

### Tested the behaviour in the following platforms
- [x] iOS
- [x] Mac
- [ ] Android
- [ ] Windows

### Output Screenshot
Before Issue Fix | After Issue Fix |
|----------|----------|
|<video width="100" height="100" alt="Before Fix"
src="https://github.com/user-attachments/assets/8f31b5fe-6482-490c-b6f4-c77376257042">|<video
width="100" height="100" alt="After Fix"
src="https://github.com/user-attachments/assets/d0b2e698-8843-4d9b-a773-0bd3db427b7c">|
…t tab becomes invisible (#34372)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
Setting IsVisible = false on the first tab's page in a Shell with 7 tabs
causes the bottom tab bar to display incorrect tabs on Android and
breaks sub-page navigation on iOS.

- On Android, the tab bar is not rebuilt — Tab1 remains visible and Tab5
ends up highlighted at the wrong index (Tab4 appears selected instead of
Tab5).
- On iOS, navigating to a sub-page from Tab5 after Tab1 is hidden pushes
onto MoreNavigationController instead of the tab's own navigation stack,
causing the page to not display.
- Both bugs are triggered by _tab1Page.IsVisible = false followed by
Shell.Current.GoToAsync("///Tab5").

### Root Cause
**Android**: SetupMenu() has an early return if (DisplayedPage is null).
When Tab1 becomes invisible, Shell transitions CurrentItem to a
lazy-loaded tab (ContentTemplate), making DisplayedPage transiently
null. SetupMenu() returns early, leaving BottomNavigationView stale with
Tab1 still in the menu. When GoToAsync("///Tab5") fires,
GetItems().IndexOf(Tab5) returns 3 (Tab1 excluded), but menu.GetItem(3)
points to Tab4 in the stale menu — wrong tab is highlighted.

**iOS**: After Tab1 is removed, IsInMoreTab is never recalculated. Tab5
was at index 4 (IsInMoreTab = true) before removal; after it shifts to
index 3 (a direct tab), its renderer still holds IsInMoreTab = true,
causing GoToAsync("Page51") to be routed through
MoreNavigationController where it fails to display.

### Description of Change
**Android**: Changed SetupMenu() guard from if (DisplayedPage is null) →
if (DisplayedPage is null && !_menuSetup). Allows rebuild during tab
transitions when menu was already initialized, first-time setup guard
unchanged.

**iOS**: Added UpdateIsInMoreTabForRenderers() after tab removal in
OnShellItemsChanged. Recalculates IsInMoreTab for all remaining view
controllers based on their new position relative to the 5-tab limit.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
 
### Issues Fixed
  
Fixes #34343 

### Output  ScreenShot

Android

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/6ec28df8-47f2-490b-981b-170fc4d7dea9"
>| <video
src="https://github.com/user-attachments/assets/91f2faf3-6153-4951-975c-e25c19fa4ef3">|

iOS

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/2b632528-082e-4eb1-8d3a-09871c78c903"
>| <video
src="https://github.com/user-attachments/assets/8122082c-80a1-4408-a51e-0069244f0b69">|
…erView item holders (#34452)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Root Cause:

The issue occurs because of RecyclerView reusing stale ViewHolders from
its shared recycled-view pool when CollectionView.EmptyView is swapped
on Android. When EmptyView or EmptyViewTemplate is updated,
EmptyViewAdapter._emptyItemViewType changes. However, the RecyclerView
recycled-view pool may still contain ViewHolders created for previous
item view types. As a result, RecyclerView can reuse one of these stale
holders at the empty position, causing a previously filtered data item
view to be rendered instead of the intended EmptyView.

### Fix Description:

The fix involves clearing the RecyclerView recycled-view pool during the
EmptyView update path. Specifically, GetRecycledViewPool().Clear() is
added in MauiRecyclerView.UpdateEmptyView() and executed only when the
currently attached adapter is _emptyViewAdapter (GetAdapter() ==
_emptyViewAdapter). This ensures that any stale ViewHolders are removed
before _emptyViewAdapter.NotifyDataSetChanged() refreshes the view,
preventing RecyclerView from reusing incorrect holders while keeping the
existing adapter behavior unchanged.

### Issues Fixed
Fixes #34122

### Tested the behaviour in the following platforms
- [x] iOS
- [x] Mac
- [x] Android
- [x] Windows

**Note:** 

### Output Screenshot
Before Issue Fix | After Issue Fix |
|----------|----------|
|<video width="100" height="100" alt="Before Fix"
src="https://github.com/user-attachments/assets/f0dda99c-9eff-4078-8b87-72822fa52e13">|<video
width="100" height="100" alt="After Fix"
src="https://github.com/user-attachments/assets/ac702989-048c-440d-9f3c-a767b0f6eb4f">|

---------

Co-authored-by: Shane Neuville <5375137+PureWeen@users.noreply.github.com>
Co-authored-by: Jakub Florkowski <kubaflo123@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…sing scroll/rendering issues (#27093)

### Description of Change

Vertical padding set on the recyclerView affected the rendering process.
It is much more efficient to fully rely on ItemDecoration to manage
spacing between items.

### Issues Fixed

Fixes #24511
Fixes #8422
Fixes #18367
Fixes #17127
Fixes #30979
Fixes #31966

|Before|After|
|--|--|
<video
src="https://github.com/user-attachments/assets/e19124a4-c0ba-4796-9906-f6179fa77ba1"
width="300px"/>|<video
src="https://github.com/user-attachments/assets/f60fee7b-41dc-4b08-a7a0-a464c1a897dc"
width="300px"/>|
<video
src="https://github.com/user-attachments/assets/3827b6fc-bed2-4a80-8422-2ea7602e9970"
width="300px"/>|<video
src="https://github.com/user-attachments/assets/55daaee6-f198-40d9-b20a-fbb185001954"
width="300px"/>|

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ews when SearchBoxVisibility changes at runtime (#33774)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Root Cause

When `SearchBoxVisibility` is `Collapsible`,
`ShellToolbarTracker.UpdateToolbarItems` adds a placeholder menu item
(id `_placeholderMenuItemId`) to the toolbar menu and sets the
`_searchView.View` as that item's action view. When transitioning to
`Expanded`, the old code only checked whether `_searchView.View.Parent
!= _platformToolbar` and added the view directly to the toolbar — but
never removed the placeholder menu item from the menu. As a result, both
the menu item (showing the collapsible search icon) and the
directly-added expanded search view were displayed simultaneously.

The reverse transition (Expanded → Collapsible) did not have this
problem because the Collapsible path always calls
`menu.RemoveItem(_placeholderMenuItemId)` before re-adding it fresh.

### Description of Change

In `ShellToolbarTracker.cs`, the `Expanded` branch of
`UpdateToolbarItems` now explicitly removes the placeholder menu item
from the toolbar menu before adding `_searchView.View` directly to
`_platformToolbar`:

```csharp
else if (SearchHandler.SearchBoxVisibility == SearchBoxVisibility.Expanded)
{
    // Remove the placeholder menu item, if it exists, added for collapsible mode.
    if (menu.FindItem(_placeholderMenuItemId) is not null)
    {
        menu.RemoveItem(_placeholderMenuItemId);
    }

    if (_searchView.View.Parent != _platformToolbar)
    {
        _platformToolbar.AddView(_searchView.View);
    }
}
```

Several null-check expressions were also modernized from `!= null` / `==
null` to the C# `is not null` / `is null` pattern as part of the same
edit.

### Files Changed

- **`ShellToolbarTracker.cs`** — Core fix: remove placeholder menu item
in the Expanded branch; style cleanup (null-checks, brace style)
- **`TestCases.HostApp/Issues/Issue33772.cs`** — New test page: Shell
with buttons to toggle `SearchBoxVisibility` between Collapsible and
Expanded
- **`TestCases.Shared.Tests/Tests/Issues/Issue33772.cs`** — Two ordered
UI tests validating the transition in both directions via screenshot
- **Snapshot PNGs** — Baseline screenshots for Android, iOS, and Windows

### Issues Fixed

Fixes #33772

### Platforms Tested

- [x] Android
- [x] iOS
- [ ] Mac
- [ ] Windows
</details>


### Screenshot
| Before Fix | After Fix |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/716652d6-7f39-4c64-8fa9-262dd8cb2908">
| <video
src="https://github.com/user-attachments/assets/403990ac-d6fd-4b8c-8114-ff1cdf955df3">
|
…et to null (#31340)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details 

- The BackgroundColor and Background of a ContentView are not properly
cleared when set to null on Android and iOS platforms.

### Root Cause

- The existing platform-specific logic on both Android and iOS only
handled background clearing for specific view types, such as
LayoutViewGroup or LayoutView. ContentView was not included in these
type checks. As a result, if a ContentView previously had a background
applied—such as a color or gradient—and was later set to null, the
background remained visible. This occurred because the rendering logic
did not execute the background-clearing code for ContentView, leading to
incorrect visual behavior.

### Description of Change

- Updated UpdateBackground extension methods on Android and iOS to
ensure that setting the background or background color to null correctly
clears the background for both LayoutView and ContentView types.


### Issues Fixed
Fixes #18933 

### Validated the behaviour in the following platforms

- [x] Windows
- [x] Android
- [x] iOS
- [x] Mac

### Output
| Platform | Before | After |
|----------|----------|----------|
| Android | <video
src="https://github.com/user-attachments/assets/5d3ac9aa-3dbe-4841-9847-6b7a1484e141">
| <video
src="https://github.com/user-attachments/assets/5ccb6afb-8d3e-420b-8255-e0325fe6e6ad">
|
| iOS | <video
src="https://github.com/user-attachments/assets/9d7659f5-3409-4677-862e-fb0ee1853128">
| <video
src="https://github.com/user-attachments/assets/8851de12-bdc5-4633-aaec-9bf2be940450">
|
… between flyout items (#29883)

### Root Cause:
- On Android, the BottomNavigationView initially has a ColorDrawable
with a white background, which is the default color during its creation.
- Later, the BottomNavigationView background is changed to a
ColorChangeRevealDrawable with an animation, transitioning to the color
specified by IShellAppearanceElement.EffectiveTabBarBackgroundColor.
- Since the previous background color (white) differs from the current
color (black), the animation displays a transition from white to black
when switching pages.
### Description of Change:
- To resolve this issue, the
IShellAppearanceElement.EffectiveTabBarBackgroundColor is applied to the
background of the BottomNavigationView immediately after its creation.
- This ensures that no animation is triggered later, as the previous
background color (black) and the current color (black) remain the same.
 
**Tested the behaviour in the following platforms.**
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Reference
N/A
 
### Issues Fixed
Fixes #16522
 
### Screenshots
 
| Before | After |
|--------|-------|
| <img
src="https://github.com/user-attachments/assets/3e14b05f-70dd-4a17-bd85-01bf53f37095"
width="300" height="600"> | <img
src="https://github.com/user-attachments/assets/5320a886-ba23-4142-b4fb-6d00a53f3323"
width="300" height="600"> |
…th nullable types (#33429)

> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

## Description

Fixes #33420

This PR fixes a that occurs when using with nullable types during Shell
navigation.

## Root Cause

In `ShellContent.ApplyQueryAttributes`, the code used
`Convert.ChangeType(value, prop.PropertyType)` which fails when the
property type is nullable (e.g., `long?`, `int?`) because
`Convert.ChangeType` does not handle `Nullable<T>` types directly.

When a user navigates with a nullable parameter like:
```csharp
Dictionary<string, object> param = new() {
    { nameof(DetailsPage.ID), (long?)1 }
};
await Shell.Current.GoToAsync(nameof(DetailsPage), param);
```

And the destination page has:
```csharp
[QueryProperty(nameof(ID), nameof(ID))]
public partial class DetailsPage : ContentPage
{
    public long? ID { get; set; }
}
```

The app would crash with `InvalidCastException`.

## Solution

Use `Nullable.GetUnderlyingType()` to extract the underlying type before
conversion:

1. Check if the property type is nullable using
`Nullable.GetUnderlyingType()`
2. If nullable, extract the underlying type (e.g., `long` from `long?`)
3. Convert the value to the underlying type
4. Assign (automatic wrapping in nullable occurs)
5. Handle null values explicitly

## Changes

**Fix:**
- `src/Controls/src/Core/Shell/ShellContent.cs` - Added nullable type
handling in `ApplyQueryAttributes`

**Tests:**
- Added UI test `Issue33420` that reproduces the issue and verifies the
fix
- Test navigates with a nullable `long?` parameter and verifies
navigation succeeds

## Testing

✅ Tests fail without the fix (reproduces the crash)
✅ Tests pass with the fix (navigation works)
✅ Verified on iOS simulator

## Platforms Affected

All platforms (iOS, Android, Windows, MacCatalyst) - Shell navigation
code is shared.

---------
@PureWeen
Copy link
Copy Markdown
Member

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

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

…derer merge

- Remove iOSLargeTitlePage.xaml/xaml.cs (main converted to C#-only)
- Properly 3-way merge ShellItemRenderer.cs: keep main's ViewDidAppear
  + net11.0's badge support, fix missing closing braces
- Take main's XmlName.cs (adds xCode) + net11.0's xShared
- Combine AnalyzerReleases with renumbered MAUIX2015 → MAUIX2017

Validated with dotnet cake - 0 errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sheiksyedm
Copy link
Copy Markdown
Contributor

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

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

…34804)

## Problem

Android binding builds invoke Gradle to resolve Maven dependencies
(e.g., `androidx` packages). When Gradle downloads fail due to transient
DNS/network issues on CI agents, the build fails with:

```
gradlew exited with code 1
```

This is intermittent and often passes on retrigger, but wastes CI
capacity and blocks PRs. See #34800 for recent examples.

## Fix

Add a shared `cache-gradle.yml` template using the AzDO
[`Cache@2`](https://learn.microsoft.com/azure/devops/pipelines/release/caching)
task to persist `~/.gradle/caches` between pipeline runs. When the cache
is warm, Gradle resolves dependencies locally instead of downloading
from Maven Central, eliminating transient network failures.

The cache key includes:
- `Agent.OS` — separate caches for macOS/Windows/Linux
- `gradle-wrapper.properties` — invalidates when Gradle version changes
- `build.gradle` files — invalidates when dependencies change

The template is called from `provision.yml` so it applies to all
pipelines (build, pack, device tests, UI tests).

## Prior art

Adapted from the same approach in dotnet/android:
dotnet/android@0a68102
(PR #10986)

Fixes #34800

---------

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

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

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

…ap definition

- Update SourceGen and XAML unit tests to reference MAUIX2017 (was
  MAUIX2015, renumbered to avoid collision with main's XCodeNotChildOfRoot)
- Align TestCategory.Map to use nameof(Map) matching net11.0 to avoid
  duplicate definition when GitHub merges PR branch with net11.0

Validated with dotnet cake - 0 errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen
Copy link
Copy Markdown
Member

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

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

…on merge

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen PureWeen closed this Apr 22, 2026
@PureWeen PureWeen deleted the merge/main-to-net11.0 branch April 22, 2026 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.