Content shown twice

Hi everyone

I've created a framework of my own to create different overlays by using a RelativeLayout: 1 for the background image of the page, 1 overlay for the actual XAML-code of any ContentPage and 1 overlay with an ActivityIndicator.
In Android and UWP everything works fine, but for some reason iOS draws the Content of the ContentPage twice. In the Screenshot2.png you can see that I've scrolled down a bit and that the content is drawn twice; one part scrolls down as expected but the other part just remains at the exact same place.



The overlays are created in the OnAppearing-method and looks somewhat like this:

public class BaseContentPage : ContentPage
{
    public View MyContent
    {
        get { return Content; }
        set
        {
            Device.BeginInvokeOnMainThread(() =>
            {
                Content = new CustomViewOverlay().CreateOverlay(value);
            });
        }
    }

    protected override void OnAppearing
    {
        base.OnAppearing();
        MyContent = Content;
    }
}

public class CustomViewOverlay
{
    public CustomViewOverlay()
    {
        _backgroundImage = ..;
        _activityIndicator = ...;
    }

    public View CreateOverlay(View currentPageContent)
    {
        var relativeLayout = new RelativeLayout();

        AddBackgroundImageToRelativeLayout(relativeLayout);
        AddCurrentPageContentToRelativeLayout(relativeLayout, currentPageContent);
        AddActivityIndicatorToRelativeLayout(relativeLayout);

        return relativeLayout;
    }

    private void AddBackgroundImageToRelativeLayout(RelativeLayout relativeLayout)
    {
        relativeLayout.Children.Add(_backgroundImage;
            Constraint.RelativeToParent((parent) => parent.X),
            Constraint.RelativeToParent((parent) => parent.Y),
            Constraint.RelativeToParent((parent) => parent.Width),
            Constraint.RelativeToParent((parent) => parent.Height)
        );
    }

        private void AddCurrentPageContentToRelativeLayout(RelativeLayout relativeLayout, View currentPageContent)
        {
            relativeLayout.Children.Add(currentPageContent,
                Constraint.RelativeToParent((parent) => parent.X),
                Constraint.RelativeToParent((parent) => parent.Y),
                Constraint.RelativeToParent((parent) => parent.Width),
                Constraint.RelativeToParent((parent) => parent.Height)
            );
        }

    private void AddActivityIndicatorToRelativeLayout(RelativeLayout relativeLayout)
    {
        relativeLayout.Children.Add(_activityIndicator,
            Constraint.RelativeToParent((parent) =>
            {
                return (parent.Width / 2) - (_activityIndicator.Width / 2);
            }),
            Constraint.RelativeToParent((parent) =>
            {
                return (parent.Height / 2) - (_activityIndicator.Height / 2);
            })
        );
    }
}

What am I doing wrong? How can this be solved?

Answers

  • StefaanAvonds.3725StefaanAvonds.3725 USMember ✭✭✭

    UPDATE: I also have a RelativeLayout-renderer for iOS. Else it isn't scrollable:

        public class BaseRelativeLayoutRenderer : ViewRenderer<RelativeLayout, UIView>
        {
            protected override void OnElementChanged(ElementChangedEventArgs<RelativeLayout> e)
            {
                base.OnElementChanged(e);
    
                if (e.NewElement == null) return;
    
                var fullSize = new CoreGraphics.CGRect(e.NewElement.X, e.NewElement.Y, e.NewElement.Width, e.NewElement.Height);
                var native = new UICollectionView(fullSize, new UICollectionViewLayout());
    
                foreach (var item in e.NewElement.Children)
                {
                    var renderer = Platform.CreateRenderer(item);
                    var size = new CoreGraphics.CGRect(item.X, item.Y, item.Width, item.Height);
                    renderer.NativeView.Frame = size;
    
                    renderer.NativeView.AutoresizingMask = UIViewAutoresizing.All;
                    renderer.NativeView.ContentMode = UIViewContentMode.ScaleToFill;
    
                    renderer.Element.Layout(size.ToRectangle());
    
                    renderer.NativeView.SetNeedsLayout();
    
                    native.Add(renderer.NativeView);
                }
    
                SetNativeControl(native);
            }
        }
    
  • JohnMillerJohnMiller USForum Administrator, Xamarin Team Xamurai

    @StefaanAvonds.3725,

    I think the best option would be to remove the renderer and let's try and resolve why iOS is not allowing input. Could you explain in more detail what issue you are having with the user input? Is scrolling not working, or some other entry?

  • StefaanAvonds.3725StefaanAvonds.3725 USMember ✭✭✭

    @JohnMiller

    If the custom renderer isn't used, no user input is available: an Entry cannot be focused, a Button is not pressable, the ScrollView is unable to scroll, ...
    By using the custom renderer it does work.

    In the renderer I can see that NativeView.Subviews indeed rendered everything from inside the RelativeLayout declared in the PCL. However the user input is unavailable (as stated above). By calling SetNativeControl(native) I re-render everything from the RelativeLayout and thus create a new view in NativeView.Subviews which does respond to user input. The downside of this is that the other views in NativeView.Subviews remain at the exact same location as you can see in the screenshots above.

    I did create an ugly work-around; after SetNativeControl(native) I do the following:

    NativeView.Subviews[1].Hidden = true;
    

    Because of my own framework I create a RelativeLayout with different overlays:
    1. Overlay with the BackgroundImage
    2. Overlay with the actual XAML-code specific for every ContentPage
    3. Overlay with an ActivityIndicator at center screen

    So I know for sure that the second subview (index = 1) must be the actual content of the XAML-page. If this subview is set to Hidden = true the duplicate view is suddenly invisible.

    This really is an ugle solution and I know for sure I'm doing something wrong. What could be a solution other than this work-around?

  • JohnMillerJohnMiller USForum Administrator, Xamarin Team Xamurai

    @StefaanAvonds.3725,

    Yeah, you shouldn't need to use a renderer like this. The use of a UICollectionView like this is very odd.

    My guess right now is that something is intercepting the user input and not passing it down to the child views. Can you confirm if you have the same issue with the following:

    • Remove the renderer (comment it out so it's not used)
    • Comment out the AddBackgroundImageToRelativeLayout and AddActivityIndicatorToRelativeLayout in the CreateOverlay method so just the page content is used.

    Then, confirm if scrolling and user input works as expected. I'm wondering if the layered content is causing an issue and I want to narrow it down.

    Also, make sure you have the problem with the latest Xamarin.Forms version (2.3.3.180).

  • StefaanAvonds.3725StefaanAvonds.3725 USMember ✭✭✭

    @JohnMiller

    Now the renderer isn't used anymore and only AddCurrentPageToRelativeLayout is executed. The XAML is still not responding to user-input.

    The first page of my application is a login-screen so it doesn't need a ScrollView. However if I do add a ScrollView (and some dummy Labels) to it, nothing is shown...

    XAML

        <StackLayout Orientation="Vertical">
          <StackLayout Orientation="Vertical">
            <Label Text="{resources:Translate LoginPageLabelUsername}" />
            <Entry Text="{Binding UsernameText, Mode=TwoWay}" StyleId="UserName" Placeholder="{resources:Translate LoginPageLabelUsername}" IsEnabled="{Binding IsEntryControlEnabled, Mode=TwoWay}" TextChanged="txtUsername_TextChanged" Completed="txtUsername_Completed" />
    
            <Label Text="{resources:Translate LoginPageLabelPassword}" />
            <Entry Text="{Binding PasswordText, Mode=TwoWay}" StyleId="Password" Placeholder="{resources:Translate LoginPageLabelPassword}" IsEnabled="{Binding IsEntryControlEnabled, Mode=TwoWay}" IsPassword="True" TextChanged="txtPassword_TextChanged" Completed="txtPassword_Completed" />
    
            <StackLayout Orientation="Horizontal" HorizontalOptions="EndAndExpand">
              <Label Text="{resources:Translate LoginPageRememberCredentials}" FontSize="Micro" />
              <Switch IsToggled="{Binding RememberCredentials}" />
            </StackLayout>
          </StackLayout>
    
          <StackLayout>
            <Label Text="{Binding InformationText, Mode=TwoWay}" TextColor="Red" FontSize="Medium" HorizontalOptions="CenterAndExpand" />
          </StackLayout>
    
          <StackLayout>
            <Button Text="{resources:Translate LoginPageButtonLogin}" IsEnabled="{Binding IsButtonLoginEnabled, Mode=TwoWay}" Clicked="btnLogin_Clicked" />
          </StackLayout>
    
          <StackLayout HorizontalOptions="End" Padding="0, 20, 0, 0">
            <Button Text="{resources:Translate LoginPageButtonSyncLogins}" IsEnabled="{Binding IsButtonSyncLoginsEnabled, Mode=TwoWay}" WidthRequest="75" Clicked="btnSyncLogins_Clicked" />
          </StackLayout>
    
          <Label Text="TESTTESTESTESTESTESTESTESTESTESTESTESTESSTES" />
          <Label Text="TESTTESTESTESTESTESTESTESTESTESTESTESTESSTES" />
          <Label Text="TESTTESTESTESTESTESTESTESTESTESTESTESTESSTES" />
          <Label Text="TESTTESTESTESTESTESTESTESTESTESTESTESTESSTES" />
          <Label Text="TESTTESTESTESTESTESTESTESTESTESTESTESTESSTES" />
          <Label Text="TESTTESTESTESTESTESTESTESTESTESTESTESTESSTES" />
          <Label Text="TESTTESTESTESTESTESTESTESTESTESTESTESTESSTES" />
          <Label Text="TESTTESTESTESTESTESTESTESTESTESTESTESTESSTES" />
        </StackLayout>
    

    This shows the following:

    With a ScrollView around everything:

    What am I doing wrong? Is it bad practice to use that many StackLayouts ...?

  • StefaanAvonds.3725StefaanAvonds.3725 USMember ✭✭✭

    @JohnMiller

    I did a little test and found something rather strange.

    When using the following XAML everything works fine (page is shown properly, page is scrollable, entries can be focused, ...):

      <RelativeLayout>
        <ScrollView RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent, Property=X, Factor=1}"
                    RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent, Property=Y, Factor=1}"
                    RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToParent, Property=Width, Factor=1}"
                    RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToParent, Property=Height, Factor=1}">
          <StackLayout Orientation="Vertical">
            <Label Text="{resources:Translate LoginPageLabelUsername}" />
            <Entry Text="{Binding UsernameText, Mode=TwoWay}" StyleId="UserName" Placeholder="{resources:Translate LoginPageLabelUsername}" IsEnabled="{Binding IsEntryControlEnabled, Mode=TwoWay}" TextChanged="txtUsername_TextChanged" Completed="txtUsername_Completed" />
    
            <Label Text="{resources:Translate LoginPageLabelPassword}" />
            <Entry x:Name="txtPassword" Text="{Binding PasswordText, Mode=TwoWay}" StyleId="Password" Placeholder="{resources:Translate LoginPageLabelPassword}" IsEnabled="{Binding IsEntryControlEnabled, Mode=TwoWay}" IsPassword="True" TextChanged="txtPassword_TextChanged" Completed="txtPassword_Completed" />
    
            <StackLayout x:Name="stackRememberCredentials" Orientation="Horizontal" HorizontalOptions="EndAndExpand">
              <Label Text="{resources:Translate LoginPageRememberCredentials}" FontSize="Micro" />
              <Switch IsToggled="{Binding RememberCredentials}" />
            </StackLayout>
    
            <Label Text="{Binding InformationText, Mode=TwoWay}" TextColor="Red" FontSize="Medium" HorizontalOptions="CenterAndExpand" />
    
            <Button Text="{resources:Translate LoginPageButtonLogin}" IsEnabled="{Binding IsButtonLoginEnabled, Mode=TwoWay}" Clicked="btnLogin_Clicked" />
    
            <StackLayout HorizontalOptions="End" Padding="0, 20, 0, 0">
              <Button Text="{resources:Translate LoginPageButtonSyncLogins}" IsEnabled="{Binding IsButtonSyncLoginsEnabled, Mode=TwoWay}" WidthRequest="75" Clicked="btnSyncLogins_Clicked" />
            </StackLayout>
    
            <Label Text="TESTTESTESTESTESTESTESTESTESTESTESTESTESSTES" />
            <Label Text="TESTTESTESTESTESTESTESTESTESTESTESTESTESSTES" />
            <Label Text="TESTTESTESTESTESTESTESTESTESTESTESTESTESSTES" />
              <Label Text="TESTTESTESTESTESTESTESTESTESTESTESTESTESSTES" />
              <Label Text="TESTTESTESTESTESTESTESTESTESTESTESTESTESSTES" />
            <Label Text="TESTTESTESTESTESTESTESTESTESTESTESTESTESSTES" />
              <Label Text="TESTTESTESTESTESTESTESTESTESTESTESTESTESSTES" />
              <Label Text="TESTTESTESTESTESTESTESTESTESTESTESTESTESSTES" />
          </StackLayout>
        </ScrollView>
      </RelativeLayout>
    

    In this case, no overlay is added.

    However if I remove the RelativeLayout and its Constraints from the ScrollView, and also execute the code of the overlays (which look the same to my only written in C#) I get a blank page ...

    How is this possible?

  • JohnMillerJohnMiller USForum Administrator, Xamarin Team Xamurai

    @StefaanAvonds.3725,

    What am I doing wrong? Is it bad practice to use that many StackLayouts ...?

    You don't need this many StackLayout controls for what you are doing. It looks like all you're doing is positioning the child inside the StackLayout and adding some padding. You can instead do that directly on the child. For example, this is your original:

    <StackLayout HorizontalOptions="End" Padding="0, 20, 0, 0">
              <Button Text="{resources:Translate LoginPageButtonSyncLogins}" IsEnabled="{Binding IsButtonSyncLoginsEnabled, Mode=TwoWay}" WidthRequest="75" Clicked="btnSyncLogins_Clicked" />
            </StackLayout>
    

    You can instead do this:

    <Button Text="{resources:Translate LoginPageButtonSyncLogins}" IsEnabled="{Binding IsButtonSyncLoginsEnabled, Mode=TwoWay}" WidthRequest="75" Clicked="btnSyncLogins_Clicked" HorizontalOptions="End" Margin="0,20,0,0"/>
    

    I added the HorizontalOptions directly to the control and added a Margin instead of using Padding for the parent.

    How is this possible?

    My guess is that a constraint is not correct (or some other error) and it's causing the page to not be able to render the content. Could you create a .zip with the solution in it so I can take a look? Delete the "bin" "obj" folders. Also, delete the content in the "packages" folders to reduce the size of the .zip and upload here. I'll run it and investigate.

Sign In or Register to comment.