proper handling for multibyte characters

// FREEBIE
This commit is contained in:
Michael Kirk 2017-09-01 16:49:03 -04:00
parent 362b383785
commit ae174d4a87
4 changed files with 67 additions and 43 deletions

View File

@ -132,6 +132,8 @@
451DE9FE1DC1A28200810E42 /* SyncPushTokensJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451DE9FC1DC1A28200810E42 /* SyncPushTokensJob.swift */; };
452037D11EE84975004E4CDF /* DebugUISessionState.m in Sources */ = {isa = PBXBuildFile; fileRef = 452037D01EE84975004E4CDF /* DebugUISessionState.m */; };
4520D8D51D417D8E00123472 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4520D8D41D417D8E00123472 /* Photos.framework */; };
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */; };
4521C3C11F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */; };
452C468F1E427E200087B011 /* OutboundCallInitiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452C468E1E427E200087B011 /* OutboundCallInitiator.swift */; };
452C46901E427E200087B011 /* OutboundCallInitiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452C468E1E427E200087B011 /* OutboundCallInitiator.swift */; };
452D1EE81DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452D1EE71DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift */; };
@ -593,6 +595,7 @@
452037CF1EE84975004E4CDF /* DebugUISessionState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUISessionState.h; sourceTree = "<group>"; };
452037D01EE84975004E4CDF /* DebugUISessionState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUISessionState.m; sourceTree = "<group>"; };
4520D8D41D417D8E00123472 /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; };
4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldHelper.swift; sourceTree = "<group>"; };
4526BD481CA61C8D00166BC8 /* OWSMessageEditing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageEditing.h; sourceTree = "<group>"; };
452C468E1E427E200087B011 /* OutboundCallInitiator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutboundCallInitiator.swift; sourceTree = "<group>"; };
452D1EE71DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MesssagesBubblesSizeCalculatorTest.swift; path = Models/MesssagesBubblesSizeCalculatorTest.swift; sourceTree = "<group>"; };
@ -1456,6 +1459,7 @@
76EB04FB18170B33006006FC /* Util.h */,
45F170D51E315310003FC1F2 /* Weak.swift */,
45F170CB1E310E22003FC1F2 /* WeakTimer.swift */,
4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */,
);
path = util;
sourceTree = "<group>";
@ -2370,6 +2374,7 @@
4574A5D61DD6704700C6B692 /* CallService.swift in Sources */,
34B3F8721E8DF1700035BE1A /* AdvancedSettingsTableViewController.m in Sources */,
45F170D61E315310003FC1F2 /* Weak.swift in Sources */,
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */,
34B3F8891E8DF1700035BE1A /* OWSConversationSettingsViewController.m in Sources */,
34C42D671F4734ED0072EC04 /* TSUnreadIndicatorInteraction.m in Sources */,
34B3F87E1E8DF1700035BE1A /* InboxTableViewCell.m in Sources */,
@ -2421,6 +2426,7 @@
45BB93391E688E14001E3939 /* UIDevice+featureSupport.swift in Sources */,
B660F7211C29988E00687D6E /* SignalKeyingStorage.m in Sources */,
B660F7221C29988E00687D6E /* VersionMigrations.m in Sources */,
4521C3C11F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */,
45D231781DC7E8F10034FA89 /* SessionResetJob.swift in Sources */,
458967111DC117CC00E9DD21 /* AccountManagerTest.swift in Sources */,
45F659831E1BE77000444429 /* NonCallKitCallUIAdaptee.swift in Sources */,

View File

@ -13,8 +13,8 @@ extern NSString *const kNSNotificationName_ProfileWhitelistDidChange;
extern NSString *const kNSNotificationKey_ProfileRecipientId;
extern NSString *const kNSNotificationKey_ProfileGroupId;
extern const NSUInteger kOWSProfileManager_MaxAvatarDiameter;
extern const NSUInteger kOWSProfileManager_NameDataLength;
extern const NSUInteger kOWSProfileManager_MaxAvatarDiameter;
@class TSThread;
@class OWSAES256Key;

View File

@ -452,48 +452,10 @@ NSString *const kProfileView_LastPresentedDate = @"kProfileView_LastPresentedDat
replacementString:(NSString *)insertionText
{
// TODO: Possibly filter invalid input.
// Prevent crashing undo bug
if (editingRange.length + editingRange.location > textField.text.length) {
return NO;
}
NSUInteger (^byteLength)(NSString *) = ^NSUInteger(NSString *string) {
if (string == nil) {
return 0;
}
return [string dataUsingEncoding:NSUTF8StringEncoding].length;
};
NSUInteger lengthOfRemainingExistingString
= byteLength(textField.text) - byteLength([textField.text substringWithRange:editingRange]);
NSUInteger newLength = lengthOfRemainingExistingString + byteLength(insertionText);
DDLogVerbose(@"%@ newLength: %lu", self.tag, (unsigned long)newLength);
if (newLength <= kOWSProfileManager_NameDataLength) {
return YES;
}
// Don't allow any change if inserting a single char (typically this means typing)
if (insertionText.length < 2) {
return NO;
}
// However if pasting, accept as much of the string as possible.
NSUInteger availableSpace = kOWSProfileManager_NameDataLength - lengthOfRemainingExistingString;
NSString *acceptableSubstring = @"";
for (NSUInteger i = 0; i <= insertionText.length; i++) {
NSString *maybeAcceptableSubstring = [insertionText substringWithRange:NSMakeRange(0, i)];
if (byteLength(maybeAcceptableSubstring) <= availableSpace) {
acceptableSubstring = maybeAcceptableSubstring;
}
}
textField.text = [textField.text stringByReplacingCharactersInRange:editingRange withString:acceptableSubstring];
// We've already handled any valid editing manually, so prevent further changes.
return NO;
return [TextFieldHelper textField:textField
shouldChangeCharactersInRange:editingRange
replacementString:insertionText
byteLimit:kOWSProfileManager_NameDataLength];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField

View File

@ -0,0 +1,56 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
import Foundation
import UIKit
@objc class TextFieldHelper: NSObject {
// Used to implement the UITextFieldDelegate method: `textField:shouldChangeCharactersInRange:replacementString`
// Takes advantage of Swift's superior unicode handling to append partial pasted text without splitting multi-byte characters.
class func textField(_ textField: UITextField, shouldChangeCharactersInRange editingRange: NSRange, replacementString: String, byteLimit: UInt) -> Bool {
let byteLength = { (string: String) -> UInt in
return UInt(string.utf8.count)
}
let existingString = textField.text ?? ""
// Given an NSRange, we need to interact with the NS flavor of substring
let removedString = (existingString as NSString).substring(with: editingRange)
let lengthOfRemainingExistingString = byteLength(existingString) - byteLength(removedString)
let newLength = lengthOfRemainingExistingString + byteLength(replacementString)
if (newLength <= byteLimit) {
return true
}
// Don't allow any change if inserting a single char is already over the limit (typically this means typing)
if (replacementString.characters.count < 2) {
return false
}
// However if pasting, accept as much of the string as possible.
let availableSpace = byteLimit - lengthOfRemainingExistingString
var acceptableSubstring = ""
for (_, char) in replacementString.characters.enumerated() {
var maybeAcceptableSubstring = acceptableSubstring
maybeAcceptableSubstring.append(char)
if (byteLength(maybeAcceptableSubstring) <= availableSpace) {
acceptableSubstring = maybeAcceptableSubstring
} else {
break
}
}
textField.text = (existingString as NSString).replacingCharacters(in: editingRange, with:acceptableSubstring)
// We've already handled any valid editing manually, so prevent further changes.
return false
}
}