Migrate KMTextField to Swift

This commit is contained in:
Gabriel Wong 2017-10-06 12:11:20 +07:00
parent 6e8fe32680
commit 6de84cc35e
9 changed files with 377 additions and 394 deletions

View file

@ -42,6 +42,7 @@
98FFB3D41B67006F00289F0E /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98FFB3D31B67006F00289F0E /* WebKit.framework */; };
98FFB3D51B67008000289F0E /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98FFB3D31B67006F00289F0E /* WebKit.framework */; };
C040E50E1F85FF8A00901EE4 /* KeyPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C040E50D1F85FF8A00901EE4 /* KeyPreviewView.swift */; };
C040E5101F8606E300901EE4 /* KeymanTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C040E50F1F8606E300901EE4 /* KeymanTextField.swift */; };
C040E5121F86107E00901EE4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C040E5111F86107E00901EE4 /* AppDelegate.swift */; };
C040E5141F86108900901EE4 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C040E5131F86108900901EE4 /* MainViewController.swift */; };
C04514881F85D7F500D88416 /* KeyboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04514871F85D7F500D88416 /* KeyboardViewController.swift */; };
@ -65,13 +66,11 @@
C06D37441F81F5C400F61AE0 /* KeyboardNameTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08C62121F67C31100268D03 /* KeyboardNameTableViewCell.swift */; };
C06D37451F81F5C400F61AE0 /* KMManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F273AB9815641EC500A47CEE /* KMManager.m */; };
C06D37461F81F5C400F61AE0 /* PopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A5FF361F6682EB00BE740C /* PopoverView.swift */; };
C06D37471F81F5C400F61AE0 /* KMTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = F273AB9315641C3F00A47CEE /* KMTextField.m */; };
C06D37481F81F5C400F61AE0 /* KMTextFieldDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = F273ABA21564419700A47CEE /* KMTextFieldDelegateProxy.m */; };
C06D37491F81F5C400F61AE0 /* KMTextViewDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 6CBD0DE1151EC30300D70779 /* KMTextViewDelegateProxy.m */; };
C06D374A1F81F5C400F61AE0 /* KMTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6C0A1406151EA8B3007FA4AD /* KMTextView.m */; };
C06D374B1F81F5F400F61AE0 /* Keyman.h in Headers */ = {isa = PBXBuildFile; fileRef = F27FCB67157FFE0B00FBBA20 /* Keyman.h */; settings = {ATTRIBUTES = (Public, ); }; };
C06D374D1F81FFF900F61AE0 /* KMManager.h in Headers */ = {isa = PBXBuildFile; fileRef = F273AB9715641EC500A47CEE /* KMManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
C06D374E1F81FFF900F61AE0 /* KMTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = F273AB9215641C3F00A47CEE /* KMTextField.h */; settings = {ATTRIBUTES = (Public, ); }; };
C06D374F1F81FFF900F61AE0 /* KMTextFieldDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = F273ABA11564419700A47CEE /* KMTextFieldDelegateProxy.h */; settings = {ATTRIBUTES = (Public, ); }; };
C06D37521F82001100F61AE0 /* KMTextView.h in Headers */ = {isa = PBXBuildFile; fileRef = 6C0A1405151EA8B3007FA4AD /* KMTextView.h */; settings = {ATTRIBUTES = (Public, ); }; };
C06D37531F82001100F61AE0 /* KMTextViewDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 6CBD0DE0151EC30300D70779 /* KMTextViewDelegateProxy.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -188,6 +187,7 @@
98FFB3D31B67006F00289F0E /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
C034BBBD1F7CCE7D00021FF5 /* KeyboardMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardMenuView.swift; sourceTree = "<group>"; };
C040E50D1F85FF8A00901EE4 /* KeyPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPreviewView.swift; sourceTree = "<group>"; };
C040E50F1F8606E300901EE4 /* KeymanTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeymanTextField.swift; sourceTree = "<group>"; };
C040E5111F86107E00901EE4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
C040E5131F86108900901EE4 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
C04514861F85D7F500D88416 /* SystemKeyboard-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SystemKeyboard-Bridging-Header.h"; sourceTree = "<group>"; };
@ -217,8 +217,6 @@
F26071FF158F8C6000F37184 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
F2607201158F8C9600F37184 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
F2607203158F8CA200F37184 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
F273AB9215641C3F00A47CEE /* KMTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = KMTextField.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
F273AB9315641C3F00A47CEE /* KMTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = KMTextField.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
F273AB9715641EC500A47CEE /* KMManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = KMManager.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
F273AB9815641EC500A47CEE /* KMManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = KMManager.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
F273AB9A15641F0E00A47CEE /* CWLSynthesizeSingleton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CWLSynthesizeSingleton.h; sourceTree = "<group>"; };
@ -562,10 +560,9 @@
F273AB9E156440CD00A47CEE /* UITextField */ = {
isa = PBXGroup;
children = (
F273AB9215641C3F00A47CEE /* KMTextField.h */,
F273AB9315641C3F00A47CEE /* KMTextField.m */,
F273ABA11564419700A47CEE /* KMTextFieldDelegateProxy.h */,
F273ABA21564419700A47CEE /* KMTextFieldDelegateProxy.m */,
C040E50F1F8606E300901EE4 /* KeymanTextField.swift */,
);
name = UITextField;
sourceTree = "<group>";
@ -633,7 +630,6 @@
C06D372F1F81F4E100F61AE0 /* KeymanEngine.h in Headers */,
C06D374B1F81F5F400F61AE0 /* Keyman.h in Headers */,
C06D374D1F81FFF900F61AE0 /* KMManager.h in Headers */,
C06D374E1F81FFF900F61AE0 /* KMTextField.h in Headers */,
C06D374F1F81FFF900F61AE0 /* KMTextFieldDelegateProxy.h in Headers */,
C06D37521F82001100F61AE0 /* KMTextView.h in Headers */,
C06D37531F82001100F61AE0 /* KMTextViewDelegateProxy.h in Headers */,
@ -866,10 +862,10 @@
C06D37421F81F5C400F61AE0 /* KeyboardPickerButton.swift in Sources */,
C06D37431F81F5C400F61AE0 /* KeyboardPickerBarButtonItem.swift in Sources */,
C06D37441F81F5C400F61AE0 /* KeyboardNameTableViewCell.swift in Sources */,
C040E5101F8606E300901EE4 /* KeymanTextField.swift in Sources */,
C06D37451F81F5C400F61AE0 /* KMManager.m in Sources */,
C045148A1F85DF9100D88416 /* KeymanInputViewController.swift in Sources */,
C06D37461F81F5C400F61AE0 /* PopoverView.swift in Sources */,
C06D37471F81F5C400F61AE0 /* KMTextField.m in Sources */,
C06D37481F81F5C400F61AE0 /* KMTextFieldDelegateProxy.m in Sources */,
C06D37491F81F5C400F61AE0 /* KMTextViewDelegateProxy.m in Sources */,
C06D374A1F81F5C400F61AE0 /* KMTextView.m in Sources */,

View file

@ -6,7 +6,7 @@
//
//
// A one-stop hub for:
// - global settings and behaviour for KMTextView and KMTextField
// - global settings and behaviour for KMTextView and KeymanTextField
// - displaying the keyboard picker/downloader
// - all interaction with the Keyman servers (polling for info, downloading keyboards, etc)
//
@ -29,7 +29,7 @@
// [[KMManager sharedInstance] setKeyboardWithID:kbID languageID:langID keyboardName:kbName languageName:langName font:kbFont, oskFont:nil];
//
//
// NOTES: 1) The easiest way to use this SDK is to simply use KMTextViews and KMTextFields in place of their UIKit counterparts
// NOTES: 1) The easiest way to use this SDK is to simply use KMTextViews and KeymanTextFields in place of their UIKit counterparts
// 2) All keyboard downloading and picking can be handled by globe key on the keyboard, displaying a KMKeyboardPickerButton or a
// KMKeyboardPickerBarButtonItem
// - when tapped, these will display a UI for the user to navigate all available Keyman keyboards
@ -191,7 +191,7 @@ typedef enum {
// Displays a list of available keyboards and allows a user to add/download new keyboards or remove existing ones
// - it is recommended that you pass in your current UIViewController rather than the navigation controller
// - to enable/disable the keyboard picker on the keyboard, see KMTextView/KMTextField class reference
// - to enable/disable the keyboard picker on the keyboard, see KMTextView/KeymanTextField class reference
- (void)showKeyboardPickerInViewController:(UIViewController *)viewController shouldAddKeyboard:(BOOL)shouldAddKeyboard;
- (void)dismissKeyboardPicker:(UIViewController *)viewController;

View file

@ -10,7 +10,6 @@
#import "KMManager.h"
#import "KMManager+Internal.h"
#import "KMURLProtocol.h"
#import "KMTextField.h"
#import "KMTextView.h"
#import "UIView+Helpers.h"
#import <QuartzCore/QuartzCore.h>
@ -904,8 +903,8 @@ CWL_SYNTHESIZE_SINGLETON_FOR_CLASS_WITH_ACCESSOR(KMManager, sharedInstance);
id firstResponder = [[[UIApplication sharedApplication] keyWindow] findFirstResponder];
if ([firstResponder isKindOfClass:[KMTextView class]])
[(KMTextView *)firstResponder dismissKeyboard];
else if ([firstResponder isKindOfClass:[KMTextField class]])
[(KMTextField *)firstResponder dismissKeyboard];
else if ([firstResponder isKindOfClass:[KeymanTextField class]])
[(KeymanTextField *)firstResponder dismissKeyboard];
}
KeyboardPickerViewController *vc = [[KeyboardPickerViewController alloc] init];

View file

@ -1,34 +0,0 @@
//
// KMTextField.h
// Keyman Engine
//
// Copyright (c) 2017 SIL International. All rights reserved.
//
// KMTextField requires iOS 6.0 or greater due to use of the UITextInput protocol
#import <UIKit/UIKit.h>
#import "KMTextFieldDelegateProxy.h"
@interface KMTextField : UITextField <UITextFieldDelegate> {
KMTextFieldDelegateProxy *delegateProxy_;
NSTimer *selectionPollingTimer_;
NSInteger lastSelectionPosition_;
NSInteger lastSelectionLength_;
UIViewController *viewController; // viewController should be set to main view controller to enable keyboard picker.
BOOL shouldSetCustomFontOnKeyboardChange_; // if YES, sets Keyman custom font (if any) to KMTextField instance on keyboard change event, defaults to YES.
BOOL isInputClickSoundEnabled_; // if YES, plays keyboard click sound whenever input received, defaults to YES.
}
@property (nonatomic, strong) UIViewController *viewController;
@property (nonatomic, assign) BOOL shouldSetCustomFontOnKeyboardChange;
@property (nonatomic, assign) BOOL isInputClickSoundEnabled;
// Use this KMTextFieldDelegate instead of the normal UITextFieldDelegate.
// - All of the normal UITextFieldDelegate methods are supported.
- (void)setKeymanDelegate:(id<KMTextFieldDelegate>)keymanDelegate;
// Dismisses the keyboard if this textview is the first responder.
// - Use this instead of [resignFirstResponder] as it also resigns the Keyman keyboard's responders.
- (void)dismissKeyboard;
@end

View file

@ -1,335 +0,0 @@
//
// KMTextField.m
// Keyman Engine
//
// Created by Patrick Weekes on 12-05-16.
// Updated by Serkan Kurt on 17/05/2013.
// Copyright (c) 2017 SIL International. All rights reserved.
//
#import "KMTextField.h"
#import "KMManager+Internal.h"
#import <AudioToolbox/AudioToolbox.h>
@interface KMTextField ()
@property (nonatomic, strong) KMTextFieldDelegateProxy *delegateProxy;
@property (nonatomic, strong) NSTimer *selectionPollingTimer;
@property (nonatomic, assign) NSInteger lastSelectionPosition;
@property (nonatomic, assign) NSInteger lastSelectionLength;
@property (nonatomic, assign) BOOL shouldUpdateKMText;
@end
@implementation KMTextField
@synthesize delegateProxy = delegateProxy_;
@synthesize selectionPollingTimer = selectionPollingTimer_;
@synthesize lastSelectionPosition = lastSelectionPosition_;
@synthesize lastSelectionLength = lastSelectionLength_;
@synthesize viewController;
@synthesize shouldSetCustomFontOnKeyboardChange;
@synthesize isInputClickSoundEnabled;
@synthesize shouldUpdateKMText;
#pragma mark - Object Admin
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self.selectionPollingTimer invalidate];
self.delegate = nil;
}
- (id)init {
return [self initWithFrame:CGRectZero];
}
- (id)initWithFrame:(CGRect)frame {
shouldSetCustomFontOnKeyboardChange = YES;
isInputClickSoundEnabled = YES;
if (self = [super initWithFrame:frame]) {
self.delegateProxy = [[KMTextFieldDelegateProxy alloc] init];
self.delegate = self.delegateProxy;
if ([self respondsToSelector:@selector(inputAssistantItem)]) {
UITextInputAssistantItem *inputAssistantItem = [self inputAssistantItem];
inputAssistantItem.leadingBarButtonGroups = @[];
inputAssistantItem.trailingBarButtonGroups = @[];
}
[KMManager sharedInstance]; // Preload webview keyboard
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textfieldTextDidChange:) name:UITextFieldTextDidChangeNotification object:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardChanged:) name:kKeymanKeyboardChangedNotification object:nil];
self.lastSelectionPosition = 0;
self.lastSelectionLength = 0;
NSString *minVersionString = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"MinimumOSVersion"];
if ([minVersionString hasPrefix:@"4"] && [KMManager sharedInstance].debugPrintingOn)
[[KMManager sharedInstance] KMLog:@"KMTextField requires iOS 5.0 onwards. Try KMTextView instead or change your deployment target." checkDebugPrinting:NO];
}
return self;
}
#pragma mark - Class Overrides
- (UIView *)inputView {
[KMManager sharedInstance].webDelegate = self;
return [KMManager inputView];
}
// Prevent the delegate being set to anything but self
- (void)setDelegate:(id<UITextFieldDelegate>)delegate {
// Allow clearing of delegate
if (!delegate) {
[super setDelegate:nil];
} else {
if (delegate != self.delegateProxy)
[[KMManager sharedInstance] KMLog:[NSString stringWithFormat:@"KMTextField: 0x%x Trying to set KMTextField's delegate directly. Use -[setKeymanDelegate:] instead.", (uint)self] checkDebugPrinting:YES];
[super setDelegate:self.delegateProxy];
}
}
#pragma mark - Public Methods
- (void)setKeymanDelegate:(id<KMTextFieldDelegate>)keymanDelegate {
self.delegateProxy.keymanDelegate = keymanDelegate;
[[KMManager sharedInstance] KMLog:[NSString stringWithFormat:@"KMTextField: 0x%x keymanDelegate set to: %@", (uint)self, keymanDelegate] checkDebugPrinting:YES];
}
- (void)dismissKeyboard {
[[KMManager sharedInstance] KMLog:[NSString stringWithFormat:@"KMTextField: 0x%x Dismissing keyboard. Was first responder:%@", (uint)self, [self isFirstResponder]?@"YES":@"NO"] checkDebugPrinting:YES];
[self resignFirstResponder];
[[KMManager inputView] endEditing:YES];
}
- (void)setText:(NSString *)text {
if (text == nil)
[super setText:@""];
else
[super setText:text];
[KMManager setKMText:self.text];
UITextRange *textRange = self.selectedTextRange;
NSRange newRange = NSMakeRange([self offsetFromPosition:[self beginningOfDocument] toPosition:textRange.start], [self offsetFromPosition:textRange.start toPosition:textRange.end]);
[KMManager setKMSelectionRange:newRange manually:NO];
}
#pragma mark - KMWebViewDelegate
- (void)updatedFragment:(NSString *)fragment {
if (fragment && [fragment rangeOfString:@"insertText"].location != NSNotFound) {
if ([[KMManager sharedInstance] isSubKeysMenuVisible])
return;
if (isInputClickSoundEnabled) {
AudioServicesPlaySystemSound(0x450);
isInputClickSoundEnabled = NO; // Disable input click sound for 0.1 second to ensure it plays for single key stroke.
[self performSelector:@selector(enableInputClickSound) withObject:nil afterDelay:0.1f];
}
NSRange range1 = [fragment rangeOfString:@"+dn="];
NSRange range2 = [fragment rangeOfString:@"+s="];
range1.location += range1.length;
range1.length = range2.location - range1.location;
NSInteger dn = [[fragment substringWithRange:range1] integerValue];
range2.location += range2.length;
range2.length = [fragment length] - range2.location;
NSString *s = [fragment substringWithRange:range2];
NSMutableString *mText = nil;
NSArray *unicodes = nil;
if ([s length])
unicodes = [s componentsSeparatedByString: @","];
NSScanner *scanner = nil;
for(int i=0; i<[unicodes count]; i++) {
scanner = [NSScanner scannerWithString:[unicodes objectAtIndex:i]];
unsigned int unicode;
[scanner scanHexInt:&unicode];
if(mText == nil)
mText = [NSMutableString stringWithFormat:@"%C", (unsigned short) unicode];
else
[mText appendString:[NSString stringWithFormat:@"%C", (unsigned short) unicode]];
}
if (mText == nil)
mText = [NSMutableString stringWithString:@""];
NSString *text = [NSString stringWithString:mText];
mText = nil;
UITextRange *textRange = self.selectedTextRange;
NSRange selRange = NSMakeRange([self offsetFromPosition:[self beginningOfDocument] toPosition:textRange.start], [self offsetFromPosition:textRange.start toPosition:textRange.end]);
if (dn <= 0) {
if(selRange.length == 0)
[self insertText:text];
else {
NSString *t = self.text;
NSString *r = [t stringByReplacingCharactersInRange:selRange withString:text];
self.text = r;
}
}
else {
if (![s length]) {
for (int i=0;i<dn;i++)
[self deleteBackward];
}
else {
if(selRange.length == 0) {
for (int i=0;i<dn;i++)
[self deleteBackward];
[self insertText:text];
}
else {
NSString *t = self.text;
NSString *r = [t stringByReplacingCharactersInRange:selRange withString:text];
self.text = r;
}
}
}
}
else if (fragment && [fragment rangeOfString:@"hideKeyboard"].location != NSNotFound) {
[self dismissKeyboard];
}
else if (fragment && [fragment rangeOfString:@"menuKeyUp"].location != NSNotFound) {
if (viewController != nil)
[[KMManager sharedInstance] showKeyboardPickerInViewController:viewController shouldAddKeyboard:NO];
else
[[KMManager sharedInstance] switchToNextKeyboard];
}
}
#pragma mark - UITextFieldDelegate Hooks
- (void)setSelectedTextRange:(UITextRange *)selectedTextRange {
[super setSelectedTextRange:selectedTextRange];
NSRange newRange = NSMakeRange([self offsetFromPosition:[self beginningOfDocument] toPosition:selectedTextRange.start], [self offsetFromPosition:selectedTextRange.start toPosition:selectedTextRange.end]);
[KMManager setKMSelectionRange:newRange manually:NO];
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
shouldUpdateKMText = YES; // Enable text update to catch copy/paste operations
return YES;
}
- (void)textfieldTextDidChange:(NSNotification *)notification {
if (shouldUpdateKMText) { // Catches copy/paste operations
[KMManager setKMText:self.text];
UITextRange *textRange = self.selectedTextRange;
NSRange newRange = NSMakeRange([self offsetFromPosition:[self beginningOfDocument] toPosition:textRange.start], [self offsetFromPosition:textRange.start toPosition:textRange.end]);
[KMManager setKMSelectionRange:newRange manually:NO];
shouldUpdateKMText = NO;
}
}
- (BOOL)textFieldShouldClear:(KMTextField *)textField {
if (textField == self)
[KMManager clearText];
return YES;
}
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
int iosVersion = [[[UIDevice currentDevice] systemVersion] intValue];
if (iosVersion < 7)
return YES;
NSString *lrm = @"\u200e";
NSString *rlm = @"\u202e";
NSString *keyboardID = [KMManager sharedInstance].keyboardID;
NSString *languageID = [KMManager sharedInstance].languageID;
UITextWritingDirection textWD = [self baseWritingDirectionForPosition:[self beginningOfDocument] inDirection:UITextStorageDirectionForward];
if ([[KMManager sharedInstance] isRTLKeyboardWithID:keyboardID languageID:languageID]) {
if (textWD != UITextWritingDirectionRightToLeft) {
if ([self.text rangeOfString:lrm].location == 0) {
NSMutableString *mText = [self.text mutableCopy];
[mText deleteCharactersInRange:NSMakeRange(0, 1)];
[self setText:[NSString stringWithString:mText]];
mText = nil;
}
if (![self.text length] || [self.text rangeOfString:rlm].location != 0)
[self setText:[NSString stringWithFormat:@"%@%@", rlm, self.text]];
[self makeTextWritingDirectionRightToLeft:nil];
}
}
else {
if (textWD != UITextWritingDirectionLeftToRight) {
if ([self.text rangeOfString:rlm].location == 0) {
NSMutableString *mText = [self.text mutableCopy];
[mText deleteCharactersInRange:NSMakeRange(0, 1)];
[self setText:[NSString stringWithString:mText]];
mText = nil;
}
if (![self.text length] || [self.text rangeOfString:lrm].location != 0)
[self setText:[NSString stringWithFormat:@"%@%@", lrm, self.text]];
[self makeTextWritingDirectionLeftToRight:nil];
}
}
return YES;
}
- (void)textFieldDidBeginEditing:(KMTextField *)textField {
[KMManager sharedInstance].webDelegate = self;
NSString *keyboardID = [[KMManager sharedInstance] keyboardID];
NSString *languageID = [[KMManager sharedInstance] languageID];
NSString *fontName = [[KMManager sharedInstance] fontNameForKeyboardWithID:keyboardID languageID:languageID];
CGFloat fontSize = [self.font pointSize];
if (fontName != nil)
[self setFont:[UIFont fontWithName:fontName size:fontSize]];
else
[self setFont:[UIFont systemFontOfSize:fontSize]];
[[KMManager sharedInstance] KMLog:[NSString stringWithFormat:@"TextField setFont: %@", [self font].familyName] checkDebugPrinting:YES];
// copy this textField's text to the webview
[KMManager setKMText:self.text];
UITextRange *textRange = self.selectedTextRange;
NSRange newRange = NSMakeRange([self offsetFromPosition:[self beginningOfDocument] toPosition:textRange.start], [self offsetFromPosition:textRange.start toPosition:textRange.end]);
[KMManager setKMSelectionRange:newRange manually:NO];
[[KMManager sharedInstance] KMLog:[NSString stringWithFormat:@"KMTextField: 0x%x Became first responder. Value: %@", (uint)self, [self.text length]?self.text:@"<blank>"] checkDebugPrinting:YES];
}
- (BOOL)textFieldShouldEndEditing:(KMTextField *)textField {
if (textField == self)
[self resignFirstResponder];
return YES;
}
#pragma mark - Keyman notifications
- (void)keyboardChanged:(NSNotification *)notification {
if (!self.shouldSetCustomFontOnKeyboardChange)
return;
NSDictionary *kbInfo = [[notification userInfo] objectForKey:kKeymanKeyboardInfoKey];
NSString *keyboardID = [kbInfo objectForKey:kKeymanKeyboardIdKey];
NSString *languageID = [kbInfo objectForKey:kKeymanLanguageIdKey];
NSString *fontName = [[KMManager sharedInstance] fontNameForKeyboardWithID:keyboardID languageID:languageID];
CGFloat fontSize = [self.font pointSize];
if (fontName != nil)
[self setFont:[UIFont fontWithName:fontName size:fontSize]];
else
[self setFont:[UIFont systemFontOfSize:fontSize]];
if ([self isFirstResponder]) {
[self resignFirstResponder];
[self becomeFirstResponder];
}
[[KMManager sharedInstance] KMLog:[NSString stringWithFormat:@"TextField setFont: %@", [self font].familyName] checkDebugPrinting:YES];
}
#pragma mark - Private Methods
- (void)enableInputClickSound {
isInputClickSoundEnabled = YES;
}
@end

