在UIWebView中的contentEditable / designMode时自动滚动

时间:2022-11-24 09:22:30

I am attempting a rich-text editor (with HTML export capability) for an iPhone application I am working on, and decided to use iOS 5's WebKit support for contentEditable/designMode. I have hit a wall with one issue which is breaking for what I need. When editing the content in the UIWebView, there is no automatic scrolling to follow the cursor, like, for example, in UITextView. When typing, the cursor continues under the scrollView and the user has to manually scroll up.

我正在为我正在处理的iPhone应用程序尝试一个富文本编辑器(具有HTML导出功能),并决定使用iOS 5的webKit支持contentEditable / designMode。我已经遇到了一个问题,这个问题正在打破我的需求。在UIWebView中编辑内容时,没有自动滚动跟随光标,例如,在UITextView中。键入时,光标在scrollView下继续,用户必须手动向上滚动。

Here is some relevant code:

这是一些相关的代码:

- (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];
}

Any ideas how to remedy this issue? I am not sure if this also occurs in Safari or only in UIWebView's WebKit implementation.

有任何想法如何解决这个问题?我不确定这是否也发生在Safari中或仅在UIWebView的WebKit实现中。

If you hit this problem, make sure to head over to https://bugreport.apple.com and duplicate rdar://16455638.

如果您遇到此问题,请务必访问https://bugreport.apple.com并复制rdar:// 16455638。

5 个解决方案

#1


10  

After hours of research I found a better solution, so I thought I'd share.

经过几个小时的研究,我发现了一个更好的解决方案,所以我想我会分享。

There is a bug in iOS 5's contentEditable implementation where the main scrollView will not scroll with the caret. If making the body tag (or any dynamically sized element) contentEditable, the caret will always go off screen.

iOS 5的contentEditable实现中存在一个错误,其中主scrollView不会使用插入符号滚动。如果使body标签(或任何动态大小的元素)contentEditable,插入符号将始终关闭屏幕。

If you set an editable div to overflow: scroll, you'll notice that the div will scroll. The div's scrolling doesn't "bounce" or have scroll bars by default, but you can apply the -webkit-overflow-scrolling: touch attribute to the div to fix this.

如果你设置一个可编辑的div溢出:滚动,你会注意到div将滚动。 div的滚动默认情况下不会“反弹”或滚动条,但您可以将-webkit-overflow-scrolling:touch属性应用于div来修复此问题。

With this information, you can fix it with a wrapper like so:

有了这些信息,您可以使用如下包装器修复它:

<!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>

Basically you are scrolling the div instead of the document.

基本上你是滚动div而不是文档。

Unfortuantly the div's "scrollView" isn't aware of the virtual keyboard, so the caret will disappear behind the keyboard. However, you'll notice that the caret position is still on screen at the bottom behind the keyboard. So to fix that, reduce the height of the div/UIWebView to accommodate the keyboard.

不幸的是,div的“scrollView”并不知道虚拟键盘,因此插入符将在键盘后面消失。但是,您会注意到插入位置仍位于键盘后面的屏幕上。所以要解决这个问题,请减小div / UIWebView的高度以适应键盘。

Something else you might want to do is disable scrolling on the main scrollView:

您可能想要做的其他事情是禁用主scrollView上的滚动:

webView.scrollView.scrollEnabled = NO;

The main scrollView shouldn't scroll anyway, but it should prevent any scrolling glitches.

主scrollView不应该滚动,但它应该防止任何滚动故障。

#2


3  

To answer my own question, we eventually ended up doing a lot of work to ensure this works correctly across multiple iOS versions. We found out that all the scroll events were due to the UIWebView trying to manage its UIWebScrollView. We decided instead of using a UIWebView, we would take the internal UIWebDocumentView/UIWebBrowserView and add it to our own scroll view. This allowed us to managed the content size and scrolling ourselves and removed most of the problems we previously experienced.

为了回答我自己的问题,我们最终做了很多工作,以确保在多个iOS版本中正常工作。我们发现所有滚动事件都是由于UIWebView试图管理其UIWebScrollView。我们决定不使用UIWebView,而是使用内部UIWebDocumentView / UIWebBrowserView并将其添加到我们自己的滚动视图中。这使我们能够管理内容大小并滚动自己并删除我们以前遇到的大多数问题。

#3


1  

First You have to register keyboard notifications, and in that keyboardWillShow method, trigger a scroll method with 0.1 timer interval.

首先你需要注册键盘通知,并在该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];

    }

Make one member variable in the .h file and and allocate memory for it in init() method.

在.h文件中创建一个成员变量,并在init()方法中为其分配内存。

  NSString    *prevStr;

inside that scroll method, do the following.

在滚动方法中,执行以下操作。

-(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];   
    }

}

This will allow you to scroll to the bottom when you are editing the content and the content is larger than the WebView frame. And at other times you will be able to scroll to the top of the page(autoscroll will be put hold at that time).

