DLToolkit.Forms.Controls icon indicating copy to clipboard operation
DLToolkit.Forms.Controls copied to clipboard

Extra new elements from OnElementChanged in UIView on iOS

Open j2bmw opened this issue 4 years ago • 6 comments

I am doing video matrix views (1x1, 2x2, 3x3 and 4x4 views) using FlowListView. For iOS app, UIView renderer is used to integrate with a third party video SDK. Here is the FlowListView.

` <flv:FlowListView x:Name="VideoFlowList" FlowColumnCount="{Binding ColumnCount}" RowHeight="{Binding RowHeight, Mode=TwoWay}" SeparatorVisibility="None" HasUnevenRows="false" BackgroundColor="Transparent" FlowColumnMinWidth="80" FlowTotalRecords="{Binding TotalRecords, Mode=TwoWay}" FlowItemsSource="{Binding Items}">

	<flv:FlowListView.FlowColumnTemplate>
		<DataTemplate>
			<Grid x:Name="VideoGrid" Padding="2" BackgroundColor="{Binding SelectedBorderColour, Mode=TwoWay}" RowSpacing="1" ColumnSpacing="1"
				  IsVisible="{Binding Visible}">
				<Grid.RowDefinitions>
					<RowDefinition Height="*" />
				</Grid.RowDefinitions>
				<Grid.ColumnDefinitions>
					<ColumnDefinition Width="*" />
				</Grid.ColumnDefinitions>
				<video:CameraView x:Name="MyCameraView" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" BackgroundColor="Black" />
				<Image x:Name="AddIcon" Source="panel_add.png" Grid.Row="0" Grid.Column="0" IsVisible="{Binding CameraNotAssigned}"
					   HorizontalOptions="Center" VerticalOptions="Center" Aspect="AspectFit" BackgroundColor="Transparent" WidthRequest="50" HeightRequest="50">
					<Image.GestureRecognizers>
						<TapGestureRecognizer Command="{Binding BindingContext.AddCameraCommand, Source={x:Reference BrowseItemsPage}}" 
											  CommandParameter="{x:Reference VideoGrid}" NumberOfTapsRequired="1" />
					</Image.GestureRecognizers>
				</Image>

				<Label x:Name="Label" HorizontalOptions="Fill" HorizontalTextAlignment="Center" VerticalOptions="End"
					   BackgroundColor="Silver" Opacity="0.5" Text="{Binding CameraName, Mode=TwoWay}" TextColor="Black"/>
				<!--<Grid.GestureRecognizers>
					<TapGestureRecognizer Command="{Binding BindingContext.VideoGridTappedCommand, Source={x:Reference BrowseItemsPage}}" 
										  CommandParameter="{x:Reference VideoGrid}" NumberOfTapsRequired="1" />
				</Grid.GestureRecognizers>-->
			</Grid>
		</DataTemplate>
	</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>`

Here is the code of the ViewRenderer.

`public class VideoRender : ViewRenderer<CameraView, UIVideoView> { private UIVideoView _videoView;

protected override void OnElementChanged(ElementChangedEventArgs<CameraView> e)
{
    base.OnElementChanged(e);
    if (e.OldElement != null)
    {
        //Unsubscribe
        _videoView.Tapped -= OnVideoViewTapped;
        Log.Debug($"OldElement CameraIndex {e.OldElement.CameraIndex}, PlayWndHandle {e.OldElement.PlayWndHandle}");
    }
    if (e.NewElement != null)
    {
        //Control is TNativeView, CameraView
        if (Control == null)
        {
            _videoView = new UIVideoView(Element);
            SetNativeControl(_videoView);
        }

        //Element.PlayWndHandle = Handle;

        if (_videoView.Subviews.Length > 0)
        {
            Element.PlayWndHandle = _videoView.Subviews[0].Handle;
        }
        App.PlayWndHandles.Add(Element.PlayWndHandle);
        Log.Debug($"NewElement CameraIndex {Element.CameraIndex}, PlayWndHandle {Element.PlayWndHandle}");
        Log.Debug($"App.PlayWndHandles[{App.PlayWndHandles.Count - 1}]: {Element.PlayWndHandle}");

        // Subscribe
        _videoView.Tapped += OnVideoViewTapped;
    }
}

}`