View file

@ -7,7 +7,7 @@
//
#import "KMTextFieldDelegateProxy.h"
#import "KMTextField.h"
#import "KeymanEngine/KeymanEngine-Swift.h"
@implementation KMTextFieldDelegateProxy
@ -22,7 +22,7 @@
#pragma mark - UITextFieldDelegate
// NOTE: the return value from the textField's hook is ignored
- (BOOL)textFieldShouldBeginEditing:(KMTextField *)textField {
- (BOOL)textFieldShouldBeginEditing:(KeymanTextField *)textField {
if ([textField respondsToSelector:@selector(textFieldShouldBeginEditing:)]) {
[textField textFieldShouldBeginEditing:textField];
}
@ -32,7 +32,7 @@
return YES;
}
- (void)textFieldDidBeginEditing:(KMTextField *)textField {
- (void)textFieldDidBeginEditing:(KeymanTextField *)textField {
if ([textField respondsToSelector:@selector(textFieldDidBeginEditing:)]) {
[textField textFieldDidBeginEditing:textField];
}
@ -42,7 +42,7 @@
}
// NOTE: the return value from the textField's hook is ignored
- (BOOL)textFieldShouldEndEditing:(KMTextField *)textField {
- (BOOL)textFieldShouldEndEditing:(KeymanTextField *)textField {
if ([textField respondsToSelector:@selector(textFieldShouldEndEditing:)]) {
[textField textFieldShouldEndEditing:textField];
}
@ -53,7 +53,7 @@
return YES;
}
- (void)textFieldDidEndEditing:(KMTextField *)textField {
- (void)textFieldDidEndEditing:(KeymanTextField *)textField {
if ([textField respondsToSelector:@selector(textFieldDidEndEditing:)]) {
[textField textFieldDidEndEditing:textField];
}
@ -63,7 +63,7 @@
}
// NOTE: the return value from the textField's hook is ignored
- (BOOL)textField:(KMTextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
- (BOOL)textField:(KeymanTextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if ([textField respondsToSelector:@selector(textField:shouldChangeCharactersInRange:replacementString:)]) {
[textField textField:textField shouldChangeCharactersInRange:range replacementString:string];
}
@ -75,7 +75,7 @@
}
// NOTE: the return value from the textField's hook is ignored
- (BOOL)textFieldShouldClear:(KMTextField *)textField {
- (BOOL)textFieldShouldClear:(KeymanTextField *)textField {
if ([textField respondsToSelector:@selector(textFieldShouldClear:)]) {
[textField textFieldShouldClear:textField];
}
@ -87,7 +87,7 @@
}
// NOTE: the return value from the textField's hook is ignored
- (BOOL)textFieldShouldReturn:(KMTextField *)textField {
- (BOOL)textFieldShouldReturn:(KeymanTextField *)textField {
if ([textField respondsToSelector:@selector(textFieldShouldReturn:)]) {
[textField textFieldShouldReturn:textField];
}

View file

@ -0,0 +1,358 @@
//
// KeymanTextField.swift
// KeymanEngine
//
// Created by Gabriel Wong on 2017-10-05.
// Copyright © 2017 SIL International. All rights reserved.
//
import AudioToolbox
import UIKit
public class KeymanTextField: UITextField, UITextFieldDelegate {
// viewController should be set to main view controller to enable keyboard picker.
public var viewController: UIViewController?
// Sets Keyman custom font (if any) to KeymanTextField instance on keyboard change event
public var shouldSetCustomFontOnKeyboardChange = true
public var isInputClickSoundEnabled = true
private var delegateProxy: KMTextFieldDelegateProxy?
private var shouldUpdateKMText = false
// MARK: - Object Admin
deinit {
NotificationCenter.default.removeObserver(self)
delegate = nil
}
public convenience init() {
self.init(frame: CGRect.zero)
}
public override init(frame: CGRect) {
super.init(frame: frame)
delegateProxy = KMTextFieldDelegateProxy()
delegate = delegateProxy
if #available(iOS 9.0, *) {
inputAssistantItem.leadingBarButtonGroups = []
inputAssistantItem.trailingBarButtonGroups = []
}
KMManager.sharedInstance() // Preload webview keyboard
NotificationCenter.default.addObserver(self, selector: #selector(self.textFieldTextDidChange),
name: .UITextFieldTextDidChange, object: self)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardChanged),
name: NSNotification.Name.keymanKeyboardChanged, object: nil)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
delegateProxy = KMTextFieldDelegateProxy(self)
delegate = delegateProxy
if #available(iOS 9.0, *) {
inputAssistantItem.leadingBarButtonGroups = []
inputAssistantItem.trailingBarButtonGroups = []
}
KMManager.sharedInstance() // Preload webview keyboard
NotificationCenter.default.addObserver(self, selector: #selector(self.textFieldTextDidChange),
name: .UITextFieldTextDidChange, object: self)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardChanged),
name: NSNotification.Name.keymanKeyboardChanged, object: nil)
}
// MARK: - Class Overrides
public override var inputView: UIView? {
get {
KMManager.sharedInstance().webDelegate = self
return KMManager.inputView()
}
set(inputView) {
super.inputView = inputView
}
}
// Prevent the delegate being set to anything but the delegateProxy
public override weak var delegate: UITextFieldDelegate? {
get {
return super.delegate
}
set(delegate) {
// Allow clearing of delegate
guard let delegate = delegate else {
super.delegate = nil
return
}
if delegate !== delegateProxy {
KMManager.sharedInstance().kmLog(
"Trying to set KeymanTextField's delegate directly. Use setKeymanDelegate() instead.",
checkDebugPrinting: true)
}
super.delegate = delegateProxy
}
}
// MARK: - Public Methods
// Use this KMTextFieldDelegate instead of the normal UITextFieldDelegate.
// All of the normal UITextFieldDelegate methods are supported.
@objc public func setKeymanDelegate(_ keymanDelegate: KMTextFieldDelegate?) {
delegateProxy?.keymanDelegate = keymanDelegate
KMManager.sharedInstance().kmLog(
"KeymanTextField: \(self.debugDescription) keymanDelegate set to: \(keymanDelegate.debugDescription)",
checkDebugPrinting: true)
}
// Dismisses the keyboard if this textview is the first responder.
// - Use this instead of [resignFirstResponder] as it also resigns the Keyman keyboard's responders.
@objc public func dismissKeyboard() {
KMManager.sharedInstance().kmLog(
"KeymanTextField: \(self.debugDescription) Dismissing keyboard. Was first responder:\(isFirstResponder)",
checkDebugPrinting: true)
resignFirstResponder()
KMManager.inputView().endEditing(true)
}
public override var text: String! {
get {
return super.text ?? ""
}
set(text) {
if let text = text {
super.text = text
} else {
super.text = ""
}
KMManager.setKMText(self.text)
let textRange = selectedTextRange!
let newRange = NSRange(location: offset(from: beginningOfDocument, to: textRange.start),
length: offset(from: textRange.start, to: textRange.end))
KMManager.setKMSelectionRange(newRange, manually: false)
}
}
// MARK: - KMWebViewDelegate
@objc public func updatedFragment(_ fragment: String) {
if fragment.contains("insertText") {
processInsertText(fragment)
} else if fragment.contains("hideKeyboard") {
dismissKeyboard()
} else if fragment.contains("menuKeyUp") {
if let viewController = viewController {
KMManager.sharedInstance().showKeyboardPicker(in: viewController, shouldAddKeyboard: false)
} else {
KMManager.sharedInstance().switchToNextKeyboard()
}
}
}
private func processInsertText(_ fragment: String) {
if KMManager.sharedInstance().isSubKeysMenuVisible() {
return
}
if isInputClickSoundEnabled {
AudioServicesPlaySystemSound(0x450)
// Disable input click sound for 0.1 second to ensure it plays for single key stroke.
isInputClickSoundEnabled = false
perform(#selector(self.enableInputClickSound), with: nil, afterDelay: 0.1)
}
// TODO: Refactor duplicate logic in KeymanInputViewController and KMTextView
let dnRange = fragment.range(of: "+dn=")!
let sRange = fragment.range(of: "+s=")!
let dn = Int(fragment[dnRange.upperBound..<sRange.lowerBound])!
let s = fragment[sRange.upperBound...]
let hexStrings: [String]
if !s.isEmpty {
hexStrings = s.components(separatedBy: ",")
} else {
hexStrings = []
}
let codeUnits = hexStrings.map { hexString -> UInt16 in
let scanner = Scanner(string: hexString)
var codeUnit: UInt32 = 0
scanner.scanHexInt32(&codeUnit)
return UInt16(codeUnit)
}
let text = String(utf16CodeUnits: codeUnits, count: codeUnits.count)
let textRange = selectedTextRange!
let selRange = NSRange(location: offset(from: beginningOfDocument, to: textRange.start),
length: offset(from: textRange.start, to: textRange.end))
if dn <= 0 {
if selRange.length == 0 {
insertText(text)
} else {
self.text = (self.text! as NSString).replacingCharacters(in: selRange, with: text)
}
} else {
if s.isEmpty {
for _ in 0..<dn {
deleteBackward()
}
} else {
if selRange.length == 0 {
for _ in 0..<dn {
deleteBackward()
}
insertText(text)
} else {
self.text = (self.text! as NSString).replacingCharacters(in: selRange, with: text)
}
}
}
}
// MARK: - UITextFieldDelegate Hooks
public override var selectedTextRange: UITextRange? {
didSet(range) {
guard let range = range else {
return
}
let newRange = NSRange(location: offset(from: beginningOfDocument, to: range.start),
length: offset(from: range.start, to: range.end))
KMManager.setKMSelectionRange(newRange, manually: false)
}
}
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
shouldUpdateKMText = true // Enable text update to catch copy/paste operations
return true
}
@objc public func textFieldTextDidChange(_ notification: Notification) {
if shouldUpdateKMText {
// Catches copy/paste operations
KMManager.setKMText(text)
let textRange = selectedTextRange!
let newRange = NSRange(location: offset(from: beginningOfDocument, to: textRange.start),
length: offset(from: textRange.start, to: textRange.end))
KMManager.setKMSelectionRange(newRange, manually: false)
shouldUpdateKMText = false
}
}
public func textFieldShouldClear(_ textField: UITextField) -> Bool {
if textField == self {
KMManager.clearText()
}
return true
}
public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
let leftToRightMark = "\u{200e}"
let rightToLeftOverride = "\u{202e}"
let keyboardID = KMManager.sharedInstance().keyboardID
let languageID = KMManager.sharedInstance().languageID
let textWD = baseWritingDirection(for: beginningOfDocument, in: .forward)
if KMManager.sharedInstance().isRTLKeyboard(withID: keyboardID, languageID: languageID) {
if textWD != .rightToLeft {
if text.hasPrefix(leftToRightMark) {
text = String(text.utf16.dropFirst())
}
if text.isEmpty || !text.hasPrefix(rightToLeftOverride) {
text = "\(rightToLeftOverride)\(text!)"
}
makeTextWritingDirectionRightToLeft(nil)
}
} else {
if textWD != .leftToRight {
if text.hasPrefix(rightToLeftOverride) {
text = String(text.utf16.dropFirst())
}
if text.isEmpty || !text.hasPrefix(leftToRightMark) {
text = "\(leftToRightMark)\(text!)"
}
makeTextWritingDirectionLeftToRight(nil)
}
}
return true
}
public func textFieldDidBeginEditing(_ textField: UITextField) {
KMManager.sharedInstance().webDelegate = self
let keyboardID = KMManager.sharedInstance().keyboardID
let languageID = KMManager.sharedInstance().languageID
let fontName = KMManager.sharedInstance().fontNameForKeyboard(withID: keyboardID, languageID: languageID)
let fontSize = font?.pointSize ?? UIFont.systemFontSize
if let fontName = fontName {
font = UIFont(name: fontName, size: fontSize)
} else {
font = UIFont.systemFont(ofSize: fontSize)
}
KMManager.sharedInstance().kmLog("TextField setFont: \(String(describing: font?.familyName))",
checkDebugPrinting: true)
// copy this textField's text to the webview
KMManager.setKMText(text)
let textRange = selectedTextRange!
let newRange = NSRange(location: offset(from: beginningOfDocument, to: textRange.start),
length: offset(from: textRange.start, to: textRange.end))
KMManager.setKMSelectionRange(newRange, manually: false)
KMManager.sharedInstance().kmLog(
"KeymanTextField: \(self.debugDescription) Became first responder. Value: \(String(describing: text))",
checkDebugPrinting: true)
}
public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
if textField == self {
resignFirstResponder()
}
return true
}
// MARK: - Keyman notifications
@objc public func keyboardChanged(_ notification: Notification) {
if !shouldSetCustomFontOnKeyboardChange {
return
}
let kbInfo = notification.userInfo?[kKeymanKeyboardInfoKey] as? [AnyHashable: Any]
let keyboardID = kbInfo?[kKeymanKeyboardIdKey] as? String
let languageID = kbInfo?[kKeymanLanguageIdKey] as? String
let fontName = KMManager.sharedInstance().fontNameForKeyboard(withID: keyboardID, languageID: languageID)
let fontSize = font?.pointSize ?? UIFont.systemFontSize
if let fontName = fontName {
font = UIFont(name: fontName, size: fontSize)
} else {
font = UIFont.systemFont(ofSize: fontSize)
}
if isFirstResponder {
resignFirstResponder()
becomeFirstResponder()
}
KMManager.sharedInstance().kmLog(
"KeymanTextField \(self.debugDescription) setFont: \(font!.familyName)", checkDebugPrinting: true)
}
@objc public func enableInputClickSound() {
isInputClickSoundEnabled = true
}
}

View file

@ -12,5 +12,4 @@
#import "KMTextView.h"
#import "KMTextViewDelegateProxy.h"
#import "KMTextField.h"
#import "KMTextFieldDelegateProxy.h"

View file

@ -12,7 +12,7 @@ import QuartzCore
class MainViewController: UIViewController, UIAlertViewDelegate, KMTextViewDelegate, KMTextFieldDelegate {
var textView1: KMTextView!
var textView2: KMTextField!
var textView2: KeymanTextField!
var textView3: UITextView!
deinit {
@ -90,7 +90,7 @@ class MainViewController: UIViewController, UIAlertViewDelegate, KMTextViewDeleg
// Text Views/Fields
textView1 = KMTextView()
textView2 = KMTextField()
textView2 = KeymanTextField()
textView3 = UITextView()
setTextViewStyle(textView1)
setTextFieldStyle(textView2)