4

I have a UIWebView with some basic html that acts as a simple editor.

// in initWithFrame: of UIView subclass.
_editorHTML = [@"<!doctype html>"
                   "<html>"
                   "<head>"
                   "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
                   "<style type=\"text/css\">"
                   "#content { font-family:Arial, Helvetica, sans-serif; font-size:1em; } "
                   "</style>"
                   "<script type=\"text/javascript\">"
                   "function load()"
                   "{"
                   "    window.location.href = 'ready://' + document.body.offsetHeight;"
                   "}"
                   "</script>"
                   "</head>"
                   "<body id=\"bodyDiv\" onload=\"load()\">"
                   "<div id=\"content\" contenteditable=\"true\">%@</div>"
                   "</body>"
                   "</html>" retain];

_webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
_webView.delegate = self;
_webView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
_webView.scrollView.bounces = NO;
[_webView loadHTMLString:[NSString stringWithFormat:_editorHTML, @""] baseURL:[NSURL fileURLWithPath:directory]];

- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSURL *url = [request URL];
    if (navigationType == UIWebViewNavigationTypeOther) {
        if ([[url scheme] isEqualToString:@"ready"]) {

            int height = (int)_webView.frame.size.height;
            [webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"document.getElementById('content').style.minHeight = '%dpx'", height - WEBVIEW_DIVHEIGHT_OFFSET]];

            return NO;
        }
    }
    return YES;
}

So, how can I determine the cursor position in my div so I can update my scrollView to make sure that the content is visible above the UIKeyboard thus not having to make the user manually scroll to keep the text in view?

3 Answers3

7

You can get the selected range from the selection and use its getClientRects() method.

Live demo: http://jsfiddle.net/timdown/xMEjD/

Code:

function getCaretClientPosition() {
    var x = 0, y = 0;
    var sel = window.getSelection();
    if (sel.rangeCount) {
        var range = sel.getRangeAt(0);
        if (range.getClientRects) {
            var rects = range.getClientRects();
            if (rects.length > 0) {
                x = rects[0].left;
                y = rects[0].top;
            }
        }
    }
    return { x: x, y: y };
}
Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • @developerdoug: Yes. Is that what you wanted? – Tim Down Jun 21 '12 at 08:19
  • @IvorySantos: It may do in some browsers, but it won't in Firefox, which does not have any association between the `Selection` returned by `window.getSelection()` and selections inside textareas and inputs. – Tim Down Feb 16 '13 at 14:57
  • 2
    @IvorySantos: In fact it doesn't work for textareas in any browser I tried. – Tim Down Feb 16 '13 at 15:03
  • 2
    This only works if there is text when you hit return. If you move the cursor to the end of the line and hit enter to create some returns, it won't show an accurate value. – Nic Hubbard Aug 12 '14 at 17:41
  • Awesome answer. Worked great for my `
    `!
    – diegoreymendez Oct 03 '14 at 13:26
  • 1
    It is returning wrong position ( (x,y) = (0,0) ) when I type first letter, right after new line. (after return key in iOS). Any idea ? – Ankush Sep 19 '16 at 11:13
  • In case of multiple
    when we type in first row of any section it returns same i.e. y=0. But from next line i.e Line2 , Line 3 .... Line n it works perfect.
    – Ankush Sep 29 '16 at 13:40
5

@timdown Interestingly enough, your script always returns 0,0 for the beginning of a line. The script from [question]: Coordinates of selected text in browser page does return the right Y value, though.

Repeated here for ease of searching:

function getSelectionCoords() {
    var sel = document.selection, range;
    var x = 0, y = 0;
    if (sel) {
        if (sel.type != "Control") {
            range = sel.createRange();
            range.collapse(true);
            x = range.boundingLeft;
            y = range.boundingTop;
        }
    } else if (window.getSelection) {
        sel = window.getSelection();
        if (sel.rangeCount) {
            range = sel.getRangeAt(0).cloneRange();
            if (range.getClientRects) {
                range.collapse(true);
                var rect = range.getClientRects()[0];
                x = rect.left;
                y = rect.top;
            }
        }
    }
    return { x: x, y: y };
}
Community
  • 1
  • 1
Dafydd Williams
  • 1,232
  • 12
  • 14
  • 1
    In my tests, the answer from the different question linked here *does* fix the problem, but the piece of code repeated here does *not*. The trick is to add a DOM element then get its position, go look at the code here: http://stackoverflow.com/a/6847328/135712 – Vincent Tourraine Sep 03 '14 at 15:02
1

This solution is an evolution of the accepted answer. Fixes the newLine issue in WebKit.

function getCaretClientPosition() {
    var x = 0, y = 0;
    var sel = window.getSelection();
    if (sel.rangeCount) {

        var range = sel.getRangeAt(0);
        var needsToWorkAroundNewlineBug = (range.startContainer.nodeName.toLowerCase() == 'p'
                                           && range.startOffset == 0);

        if (needsToWorkAroundNewlineBug) {
            x = range.startContainer.offsetLeft;
            y = range.startContainer.offsetTop;
        } else {
            if (range.getClientRects) {
                var rects = range.getClientRects();
                if (rects.length > 0) {
                    x = rects[0].left;
                    y = rects[0].top;
                }
            }
        }
    }
    return { x: x, y: y };
}
diegoreymendez
  • 1,997
  • 18
  • 20