У меня есть кроссплатформенное приложение на формах Xamarin для Android и iOS.
В некоторых представлениях я использую ObservableCollection в качестве источника ListView
.
В представлении списка есть кнопки, с которыми пользователи могут взаимодействовать и изменять пользовательский интерфейс.
На андроиде все работает нормально, но когда я тестировал его на iOS, изменения выполняются только после 1 клика.
Я использовал архитектуру MVVM для удовлетворения своих потребностей, и она очень хорошо работает на Android. Единственная проблема - iPhone.
c # код:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ConvidarAmigosEmail : ContentView
{
public ConvidarAmigosEmail ()
{
BindingContext = new ConvidarAmigosEmailViewModel();
InitializeComponent ();
}
private void ListaEmail_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem == null) return;
listaEmail.SelectedItem = null;
}
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
}
}
public class ConvidarAmigosEmailViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Contacto> _contactosComEmail;
public ObservableCollection<Contacto> ContactosComEmail { get => _contactosComEmail; set { _contactosComEmail = value; OnPropertyChanged(nameof(ContactosComEmail)); } }
public ICommand SeleccionarContacto
{
get => new Command<Contacto>((contacto) =>
{
if (!contacto.Seleccionado)
{
contacto.Seleccionado = !contacto.Seleccionado;
contacto.Opcao = AppResource.Seleccionado;
ContactosSeleccionadosEmail.Add(contacto);
}
else
{
contacto.Seleccionado = !contacto.Seleccionado;
contacto.Opcao = AppResource.Select;
ContactosSeleccionadosEmail.Remove(contacto);
}
});
}
public ICommand SeleccionarTodos
{
get => new Command(() =>
{
if (ContactosComEmail.Count != ContactosSeleccionadosEmail.Count)
{
ContactosSeleccionadosEmail.Clear();
foreach (var obj in ContactosComEmail)
{
obj.Seleccionado = true;
obj.Opcao = AppResource.Seleccionado;
ContactosSeleccionadosEmail.Add(obj);
}
}
else
{
ContactosSeleccionadosEmail.Clear();
foreach (var obj in ContactosComEmail)
{
obj.Seleccionado = false;
obj.Opcao = AppResource.Select;
}
}
});
}
public List<Contacto> ContactosSeleccionadosEmail { get; set; } = new List<Contacto>();
private string _textoSelectTodos = AppResource.SelTodos;
public string TextoSelectTodos
{
get
{
if (ContactosSeleccionadosEmail.Count == ContactosComEmail.Count)
{
_textoSelectTodos = AppResource.Anull;
OnPropertyChanged(nameof(TextoSelectTodos));
return _textoSelectTodos;
}
_textoSelectTodos = AppResource.SelTodos;
OnPropertyChanged(nameof(TextoSelectTodos));
return _textoSelectTodos;
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged == null)
return;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ConvidarAmigosEmailViewModel()
{
ContactosComEmail = new ObservableCollection<Contacto>();
ObterContactos();
}
#region ObterContactos
/*
Params Entrada => nenhum
Params Saída => nenhum
Lógica => Passo 1 : Acede a lista de contactos do telefone e extrai os dados;
Passo 2 : Coloca os contactos com email na variável ContactosE; os que não têm email em ContactosT
Passo 3 : ContentLoader para aceder ao caminho real do ficheiro de imagem (foto) do contacto;
Passo 4 : Adiciona os contactos com Email a uma lista da classe "Contacto", Contactos#. Os sem email para ContactosT
Plugins : Obter permissoes => https://www.nuget.org/packages/plugin.permissions/;
Aceder aos contactos =>
*/
public async void ObterContactos()
{
//verifica se aplicacao tem permissao pra aceder a lista de contactos
var statusPermissao = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Contacts);
//requisitar permissao
if (statusPermissao == PermissionStatus.Unknown)
{
var resposta = await CrossPermissions.Current.RequestPermissionsAsync(Permission.Contacts);
statusPermissao = resposta[Permission.Contacts];
}
if (statusPermissao == PermissionStatus.Denied)
{
var op = await App.Current?.MainPage?.DisplayAlert(AppResource.contactosNegados, AppResource.SemFuncionalidade, AppResource.Definicoes, AppResource.MaisTarde);
if (op == true)
{
CrossPermissions.Current.OpenAppSettings();
}
else
{
await App.Current?.MainPage?.Navigation?.PopAsync();
}
}
if (statusPermissao == PermissionStatus.Granted)
{
var contactos = await Plugin.ContactService.CrossContactService.Current.GetContactListAsync();
//contactos com email
var contactosE = contactos.Where(x => x.Email != null).OrderBy(x => x.Name).ToList();
//imagem inicial de cada contacto, caso esta não tenha imagem de perfil.
var aux = ImageSource.FromResource("KiaiDay.Images.user.png");
foreach (var c in contactosE)
{
aux = ImageSource.FromResource("KiaiDay.Images.user.png");
if (c.PhotoUri != null)
{
//caso tenho imagem de perfil, aceder ao caminho real no ficheiro e extrair a imagem
var contentLoader = DependencyService.Get<IContentLoader>();
var uri = new Uri(c.PhotoUri);
aux = contentLoader.LoadFromContentUri(uri);
}
ContactosComEmail.Add(
new Contacto()
{
Email = c.Email,
Foto = aux,
Numero = c.Number,
Nome = c.Name,
Opcao = AppResource.Select
});
}
}
else
{
//await App.Current?.MainPage?.DisplayAlert(AppResource.contactosNegados, AppResource.SemFuncionalidade, AppResource.ok);
//App.Current?.MainPage?.Navigation?.PopAsync();
}
}
#endregion
}
код xaml:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="KiaiDay.Views.PosLogin.ConvidarAmigosEmail" x:Name="pagina"
xmlns:FFIL="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
xmlns:FFT="clr-namespace:FFImageLoading.Transformations;assembly=FFImageLoading.Transformations">
<ContentView.Content>
<Grid BackgroundColor="White" RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="7*"/>
</Grid.RowDefinitions>
<Grid ColumnSpacing="0" Grid.Row="0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Frame Grid.Row="0" Grid.Column="2" Padding="8" BackgroundColor="#f2f2f2" Margin="-25,0,0,0"
HorizontalOptions="CenterAndExpand" VerticalOptions="Center" BorderColor="LightGray" HasShadow="False">
<Frame.CornerRadius>
<OnPlatform Android="80" iOS="10"/>
</Frame.CornerRadius>
<Label Grid.ColumnSpan="2" Text="{Binding TextoSelectTodos}" FontFamily="{StaticResource RegularFont}" TextColor="{Binding CorSelectTodos}" FontSize="10" VerticalOptions="FillAndExpand" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>
<Frame.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Command="{Binding SeleccionarTodos}"/>
</Frame.GestureRecognizers>
</Frame>
</Grid>
<ListView SeparatorColor="LightGray" ItemsSource="{Binding ContactosComEmail}" HasUnevenRows="True" x:Name="listaEmail"
Margin="30,0,30,0" ItemSelected="ListaEmail_ItemSelected" IsGroupingEnabled="False" Grid.Row="1" CachingStrategy="RetainElement">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid ColumnSpacing="10">
<Grid.RowDefinitions>
<RowDefinition Height="3"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="3"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="5*"/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<FFIL:CachedImage Grid.Row="1" Grid.Column="0" Grid.RowSpan="2"
Source="{Binding Foto}" DownsampleToViewSize="True" BitmapOptimizations="False"
Aspect="AspectFit" RetryCount="0" RetryDelay="250" HeightRequest="50"
LoadingPriority="Highest" HorizontalOptions="Center" VerticalOptions="Center"
FadeAnimationForCachedImages="False" WidthRequest="50">
<FFIL:CachedImage.Transformations>
<FFT:CircleTransformation/>
</FFIL:CachedImage.Transformations>
</FFIL:CachedImage>
<Label Grid.Row="1" Grid.Column="1" Text="{Binding Nome}" FontFamily="{StaticResource RegularFont}" TextColor="#3c3c3b" LineBreakMode="TailTruncation"/>
<Label Grid.Row="2" Grid.Column="1" Text="{Binding Email}" FontFamily="{StaticResource RegularFont}" TextColor="#3c3c3b" LineBreakMode="TailTruncation"/>
<Frame Grid.Row="1" Grid.Column="2" Padding="8" BackgroundColor="#f2f2f2" Grid.RowSpan="2"
HorizontalOptions="CenterAndExpand" VerticalOptions="Center" BorderColor="LightGray" HasShadow="False">
<Frame.CornerRadius>
<OnPlatform Android="80" iOS="10"/>
</Frame.CornerRadius>
<Grid HorizontalOptions="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="6*"/>
</Grid.ColumnDefinitions>
<FFIL:CachedImage Source="{Binding Icone}" HeightRequest="8" WidthRequest="8" BackgroundColor="Transparent" Grid.Column="0"
VerticalOptions="Center" LoadingPriority="Highest" RetryCount="0" RetryDelay="250" DownsampleToViewSize="True" BitmapOptimizations="False"/>
<Label Text="{Binding Opcao}" FontFamily="{StaticResource RegularFont}" FontSize="8" TextColor="{Binding Cor}" Grid.Column="1" VerticalOptions="Center"/>
</Grid>
<Frame.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Command="{Binding Source={x:Reference pagina},Path=BindingContext.SeleccionarContacto}" CommandParameter="{Binding .}" Tapped="TapGestureRecognizer_Tapped"/>
</Frame.GestureRecognizers>
</Frame>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ContentView.Content>
</ContentView>
Я ожидаю, что ObservableCollection обновит пользовательский интерфейс на iOS без необходимости нажатия.
РЕДАКТИРОВАТЬ:
Использованные плагины => https://github.com/jamesmontemagno/PermissionsPlugin;
https://github.com/jamesmontemagno/ContactsPlugin;
определение модели
public class Contacto : INotifyPropertyChanged, IEntity
{
public event PropertyChangedEventHandler PropertyChanged;
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Nome { get; set; }
public string Email { get; set; }
public string Numero { get; set; }
[Ignore]
public ImageSource Foto { get; set; }
private ImageSource _icone = ImageSource.FromFile("mais.png");
[Ignore] public ImageSource Icone
{
get
{
if (Seleccionado)
{
_icone = ImageSource.FromFile("certo.png");
OnPropertyChanged(nameof(Icone));
return _icone;
}
_icone = ImageSource.FromFile("mais.png");
OnPropertyChanged(nameof(Icone));
return _icone;
}
set
{
_icone = value;
OnPropertyChanged(nameof(Icone));
}
}
private string _opcao;
[Ignore]
public string Opcao
{
get
{
return _opcao;
}
set
{
_opcao = value;
OnPropertyChanged(nameof(Opcao));
}
}
private bool _seleccionado = false;
public bool Seleccionado
{
get
{
return _seleccionado;
}
set
{
_seleccionado = value;
OnPropertyChanged(nameof(Seleccionado));
}
}
private Color _cor = Color.FromHex("#3c3c3b");
[Ignore]
public Color Cor
{
get
{
if (Seleccionado)
{
_cor = Color.FromHex("#4297d3");
OnPropertyChanged(nameof(Cor));
return _cor;
}
_cor = Color.FromHex("#3c3c3b");
OnPropertyChanged(nameof(Cor));
return _cor;
}
set
{
_cor = value;
OnPropertyChanged(nameof(Cor));
}
}
#region INotifyPropertyChanged Implementation
void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged == null)
return;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
EDIT2: После изменения конструктора модели вида, вид обновлялся, но я не мог ничего сделать. Это выглядело так, как будто оно было заблокировано.
public ConvidarAmigosEmailViewModel()
{
ContactosComEmail = new ObservableCollection<Contacto>();
Task.Run(async () => { await ObterContactos(); });
}
EDIT3: Потратив много времени, пытаясь выяснить, что происходит, я пришел к выводу, что проблема была в этой части кода:
<Frame Grid.Row="1" Grid.Column="2" Padding="8" BackgroundColor="#f2f2f2" Grid.RowSpan="2" HorizontalOptions="CenterAndExpand" VerticalOptions="Center" HasShadow="False">
<Frame.CornerRadius>
<OnPlatform Android="80" iOS="10"/>
</Frame.CornerRadius>
<Grid HorizontalOptions="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="6*"/>
</Grid.ColumnDefinitions>
<FFIL:CachedImage Source="{Binding Icone}" HeightRequest="8" WidthRequest="8" BackgroundColor="Transparent" Grid.Column="0"
DownsampleToViewSize="True" BitmapOptimizations="False" Aspect="AspectFit" RetryCount="0" RetryDelay="250"
LoadingPriority="Highest" HorizontalOptions="Center" VerticalOptions="Center" FadeAnimationForCachedImages="False"/>
<Label Text="{Binding Opcao}" FontFamily="{StaticResource RegularFont}" FontSize="8" TextColor="{Binding Cor}" Grid.Column="1" VerticalOptions="Center"/>
</Grid>
<Frame.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Command="{Binding Source={x:Reference pagina},Path=BindingContext.SeleccionarContacto}" CommandParameter="{Binding .}"/>
</Frame.GestureRecognizers>
</Frame>
Я не знаю, почему это происходит ...