+3

React Native Unsigned Input: Giải pháp nhập liệu không dấu cho bàn phím Vietnamese và các ngôn ngữ có dấu khác

1. Giới thiệu về plugin

  • Trong các ứng dụng di động, việc nhập liệu chữ không dấu là một yêu cầu phổ biến. Tuy nhiên, việc xử lý nhập liệu này trong React Native có thể gặp một số khó khăn. Để giải quyết vấn đề này, mình đã phát triển thư viện này.

  • Plugin này cung cấp một thành phần nhập liệu dựa trên TextInput của React Native, giúp giới hạn việc nhập liệu chỉ gồm các ký tự không dấu. Điều này đồng nghĩa với việc người dùng chỉ có thể nhập các giá trị số dương và không thể nhập các ký tự không phải số.

2. Cách sử dụng plugin

Để bắt đầu sử dụng plugin này, bạn cần cài đặt nó vào dự án React Native của bạn:

npm install @tdduydev/react-native-unsigned-input

Sau khi cài đặt xong, bạn có thể sử dụng thành phần UnsignedInput trong dự án của mình như sau:

import React from 'react';
import { View } from 'react-native';
import UnsignedInput from '@tdduydev/react-native-unsigned-input';

const App = () => {
  return (
    <View>
      <UnsignedInput />
    </View>
  );
};

export default App;

3. Ưu điểm của plugin

  • Dễ dàng sử dụng: Chỉ cần cài đặt và sử dụng thành phần UnsignedInput trong dự án của bạn.
  • Tích hợp nhanh chóng: Plugin được phát triển dựa trên TextInput của React Native, giúp tích hợp dễ dàng vào các ứng dụng hiện có.
  • Tiết kiệm thời gian: Giảm bớt công việc liên quan đến xử lý nhập liệu chữ không dấu.

4. Nhược điểm

  • Chưa hỗ trợ đa ngôn ngữ: Nếu ứng dụng của bạn cần hỗ trợ đa ngôn ngữ, bạn cần tự thêm tính năng này.
  • Tùy chỉnh giới hạn: Plugin chỉ hỗ trợ nhập liệu chữ không dấu, nếu bạn muốn tùy chỉnh giới hạn nhập liệu (ví dụ: chỉ cho phép nhập số lớn hơn 10), bạn sẽ phải thực hiện điều này bằng cách sửa mã nguồn hoặc thêm các xử lý bổ sung.

5. Một số ví dụ về tùy chỉnh plugin

Ví dụ 1: Thêm placeholder và kiểu nhập liệu

import React from 'react';
import { View } from 'react-native';
import UnsignedInput from '@tdduydev/react-native-unsigned-input';

const App = () => {
  return (
    <View>
      <UnsignedInput
        placeholder="Nhập số không dấu"
        keyboardType="number-pad"
      />
    </View>
  );
};

export default App;

Ví dụ 2: Thêm sự kiện thay đổi giá trị và xử lý nhập liệu

import React, { useState } from 'react';
import { View, Text } from 'react-native';
import UnsignedInput from '@tdduydev/react-native-unsigned-input';

const App = () => {
  const [value, setValue] = useState('');

  const handleChange = (text) => {
    setValue(text);
  };

  return (
    <View>
      <UnsignedInput onChangeText={handleChange} />
      <Text>Giá trị nhập: {value}</Text>
    </View>
  );
};

export default App;

6. Phân tích sâu về code của native ios và native android

native ios

//
//  ReactNativeUnsignedDelegate.m
//  UnsignedInput
//
//  Created by DUY TRAN on 27/03/2023.
//  Copyright © 2023 Facebook. All rights reserved.
//

#import <UIKit/UIKit.h>

@protocol InputListener <UITextFieldDelegate>
@end

@interface ReactNativeUnsignedDelegate : NSObject <UITextFieldDelegate>

@property (nonatomic, weak) id<InputListener> listener;
@property (nonatomic, copy) void (^onChange)(UITextField *textField, NSString *value);

- (instancetype)initWithListener:(id<InputListener>)listener
                        onChange:(void (^)(UITextField *textField, NSString *value))onChange;

@end

@implementation ReactNativeUnsignedDelegate