这将允许您在编辑内容时滚动到底部,并且内容大于WebView框架。而在其他时候,您将能够滚动到页面顶部(此时将保持自动滚动)。

#4


0  

I couldn't do your trick of setting "overflow: scroll" since it messed up our already good css (if we changed the overflow when clicking the editable div, then the layout got messed up). Went with this:

我无法设置“溢出:滚动”的技巧,因为它搞砸了我们已经很好的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
    }
});

Hope this helps someone. PITA

希望这有助于某人。 PITA

#5


0  

You can try to get caret frame with little bit tricky hack and manually scroll to visible rect.

您可以尝试通过一点点棘手的黑客获取插入框架并手动滚动到可见的矩形。

// 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
}

#1


10  

After hours of research I found a better solution, so I thought I'd share.

经过几个小时的研究,我发现了一个更好的解决方案,所以我想我会分享。

There is a bug in iOS 5's contentEditable implementation where the main scrollView will not scroll with the caret. If making the body tag (or any dynamically sized element) contentEditable, the caret will always go off screen.

iOS 5的contentEditable实现中存在一个错误,其中主scrollView不会使用插入符号滚动。如果使body标签(或任何动态大小的元素)contentEditable,插入符号将始终关闭屏幕。

If you set an editable div to overflow: scroll, you'll notice that the div will scroll. The div's scrolling doesn't "bounce" or have scroll bars by default, but you can apply the -webkit-overflow-scrolling: touch attribute to the div to fix this.

如果你设置一个可编辑的div溢出:滚动,你会注意到div将滚动。 div的滚动默认情况下不会“反弹”或滚动条,但您可以将-webkit-overflow-scrolling:touch属性应用于div来修复此问题。

With this information, you can fix it with a wrapper like so:

有了这些信息,您可以使用如下包装器修复它:

<!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>

Basically you are scrolling the div instead of the document.

基本上你是滚动div而不是文档。

Unfortuantly the div's "scrollView" isn't aware of the virtual keyboard, so the caret will disappear behind the keyboard. However, you'll notice that the caret position is still on screen at the bottom behind the keyboard. So to fix that, reduce the height of the div/UIWebView to accommodate the keyboard.

不幸的是,div的“scrollView”并不知道虚拟键盘,因此插入符将在键盘后面消失。但是,您会注意到插入位置仍位于键盘后面的屏幕上。所以要解决这个问题,请减小div / UIWebView的高度以适应键盘。

Something else you might want to do is disable scrolling on the main scrollView:

您可能想要做的其他事情是禁用主scrollView上的滚动:

webView.scrollView.scrollEnabled = NO;

The main scrollView shouldn't scroll anyway, but it should prevent any scrolling glitches.

主scrollView不应该滚动,但它应该防止任何滚动故障。

#2


3  

To answer my own question, we eventually ended up doing a lot of work to ensure this works correctly across multiple iOS versions. We found out that all the scroll events were due to the UIWebView trying to manage its UIWebScrollView. We decided instead of using a UIWebView, we would take the internal UIWebDocumentView/UIWebBrowserView and add it to our own scroll view. This allowed us to managed the content size and scrolling ourselves and removed most of the problems we previously experienced.

为了回答我自己的问题,我们最终做了很多工作,以确保在多个iOS版本中正常工作。我们发现所有滚动事件都是由于UIWebView试图管理其UIWebScrollView。我们决定不使用UIWebView,而是使用内部UIWebDocumentView / UIWebBrowserView并将其添加到我们自己的滚动视图中。这使我们能够管理内容大小并滚动自己并删除我们以前遇到的大多数问题。

#3


1  

First You have to register keyboard notifications, and in that keyboardWillShow method, trigger a scroll method with 0.1 timer interval.

首先你需要注册键盘通知,并在该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];

    }

Make one member variable in the .h file and and allocate memory for it in init() method.

在.h文件中创建一个成员变量,并在init()方法中为其分配内存。

  NSString    *prevStr;

inside that scroll method, do the following.

在滚动方法中,执行以下操作。

-(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];   
    }

}

This will allow you to scroll to the bottom when you are editing the content and the content is larger than the WebView frame. And at other times you will be able to scroll to the top of the page(autoscroll will be put hold at that time).

这将允许您在编辑内容时滚动到底部,并且内容大于WebView框架。而在其他时候,您将能够滚动到页面顶部(此时将保持自动滚动)。

#4


0  

I couldn't do your trick of setting "overflow: scroll" since it messed up our already good css (if we changed the overflow when clicking the editable div, then the layout got messed up). Went with this:

我无法设置“溢出:滚动”的技巧,因为它搞砸了我们已经很好的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
    }
});

Hope this helps someone. PITA

希望这有助于某人。 PITA

#5


0  

You can try to get caret frame with little bit tricky hack and manually scroll to visible rect.

您可以尝试通过一点点棘手的黑客获取插入框架并手动滚动到可见的矩形。

// 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
}