Edited at

あなたの文字制限をしているUITextFieldはクラッシュしているかもしれない

More than 1 year has passed since last update.

UITextFieldで入力した文字に対して何らかの制限を加えたい時はよくあると思います。文字数を制限したり、数字のみを入力させたい場合などです。

サンプルコードを用意しました。

https://github.com/akuraru/CrashTextField

数字のみを入力制限させたい場合の雑なサンプルは以下のようなものになると思います。このコードはクラッシュします。


ViewController.swift

extension ViewController: UITextFieldDelegate {

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let b = string.isEmpty || string == String(Int(string) ?? 0)
return b
}
}


クラッシュの再現方法


  1. UITextFieldに制限される文字列をペーストする

  2. 端末をシェイクして取り消しアラートを表示する(シミュレータだと⌘+⌃+Z)

  3. 取り消す

  4. クラッシュ


対策1

入力を受け付けない場合、UndoManagaerをリセットします。これで、取り消しができなくなります。


ViewController.swift

extension ViewController: UITextFieldDelegate {

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let b = string.isEmpty || string == String(Int(string) ?? 0)

if !b {
textField.undoManager?.removeAllActions()
}

return b
}
}



対策2

シェイクで編集する機能を無効にします。この方法はアプリ全体で無効になってしまうのでできればやらないようにしたほうがいいでしょう。


AppDelegate.swift

application.applicationSupportsShakeToEdit = false



クラッシュの内容(おまけ)

アプリケーションのどこで起こったクラッシュなのか全くわからないクラッシュログが発生する

2018-05-21 22:20:05.268665+0900 CrashTextField[47789:859156] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSBigMutableString substringWithRange:]: Range {0, 38} out of bounds; string length 0'

*** First throw call stack:
(
0 CoreFoundation 0x00000001127fd1e6 __exceptionPreprocess + 294
1 libobjc.A.dylib 0x000000010ec76031 objc_exception_throw + 48
2 CoreFoundation 0x0000000112872975 +[NSException raise:format:] + 197
3 Foundation 0x000000010e663e72 -[NSString substringWithRange:] + 131
4 UIKit 0x0000000110360b1e -[NSTextStorage(UIKitUndoExtensions) _undoRedoAttributedSubstringFromRange:] + 136
5 UIKit 0x0000000110360f7d -[_UITextUndoOperationReplace undoRedo] + 319
6 Foundation 0x000000010e711695 -[_NSUndoStack popAndInvoke] + 280
7 Foundation 0x000000010e711424 -[NSUndoManager undoNestedGroup] + 433
8 UIKit 0x000000010f5225aa __58-[UIApplication _showEditAlertViewWithUndoManager:window:]_block_invoke.2495 + 31
9 UIKit 0x000000010f8db425 -[UIAlertController _invokeHandlersForAction:] + 105
10 UIKit 0x000000010f8dbe2a __103-[UIAlertController _dismissAnimated:triggeringAction:triggeredByPopoverDimmingView:dismissCompletion:]_block_invoke.461 + 16
11 UIKit 0x000000010f683d02 -[UIPresentationController transitionDidFinish:] + 1346
12 UIKit 0x000000010f687b72 __56-[UIPresentationController runTransitionForCurrentState]_block_invoke.436 + 183
13 UIKit 0x000000011026b274 -[_UIViewControllerTransitionContext completeTransition:] + 102
14 UIKit 0x000000010f5d410d -[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:] + 859
15 UIKit 0x000000010f5a6f09 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 343
16 UIKit 0x000000010f5a754c -[UIViewAnimationState animationDidStop:finished:] + 293
17 UIKit 0x000000010f5a7600 -[UIViewAnimationState animationDidStop:finished:] + 473
18 QuartzCore 0x00000001160117a9 _ZN2CA5Layer23run_animation_callbacksEPv + 323
19 libdispatch.dylib 0x000000011395d848 _dispatch_client_callout + 8
20 libdispatch.dylib 0x000000011396892b _dispatch_main_queue_callback_4CF + 628
21 CoreFoundation 0x00000001127bfc99 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
22 CoreFoundation 0x0000000112783ea6 __CFRunLoopRun + 2342
23 CoreFoundation 0x000000011278330b CFRunLoopRunSpecific + 635
24 GraphicsServices 0x0000000115189a73 GSEventRunModal + 62
25 UIKit 0x000000010f5120b7 UIApplicationMain + 159
26 CrashTextField 0x000000010e36db77 main + 55
27 libdyld.dylib 0x00000001139da955 start + 1
)


参考

https://www.jianshu.com/p/b302a7c66741