- (instancetype)initWithListener:(id<InputListener>)listener
                        onChange:(void (^)(UITextField *textField, NSString *value))onChange {
    self = [super init];
    if (self) {
        _listener = listener;
        _onChange = onChange;
    }
    return self;
}

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {

    // Chuẩn hóa chuỗi nhập vào bằng cách loại bỏ dấu tiếng Việt và khoảng trắng
       NSString *normalizedString = [[[string decomposedStringWithCanonicalMapping] stringByFoldingWithOptions:NSDiacriticInsensitiveSearch locale:[NSLocale currentLocale]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
       // Thay thế chuỗi nhập vào bằng chuỗi đã được chuẩn hóa
       textField.text = [textField.text stringByReplacingCharactersInRange:range withString:normalizedString];
    
    self.onChange(textField,textField.text);

    return NO;
}

// MARK: default RNTextInput handlers
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
    return [self.listener respondsToSelector:@selector(textFieldShouldBeginEditing:)] ? [self.listener textFieldShouldBeginEditing:textField] : YES;
}

- (void)textFieldDidBeginEditing:(UITextField *)textField {
    [self.listener textFieldDidBeginEditing:textField];
}

- (BOOL)textFieldShouldEndEditing:(UITextField *)textField {
    return [self.listener respondsToSelector:@selector(textFieldShouldEndEditing:)] ? [self.listener textFieldShouldEndEditing:textField] : YES;
}

- (void)textFieldDidEndEditing:(UITextField *)textField {
    [self.listener textFieldDidEndEditing:textField];
}

- (void)textFieldDidEndEditing:(UITextField *)textField reason:(UITextFieldDidEndEditingReason)reason API_AVAILABLE(ios(10.0)) {
    if ([self.listener respondsToSelector:@selector(textFieldDidEndEditing:reason:)]) {
        [self.listener textFieldDidEndEditing:textField reason:reason];
    } else {
        [self.listener textFieldDidEndEditing:textField];
    }
}

@end

  1. Khai báo và import thư viện:
#import <UIKit/UIKit.h>

Thư viện UIKit được import để sử dụng các thành phần UI của iOS.

  1. Khai báo giao diện InputListener:
@protocol InputListener <UITextFieldDelegate>
@end

Giao diện InputListener kế thừa từ UITextFieldDelegate, cho phép xử lý các sự kiện liên quan đến UITextField.

  1. Khai báo lớp ReactNativeUnsignedDelegate:
@interface ReactNativeUnsignedDelegate : NSObject <UITextFieldDelegate>

@property (nonatomic, weak) id<InputListener> listener;
@property (nonatomic, copy) void (^onChange)(UITextField *textField, NSString *value);

- (instancetype)initWithListener:(id<InputListener>)listener
                        onChange:(void (^)(UITextField *textField, NSString *value))onChange;

@end

Lớp ReactNativeUnsignedDelegate kế thừa từ NSObject và tuân thủ giao diện UITextFieldDelegate. Lớp này có hai thuộc tính

  • listener: Một đối tượng tuân thủ giao diện InputListener, giúp xử lý các sự kiện của UITextField.
  • onChange: Một block được gọi khi giá trị của UITextField thay đổi.

Lớp này cũng có một phương thức khởi tạo initWithListener:onChange.

  1. Thực thi lớp ReactNativeUnsignedDelegate:
@implementation ReactNativeUnsignedDelegate

Phần này chứa các phương thức được gọi khi xử lý sự kiện của UITextField.

  1. Phương thức khởi tạo initWithListener:onChange:
- (instancetype)initWithListener:(id<InputListener>)listener
                        onChange:(void (^)(UITextField *textField, NSString *value))onChange {
    self = [super init];
    if (self) {
        _listener = listener;
        _onChange = onChange;
    }
    return self;
}

Phương thức này khởi tạo một đối tượng của lớp ReactNativeUnsignedDelegate với đối listener và block onChange.

  1. Phương thức textField:shouldChangeCharactersInRange:replacementString:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string 

Phương thức này được gọi khi có sự thay đổi ký tự trong UITextField. Nó chuẩn hóa chuỗi nhập vào bằng cách loại bỏ dấu tiếng Việt và khoảng trắng, sau đó thay thế chuỗi nhập vào bằng chuỗi đã được chuẩn hóa.

  1. Các phương thức xử lý sự kiện của UITextField:

Phần này chứa các phương thức xử lý sự kiện của UITextField,như textFieldShouldBeginEditing, textFieldDidBeginEditing, textFieldShouldEndEditing, textFieldDidEndEditingtextFieldDidEndEditing:reason. Các phương thức này được gọi khi có các sự kiện liên quan đến việc bắt đầu và kết thúc nhập liệu của UITextField.

Ví dụ:

- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
    return [self.listener respondsToSelector:@selector(textFieldShouldBeginEditing:)] ? [self.listener textFieldShouldBeginEditing:textField] : YES;
}

Phương thức textFieldShouldBeginEditing kiểm tra xem listener có thực hiện phương thức textFieldShouldBeginEditing hay không. Nếu có, phương thức này sẽ gọi textFieldShouldBeginEditing của listener, ngược lại trả về YES.

  1. Kết thúc cài đặt lớp ReactNativeUnsignedDelegate:

