Автоматическая прокрутка при contentEditable/designMode в UIWebView

Я пытаюсь использовать текстовый редактор (с возможностью экспорта HTML) для приложения iPhone, над которым я работаю, и решил использовать поддержку WebKit iOS 5 для contentEditable/designMode. Я ударил стену с одной проблемой, которая ломается для того, что мне нужно. При редактировании содержимого в UIWebView автоматическая прокрутка курсора отсутствует, как, например, в UITextView. При вводе курсора курсор продолжается под scrollView, и пользователь должен вручную прокручивать вверх.

вот некоторые соответствующие код:

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    NSString *string = @"document.body.contentEditable=true;document.designMode='on';void(0)";
    [webView stringByEvaluatingJavaScriptFromString:string];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
}

любые идеи, как исправить эту проблему? Я не уверен, что это также происходит в Safari или только в UIWebViewреализация WebKit.

если вы нажмете на эту проблему, не забудьте перейти кhttps://bugreport.apple.com и дублировать rdar://16455638.

5 ответов


после нескольких часов исследований я нашел лучшее решение, поэтому я решил поделиться.

существует ошибка в contentEditable реализации iOS 5, где основной scrollView не будет прокручиваться с помощью курсора. Если сделать тег тела (или любой элемент динамически размера) contentEditable, курсор всегда будет уходить с экрана.

Если вы установите редактируемый div в overflow: scroll, вы заметите, что div будет прокручивать. Прокрутка div не "подпрыгивает" или имеет полосы прокрутки по умолчанию, но вы можете применить -webkit-overflow-scrolling: touch атрибут div, чтобы исправить это.

С этой информацией, вы можете исправить это с помощью обертки, как так:

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=no;"/>
<style type="text/css">
    html, body {height: 100%;}
    body {
        margin: 0;
    }
    #wrap {
        height: 100%;
        width: 100%;
        overflow-y: scroll;
        -webkit-overflow-scrolling: touch;
    }
    #editor {
        padding: 10px;
    }
</style>
</head>
<body>
<div id="wrap">
    <div id="editor" contenteditable="true">

    </div>
</div>
</body>
</html>

В основном вы прокручиваете div вместо документа.

к сожалению, "scrollView" div не знает о виртуальной клавиатуре, поэтому каретка исчезнет за клавиатурой. Однако вы заметите, что позиция курсора по-прежнему находится на экране внизу за клавиатурой. Так чтобы исправить это, уменьшите высоту div / UIWebView для размещения клавиатуры.

что-то еще, что вы можете сделать, это отключить прокрутку на главном scrollView:

webView.scrollView.scrollEnabled = NO;

основной scrollView не должен прокручиваться в любом случае, но он должен предотвратить любые сбои прокрутки.


чтобы ответить на мой собственный вопрос, мы в конечном итоге сделали много работы, чтобы убедиться, что это работает правильно в нескольких версиях iOS. Мы узнали, что все события прокрутки были связаны с UIWebView пытается управлять его UIWebScrollView. Мы решили вместо использования UIWebView, мы бы взяли внутренний UIWebDocumentView/UIWebBrowserView и добавьте его в наш собственный вид прокрутки. Это позволило нам управлять размером контента и прокручивать себя и удалить большинство проблем, которые мы ранее испытывали.


сначала вы должны зарегистрировать уведомления клавиатуры, и в этом keyboardWillShow метод, вызвать метод прокрутки с интервалом 0,1 таймера.

 -(void) keyboardWillShow:(NSNotification *)note
    {

        timer   =   [NSTimer    scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(scroll) userInfo:nil repeats:YES];

    }

    -(void) keyboardWillHide:(NSNotification *)note
    {
        [timer invalidate];

    }

переменная одного члена .H-файл и выделить для него память в методе init ().

  NSString    *prevStr;

внутри этого метода прокрутки выполните следующее.

-(void)scroll{

    if (![prevStr isEqualToString:[WebView stringByEvaluatingJavaScriptFromString:@"document.body.innerHTML"]]) {
        prevStr  =   [[WebView stringByEvaluatingJavaScriptFromString:@"document.body.innerHTML"] retain];
        NSInteger height = [[WebView stringByEvaluatingJavaScriptFromString:@"document.body.offsetHeight;"] intValue];
        NSString* javascript = [NSString stringWithFormat:@"window.scrollBy(0, %d);", height];   
        [WebView stringByEvaluatingJavaScriptFromString:javascript];   
    }

}

Это позволит вам прокручивать вниз, когда вы редактируете содержимое, и содержимое больше, чем фрейм WebView. И на в других случаях вы сможете прокрутить до верхней части страницы (автопрокрутка будет удерживаться в это время).


Я не мог сделать ваш трюк установки "overflow: scroll", так как он испортил наш уже хороший css (если мы изменили переполнение при нажатии редактируемого div, то макет испортился). Пошел с этим:

$(this).on('keypress',function(e){
    //$(this) is the contenteditable div
    var lineHeight = parseInt($(this).css('line-height')) + 2;
    //gets the height of the div        
    newh=$(this).height();
    if (typeof oldh === 'undefined')
        oldh=newh;//saves the current height
    if(oldh!=newh)//checks if the height changes
    {
        //if height changes, scroll up by 1 line (plus a little 2 px)            
        window.scrollBy(0,lineHeight);
        oldh=newh;//resave the saved height since it changed
    }
});

надеюсь, это кому-то поможет. Пита!--2-->


вы можете попытаться получить рамку каретки с немного сложным взломом и вручную прокрутить до видимой прямой кишки.

// Call this after every change in editable HTML document
func bodyDidChanged() {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
        let caretRect = self.caretRect(inView: self.webView)
        self.webView.scrollView.scrollRectToVisible(caretRect.insetBy(dx: -2, dy: -2), animated: true)
    }
}

private func caretRect(inView parentView: UIView? ) -> CGRect {
    guard let parentView = parentView else {
        return CGRect.null
    }

    var rect: CGRect?
    for view in parentView.subviews {
        if view.isKind(of: NSClassFromString("UITextSelectionView")!) {
            // If have a selected text, then we seeking last blue dot (grabber) inside rangeView
            var rangeView = view.subviews.first?.subviews.last
            if rangeView == nil {
                // If text not selected then carret frame is same as rangeView frame
                rangeView = view.subviews.first
            }
            rect = rangeView?.frame
            break
        } else {
            rect = caretRect(inView: view)
            if let rect = rect, !rect.isNull {
                break
            }
        }
    }
    return rect ?? CGRect.null
}