Skip to content

[Bug] SegmentedControl Invisible in Shell.TitleView on iOS 26 (Liquid Glass Rendering Issue) #38

Description

@riccardodangelo

Description

On iOS 26, the SegmentedControl becomes completely invisible when placed inside a Shell.TitleView. The issue appears to be caused by layout measurement and rendering changes related to the new "Liquid Glass" UI design of the UINavigationBar.
The control can be forced to appear on the iOS 26 simulator with specific workarounds, but these workarounds fail on a physical device, where the control remains invisible. This suggests a deep issue with the native hardware rendering pipeline on iOS 26.

Steps to Reproduce

  1. Create a standard .NET MAUI Shell application.
  2. Add the Plugin.SegmentedControl.Maui NuGet package.
  3. In the code-behind of a ContentPage, programmatically create the SegmentedControl and place it inside a container, such as a Grid.
// Create a container with an explicit height
var titleContainer = new Grid { HeightRequest = 44 };

// Create the SegmentedControl
var segmentedControl = new SegmentedControl();
segmentedControl.Children.Add(new SegmentedControlOption { Text = "Upcoming" });
segmentedControl.Children.Add(new SegmentedControlOption { Text = "Past" });

// Add control to container
titleContainer.Children.Add(segmentedControl);
  1. Set this container as the TitleView for the page.
    Shell.SetTitleView(this, titleContainer);

  2. Deploy and run the application on a physical iPhone running iOS 26.

  3. Navigate to the page and observe the navigation bar.

Expected Behavior

  • The SegmentedControl should be visible and interactive within the TitleView of the navigation bar, as it was on iOS versions prior to 26.

Actual Behavior

  • -The SegmentedControl is completely invisible on a physical device. The space for the TitleView is allocated, but the control itself is not rendered.
  • Note: On the iOS 26 simulator, the control does appear if the workarounds mentioned in the steps (using a Grid with HeightRequest and adding children immediately) are applied. The failure is specific to physical hardware.

Basic Information

  • Version with issue: 1.4.36 (in iOS 26.x)
  • Last known good version: 1.4.36 (on iOS 18)

Screenshots, Attachments, Links

Image with:

Image

Screenshot 1: Correct behavior on iOS < 26.
Screenshot 2: The invisible control on a physical iPhone with iOS 26.

