NativeScript: RadAutoCompleteTextView crashes on some of IOS 14.2 devices

Environment Provide version numbers for the following components (information can be retrieved by running tns info in your project folder or by inspecting the package.json of the project):

  • CLI: 7.0.11
  • Cross-platform modules: See below
  • Android Runtime: N/A
  • iOS Runtime: 14.2
  • XCode Version: 12.2 (released on 11/12/20)
  • Plugin(s): RadAutoCompleteTextView
  • package.json
 "dependencies": {
    "@angular-devkit/schematics": "^10.2.0",
    "@angular/animations": "10.2.3",
    "@angular/common": "10.2.3",
    "@angular/compiler": "10.2.3",
    "@angular/core": "10.2.3",
    "@angular/forms": "10.2.3",
    "@angular/http": "~8.0.0-beta.10",
    "@angular/platform-browser": "10.2.3",
    "@angular/platform-browser-dynamic": "10.2.3",
    "@angular/router": "10.2.3",
    "@nativescript/schematics": "10.1.0",
    "@nativescript/angular": "10.1.7",
    "@nativescript/core": "~7.0.13",
    "nativescript-theme-core": "2.0.24",
    "nativescript-ui-autocomplete": "7.0.2",
    "nativescript-ui-sidedrawer": "9.0.3",
    "reflect-metadata": "~0.1.13",
    "rxjs": "~6.6.3",
    "zone.js": "~0.11.3"
  },
  "devDependencies": {
    "@angular/compiler-cli": "10.2.3",
    "@nativescript/android": "7.0.1",
    "@nativescript/ios": "7.0.5",
    "@nativescript/webpack": "~3.0.8",
    "@ngtools/webpack": "~10.2.0",
    "node-sass": "4.12.0",
    "typescript": "3.9.7"

Describe the bug NativeScript encountered a fatal error: Uncaught TypeError: Cannot read property ‘itemView’ of undefined at push… /node_modules/nativescript-ui-autocomplete/ui-autocomplete.js.SuggestionViewCell.layoutSubviews( file: node_modules/nativescript-ui-autocomplete/ui-autocomplete.ios.js:59:0)

To Reproduce *** HTML Layout ***

        <StackLayout tkExampleTitle tkToggleNavButton padding="5">
            <Label class="font-italic m-t-10" text="Brand Name"></Label>
            <RadAutoCompleteTextView #autocompleteBrandsRef suggestMode="Suggest" displayMode="Plain" completionMode="Contains" (textChanged)="brandNameChanged($event)">
                <SuggestionView tkAutoCompleteSuggestionView suggestionViewHeight="150">
                    <ng-template tkSuggestionItemTemplate let-item="item">
                        <StackLayout orientation="vertical" padding="10">
                            <Label [text]="item.text"></Label>
                        </StackLayout>
                    </ng-template>
                </SuggestionView>
            </RadAutoCompleteTextView>
            <StackLayout ios:class="hr-dark"></StackLayout>
        </StackLayout>

*** Typescript class ***

import { Component, Inject, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ModalDialogOptions, ModalDialogService } from "nativescript-angular/modal-dialog";
import { TokenModel } from "nativescript-ui-autocomplete";
import { RadAutoCompleteTextViewComponent } from "nativescript-ui-autocomplete/angular/autocomplete-directives";
import { PageBase } from "~/bases/page.base";
import { SelectDialog } from "~/dialogs/select/select.dialog";
import { isString } from "~/helpers/string.helper";
import { SearchFoodRequestModel } from "~/models/serch.food.request.model";
import { TestModel } from "~/models/test.model";
import { TestRequestModel } from "~/models/test.request.model";
import { SearchFoodService } from "~/services/search.food.service";
import { SharedService } from "~/services/shared.service";

@Component({
    selector: "CheckFood",
    moduleId: module.id,
    templateUrl: "./check.food.component.html",
    styleUrls: ["./check.food.component.scss"]
})
export class CheckFoodComponent extends PageBase implements OnInit {

    @ViewChild("autocompleteBrandsRef", { static: true })
    private autocompleteBrands: RadAutoCompleteTextViewComponent;
    private readonly title: string = "Check Specific Food";
    private readonly count: number = 10;
    private isOpenedDialog: boolean;
    private brandName: string;
    private products: Array<string> = [];
    public productName: string;
    public testModel: TestModel;

    constructor(
        @Inject(SharedService) private sharedService: SharedService,
        @Inject(SearchFoodService) private searchFoodService: SearchFoodService,
        @Inject(ModalDialogService) private modalDialogService: ModalDialogService,
        @Inject(ViewContainerRef) private viewContainerRef: ViewContainerRef
    ) {
        super();
    }

    ngOnInit(): void {
        this.sharedService.titleSubject.next(this.title);
        this.setAutocompleteBrands();
    }

    private setAutocompleteBrands = (): void => {
        if (this.autocompleteBrands != null) {
            this.autocompleteBrands.nativeElement.loadSuggestionsAsync = (value: string): Promise<Array<TokenModel>> => {
                const promise: Promise<Array<TokenModel>> = new Promise<Array<TokenModel>>((resolve): void => {
                    if (isString(value, 1)) {
                        const request: SearchFoodRequestModel = new SearchFoodRequestModel({
                            searchText: value,
                            limit: this.count
                        });
                        this.searchFoodService.searchBrands(request).subscribe((success): void => {
                            resolve(this.getTokenModels(success));
                        }, (error): void => {
                            resolve([]);
                            this.errorMessage = this.getErrorMessage(error);
                        })
                    }
                    else {
                        resolve([]);
                    }
                });
                return promise;
            }
        }
    }

    public getTokenModels = (success: Array<string>): Array<TokenModel> => {
        let result: Array<TokenModel> = [];
        if (Array.isArray(success)) {
            result = success.map((item): TokenModel => {
                return new TokenModel(item, null);
            });
        }
        return result;
    }

    private getProducts = (): void => {
        if (isString(this.brandName, 1)) {
            this.resetProducts();
            this.searchFoodService.getProducts(this.brandName).subscribe((success) => {
                if (Array.isArray(success)) {
                    this.products = success;
                }
            });
        }
    }

    private getTest = (): void => {
        if (isString(this.brandName, 1) && isString(this.productName, 1)) {
            const model = new TestRequestModel({
                brandName: this.brandName,
                productName: this.productName
            });
            this.searchFoodService.getTest(model).subscribe((success) => {
                this.testModel = success;
            });
        }
        else {
            this.testModel = null;
        }
    }

    public brandNameChanged = (event): void => {
        if (event != null) {
            this.brandName = event.text;
            this.getProducts();
        }
    }

    public get isReadOnly(): boolean {
        let result: boolean = true;
        if (isString(this.brandName, 1)) {
            result = false;
        }
        return result;
    }

    private resetProducts = (): void => {
        this.products = [];
        this.productName = null;
        this.testModel = null;
    }

    private resetAutocompleteBrands = (): void => {
        if (this.autocompleteBrands != null && this.autocompleteBrands.autoCompleteTextView != null) {
            this.autocompleteBrands.autoCompleteTextView.resetAutoComplete();
        }
    }

    public reset = (): void => {
        this.resetProducts();
        this.brandName = null;
        this.resetAutocompleteBrands();
    }

    public openSelectDialog = (): void => {
        if (this.products.length != 0 && !this.isOpenedDialog) {
            const options: ModalDialogOptions = {
                viewContainerRef: this.viewContainerRef,
                fullscreen: true,
                context: {
                    items: this.products,
                    selected: this.productName
                }
            };
            this.isOpenedDialog = true;
            this.modalDialogService.showModal(SelectDialog, options).then((value): void => {
                this.productName = value;
                this.getTest();
                this.isOpenedDialog = false;
            });
        }
    }
}

Expected behavior Code and Layout working fine on all versions of Android and IOS 14.1 and below.

Sample project See above

Additional context The error occurs upon a user type a first letter and the data come across. Emulator iPhone.

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 4
  • Comments: 25

Most upvoted comments

@aaltotsky @rnmhdn @kubilaycaglayan

I have modified in package and its work for me could you please check if its work for you or not. you can download package from here

and then run npm install nativescript-ui-autocomplete-7.0.2.tgz which will install packge locally.

@bkulov I cannot provide here the full implementation, as the work been done for our customer. However, I will provide the basic idea: We create a text control with a list control:

<StackLayout class="form">
                <GridLayout class="input-border" columns="*,auto" height="50" verticalAlignment="center">
                    <TextField [(ngModel)]="userSearchName" hint="Enter ..." col="0" class="-border input"
                               (textChange)="dropDownVisibleChange($event)" verticalAlignment="top" color="#000000"></TextField>
                    <Label col="1" class="emoji" text="&#x274C;" verticalAlignment="center" (tap)="reset()" class="p-0 m-0"></Label>
                </GridLayout>
            </StackLayout>


            <ScrollView visibility="{{showList}}" height="600px"
                        class="scroll-view">

                <ListView [items]="listItems" (itemTap)="onItemTap($event)"
                          class="-border scroll-view" style="height:550px">
                    <ng-template let-item="item">
                        <FlexboxLayout flexDirection="row">
                            <Label [text]="item" class="t-12"
                                   verticalAlignment="center" style="width: 100%">
                            </Label>
                        </FlexboxLayout>
                    </ng-template>
                </ListView>

            </ScrollView>

And the code:

dropDownVisibleChange = (args: ItemEventData) => {
        if (args != null) {
            let textField = <TextField>args.object;

            this.loadSuggestionsAsync(textField.text);
            this.userSearchName = textField.text;
             this.getProducts();
        }
    }

onItemTap(args: ItemEventData): void {
        this.selectedListPickerIndex = args.index;
        this.userSearchName = this.listItems[this.selectedListPickerIndex];
        this.showList = "collapsed";
    }

@jitendraP-ashutec We moved away from using this package. We replace it with a combination of the 2 standard controls. Our solution is working fine for us. Obviously, this package is not supported by vendor as the issue been reported 6 months ago.