Tóm lại, đoạn mã trên là một file mã nguồn Objective-C được sử dụng trong ứng dụng React Native để hỗ trợ việc nhập liệu không dấu. Nó tạo ra một lớp tuân thủ giao diện UITextFieldDelegate để xử lý các sự kiện của UITextField, giúp chuẩn hóa chuỗi nhập vào bằng cách loại bỏ dấu tiếng Việt và khoảng trắng.

native android

  1. Khai báo package và import các thư viện cần thiết:
import android.text.InputType
import android.text.TextWatcher
import android.widget.EditText
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.uimanager.UIManagerModule
  1. Khai báo lớp ReactNativeUnsignedInputModule:
class ReactNativeUnsignedInputModule(private val reactContext: ReactApplicationContext) :
  ReactContextBaseJavaModule(reactContext) 

Lớp ReactNativeUnsignedInputModule kế thừa từ ReactContextBaseJavaModule và nhận một tham số reactContext kiểu ReactApplicationContext.

  1. Override phương thức getName():
override fun getName(): String {
    return NAME
}

Phương thức này trả về tên của module, trong trường hợp này là ReactNativeUnsignedInput.

  1. Khởi tạo và quản lý listeners:
private val listeners = hashMapOf<String, TextWatcher?>()

listeners là một HashMap chứa các TextWatcher dùng để lắng nghe sự kiện thay đổi của EditText.

  1. Phương thức applyUnsigned(reactNode: Int):
@ReactMethod
fun applyUnsigned(reactNode: Int) {
  ...
}

Phương thức này được đánh dấu là @ReactMethod, cho phép gọi từ mã JavaScript của React Native. Phương thức này nhận vào reactNode là ID của EditText cần áp dụng nhập liệu không dấu.

  1. Thêm UnsignedTextWatcher vào EditText:
val editText = viewRegistry.resolveView(reactNode) as EditText
val listener = UnsignedTextWatcher(editText)
listeners.set(getKey(reactNode), listener)
editText.addTextChangedListener(listener)

Phần mã này lấy ra đối tượng EditText từ viewRegistry, khởi tạo một UnsignedTextWatcher, lưu vào listeners, và thêm vào EditText để lắng nghe sự kiện thay đổi.

  1. Phương thức getKey(reactNode: Int):
private fun getKey(reactNode: Int): String {
    return reactNode.toString()
}

Phương thức này chuyển đổi reactNode (kiểu Int) thành chuỗi và trả về để sử dụng làm key cho listeners.

Full-code

package com.unsignedinput

import android.text.InputType
import android.text.TextWatcher
import android.widget.EditText
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.uimanager.UIManagerModule

class ReactNativeUnsignedInputModule(private val reactContext: ReactApplicationContext) :
  ReactContextBaseJavaModule(reactContext) {

  override fun getName(): String {
    return NAME
  }

  private val listeners = hashMapOf<String, TextWatcher?>()

  @ReactMethod
  fun applyUnsigned(reactNode: Int) {
    val uiManager = reactContext.getNativeModule(UIManagerModule::class.java)!!

    uiManager.addUIBlock { viewRegistry ->
      val editText = viewRegistry.resolveView(reactNode) as EditText
      val listener = UnsignedTextWatcher(editText)
      listeners.set(getKey(reactNode), listener)
      editText.addTextChangedListener(listener)
      // Add this line to set the input type to password
      editText.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD)
    }
  }

 

  private fun safeResolveString(map: ReadableMap, key: String): String? {
    return try {
      map.getString(key)
    } catch (e: Exception) {
      null
    }
  }
  private fun safeResolveString(map: ReadableMap, key: String, defaultValue: String): String {
    return safeResolveString(map, key) ?: defaultValue
  }

  private fun safeResolveInt(map: ReadableMap, key: String): Int? {
    return try {
      map.getInt(key)
    } catch (e: Exception) {
      null
    }
  }
  private fun safeResolveInt(map: ReadableMap, key: String, defaultValue: Int): Int {
    return safeResolveInt(map, key) ?: defaultValue
  }

  companion object {
    const val NAME = "ReactNativeUnsignedInput"
  }
}

Tóm lại, Nó tạo ra một lớp ReactNativeUnsignedInputModule kế thừa từ ReactContextBaseJavaModule và tuân thủ giao diện TextWatcher để xử lý các sự kiện của EditText, giúp chuẩn hóa chuỗi nhập vào bằng cách loại bỏ dấu tiếng Việt và khoảng trắng.

Demo

Như vậy, @tdduydev/react-native-unsigned-input là một plugin hữu ích, giúp bạn dễ dàng nhập liệu không dấu trong các ứng dụng React Native. Tuy nhiên, để tận dụng tối đa plugin này, bạn cần phải tùy chỉnh và thêm các xử lý bổ sung phù hợp với yêu cầu của ứng dụng. Chúc các bạn thành công với việc sử dụng và tùy biến plugin này!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí