Commit 27c20237 authored by shiyunjie's avatar shiyunjie

first commit

parents
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="JSX" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/agera-app-mask-input.iml" filepath="$PROJECT_DIR$/.idea/agera-app-mask-input.iml" />
</modules>
</component>
</project>
\ No newline at end of file
This diff is collapsed.
.DS_Store
.\#*
/node_modules
\#*
npm-debug.log
node_modules
*.tgz
.idea
.vscode
apply plugin: 'com.android.library'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
minSdkVersion 16
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
}
}
}
dependencies {
compile 'com.facebook.react:react-native:+'
compile 'com.redmadrobot:inputmask:2.3.0'
}
/**
* Automatically generated file. DO NOT MODIFY
*/
package com.RNTextInputMask;
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.RNTextInputMask";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0";
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.RNTextInputMask"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="22" />
</manifest>
\ No newline at end of file
[{"outputType":{"type":"AAPT_FRIENDLY_MERGED_MANIFESTS"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"react-native-text-input-mask-debug.aar","fullName":"debug","baseName":"debug"},"path":"AndroidManifest.xml","properties":{"packageId":"com.RNTextInputMask","split":""}}]
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/jniLibs"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/debug/jniLibs"/></dataSet></merger>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/shaders"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/debug/shaders"/></dataSet></merger>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/assets"/><source path="/Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/build/intermediates/shader_assets/debug/compileDebugShaders/out"/></dataSet><dataSet config="debug" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/debug/assets"/></dataSet></merger>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<merger version="3"><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="main$Generated" generated="true" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/res"/><source path="/Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/build/generated/res/rs/debug"/><source path="/Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/build/generated/res/resValues/debug"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="main" generated-set="main$Generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/res"/><source path="/Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/build/generated/res/rs/debug"/><source path="/Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/build/generated/res/resValues/debug"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="debug$Generated" generated="true" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/debug/res"/></dataSet><dataSet aapt-namespace="http://schemas.android.com/apk/res-auto" config="debug" generated-set="debug$Generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:&lt;dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="/Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/debug/res"/></dataSet><mergedItems/></merger>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.RNTextInputMask"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="22" />
</manifest>
\ No newline at end of file
[{"outputType":{"type":"MERGED_MANIFESTS"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"react-native-text-input-mask-debug.aar","fullName":"debug","baseName":"debug"},"path":"../../library_manifest/debug/AndroidManifest.xml","properties":{"packageId":"com.RNTextInputMask","split":""}}]
\ No newline at end of file
This diff is collapsed.
[{"name":"resources","index":0,"scopes":["PROJECT"],"types":["NATIVE_LIBS"],"format":"DIRECTORY","present":false}]
\ No newline at end of file
-- Merging decision tree log ---
manifest
ADDED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:1-3:12
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:1-3:12
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:1-3:12
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:1-3:12
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:1-3:12
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:1-3:12
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:1-3:12
package
ADDED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:2:11-40
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml
android:versionName
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml
ADDED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:1-3:12
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml
android:versionCode
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml
ADDED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:1-3:12
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml
xmlns:android
ADDED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:11-69
uses-sdk
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml reason: use-sdk injection requested
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml
android:targetSdkVersion
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml
ADDED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml
android:minSdkVersion
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml
ADDED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml
INJECTED from /Users/shiyunjie/WebstormProjects/HG/BasicApp/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml
This diff is collapsed.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.RNTextInputMask">
</manifest>
package com.RNTextInputMask;
import android.app.Activity;
import android.text.Editable;
import android.widget.EditText;
import android.text.TextWatcher;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.UIBlock;
import com.facebook.react.uimanager.NativeViewHierarchyManager;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.Callback;
import com.redmadrobot.inputmask.MaskedTextChangedListener;
import com.redmadrobot.inputmask.PolyMaskTextChangedListener;
import com.redmadrobot.inputmask.model.CaretString;
import com.redmadrobot.inputmask.helper.Mask;
import android.support.annotation.NonNull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RNTextInputMaskModule extends ReactContextBaseJavaModule {
ReactApplicationContext reactContext;
public RNTextInputMaskModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
@Override
public String getName() {
return "RNTextInputMask";
}
@ReactMethod
public void mask(final String maskString,
final String inputValue,
final Callback onResult) {
final Mask mask = new Mask(maskString);
final String input = inputValue;
final Mask.Result result = mask.apply(
new CaretString(
input,
input.length()
),
true
);
final String output = result.getFormattedText().getString();
onResult.invoke(output);
}
@ReactMethod
public void unmask(final String maskString,
final String inputValue,
final Callback onResult) {
final Mask mask = new Mask(maskString);
final String input = inputValue;
final Mask.Result result = mask.apply(
new CaretString(
input,
input.length()
),
true
);
final String output = result.getExtractedValue();
onResult.invoke(output);
}
public void setTextChanged(EditText editText, String extractedValue, String regular) {
if (regular != null) {
Pattern p = Pattern.compile(regular);
Matcher m = p.matcher(extractedValue);
//删掉不是字母或数字的字符
String str = m.replaceAll("").trim();
if (!extractedValue.equals(str)) {
editText.setText(str);
//因为删除了字符,要重写设置新的光标所在位置
editText.setSelection(str.length());
}
}
}
@ReactMethod
public void setRegular(final int view, final String regular) {
final Activity currentActivity = this.reactContext.getCurrentActivity();
final ReactApplicationContext rctx = this.reactContext;
currentActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
UIManagerModule uiManager = rctx.getNativeModule(UIManagerModule.class);
uiManager.addUIBlock(new UIBlock() {
@Override
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
final EditText editText = (EditText) nativeViewHierarchyManager.resolveView(view);
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
setTextChanged(editText, s.toString(), regular);
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
});
}
});
}
@ReactMethod
public void setMask(final int view, final String mask, final String regular) {
final Activity currentActivity = this.reactContext.getCurrentActivity();
final ReactApplicationContext rctx = this.reactContext;
currentActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
UIManagerModule uiManager = rctx.getNativeModule(UIManagerModule.class);
uiManager.addUIBlock(new UIBlock() {
@Override
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
final EditText editText = (EditText) nativeViewHierarchyManager.resolveView(view);
final MaskedTextChangedListener listener = new MaskedTextChangedListener(
mask,
true,
editText,
null,
new MaskedTextChangedListener.ValueListener() {
@Override
public void onTextChanged(boolean maskFilled, @NonNull final String extractedValue) {
setTextChanged(editText, extractedValue, regular);
}
}
);
if (editText.getTag() != null) {
editText.removeTextChangedListener((TextWatcher) editText.getTag());
}
editText.setTag(listener);
editText.addTextChangedListener(listener);
}
});
}
});
}
}
package com.RNTextInputMask;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.bridge.JavaScriptModule;
public class RNTextInputMaskPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new RNTextInputMaskModule(reactContext));
return modules;
}
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
import React, { Component } from 'react'
import {
TextInput,
findNodeHandle,
NativeModules
} from 'react-native'
const mask = NativeModules.RNTextInputMask.mask
const unmask = NativeModules.RNTextInputMask.unmask
const setMask = NativeModules.RNTextInputMask.setMask
const setRegular = NativeModules.RNTextInputMask.setRegular
export { mask, unmask, setMask }
export default class TextInputMask extends Component {
static defaultProps = {
maskDefaultValue: true,
}
masked = false
componentDidMount() {
if (this.props.maskDefaultValue &&
this.props.mask &&
this.props.value) {
mask(this.props.mask, '' + this.props.value, text =>
this.input && this.input.setNativeProps({ text }),
)
}
if (this.props.mask && !this.masked) {
this.masked = true
setMask(findNodeHandle(this.input), this.props.mask, this.props.regular)
}else {
// 处理没有传mask情况
setRegular(findNodeHandle(this.input), this.props.regular || '/(.*)/mg')
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.mask && (this.props.value !== nextProps.value)) {
mask(this.props.mask, '' + nextProps.value, text =>
this.input && this.input.setNativeProps({ text })
);
}
if (this.props.mask !== nextProps.mask) {
setMask(findNodeHandle(this.input), nextProps.mask, this.props.regular)
}
}
render() {
return (<TextInput
{...this.props}
value={undefined}
ref={ref => {
this.input = ref
if (typeof this.props.refInput === 'function') {
this.props.refInput(ref)
}
}}
onChangeText={masked => {
if (this.props.mask) {
const _unmasked = unmask(this.props.mask, masked, unmasked => {
this.props.onChangeText && this.props.onChangeText(masked, unmasked)
})
} else {
this.props.onChangeText && this.props.onChangeText(masked)
}
}}
/>);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:InputMask.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0810"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8A808ECA1D5B0FEC00A75B9C"
BuildableName = "InputMask.framework"
BlueprintName = "InputMask"
ReferencedContainer = "container:InputMask.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8A808ED41D5B0FEC00A75B9C"
BuildableName = "InputMaskTests.xctest"
BlueprintName = "InputMaskTests"
ReferencedContainer = "container:InputMask.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8A808ECA1D5B0FEC00A75B9C"
BuildableName = "InputMask.framework"
BlueprintName = "InputMask"
ReferencedContainer = "container:InputMask.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8A808ECA1D5B0FEC00A75B9C"
BuildableName = "InputMask.framework"
BlueprintName = "InputMask"
ReferencedContainer = "container:InputMask.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8A808ECA1D5B0FEC00A75B9C"
BuildableName = "InputMask.framework"
BlueprintName = "InputMask"
ReferencedContainer = "container:InputMask.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>8A808ECA1D5B0FEC00A75B9C</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>8A808ED41D5B0FEC00A75B9C</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>InputMask.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>10</integer>
</dict>
</dict>
</dict>
</plist>
//
// InputMask
//
// Created by Egor Taflanidi on 17.08.28.
// Copyright © 28 Heisei Egor Taflanidi. All rights reserved.
//
import Foundation
/**
### CaretStringIterator
Iterates over CaretString.string characters. Each ```next()``` call returns current character and adjusts iterator
position.
```CaretStringIterator``` is used by the ```Mask``` instance to iterate over the string that should be formatted.
*/
class CaretStringIterator {
private let caretString: CaretString
private var currentIndex: String.Index
/**
Constructor
- parameter caretString: ```CaretString``` object, over which the iterator is going to iterate.
- returns: Initialized ```CaretStringIterator``` pointing at the beginning of provided ```CaretString.string```
*/
init(caretString: CaretString) {
self.caretString = caretString
self.currentIndex = self.caretString.string.startIndex
}
/**
Inspect, whether ```CaretStringIterator``` has reached ```CaretString.caretPosition``` or not.
Each ```CaretString``` object contains cursor position for its ```CaretString.string```.
For the ```Mask``` instance it is important to know, whether it should adjust the cursor position or not when
inserting new symbols into the formatted line.
**Example**
Let the ```CaretString``` instance contains two symbols, with the caret at the end of the line.
```
string: ab
caret: ^
```
In this case ```CaretStringIterator.beforeCaret()``` will always return ```true``` until there's no more
characters left in the line to iterate over.
**Example 2**
Let the ```CaretString``` instance contains two symbols, with the caret at the beginning of the line.
```
string: ab
caret: ^
```
In this case ```CaretStringIterator.beforeCaret()``` will only return ```true``` for the first iteration. After the
```next()``` method is fired, ```beforeCaret()``` will return false.
- returns: ```True```, if current iterator position is less than or equal to ```CaretString.caretPosition```
*/
func beforeCaret() -> Bool {
let startIndex: String.Index = self.caretString.string.startIndex
let currentIndex: Int = self.caretString.string.distance(from: startIndex, to: self.currentIndex)
let caretPosition: Int = self.caretString.string.distance(from: startIndex, to: self.caretString.caretPosition)
return self.currentIndex <= self.caretString.caretPosition
|| (0 == currentIndex && 0 == caretPosition)
}
/**
Iterate over the ```CaretString.string```
- postcondition: Iterator position is moved to the next symbol.
- returns: Current symbol. If the iterator reached the end of the line, returns ```nil```.
*/
func next() -> Character? {
if self.currentIndex >= self.caretString.string.endIndex {
return nil
}
let character: Character = self.caretString.string.characters[self.currentIndex]
self.currentIndex = self.caretString.string.index(after: self.currentIndex)
return character
}
}
//
// InputMask
//
// Created by Egor Taflanidi on 16.08.28.
// Copyright © 28 Heisei Egor Taflanidi. All rights reserved.
//
import Foundation
/**
### Compiler
Creates a sequence of states from the mask format string.
- seealso: ```State``` class.
- complexity: ```O(formatString.characters.count)``` plus ```FormatSanitizer``` complexity.
- requires: Format string to contain only flat groups of symbols in ```[]``` and ```{}``` brackets without nested
brackets, like ```[[000]99]```. Also, ```[…]``` groups may contain only the specified characters ("0", "9", "A", "a",
"_" and "-"). Square bracket ```[]``` groups cannot contain mixed types of symbols ("0" and "9" with "A" and "a" or
"_" and "-").
```Compiler``` object is initialized and ```Compiler.compile(formatString:)``` is called during the ```Mask``` instance
initialization.
```Compiler``` uses ```FormatSanitizer``` to prepare ```formatString``` for the compilation.
*/
public class Compiler {
/**
### CompilerError
Compiler error exception type, thrown when ```formatString``` contains inappropriate character sequences.
```CompilerError``` is used by the ```Compiler``` and ```FormatSanitizer``` classes.
*/
public enum CompilerError: Error {
case WrongFormat
}
/**
Compile ```formatString``` into the sequence of states.
* "Free" characters from ```formatString``` are converted to ```FreeState```-s.
* Characters in square brackets are converted to ```ValueState```-s and ```OptionalValueState```-s.
* Characters in curly brackets are converted to ```FixedState```-s.
* End of the formatString line makes ```EOLState```.
For instance,
```
[09]{.}[09]{.}19[00]
```
is converted to sequence:
```
0. ValueState.Numeric [0]
1. OptionalValueState.Numeric [9]
2. FixedState {.}
3. ValueState.Numeric [0]
4. OptionalValueState.Numeric [9]
5. FixedState {.}
6. FreeState 1
7. FreeState 9
8. ValueState.Numeric [0]
9. ValueState.Numeric [0]
```
- parameter formatString: string with a mask format.
- seealso: ```State``` class.
- complexity: ```O(formatString.characters.count)``` plus ```FormatSanitizer``` complexity.
- requires: Format string to contain only flat groups of symbols in ```[]``` and ```{}``` brackets without nested
brackets, like ```[[000]99]```. Also, ```[…]``` groups may contain only the specified characters ("0", "9", "A", "a",
"_" and "-").
- returns: Initialized ```State``` object with assigned ```State.child``` chain.
- throws: ```CompilerError``` if ```formatString``` does not conform to the method requirements.
*/
func compile(formatString string: String) throws -> State {
let sanitizedFormat: String = try FormatSanitizer().sanitize(formatString: string)
return try self.compile(
sanitizedFormat,
valueable: false,
fixed: false
)
}
}
private extension Compiler {
func compile(_ string: String, valueable: Bool, fixed: Bool) throws -> State {
guard
let char: Character = string.characters.first
else {
return EOLState()
}
if "[" == char {
return try self.compile(
string.truncateFirst(),
valueable: true,
fixed: false
)
}
if "{" == char {
return try self.compile(
string.truncateFirst(),
valueable: false,
fixed: true
)
}
if "]" == char {
return try self.compile(
string.truncateFirst(),
valueable: false,
fixed: false
)
}
if "}" == char {
return try self.compile(
string.truncateFirst(),
valueable: false,
fixed: false
)
}
if valueable {
if "0" == char {
return ValueState(
child: try self.compile(
string.truncateFirst(),
valueable: true,
fixed: false
),
type: ValueState.StateType.Numeric
)
}
if "A" == char {
return ValueState(
child: try self.compile(
string.truncateFirst(),
valueable: true,
fixed: false
),
type: ValueState.StateType.Literal
)
}
if "_" == char {
return ValueState(
child: try self.compile(
string.truncateFirst(),
valueable: true,
fixed: false
),
type: ValueState.StateType.AlphaNumeric
)
}
if "9" == char {
return OptionalValueState(
child: try self.compile(
string.truncateFirst(),
valueable: true,
fixed: false
),
type: OptionalValueState.StateType.Numeric
)
}
if "a" == char {
return OptionalValueState(
child: try self.compile(
string.truncateFirst(),
valueable: true,
fixed: false
),
type: OptionalValueState.StateType.Literal
)
}
if "-" == char {
return OptionalValueState(
child: try self.compile(
string.truncateFirst(),
valueable: true,
fixed: false
),
type: OptionalValueState.StateType.AlphaNumeric
)
}
throw CompilerError.WrongFormat
}
if fixed {
return FixedState(
child: try self.compile(
string.truncateFirst(),
valueable: false,
fixed: true
),
ownCharacter: char
)
}
return FreeState(
child: try self.compile(
string.truncateFirst(),
valueable: false,
fixed: false),
ownCharacter: char
)
}
}
//
// InputMask
//
// Created by Egor Taflanidi on 17.08.28.
// Copyright © 28 Heisei Egor Taflanidi. All rights reserved.
//
import Foundation
/**
### FormatSanitizer
Sanitizes given ```formatString``` before it's compilation.
- complexity: ```O(2*floor(log(n)))```, and switches to ```O(n^2)``` for ```n < 20``` where
```n = formatString.characters.count```
- requires: Format string to contain only flat groups of symbols in ```[]``` and ```{}``` brackets without nested
brackets, like ```[[000]99]```. Square bracket ```[]``` groups may contain mixed types of symbols ("0" and "9" with
"A" and "a" or "_" and "-"), which sanitizer will divide into separate groups. Such that, ```[0000Aa]``` group will
be divided in two groups: ```[0000]``` and ```[Aa]```.
```FormatSanitizer``` is used by ```Compiler``` before format string compilation.
*/
class FormatSanitizer {
/**
Sanitize ```formatString``` before compilation.
In order to do so, sanitizer splits the string into groups of regular symbols, symbols in square brackets [] and
symbols in curly brackets {}. Then, characters in square brackets are sorted in a way that mandatory symbols go
before optional symbols. For instance,
```
a ([0909]) b
```
mask format is rearranged to
```
a ([0099]) b
```
- complexity: ```O(2*floor(log(n)))```, and switches to ```O(n^2)``` for ```n < 20``` where
```n = formatString.characters.count```
- requires: Format string to contain only flat groups of symbols in ```[]``` and ```{}``` brackets without nested
brackets, like ```[[000]99]```. Square bracket ```[]``` groups may contain mixed types of symbols ("0" and "9" with
"A" and "a" or "_" and "-"), which sanitizer will divide into separate groups. Such that, ```[0000Aa]``` group will
be divided in two groups: ```[0000]``` and ```[Aa]```.
- parameter formatString: mask format string.
- returns: Sanitized format string.
- throws: ```CompilerError``` if ```formatString``` does not conform to the method requirements.
*/
func sanitize(formatString string: String) throws -> String {
try self.checkOpenBraces(string)
let blocks: [String] = self.divideBlocksWithMixedCharacters(self.getFormatBlocks(string))
return self.sortFormatBlocks(blocks).joined(separator: "")
}
}
private extension FormatSanitizer {
func checkOpenBraces(_ string: String) throws {
var squareBraceOpen: Bool = false
var curlyBraceOpen: Bool = false
for char in string.characters {
if "[" == char {
if squareBraceOpen {
throw Compiler.CompilerError.WrongFormat
}
squareBraceOpen = true
}
if "]" == char {
squareBraceOpen = false
}
if "{" == char {
if curlyBraceOpen {
throw Compiler.CompilerError.WrongFormat
}
curlyBraceOpen = true
}
if "}" == char {
curlyBraceOpen = false
}
}
}
func getFormatBlocks(_ string: String) -> [String] {
var blocks: [String] = []
var currentBlock: String = ""
for char in string.characters {
if "[" == char
|| "{" == char {
if 0 < currentBlock.characters.count {
blocks.append(currentBlock)
}
currentBlock = ""
}
currentBlock += String(char)
if "]" == char
|| "}" == char {
blocks.append(currentBlock)
currentBlock = ""
}
}
if !currentBlock.isEmpty {
blocks.append(currentBlock)
}
return blocks
}
func divideBlocksWithMixedCharacters(_ blocks: [String]) -> [String] {
var resultingBlocks: [String] = []
for block in blocks {
if block.hasPrefix("[") {
var blockBuffer: String = ""
for blockCharacter in block.characters {
if blockCharacter == "[" {
blockBuffer += String(blockCharacter)
continue
}
if blockCharacter == "]" {
blockBuffer += String(blockCharacter)
resultingBlocks.append(blockBuffer)
break
}
if blockCharacter == "0"
|| blockCharacter == "9" {
if blockBuffer.contains("A")
|| blockBuffer.contains("a")
|| blockBuffer.contains("-")
|| blockBuffer.contains("_") {
blockBuffer += "]"
resultingBlocks.append(blockBuffer)
blockBuffer = "[" + String(blockCharacter)
continue
}
}
if blockCharacter == "A"
|| blockCharacter == "a" {
if blockBuffer.contains("0")
|| blockBuffer.contains("9")
|| blockBuffer.contains("-")
|| blockBuffer.contains("_") {
blockBuffer += "]"
resultingBlocks.append(blockBuffer)
blockBuffer = "[" + String(blockCharacter)
continue
}
}
if blockCharacter == "-"
|| blockCharacter == "_" {
if blockBuffer.contains("0")
|| blockBuffer.contains("9")
|| blockBuffer.contains("A")
|| blockBuffer.contains("a") {
blockBuffer += "]"
resultingBlocks.append(blockBuffer)
blockBuffer = "[" + String(blockCharacter)
continue
}
}
blockBuffer += String(blockCharacter)
}
} else {
resultingBlocks.append(block)
}
}
return resultingBlocks
}
func sortFormatBlocks(_ blocks: [String]) -> [String] {
var sortedBlocks: [String] = []
for block in blocks {
var sortedBlock: String
if block.hasPrefix("[") {
if block.contains("0")
|| block.contains("9") {
sortedBlock = self.sortBlock(block: block)
} else if block.contains("a")
|| block.contains("A") {
sortedBlock = self.sortBlock(block: block)
} else {
sortedBlock =
"["
+ String(block
.replacingOccurrences(of: "[", with: "")
.replacingOccurrences(of: "]", with: "")
.replacingOccurrences(of: "_", with: "A")
.replacingOccurrences(of: "-", with: "a")
.characters.sorted()
)
+ "]"
sortedBlock = sortedBlock
.replacingOccurrences(of: "A", with: "_")
.replacingOccurrences(of: "a", with: "-")
}
} else {
sortedBlock = block
}
sortedBlocks.append(sortedBlock)
}
return sortedBlocks
}
private func sortBlock(block: String) -> String {
return
"["
+ String(block
.replacingOccurrences(of: "[", with: "")
.replacingOccurrences(of: "]", with: "")
.characters.sorted()
)
+ "]"
}
}
//
// InputMask
//
// Created by Egor Taflanidi on 10.08.28.
// Copyright © 28 Heisei Egor Taflanidi. All rights reserved.
//
import Foundation
/**
Utility extension to make ```NSCharacterSet``` interact with ```Character``` instances.
*/
extension CharacterSet {
/**
Implements ```NSCharacterSet.characterIsMember(:unichar)``` for ```Character``` instances.
*/
func isMember(character: Character) -> Bool {
let string: String = String(character)
for char in string.unicodeScalars {
if !self.contains(char) {
return false
}
}
return true
}
}
//
// InputMask
//
// Created by Egor Taflanidi on 10.08.28.
// Copyright © 28 Heisei Egor Taflanidi. All rights reserved.
//
import Foundation
/**
Utility extension for comonly used ```Mask``` operations upon strings.
*/
extension String {
/**
Make a string by cutting the first character of current.
- returns: Current string without first character.
- throws: EXC_BAD_INSTRUCTION for empty strings.
*/
func truncateFirst() -> String {
return self.substring(from: self.index(after: self.startIndex))
}
}
//
// InputMask
//
// Created by Egor Taflanidi on 10.08.28.
// Copyright © 28 Heisei Egor Taflanidi. All rights reserved.
//
#import <UIKit/UIKit.h>
//! Project version number for InputMask.
FOUNDATION_EXPORT double InputMaskVersionNumber;
//! Project version string for InputMask.
FOUNDATION_EXPORT const unsigned char InputMaskVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <InputMask/PublicHeader.h>
This diff is collapsed.
//
// InputMask
//
// Created by Egor Taflanidi on 16.08.28.
// Copyright © 28 Heisei Egor Taflanidi. All rights reserved.
//
import Foundation
/**
### CaretString
Model object that represents string with current cursor position.
*/
public struct CaretString: CustomDebugStringConvertible, CustomStringConvertible {
/**
Text from the user.
*/
public let string: String
/**
Cursor position from the input text field.
*/
public let caretPosition: String.Index
/**
Constructor.
- parameter string: text from the user.
- parameter caretPosition: cursor position from the input text field.
*/
public init(string: String, caretPosition: String.Index) {
self.string = string
self.caretPosition = caretPosition
}
public var debugDescription: String {
get {
return "STRING: \(self.string)\nCARET POSITION: \(self.caretPosition)"
}
}
public var description: String {
get {
return self.debugDescription
}
}
}
//
// InputMask
//
// Created by Egor Taflanidi on 16.08.28.
// Copyright © 28 Heisei Egor Taflanidi. All rights reserved.
//
import Foundation
/**
### Next
Model object that represents a set of actions that should take place when transition from one ```State``` to another
occurs.
*/
struct Next {
/**
Next ```State``` of the ```Mask```.
*/
let state: State
/**
Insert a character into the resulting formatted string.
*/
let insert: Character?
/**
Pass to the next character of the input string.
*/
let pass: Bool
/**
Add character to the extracted value string.
Value is extracted from the user input string.
*/
let value: Character?
}
//
// InputMask
//
// Created by Egor Taflanidi on 16.08.28.
// Copyright © 28 Heisei Egor Taflanidi. All rights reserved.
//
import Foundation
/**
### State
State of the mask, similar to the state in regular expressions.
Each state represents a character from the mask format string.
*/
class State: CustomDebugStringConvertible, CustomStringConvertible {
/**
Next ```State```.
*/
let child: State?
/**
Abstract method.
Defines, whether the state accepts user input character or not, and which actions should take place when the
character is accepted.
- parameter character: character from the user input string.
- returns: ```Next``` object instance with a set of actions that should take place when the user input character is
accepted.
- throws: Fatal error, if the method is not implemeted.
*/
/* abstract */ func accept(character char: Character) -> Next? {
fatalError("accept(character:) method is abstract")
}
/**
Automatically complete user input.
- returns: ```Next``` object instance with a set of actions to complete user input. If no autocomplete available,
returns ```nil```.
*/
func autocomplete() -> Next? {
return nil
}
/**
Obtain the next state.
Sometimes it is necessary to override this behavior. For instance, ```State``` may want to return ```self``` as the
next state under certain conditions.
- returns: ```State``` object.
*/
func nextState() -> State {
return self.child!
}
/**
Constructor.
- parameter child: next state.
- returns: Initialized state.
*/
init(child: State?) {
self.child = child
}
var debugDescription: String {
get {
return "BASE -> " + (nil != self.child ? self.child!.debugDescription : "nil")
}
}
var description: String {
get {
return self.debugDescription
}
}
}
//
// InputMask
//
// Created by Egor Taflanidi on 16.08.28.
// Copyright © 28 Heisei Egor Taflanidi. All rights reserved.
//
import Foundation
/**
### EOLState
End-of-line state. Serves as mask format terminator character.
Does not accept any character. Always returns ```self``` as the next state, ignoring the child state given during
initialization.
*/
class EOLState: State {
convenience init() {
self.init(child: nil)
}
override init(child: State?) {
super.init(child: nil)
}
override func nextState() -> State {
return self
}
override func accept(character char: Character) -> Next? {
return nil
}
override var debugDescription: String {
get {
return "EOL"
}
}
}
//
// InputMask
//
// Created by Egor Taflanidi on 16.08.28.
// Copyright © 28 Heisei Egor Taflanidi. All rights reserved.
//
import Foundation
/**
### FixedState
Represents characters in curly braces {}.
Accepts every character but does not put it into the result string, unless the character equals the one from the mask
format. If it's not, inserts the symbol from the mask format into the result.
Always returns self as an extracted value.
*/
class FixedState: State {
let ownCharacter: Character
override func accept(character char: Character) -> Next? {
if self.ownCharacter == char {
return Next(
state: self.nextState(),
insert: char,
pass: true,
value: char
)
} else {
return Next(
state: self.nextState(),
insert: self.ownCharacter,
pass: false,
value: self.ownCharacter
)
}
}
override func autocomplete() -> Next? {
return Next(
state: self.nextState(),
insert: self.ownCharacter,
pass: false,
value: self.ownCharacter
)
}
/**
Constructor.
- parameter child: next ```State```
- parameter ownCharacter: the character in the curly braces {}
- returns: Initialized ```FixedState``` instance.
*/
init(
child: State,
ownCharacter: Character
) {
self.ownCharacter = ownCharacter
super.init(child: child)
}
override var debugDescription: String {
get {
return "{\(self.ownCharacter)} -> " + (nil != self.child ? self.child!.debugDescription : "nil")
}
}
}
//
// InputMask
//
// Created by Egor Taflanidi on 17.08.28.
// Copyright © 28 Heisei Egor Taflanidi. All rights reserved.
//
import Foundation
/**
### FreeState
Represents "free" characters outside square and curly brackets.
Accepts every character but does not put it into the result string, unless the character equals the one from the mask
format. If it's not, inserts the symbol from the mask format into the result.
Always returns ```nil``` as an extracted value, does not affect the resulting value.
*/
class FreeState: State {
let ownCharacter: Character
override func accept(character char: Character) -> Next? {
if self.ownCharacter == char {
return Next(
state: self.nextState(),
insert: char,
pass: true,
value: nil
)
} else {
return Next(
state: self.nextState(),
insert: self.ownCharacter,
pass: false,
value: nil
)
}
}
override func autocomplete() -> Next? {
return Next(
state: self.nextState(),
insert: self.ownCharacter,
pass: false,
value: nil
)
}
/**
Constructor.
- parameter child: next ```State```
- parameter ownCharacter: represented "free" character
- returns: Initialized ```FreeState``` instance.
*/
init(
child: State,
ownCharacter: Character
) {
self.ownCharacter = ownCharacter
super.init(child: child)
}
override var debugDescription: String {
get {
return "\(self.ownCharacter) -> " + (nil != self.child ? self.child!.debugDescription : "nil")
}
}
}
//
// InputMask
//
// Created by Egor Taflanidi on 17.08.28.
// Copyright © 28 Heisei Egor Taflanidi. All rights reserved.
//
import Foundation
/**
### OptionalValueState
Represents optional characters in square brackets [].
Accepts any characters, but puts into the result string only the characters of own type (see ```StateType```).
Returns accepted characters of own type as an extracted value.
- seealso: ```OptionalValueState.StateType```
*/
class OptionalValueState: State {
/**
### StateType
* ```Numeric``` stands for [9] characters
* ```Literal``` stands for [a] characters
* ```AlphaNumeric``` stands for [-] characters
*/
enum StateType {
case Numeric
case Literal
case AlphaNumeric
}
let type: StateType
func accepts(character char: Character) -> Bool {
switch self.type {
case .Numeric:
return CharacterSet.decimalDigits.isMember(character: char)
case .Literal:
return CharacterSet.letters.isMember(character: char)
case .AlphaNumeric:
return CharacterSet.alphanumerics.isMember(character: char)
}
}
override func accept(character char: Character) -> Next? {
if self.accepts(character: char) {
return Next(
state: self.nextState(),
insert: char,
pass: true,
value: char
)
} else {
return Next(
state: self.nextState(),
insert: nil,
pass: false,
value: nil
)
}
}
/**
Constructor.
- parameter child: next ```State```
- parameter type: type of the accepted characters
- seealso: ```OptionalValueState.StateType```
- returns: Initialized ```OptionalValueState``` instance.
*/
init(
child: State,
type: StateType
) {
self.type = type
super.init(child: child)
}
override var debugDescription: String {
get {
switch self.type {
case .Literal:
return "[a] -> " + (nil != self.child ? self.child!.debugDescription : "nil")
case .Numeric:
return "[9] -> " + (nil != self.child ? self.child!.debugDescription : "nil")
case .AlphaNumeric:
return "[-] -> " + (nil != self.child ? self.child!.debugDescription : "nil")
}
}
}
}
//
// InputMask
//
// Created by Egor Taflanidi on 16.08.28.
// Copyright © 28 Heisei Egor Taflanidi. All rights reserved.
//
import Foundation
/**
### ValueState
Represents mandatory characters in square brackets [].
Accepts only characters of own type (see ```StateType```). Puts accepted characters into the result string.
Returns accepted characters as an extracted value.
- seealso: ```ValueState.StateType```
*/
class ValueState: State {
/**
### StateType
* ```Numeric``` stands for [9] characters
* ```Literal``` stands for [a] characters
* ```AlphaNumeric``` stands for [-] characters
*/
enum StateType {
case Numeric
case Literal
case AlphaNumeric
}
let type: StateType
func accepts(character char: Character) -> Bool {
switch self.type {
case .Numeric:
return CharacterSet.decimalDigits.isMember(character: char)
case .Literal:
return CharacterSet.letters.isMember(character: char)
case .AlphaNumeric:
return CharacterSet.alphanumerics.isMember(character: char)
}
}
override func accept(character char: Character) -> Next? {
if !self.accepts(character: char) {
return nil
}
return Next(
state: self.nextState(),
insert: char,
pass: true,
value: char
)
}
/**
Constructor.
- parameter child: next ```State```
- parameter type: type of the accepted characters
- seealso: ```ValueState.StateType```
- returns: Initialized ```ValueState``` instance.
*/
init(
child: State,
type: StateType
) {
self.type = type
super.init(child: child)
}
override var debugDescription: String {
get {
switch self.type {
case .Literal:
return "[A] -> " + (nil != self.child ? self.child!.debugDescription : "nil")
case .Numeric:
return "[0] -> " + (nil != self.child ? self.child!.debugDescription : "nil")
case .AlphaNumeric:
return "[_] -> " + (nil != self.child ? self.child!.debugDescription : "nil")
}
}
}
}
//
// RNMask.swift
// InputMask
//
// Created by Ivan Zotov on 8/4/17.
// Copyright © 2017 Ivan Zotov. All rights reserved.
//
import Foundation
open class RNMask : NSObject {
public static func maskValue(text: String, format: String) -> String {
let mask : Mask = try! Mask.getOrCreate(withFormat: format)
let result: Mask.Result = mask.apply(
toText: CaretString(
string: text,
caretPosition: text.endIndex
),
autocomplete: true
)
return result.formattedText.string
}
public static func unmaskValue(text: String, format: String) -> String {
let mask : Mask = try! Mask.getOrCreate(withFormat: format)
let result: Mask.Result = mask.apply(
toText: CaretString(
string: text,
caretPosition: text.endIndex
),
autocomplete: true
)
return result.extractedValue
}
}
//
// InterfaceBuilderSupport.swift
// InputMask
//
// Created by Egor Taflanidi on 18.08.28.
// Copyright © 28 Heisei Egor Taflanidi. All rights reserved.
//
import Foundation
public extension MaskedTextFieldDelegate {
/**
Workaround to support Interface Builder delegate outlets.
Allows assigning ```MaskedTextFieldDelegate.listener``` within the Interface Builder.
Consider using ```MaskedTextFieldDelegate.listener``` property from your source code instead of
```MaskedTextFieldDelegate.delegate``` outlet.
*/
@IBOutlet public var delegate: NSObject? {
get {
return self.listener as? NSObject
}
set(newDelegate) {
if let listener = newDelegate as? MaskedTextFieldDelegateListener {
self.listener = listener
}
}
}
}
//
// PolyMaskTextFieldDelegate.swift
// InputMask
//
// Created by Egor Taflanidi on 10.11.28.
// Copyright © 28 Heisei Egor Taflanidi. All rights reserved.
//
import Foundation
import UIKit
/**
### PolyMaskTextFieldDelegate
UITextFieldDelegate, which applies masking to the user input, picking the most suitable mask for the text.
Might be used as a decorator, which forwards UITextFieldDelegate calls to its own listener.
*/
@IBDesignable
open class PolyMaskTextFieldDelegate: MaskedTextFieldDelegate {
fileprivate var _affineFormats: [String]
public var affineFormats: [String] {
get {
return self._affineFormats
}
set(newFormats) {
self._affineFormats = newFormats
}
}
public init(primaryFormat: String, affineFormats: [String]) {
self._affineFormats = affineFormats
super.init(format: primaryFormat)
}
public override init(format: String) {
self._affineFormats = []
super.init(format: format)
}
open override func put(text: String, into field: UITextField) {
let mask: Mask = self.pickMask(
forText: text,
caretPosition: text.endIndex,
autocomplete: self.autocomplete
)
let result: Mask.Result = mask.apply(
toText: CaretString(
string: text,
caretPosition: text.endIndex
),
autocomplete: self.autocomplete
)
field.text = result.formattedText.string
let position: Int =
result.formattedText.string.distance(from: result.formattedText.string.startIndex, to: result.formattedText.caretPosition)
self.setCaretPosition(position, inField: field)
self.listener?.textField?(
field,
didFillMandatoryCharacters: result.complete,
didExtractValue: result.extractedValue
)
}
override open func deleteText(
inRange range: NSRange,
inField field: UITextField
) -> (String, Bool) {
let text: String = self.replaceCharacters(
inText: field.text,
range: range,
withCharacters: ""
)
let mask: Mask = self.pickMask(
forText: text,
caretPosition: text.index(text.startIndex, offsetBy: range.location),
autocomplete: false
)
let result: Mask.Result = mask.apply(
toText: CaretString(
string: text,
caretPosition: text.index(text.startIndex, offsetBy: range.location)
),
autocomplete: false
)
field.text = result.formattedText.string
self.setCaretPosition(range.location, inField: field)
return (result.extractedValue, result.complete)
}
override open func modifyText(
inRange range: NSRange,
inField field: UITextField,
withText text: String
) -> (String, Bool) {
let updatedText: String = self.replaceCharacters(
inText: field.text,
range: range,
withCharacters: text
)
let mask: Mask = self.pickMask(
forText: updatedText,
caretPosition: updatedText.index(updatedText.startIndex, offsetBy: self.caretPosition(inField: field) + text.characters.count),
autocomplete: self.autocomplete
)
let result: Mask.Result = mask.apply(
toText: CaretString(
string: updatedText,
caretPosition: updatedText.index(updatedText.startIndex, offsetBy: self.caretPosition(inField: field) + text.characters.count)
),
autocomplete: self.autocomplete
)
field.text = result.formattedText.string
let position: Int =
result.formattedText.string.distance(from: result.formattedText.string.startIndex, to: result.formattedText.caretPosition)
self.setCaretPosition(position, inField: field)
return (result.extractedValue, result.complete)
}
open override var debugDescription: String {
get {
return self._affineFormats.reduce(self.mask.debugDescription) { (debugDescription: String, affineFormat: String) -> String in
return try! debugDescription + "\n" + Mask.getOrCreate(withFormat: affineFormat).debugDescription
}
}
}
}
internal extension PolyMaskTextFieldDelegate {
func pickMask(
forText text: String,
caretPosition: String.Index,
autocomplete: Bool
) -> Mask {
let primaryAffinity: Int = self.calculateAffinity(
ofMask: self.mask,
forText: text,
caretPosition: caretPosition,
autocomplete: autocomplete
)
var masks: [(Mask, Int)] = self.affineFormats.map { (affineFormat: String) -> (Mask, Int) in
let mask: Mask = try! Mask.getOrCreate(withFormat: affineFormat)
let affinity: Int = self.calculateAffinity(
ofMask: mask,
forText: text,
caretPosition: caretPosition,
autocomplete: autocomplete
)
return (mask, affinity)
}
masks.sort { (left: (Mask, Int), right: (Mask, Int)) -> Bool in
return left.1 > right.1
}
var insertIndex: Int = -1
for (index, maskAffinity) in masks.enumerated() {
if primaryAffinity >= maskAffinity.1 {
insertIndex = index
break
}
}
if (insertIndex >= 0) {
masks.insert((self.mask, primaryAffinity), at: insertIndex)
} else {
masks.append((self.mask, primaryAffinity))
}
return masks.first!.0
}
func calculateAffinity(
ofMask mask: Mask,
forText text: String,
caretPosition: String.Index,
autocomplete: Bool
) -> Int {
return mask.apply(
toText: CaretString(
string: text,
caretPosition: caretPosition
),
autocomplete: autocomplete
).affinity
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
//
// InputMask
//
// Created by Egor Taflanidi on 10.08.28.
// Copyright © 28 Heisei Egor Taflanidi. All rights reserved.
//
import XCTest
@testable import InputMask
class MaskTestCase: XCTestCase {
func mask() throws -> Mask {
return try Mask(format: self.format())
}
func format() -> String {
fatalError("format() method is abstract")
}
func testInit_nestedBrackets_throwsWrongFormatCompilerError() {
do {
_ = try Mask(format: "[[00]000]")
XCTFail()
} catch Compiler.CompilerError.WrongFormat {
// success
} catch {
XCTFail()
}
}
func testInit_mixedCharacters_initialized() {
do {
_ = try Mask(format: "[00000Aa]")
} catch Compiler.CompilerError.WrongFormat {
XCTFail()
} catch {
XCTFail()
}
}
}
//
// YearACCase.swift
// InputMask
//
// Created by Egor Taflanidi on 09.01.29.
// Copyright © 29 Heisei Egor Taflanidi. All rights reserved.
//
import XCTest
@testable import InputMask
class YearACCase: MaskTestCase {
override func format() -> String {
return "[9990] AC"
}
func testInit_correctFormat_maskInitialized() {
XCTAssertNotNil(try self.mask())
}
func testInit_correctFormat_measureTime() {
self.measure {
var masks: [Mask] = []
for _ in 1...1000 {
masks.append(
try! self.mask()
)
}
}
}
func testGetOrCreate_correctFormat_measureTime() {
self.measure {
var masks: [Mask] = []
for _ in 1...1000 {
masks.append(
try! Mask.getOrCreate(withFormat: self.format())
)
}
}
}
func testGetPlaceholder_allSet_returnsCorrectPlaceholder() {
let placeholder: String = try! self.mask().placeholder()
XCTAssertEqual(placeholder, "0000 AC")
}
func testAcceptableTextLength_allSet_returnsCorrectCount() {
let acceptableTextLength: Int = try! self.mask().acceptableTextLength()
XCTAssertEqual(acceptableTextLength, 4)
}
func testTotalTextLength_allSet_returnsCorrectCount() {
let totalTextLength: Int = try! self.mask().totalTextLength()
XCTAssertEqual(totalTextLength, 7)
}
func testAcceptableValueLength_allSet_returnsCorrectCount() {
let acceptableValueLength: Int = try! self.mask().acceptableValueLength()
XCTAssertEqual(acceptableValueLength, 1)
}
func testTotalValueLength_allSet_returnsCorrectCount() {
let totalValueLength: Int = try! self.mask().totalValueLength()
XCTAssertEqual(totalValueLength, 4)
}
func testApply_1_returns_1() {
let inputString: String = "1"
let inputCaret: String.Index = inputString.endIndex
let expectedString: String = "1"
let expectedCaret: String.Index = expectedString.endIndex
let expectedValue: String = expectedString
let result: Mask.Result = try! self.mask().apply(
toText: CaretString(
string: inputString,
caretPosition: inputCaret
)
)
XCTAssertEqual(expectedString, result.formattedText.string)
XCTAssertEqual(expectedCaret, result.formattedText.caretPosition)
XCTAssertEqual(expectedValue, result.extractedValue)
XCTAssertEqual(false, result.complete)
}
func testApply_11_returns_11() {
let inputString: String = "11"
let inputCaret: String.Index = inputString.endIndex
let expectedString: String = "11"
let expectedCaret: String.Index = expectedString.endIndex
let expectedValue: String = expectedString
let result: Mask.Result = try! self.mask().apply(
toText: CaretString(
string: inputString,
caretPosition: inputCaret
)
)
XCTAssertEqual(expectedString, result.formattedText.string)
XCTAssertEqual(expectedCaret, result.formattedText.caretPosition)
XCTAssertEqual(expectedValue, result.extractedValue)
XCTAssertEqual(false, result.complete)
}
func testApply_111_returns_111() {
let inputString: String = "111"
let inputCaret: String.Index = inputString.endIndex
let expectedString: String = "111"
let expectedCaret: String.Index = expectedString.endIndex
let expectedValue: String = expectedString
let result: Mask.Result = try! self.mask().apply(
toText: CaretString(
string: inputString,
caretPosition: inputCaret
)
)
XCTAssertEqual(expectedString, result.formattedText.string)
XCTAssertEqual(expectedCaret, result.formattedText.caretPosition)
XCTAssertEqual(expectedValue, result.extractedValue)
XCTAssertEqual(false, result.complete)
}
func testApply_1111_returns_1111() {
let inputString: String = "1111"
let inputCaret: String.Index = inputString.endIndex
let expectedString: String = "1111"
let expectedCaret: String.Index = expectedString.endIndex
let expectedValue: String = expectedString
let result: Mask.Result = try! self.mask().apply(
toText: CaretString(
string: inputString,
caretPosition: inputCaret
)
)
XCTAssertEqual(expectedString, result.formattedText.string)
XCTAssertEqual(expectedCaret, result.formattedText.caretPosition)
XCTAssertEqual(expectedValue, result.extractedValue)
XCTAssertEqual(false, result.complete)
}
func testApply_11112_returns_1111spaceAC() {
let inputString: String = "11112"
let inputCaret: String.Index = inputString.endIndex
let expectedString: String = "1111 AC"
let expectedCaret: String.Index = expectedString.endIndex
let expectedValue: String = "1111"
let result: Mask.Result = try! self.mask().apply(
toText: CaretString(
string: inputString,
caretPosition: inputCaret
)
)
XCTAssertEqual(expectedString, result.formattedText.string)
XCTAssertEqual(expectedCaret, result.formattedText.caretPosition)
XCTAssertEqual(expectedValue, result.extractedValue)
XCTAssertEqual(true, result.complete)
}
func testApplyAutocomplete_1111_returns_1111spaceAC() {
let inputString: String = "1111"
let inputCaret: String.Index = inputString.endIndex
let expectedString: String = "1111 AC"
let expectedCaret: String.Index = expectedString.endIndex
let expectedValue: String = "1111"
let result: Mask.Result = try! self.mask().apply(
toText: CaretString(
string: inputString,
caretPosition: inputCaret
),
autocomplete: true
)
XCTAssertEqual(expectedString, result.formattedText.string)
XCTAssertEqual(expectedCaret, result.formattedText.caretPosition)
XCTAssertEqual(expectedValue, result.extractedValue)
XCTAssertEqual(true, result.complete)
}
}
//
// YearCase.swift
// InputMask
//
// Created by Egor Taflanidi on 03.02.29.
// Copyright © 29 Heisei Egor Taflanidi. All rights reserved.
//
import XCTest
@testable import InputMask
class YearCase: MaskTestCase {
override func format() -> String {
return "[0099]"
}
func testInit_correctFormat_maskInitialized() {
XCTAssertNotNil(try self.mask())
}
func testInit_correctFormat_measureTime() {
self.measure {
var masks: [Mask] = []
for _ in 1...1000 {
masks.append(
try! self.mask()
)
}
}
}
func testGetOrCreate_correctFormat_measureTime() {
self.measure {
var masks: [Mask] = []
for _ in 1...1000 {
masks.append(
try! Mask.getOrCreate(withFormat: self.format())
)
}
}
}
func testGetPlaceholder_allSet_returnsCorrectPlaceholder() {
let placeholder: String = try! self.mask().placeholder()
XCTAssertEqual(placeholder, "0000")
}
func testAcceptableTextLength_allSet_returnsCorrectCount() {
let acceptableTextLength: Int = try! self.mask().acceptableTextLength()
XCTAssertEqual(acceptableTextLength, 2)
}
func testTotalTextLength_allSet_returnsCorrectCount() {
let totalTextLength: Int = try! self.mask().totalTextLength()
XCTAssertEqual(totalTextLength, 4)
}
func testAcceptableValueLength_allSet_returnsCorrectCount() {
let acceptableValueLength: Int = try! self.mask().acceptableValueLength()
XCTAssertEqual(acceptableValueLength, 2)
}
func testTotalValueLength_allSet_returnsCorrectCount() {
let totalValueLength: Int = try! self.mask().totalValueLength()
XCTAssertEqual(totalValueLength, 4)
}
func testApply_1_returns_1() {
let inputString: String = "1"
let inputCaret: String.Index = inputString.endIndex
let expectedString: String = "1"
let expectedCaret: String.Index = expectedString.endIndex
let expectedValue: String = expectedString
let result: Mask.Result = try! self.mask().apply(
toText: CaretString(
string: inputString,
caretPosition: inputCaret
)
)
XCTAssertEqual(expectedString, result.formattedText.string)
XCTAssertEqual(expectedCaret, result.formattedText.caretPosition)
XCTAssertEqual(expectedValue, result.extractedValue)
XCTAssertEqual(false, result.complete)
}
func testApply_11_returns_11() {
let inputString: String = "11"
let inputCaret: String.Index = inputString.endIndex
let expectedString: String = "11"
let expectedCaret: String.Index = expectedString.endIndex
let expectedValue: String = expectedString
let result: Mask.Result = try! self.mask().apply(
toText: CaretString(
string: inputString,
caretPosition: inputCaret
)
)
XCTAssertEqual(expectedString, result.formattedText.string)
XCTAssertEqual(expectedCaret, result.formattedText.caretPosition)
XCTAssertEqual(expectedValue, result.extractedValue)
XCTAssertEqual(true, result.complete)
}
func testApply_112_returns_112() {
let inputString: String = "112"
let inputCaret: String.Index = inputString.endIndex
let expectedString: String = "112"
let expectedCaret: String.Index = expectedString.endIndex
let expectedValue: String = expectedString
let result: Mask.Result = try! self.mask().apply(
toText: CaretString(
string: inputString,
caretPosition: inputCaret
)
)
XCTAssertEqual(expectedString, result.formattedText.string)
XCTAssertEqual(expectedCaret, result.formattedText.caretPosition)
XCTAssertEqual(expectedValue, result.extractedValue)
XCTAssertEqual(true, result.complete)
}
func testApply_1122_returns_1122() {
let inputString: String = "1122"
let inputCaret: String.Index = inputString.endIndex
let expectedString: String = "1122"
let expectedCaret: String.Index = expectedString.endIndex
let expectedValue: String = expectedString
let result: Mask.Result = try! self.mask().apply(
toText: CaretString(
string: inputString,
caretPosition: inputCaret
)
)
XCTAssertEqual(expectedString, result.formattedText.string)
XCTAssertEqual(expectedCaret, result.formattedText.caretPosition)
XCTAssertEqual(expectedValue, result.extractedValue)
XCTAssertEqual(true, result.complete)
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:RNTextInputMask.xcodeproj">
</FileRef>
</Workspace>
{
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "FD4D1DD9ED94882CDE2BBFBA505170F3AD6F47D8",
"DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
},
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
"FD4D1DD9ED94882CDE2BBFBA505170F3AD6F47D8" : 9223372036854775807,
"ED15D557B9B333C8B7761924B3DBF02994584ED6" : 9223372036854775807
},
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "860C39D2-D628-4EE8-A783-177E89BDA83B",
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
"FD4D1DD9ED94882CDE2BBFBA505170F3AD6F47D8" : "react-native-text-input-mask\/",
"ED15D557B9B333C8B7761924B3DBF02994584ED6" : "input-mask-ios\/"
},
"DVTSourceControlWorkspaceBlueprintNameKey" : "RNTextInputMask",
"DVTSourceControlWorkspaceBlueprintVersion" : 204,
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "ios\/RNTextInputMask\/RNTextInputMask.xcodeproj",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/RedMadRobot\/input-mask-ios.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "ED15D557B9B333C8B7761924B3DBF02994584ED6"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:ivanzotov\/react-native-text-input-mask.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "FD4D1DD9ED94882CDE2BBFBA505170F3AD6F47D8"
}
]
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0830"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "002789FF1F2F25CE0023E794"
BuildableName = "libRNTextInputMask.a"
BlueprintName = "RNTextInputMask"
ReferencedContainer = "container:RNTextInputMask.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "002789FF1F2F25CE0023E794"
BuildableName = "libRNTextInputMask.a"
BlueprintName = "RNTextInputMask"
ReferencedContainer = "container:RNTextInputMask.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "002789FF1F2F25CE0023E794"
BuildableName = "libRNTextInputMask.a"
BlueprintName = "RNTextInputMask"
ReferencedContainer = "container:RNTextInputMask.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>RNTextInputMask.xcscheme</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>002789FF1F2F25CE0023E794</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>RNTextInputMask.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>11</integer>
</dict>
</dict>
</dict>
</plist>
//
// RNTextInputMask.h
// RNTextInputMask
//
// Created by Ivan Zotov on 7/29/17.
//
//
#import <UIKit/UIKit.h>
#import <React/RCTBridgeModule.h>
@import InputMask;
@interface RNTextInputMask : NSObject <RCTBridgeModule, MaskedTextFieldDelegateListener>
@end
//
// RNTextInputMask.m
// RNTextInputMask
//
// Created by Ivan Zotov on 7/29/17.
//
//
#import <React/RCTBridge.h>
#import <React/RCTConvert.h>
#import <React/RCTUIManager.h>
#import <React/RCTEventDispatcher.h>
#import "RCTSinglelineTextInputView.h"
#import "RCTUITextField.h"
#import "RNTextInputMask.h"
@import InputMask;
@implementation RNTextInputMask {
NSMutableDictionary *masks;
}
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE();
- (dispatch_queue_t)methodQueue {
return self.bridge.uiManager.methodQueue;
}
RCT_EXPORT_METHOD(mask:(NSString *)maskString inputValue:(NSString *)inputValue onResult:(RCTResponseSenderBlock)onResult) {
NSString *output = [RNMask maskValueWithText:inputValue format:maskString];
onResult(@[output]);
}
RCT_EXPORT_METHOD(unmask:(NSString *)maskString inputValue:(NSString *)inputValue onResult:(RCTResponseSenderBlock)onResult) {
NSString *output = [RNMask unmaskValueWithText:inputValue format:maskString];
onResult(@[output]);
}
RCT_EXPORT_METHOD(setMask:(nonnull NSNumber *)reactNode mask:(NSString *)mask) {
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTSinglelineTextInputView *> *viewRegistry ) {
dispatch_async(dispatch_get_main_queue(), ^{
RCTSinglelineTextInputView *view = viewRegistry[reactNode];
RCTUITextField *textView = [view backedTextInputView];
if (!masks) {
masks = [[NSMutableDictionary alloc] init];
}
NSString *key = [NSString stringWithFormat:@"%@", reactNode];
MaskedTextFieldDelegate* maskedDelegate = [[MaskedTextFieldDelegate alloc] initWithFormat:mask];
masks[key] = maskedDelegate;
[masks[key] setListener:self];
textView.delegate = masks[key];
[self updateTextField:maskedDelegate textView:textView];
});
}];
}
- (void)textField:(RCTUITextField *)textField didFillMandatoryCharacters:(BOOL)complete didExtractValue:(NSString *)value
{
[self.bridge.eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
reactTag:[[textField reactSuperview] reactTag]
text:textField.attributedText.string
key:nil
eventCount:1];
}
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
[self.bridge.eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
reactTag:[[textField reactSuperview] reactTag]
text:textField.attributedText.string
key:nil
eventCount:1];
}
- (void)textFieldDidEndEditing:(RCTUITextField *)textField
{
[self.bridge.eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
reactTag:[[textField reactSuperview] reactTag]
text:textField.attributedText.string
key:nil
eventCount:1];
}
- (void)updateTextField:(MaskedTextFieldDelegate *)maskedDelegate textView:(RCTUITextField *)textView {
if(textView.attributedText.string.length> 0){
NSString *originalString = textView.attributedText.string;
NSString *croppedText = [originalString substringToIndex:[originalString length] -1];
[textView setAttributedText:[[NSAttributedString alloc] initWithString:croppedText]];
NSString *last = [originalString substringFromIndex:[originalString length] - 1];
[maskedDelegate textField:(UITextField*)textView
shouldChangeCharactersInRange: (NSRange){[textView.attributedText.string length], 0}
replacementString:last];
}
}
@end
{
"name": "agera-app-mask-input",
"version": "0.0.3",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "hungraim",
"license": "ISC"
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment