Nativescript Chat UI iOS Issues

631 views Asked by At

I am creating a chat UI interface in Nativescript and I almost have everything working, but I am having a few issues in iOS that I cannot figure out. Everything in Android works correctly.

Problem 1:

  • When I focus on the TextView and the keyboard opens up I cannot no longer scroll to the top of the chat. All the content seems to shift up (even without using IQKeyboardManager interestly enough).

Problem 2:

  • When I start typing in the TextView it instantly shifts to the bottom of the screen, hidden behind the keyboard and I cannot see what I am typing.

Here is a demo project I created on Playground that shows the problem. Playground Demo Project

Below is a GIF showing both of the issues.

Any help is appreciated. Thanks!

Chat UI Demo

2

There are 2 answers

0
Manoj On BEST ANSWER

Problem 1:

This is because IQKeyboardManager hosts a ScrollView on top of the ViewController (Page) and RadListView has its own scrollable region. The solution would be adjusting the insets when keyboard is active.

The code below takes keyboard height and caches it in application object when its first shown.

import * as application from "@nativescript/core/application";

let observer = application.ios.addNotificationObserver(
    UIKeyboardDidShowNotification,
    notification => {
        application.ios.removeNotificationObserver(
            observer,
            UIKeyboardDidShowNotification
        );
        (application as any)._keyboardHeight = notification.userInfo.valueForKey(
            UIKeyboardFrameBeginUserInfoKey
        ).CGRectValue.size.height;
    }
);

When text field is focused adjust the insets of list view

textFieldFocus() {
        console.log("Focus on TextField");
        (this
            .radList as any)._originalContentInset = this.radList.ios.contentInset;
        if ((application as any)._keyboardHeight) {
            this.radList.ios.contentInset = UIEdgeInsetsMake(
                (application as any)._keyboardHeight,
                0,
                0,
                0
            );
        } else {
            setTimeout(() => this.textFieldFocus(), 100);
        }
    }

Problem 2:

This is because the text view layouts itself when text is updated. Preventing that may keep the scroll position intact. The override below checks if the field is focused and ignores layout calls.

import { TextView } from "@nativescript/core/ui/text-view";

TextView.prototype.requestLayout = function() {
    if (
        !arguments[0] &&
        this.nativeViewProtected &&
        this.nativeViewProtected.isFirstResponder
    ) {
        this.nativeViewProtected.setNeedsLayout();
        IQKeyboardManager.sharedManager().reloadLayoutIfNeeded();
    } else {
        View.prototype.requestLayout.call(this);
    }
};

Updated Playground

0
Sebj On

The above solution did not work for me. This might seems counter-intuitive, but actually the solution is to add a wrapping ScrollView around the main layout (in my case, the direct and only child of my Page).

Take the following example:

<GridLayout rows="*, auto" width="100%" height="100%">
    <ScrollView row="0">
        <StackLayout orientation="vertical">
            <StackLayout *ngFor="let m of messages" [horizontalAlignment]="m.incoming ? 'left' : 'right'">
                <Label class="message" [text]="m.message" [class.incoming]="m.incoming" textWrap="true"></Label>
            </StackLayout>
        </StackLayout>
    </ScrollView>

    <GridLayout row="1" columns="*, auto" class="input-layout">
        <TextView class="text-view" [(ngModel)]="message"></TextView>
        
        <Button column="1" text="Send"></Button>
    </GridLayout>
</GridLayout>

It will become:

<ScrollView>
    <GridLayout rows="*, auto" width="100%" height="100%">
        <ScrollView row="0">
            <StackLayout orientation="vertical">
                <StackLayout *ngFor="let m of messages" [horizontalAlignment]="m.incoming ? 'left' : 'right'">
                    <Label class="message" [text]="m.message" [class.incoming]="m.incoming" textWrap="true"></Label>
                </StackLayout>
            </StackLayout>
        </ScrollView>

        <GridLayout row="1" columns="*, auto" class="input-layout">
            <TextView class="text-view" [(ngModel)]="message"></TextView>
            
            <Button column="1" text="Send"></Button>
        </GridLayout>
    </GridLayout>
</ScrollView>

So yes, you will have a nested ScrollView, ListView or RadListView

Kudos to @beeman and his nativescript-chat-ui sample: https://github.com/beeman/nativescript-chat-ui/blob/master/src/app/messages/containers/message-detail/message-detail.component.html