I added a subview in the UIView / UIVideoView and play the video in the subview.

void Initialize() { //place the video in subview var subView = new UIView(); subView.UserInteractionEnabled = true; AddSubview(subView); if (Subviews.Length > 0) { Subviews[0].Frame = new CoreGraphics.CGRect(0, 0, 100, 100); } }

I load all 16 items first. Then I use visibility binding to show / hide the items based on the the number of views in the matrix. The number of new elements from OnElementChanged events is 2, 6, 12 and 16 for 1, 4, 9 and 16 views respectively. Why 2, 6, 12 instead of 1, 4 and 9? It seems there is an extra row hidden for the first three?

Please note that the number of surface holders created with Android surface view is always correct each time. When switching to 1, 4, 9 and 16 views, there are 1, 4, 9 and 16 surface holders created respectively.

This approach is easier than loading just 4 items first and then adding or removing accordingly. See Xamarin forms iOS UIView Renderer intermittent OnElementChanged in some cases: https://stackoverflow.com/questions/63107557/xamarin-forms-ios-uiview-renderer-intermittent-onelementchanged-in-some-cases

j2bmw avatar Aug 01 '20 01:08 j2bmw

Screenshot_20200728-153012

j2bmw avatar Aug 01 '20 01:08 j2bmw

I tried to clear all ItemsSource items and then add new items. But the problem remains. It seems clearing / reinitializing biding items doesn't help. Please note that If I open a new page on changing view size, it works properly. I also tried to reinitialize the UIView using Init() of NSObject, Initialize(), or even Dispose() or set to null without success.

j2bmw avatar Oct 07 '20 01:10 j2bmw

I tried the sample SimpleGalleryPage and observed similar behaviour. If I remove to n items, only these items have FlowItemDisappearing visibility changed events. I want all items have the disappearing event hence all will be new elements in iOS ViewRenderer after adding more items later. Is this possible? @daniel-luberda, can you please shed light on this?

j2bmw avatar Oct 07 '20 05:10 j2bmw

@daniel-luberda, I believe you can offer your helping hand. I've stuck with this issue for long time.

In Android app, when changing the size of the matrix view, each view element / cell, regardless of the size, does FlowItemDisappearing and then FlowItemAppearing consistently. That's what we want to achieve the same on iOS app.

iOS app's behaviour is different, only some elements in the matrix view do disappearing and appearing. How can I change this behaviour?

j2bmw avatar Oct 13 '20 04:10 j2bmw

In my iOS app, I compared the events in OnElementChanged in the renderer with the events in FlowItemDisappearing and FlowItemAppearing, and found discrepancies between them. For

example, after changing matrix size from 3x3 to 1 and then from 1 to 3x3, the following is observed: In OnElementChanged: 3x3-1: deleted first row, added new element 1, OK

1-3x3: recreated the elements 6-9 in 3rd row. The problems: 1. shouldn't need to update 3rd row; 2. the handles of element 2 and 3 were not updated, remaining 0.

3x3-1: deleted the elements in 2nd row. The video plays OK in single view. But the problem is now the handles for elements 2-6 become 0. It will be a problem for next transition 1-9.

In FlowListItem appearing / disappearing events:

3x3-1: disappearing view index 0-2 and appearing 0 (new element 1), correct

1-3x3: -(3-5), +(0-2), -(6,8), +(3-5), +(6-8), this says new elements in row 1, recreated elements in row 2 and 3. The problem: it doesn't match the events in OnElementChanged. It

appears OnElementChanged missed events.

3x3-1: -(0-2), +(0) new element 1, correct

My problem is the handles of the sub views will go out of sync at the end. If each view element / cell in the new matrix disappears and then appears, there will be no problem at all.

Every tile will be refreshed.

j2bmw avatar Oct 14 '20 02:10 j2bmw

It appears that using 4 separate lists of play window handles (one for each matrix size) can overcome the handle management issue. A single view is specially handled by clearing the handles in the list because the handle will always be removed and re-created.

j2bmw avatar Oct 16 '20 04:10 j2bmw