每当我在 iOS 上键入内容时,ListView 中的 Xamarin Forms 编辑器都会失去焦点

Xamarin Forms editors in ListViews are losing focus whenever I type something on iOS

我在 Xamarin Forms 中有一个 ListView,其中包含带有编辑器的自定义 ViewCell。我有一个函数订阅了编辑器的 TextChanged 事件,该事件检查文本中是否有换行符,如果找到,它会删除它们并使编辑器失去焦点。它在 Android 上正常工作,但在 iOS 上,每当我键入 任何内容 时,编辑器都会失去焦点,而不仅仅是换行符。我该如何解决这个问题?

XAML 页面:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
             mc:Ignorable="d"
             x:Class="Partylist.Views.ChecklistPage"
             ios:Page.UseSafeArea="True">
    
     ...

    <ContentPage.Content>
        <!--Main layout of the page-->
        <StackLayout>
            <!--ListView of the checklist items-->
            <ListView x:Name="ChecklistView"
                      HeightRequest="300"
                      HasUnevenRows="true"
                      ItemSelected="OnItemSelected">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <SwipeView>
                                <!--Swipe from the right to make some options 
                                appear-->
                                <SwipeView.RightItems>
                                    <SwipeItems>
                                        <SwipeItem Invoked="OnDelete"
                                                   CommandParameter="{Binding .}"
                                                   Text="Delete"
                                                   BackgroundColor="#ff418b"
                                                   IsDestructive="true"/>
                                    </SwipeItems>
                                </SwipeView.RightItems>

                                <!--This is the actual content-->
                                <StackLayout Orientation="Horizontal"
                                         Padding="20,5"
                                         VerticalOptions="FillAndExpand">
                                    <ContentView Content="{Binding ItemCheckbox}"/>
                                    <ContentView Content="{Binding ItemEditor}"
                                             VerticalOptions="FillAndExpand"/>
                                </StackLayout>
                            </SwipeView>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
            
             ...

        </StackLayout>
    </ContentPage.Content>
</ContentPage>

代码:

using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace Partylist.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class ChecklistPage : ContentPage
    {
        // Struct for items on the checklist.
        private struct Item
        {
            public Editor ItemEditor { get; set; }
            public CheckBox ItemCheckbox { get; set; }
        }
        // Create a list of contact structs to populate the ListView.
        ObservableCollection<Item> items;
        // Flag for when an item is added to the list.
        bool itemAdded = false;

        ...

        // Override for OnAppearing().
        protected override void OnAppearing()
        {
            // Makes the page appear.
            base.OnAppearing();
            // Set the page's title to be the name of the selected list.
            Title = App.selectedList.Name;
            // Make a toolbar item appear to access the Main Checklist
            // unless we are already there.
            if (App.selectedList.ListFile.Name.EndsWith(".mchec"))
            {
                ToolbarItems.Remove(MainChecklistButton);
            }
            // Set the binding context of the page to itself.
            BindingContext = this;
            // Start the timer for the tips banner if it is stopped.
            App.tipTimer.Start();
            // Set the banner's text to the current tip's sumamry.
            tipLabel.Text = ((App)App.Current).CurrentTip.Summary;
            OnPropertyChanged("CurrentTip");
            // Subscribe the OnTipUpdate function to the tipUpdate event in the app
            // class.
            App.TipUpdate += OnTipUpdate;

            // Make the ObservableCOllection reference something.
            items = new ObservableCollection<Item>();
            // Open a stream to the list that we want to display.
            using (StreamReader listReader = new StreamReader(App.selectedList
                .ListFile.FullName))
            {
                // Loop through the file and read data into the list.
                while (!listReader.EndOfStream)
                {
                    // Create a blank item.
                    Item newItem = new Item()
                    {
                        ItemEditor = new Editor()
                        {
                            Text = listReader.ReadLine(),
                            Placeholder = "New Item",
                            IsTabStop = true,
                            AutoSize = EditorAutoSizeOption.TextChanges,
                            WidthRequest = 300
                        },
                        ItemCheckbox = new CheckBox()
                        {
                            Color = App.selectedList.ListItemColor,
                            IsChecked = bool.Parse(listReader.ReadLine())
                        }
                    };
                    // Subscribe OnTextChanged() to the new item's editor's
                    // TextChanged event.
                    newItem.ItemEditor.TextChanged += OnTextChanged;
                    // Add the new item to the list.
                    items.Add(newItem);
                    // Make the ListView update.
                    OnPropertyChanged("contacts");
                }
                // Once everything is loaded, close the file.
                listReader.Close();
                ChecklistView.ItemsSource = items;
            }
        }

        ...

        // Function for when the "Add New Contact" button is clicked.
        private void OnAddNewItemClicked(object sender, EventArgs e)
        {
            // Create a blank item.
            Item newItem = new Item()
            {
                ItemEditor = new Editor()
                {
                    Placeholder = "New Item",
                    IsTabStop = true,
                    AutoSize = EditorAutoSizeOption.TextChanges,
                    WidthRequest = 300
                },
                ItemCheckbox = new CheckBox()
                {
                    Color = App.selectedList.ListItemColor,
                    IsChecked = false
                }
            };
            // Subscribe OnTextChanged() to the new item's editor's
            // TextChanged event.
            newItem.ItemEditor.TextChanged += OnTextChanged;
            // Add the new contact to the list.
            items.Add(newItem);
            // Set the "itemAdded" flag to true.
            itemAdded = true;
            // Make the ListView update.
            ChecklistView.ItemsSource = items;
            OnPropertyChanged("items");
            // Select the new item so it can be focused.
            ChecklistView.SelectedItem = items.ElementAt(items.Count - 1);
        }

        // Function for when an item is selected, used to set the focus to
        // a newly added item in the list.
        private async void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
        {
            // Only runs this if an item was added (as opposed to being 
            // read in from the file).
            if (itemAdded)
            {
                if (e.SelectedItem == null) return;
                await Task.Delay(100); // Change the delay time if Focus() doesn't work.
                ((Item)e.SelectedItem).ItemEditor.Focus();
                ChecklistView.SelectedItem = null;
                itemAdded = false;
            }
        }

        // Function for when the text of an editor is changed.
        private void OnTextChanged(object sender, TextChangedEventArgs e)
        {
            // If the editor's text now contains a newline...
            if (((Editor)sender).Text.Contains("\n"))
            {
                // Split the string at the newline and recombine it. This
                // will get rid of the newline.
                string[] tempArray = ((Editor)sender).Text.Split('\n');
                string tempStr = "";
                foreach (string str in tempArray)
                {
                    tempStr += str;
                }
                ((Editor)sender).Text = tempStr;
                // Unfocus the editor.
                ((Editor)sender).Unfocus();
            }
            // Force the cell's size to update (it is the parent of the 
            // parent of the parent of the parent of the editor).
            ((ViewCell)((Editor)sender).Parent.Parent.Parent.Parent).ForceUpdateSize();
        }

        ...
    }
}

调用ForceUpdateSize 将导致编辑器失去焦点。您可以将 ForceUpdateSize 放入 Task.Run 方法并在 ForceUpdateSize(); 之后调用焦点。

private async void OnTextChanged(object sender, TextChangedEventArgs e)
{

    //...

    await Task.Run(()=>{

        ((ViewCell)((Editor)sender).Parent.Parent.Parent.Parent).ForceUpdateSize();

    });

    ((Editor)sender).Focus();

}

我用 CollectionView 替换了 ListView,这似乎解决了问题。

此外,CollectionView 没有 HasUnevenRows 属性,因此必须删除它并且它不使用单元格,因此也必须删除 ForceUpdateSize() 方法。