Activity

  1. changed the title [-][Bug][/-] [+][Bug] SegmentedControl Invisible in Shell.TitleView on iOS 26 (Liquid Glass Rendering Issue)[/+] on Oct 19, 2025
  2. thomasgalliker commented on Oct 20, 2025

    @thomasgalliker
    Owner

    Thanks for reporting. I just tested it on my iPhone device with a net8.0-ios app and there it works fine. Are you using net9.0 and Xcode 26 to compile your app?

  3. riccardodangelo commented on Oct 20, 2025

    @riccardodangelo
    Author

    Hi, thanks for the quick reply!
    Yes, that's correct. I can confirm that this issue is specific to the newer toolchain.

    • My Environment: .NET 9.0 with Xcode 26, targeting iOS 26.

    The good news is that I've continued debugging and have found a stable workaround that solves the problem completely on my net9.0 setup, making the control work perfectly on both the iOS 26 simulator and physical devices.

    The issue seems to be how the TitleView's container view is declared and initialized.

    The Problematic Approach (Before - Fails on net9.0 / iOS 26)

    My original code declared a specific StackLayout as a class property and initialized it in the page's constructor.

    // In the class properties:
    private StackLayout PageTicketMode;
    
    // In the page constructor:
    public MyPage()
    {
        // ...
        PageTicketMode = new StackLayout { /* ... properties ... */ };
        // ...
    }
    

    This pattern, which worked fine before, appears to cause critical layout measurement issues within the new UINavigationBar on iOS 26 when compiled with .NET 9.

    The Stable Workaround (After - Works on net9.0 / iOS 26)

    The solution is to declare the container as a more generic View and then instantiate the appropriate, platform-specific layout (Grid for iOS) "just-in-time" when it's actually needed.

    1. Change the property declaraton from StackLayout to the base View type:
    // In the class properties:
    private View PageTicketMode; // Changed from StackLayout
    
    1. Remove the container initialization from the constructor.
    2. Create the platform-specific container within the setup logic. For iOS, this involves creating the Grid that correctly handles measuremen
    // Inside the platform-specific logic (e.g., a SetupTitleView method)
    else if (DeviceInfo.Platform == DevicePlatform.iOS)
    {
        // 1. Create a Grid specifically for iOS
        var iosContainer = new Grid { HeightRequest = 44 };
    
        // 2. Create and configure the SegmentedControl
        var segmentedControl = new SegmentedControl();
        // ... add children and properties ...
        
        // 3. Add the control to the Grid
        iosContainer.Children.Add(segmentedControl);
    
        // 4. Assign the newly created Grid to the property
        PageTicketMode = iosContainer; 
    }
    

    By switching from a pre-initialized StackLayout to a "just-in-time" Grid assigned to a generic View property, the MAUI layout system now correctly measures and renders the control on physical devices with iOS 26.
    Hope this detailed information helps you pinpoint the issue in the new environment! Let me know if you need any more details.

  4. riccardodangelo commented on Oct 20, 2025

    @riccardodangelo
    Author

    Hi again,

    Following up on my previous comments about the intermittent instability. I've pulled the source code for the SegmentedControlHandler.cs on iOS and I believe I've located the exact cause of the NSInternalInconsistencyException race condition.

    The issue lies in the UpdateSegmentedControl method. It uses a destructive "remove-and-rebuild" strategy:

    private static void UpdateSegmentedControl(...)
    {
        uiSegmentedControl.RemoveAllSegments(); // This line creates the race condition
    
        for (var i = 0; i < segmentedControl.Children.Count; i++)
        {
            uiSegmentedControl.InsertSegment(...);
        }
        // ...
    }
    

    ThisRemoveAllSegments()call creates a small time window where the native UISegmentedControl is empty. On iOS 26, the OS seems to query the control's state more frequently during UI updates, and if it does so in this window, it finds 0 sections, causing the native crash. Could be caused from a massive use of animation liquid glass effect?

    A more robust approach would be to intelligently synchronize the state instead of completely rebuilding it. I've drafted a potential fix for the UpdateSegmentedControl method that avoids this issue by only rebuilding when the segment count changes:

    // Suggested robust implementation
    private static void UpdateSegmentedControl(UISegmentedControl uiSegmentedControl, SegmentedControl segmentedControl)
    {
        // 1. Intelligently synchronize the number of segments
        if (uiSegmentedControl.NumberOfSegments != segmentedControl.Children.Count)
        {
            uiSegmentedControl.RemoveAllSegments();
            for (var i = 0; i < segmentedControl.Children.Count; i++)
            {
                uiSegmentedControl.InsertSegment(segmentedControl.Children[i].Text, i, false);
            }
        }
        else
        {
            // 2. If segment count is the same, just update titles if they differ
            for (var i = 0; i < segmentedControl.Children.Count; i++)
            {
                if (uiSegmentedControl.GetTitle(i) != segmentedControl.Children[i].Text)
                {
                    uiSegmentedControl.SetTitle(segmentedControl.Children[i].Text, i);
                }
            }
        }
    
        // ... The rest of the update logic remains the same ...
    }
    

    This "diffing" approach ensures the control is never empty during simple property updates, which should eliminate the race condition.
    I'm confident this is the root cause. Thank you for your time looking into this

  5. thomasgalliker commented on Oct 20, 2025

    @thomasgalliker
    Owner

    Ok, I read trough your posts later. Thanks for the indepth analysis!
    Just one thing in advance: NSInternalInconsistencyException is usually throw when the UI thread is accessed from a background thread. Maybe we just have to dispatch the RemoveAllSegments method to run on the UI thread?

  6. riccardodangelo commented on Oct 23, 2025

    @riccardodangelo
    Author

    Hi Thomas,

    Thank you so much for your quick feedback and analysis. I really appreciate your suggestion regarding potential UI access from a background thread. That's a crucial point and certainly the most common cause for NSInternalInconsistencyException.

    I wanted to give you an update that might be helpful. I continued to run some tests and discovered a detail that, in my specific use case, seems to have completely resolved the instability.

    In addition to the workaround I mentioned (using a Grid as a container on iOS), I made one further small change: I moved the initialization of this Grid directly into my page's constructor (instead to OnAppearing), like this:

    // In my view/page constructor
    PageTicketMode = new Grid
    {
        VerticalOptions = LayoutOptions.Fill,
        HorizontalOptions = LayoutOptions.Fill,
        BackgroundColor = Colors.Transparent
    };
    

    Since implementing this upfront container initialization, I have not been able to reproduce the crash at all, neither on the simulator and on a physical device. I've tried to heavily stress the control with very rapid and continuous selection changes, but the application has remained stable.

    This leads me to believe the issue is closely tied to the timing of the .NET Maui layout cycle. It's possible that initializing the parent container early ensures that when the SegmentControl handler executes, the layout system is in a more stable and predictable state.

    Perhaps, this prevents the conditions that lead to the crash, whether they're related to threading as you suggested, or to the potential race condition I hypothesized with RemoveAllSegments...

    In any case, with this change, the control is now perfectly stable for my needs. I'm sharing this information in case it might be helpful to you or other users who might encounter similar instability in the future. As far as I'm concerned, the issue can be considered happily resolved.

    Thanks again for your help and for the great work on this plugin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions