0

We have an instant message app. We want to scroll automatically when a message is received so the new message is visible. Currently, when the first message that requires scrolling is received, the page does not scroll. That is, the new message is hidden behind our input controls. It requires another message or the user to adjust the scrollview and then it works properly.

Our xaml is something like this:

<ContentPage>
  <StackLayout x:Name="mainStackLayout">
    <customControls:NavBar />
    <ScrollView x:Name="mainScrollView">
      <StackLayout x:Name="mainScrollViewStackLayout">
        <ScrollView x:Name="messagesScrollView">
          <StackLayout x:Name="messagesScrollViewContentStackLayout">
            <!-- Messages are programmatically inserted here -->
          </StackLayout>
        </ScrollView>
      </StackLayout>
    </ScrollView>
    <StackLayout>
      <!-- a couple buttons/inputs for sending messages -->
    </StackLayout>
  </StackLayout>
</ContentPage>

And the code we call to scroll looks like this:

public void ScrollMessagesToEnd()
{

    StackLayout messagesContent = (messagesScrollView.Content as StackLayout);
    var frame = messagesContent.Children.LastOrDefault();

    if (frame != null)
    {
            messagesScrollView.ScrollToAsync(frame, ScrollToPosition.MakeVisible, true);
    }
}
6
  • where are your messages going? Is there listView somewhere? Commented Jun 9, 2017 at 19:27
  • @YuriS They are going into the StackLayout messagesScrollViewContentStackLayout programmatically. I'll add a comment so it's clear. Commented Jun 9, 2017 at 19:37
  • Inserted as what? Each message is separate layout? Why don't you use ListView? And if you do use it then you don't need ScrollView. BTW, Xamarin strongly advices against ListView inside scroll view and you are doing messages scroll view inside main scroll view. This is kind of bad design. Can you simplify it somehow? I would do one stack layout having 3 items: nav bar, listview and send stack layout. List view will be the only one scrollable. Let me know what you think. I kind of have a solution for you already but I would like to make it right :-) Commented Jun 9, 2017 at 20:13
  • Each message inserted is a custom view that extends Frame. I was not the principal dev on this page, so I can't say why we didn't use a list view other than (if I understand correctly) custom views in a listview are a pain. Again, if I remember what the original dev said correctly, we need the double scrollview because iOS was not adjusting for the keyboard correctly, but the mainScrollView allowed the page to move and make room for it. Commented Jun 9, 2017 at 20:18
  • Can you provide code (so I don't have to write it) to populate messages with test data? Not sure if we can make it work with nested ScrollViews. I would rather find a solution for iOS keyboard Commented Jun 9, 2017 at 20:20

2 Answers 2

3

Regardless of the solution, ScrollMessagesToEnd should be made async and you need to await ScrollToAsync(). Since you are now awaiting ScrollToAsync(), I would suggest explicitly running it on the UI thread.

After that, is done, you might try adding await Task.Delay(300) to the top of your method, I have had to do that before.

You may also want to try enabling and disabling animation which I have found can effect scrolling but test it on each platform since the effect will probably be different on each.

public async Task ScrollMessagesToEndAsync() //Adding Async to method name, also try to return Task and await this method in the calling code as well
{
    await Task.Delay(300); //Sometimes code runs too fast and a delay is needed, you may test whether only a specific platform needs the delay

    StackLayout messagesContent = (messagesScrollView.Content as StackLayout);
    var frame = messagesContent.Children.LastOrDefault();

    if (frame != null)
    {
        Device.BeginInvokeOnMainThread(async () => await messagesScrollView.ScrollToAsync(frame, ScrollToPosition.MakeVisible, true)); //You could try passing in false to disable animation and see if that helps
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

Unfortunately, none of this has helped. It seems highly related to our input StackLayout, as if Xamarin can't tell we need scrolling if this "hides" the message. When it finally does scroll (another message is sent or the user interacts in a way that triggers it), however, it is positioned correctly.
@GarrettDanielDeMeyer O damn I did not notice you are using a ScrollView within a ScrollView. That could be the issue, as Xamarin Forms does not support that, but I cannot say for sure. Is this happening on Android or iOS? You might be able to get away with using a grouped ListView depending on what you are going for in your layout or possibly even a popup type of control, which would allow you to scroll.
I left a comment on the question just now explaining to another commenter why we have the nested ScrollViews. Look it over and see if it helps your understanding and if we misunderstood something when adding the mainScrollView
@GarrettDanielDeMeyer Makes sense. The issue is when you have your input below the inner ScrollView (and your input is not inside another, outer ScrollView) the keyboard covers everything. Try removing the outer ScrollView to see if scrolling is fixed. If so, & your issue is iOS only, you could try this to fix the keyboard overlap issue. If that does not work there are a few other suggestions here
@GarrettDanielDeMeyer Yes, removing top-level scrolling fixes the problem. See my comment above
2

I would strongly recommend NOT use nested scroll views but if you keep them then here is the solution.

Instead

messagesScrollView.ScrollToAsync(frame, ScrollToPosition.MakeVisible, true);

Use

mainScrollView.ScrollToAsync(frame, ScrollToPosition.End, true);

or

mainScrollView.ScrollToAsync(frame, ScrollToPosition.MakeVisible, true);

4 Comments

I removed the mainScrollView from my xaml page and it fixed my scrolling problem! It introduced a couple minor UI spacing issues, but I believe I can work through this. BTW, your first and last line of code in this answer look identical to me. Although I didn't need to try them, I don't see the difference and you may want to clarify in case it helps someone else down the road.
@GarrettDanielDeMeyer How did you handle the keyboard covering the UI when the user taps on the Entry that is below the ListView?
@GarrettDanielDeMeyer Typo. I fixed the answer. If you use ScrollToPosition.End you might not need to look for a frame but I have not tested that
@hvaughan3 In such case I register for keyboard events and move the Entry above the keyboard when it shown

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.