2

So I have a app that is webview based. I have made some keyboard modifications (not the issue, removed them all for troubleshooting).

On the iPhone 6+ simulator, when in landscape mode, if something is copied, and you go to use the keyboard paste button (not the contextual one), the app crashes. I've also noticed this when trying to use the keyboard cut button.

The following crash is as follows:

    -[UIThreadSafeNode _responderForEditing]: unrecognized selector sent to instance 0x7ff4364344c0
    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIThreadSafeNode _responderForEditing]: unrecognized selector sent to instance 0x7ff4364344c0'
*** First throw call stack:
(
    0   CoreFoundation                      0x000000010df98f45 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x000000010da12deb objc_exception_throw + 48
    2   CoreFoundation                      0x000000010dfa156d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
    3   CoreFoundation                      0x000000010deeeeea ___forwarding___ + 970
    4   CoreFoundation                      0x000000010deeea98 _CF_forwarding_prep_0 + 120
    5   UIKit                               0x000000010c31eb4a -[UIKeyboardLayoutStar touchDownWithKey:atPoint:executionContext:] + 1445
    6   UIKit                               0x000000010c878ef0 -[UIKeyboardTaskExecutionContext returnExecutionToParentWithInfo:] + 278
    7   UIKit                               0x000000010c3169de -[UIKeyboardLayoutStar performHitTestForTouchInfo:touchStage:executionContextPassingUIKBTree:] + 1525
    8   UIKit                               0x000000010c31de57 -[UIKeyboardLayoutStar touchDown:executionContext:] + 533
    9   UIKit                               0x000000011df96fb5 -[UIKeyboardLayoutStarAccessibility touchDown:executionContext:] + 61
    10  UIKit                               0x000000010c8793ab -[UIKeyboardTaskQueue continueExecutionOnMainThread] + 332
    11  UIKit                               0x000000010c1347bd -[UIKeyboardLayout touchDown:] + 159
    12  UIKit                               0x000000010c1352ef -[UIKeyboardLayout touchesBegan:withEvent:] + 415
    13  UIKit                               0x000000010bee2cc2 -[UIWindow _sendTouchesForEvent:] + 308
    14  UIKit                               0x000000010bee3c06 -[UIWindow sendEvent:] + 865
    15  UIKit                               0x000000010be932fa -[UIApplication sendEvent:] + 263
    16  UIKit                               0x000000011df66a29 -[UIApplicationAccessibility sendEvent:] + 77
    17  UIKit                               0x000000010be6dabf _UIApplicationHandleEventQueue + 6844
    18  CoreFoundation                      0x000000010dec5011 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    19  CoreFoundation                      0x000000010debaf3c __CFRunLoopDoSources0 + 556
    20  CoreFoundation                      0x000000010deba3f3 __CFRunLoopRun + 867
    21  CoreFoundation                      0x000000010deb9e08 CFRunLoopRunSpecific + 488
    22  GraphicsServices                    0x000000010ead2ad2 GSEventRunModal + 161
    23  UIKit                               0x000000010be7330d UIApplicationMain + 171

Obviously since I'm using a webview, there's no actual textview to tie into it.

How would I resolve this? I don't care about passing app store review, so private API or other non apple approved methods are welcome.

Any assistance would be greatly appreciated. Thank You.

ChrisOSX
  • 724
  • 2
  • 11
  • 28

1 Answers1

2

It's Apple bug, because this selector should be passed to UIResponder, but UIThreadSafeNode is a subclass of NSObject. In order to workarount this, You can implement NSObject category:

@interface UIView (FirstResponder)

-(id)findFirstResponder;

@end

@implementation UIView (FirstResponder)

- (id)findFirstResponder
{
    if (self.isFirstResponder) {
        return self;
    }
    for (UIView *subView in self.subviews) {
        id responder = [subView findFirstResponder];
        if (responder) return responder;
    }
    return nil;
}

@end


@interface NSObject (KeyboardBUI)

-(_Nonnull id)_responderForEditing;

@end

@implementation NSObject (KeyboardBUI)

-(_Nonnull id)_responderForEditing
{
    return self;
}

-(void)cut:(nullable id)sender
{
    NSString *selection = [[self getWebView] stringByEvaluatingJavaScriptFromString:@"window.getSelection().toString()"];
    [[self getWebView] stringByEvaluatingJavaScriptFromString:@"document.execCommand('delete', false, null)"];
    [UIPasteboard generalPasteboard].string = selection;
}

-(void)paste:(nullable id)sender
{
    NSString *text = [UIPasteboard generalPasteboard].string;
    [[self getWebView] stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"document.execCommand('insertHTML', false, '%@')", text]];
}

-(void)copy:(nullable id)sender
{
    NSString *selection = [[self getWebView] stringByEvaluatingJavaScriptFromString:@"window.getSelection().toString()"];
    [UIPasteboard generalPasteboard].string = selection;
}

-(void)toggleItalics:(nullable id)sender
{
    [[self getWebView] stringByEvaluatingJavaScriptFromString:@"document.execCommand(\"Italic\")"];
}

-(void)toggleUnderline:(nullable id)sender
{
    [[self getWebView] stringByEvaluatingJavaScriptFromString:@"document.execCommand(\"Underline\")"];
}

-(void)toggleBoldface:(nullable id)sender
{
    [[self getWebView] stringByEvaluatingJavaScriptFromString:@"document.execCommand(\"Bold\")"];
}

-(UIWebView * __nullable)getWebView
{
    UIWebView *retVal = nil;
    id obj = [[[[[UIApplication sharedApplication] keyWindow] findFirstResponder] superview] superview];
    if ([obj  isKindOfClass:[UIWebView class]]) {
        retVal = obj;
    }
    return retVal;
}

@end

NOTE: I don't know if this would pass Apple Appstore Review.

UIView (FirstResponder) category from this answer.

copy/paste/cut from this answer

Community
  • 1
  • 1
Mateusz Szlosek
  • 1,175
  • 10
  • 19
  • I should have updated this question, as now I have made the switch to use WKWebView. So this is no longer an issue. But I appreciate the answer none the less. I will upvote, and hopefully if this helps somebody with the same situation they can mark as the accepted answer. – ChrisOSX Feb 04 '16 at 15:32
  • This works except you should "escape" any apostrophes in the copied text when building the `execCommand` string in `paste`. One way to do this would be: `NSString *text = [[UIPasteboard generalPasteboard].string stringByReplacingOccurrencesOfString:@"'" withString:@"'"];` – Craig May 16 '16 at 21:43