Search code examples
swiftmacoscocoawkwebview

Editable WKWebView


I am displaying .html and .xml files in WKWebView. I am concatenating all the data and displaying below string in WKWevView

sourceWebView.loadHTMLString(sourceHtmlStr, baseURL: nil)

I made my webview as editable using below, after loading the content i.e in

webView(_ webView: WKWebView, didFinish navigation: WKNavigation!){}

singleWebView.evaluateJavaScript("document.body.setAttribute('contentEditable','true')") { (anyObj: Any?, error: Error?) in
   if anyObj != nil{
       print("AnyObj in jave script : \(anyObj!)")
   }
   if error != nil{
       print("Error in Java script : \(error!)")
   }
}

Above code made the WKWebView as editable. But i am not sure how to save the edited values. I followed Link and used below code

i have below code in .js file

var richeditor = {};
var editor = document.getElementById("editor");

richeditor.insertText = function(text) {
    editor.innerHTML = text;
    window.webkit.messageHandlers.heightDidChange.postMessage(document.body.offsetHeight);
}

editor.addEventListener("input", function() {
    window.webkit.messageHandlers.textDidChange.postMessage(editor.innerHTML);
}, false)

document.addEventListener("selectionchange", function() {
    window.webkit.messageHandlers.heightDidChange.postMessage(document.body.offsetHeight);
}, false);

in WindowDidLoad:

guard let scriptPath = Bundle.main.path(forResource: "RichTextEditor", ofType: "js"),
   let scriptContent = try? String(contentsOfFile: scriptPath, encoding: String.Encoding.utf8)
else {
   print("Unable to find javscript for text editor")
   return
}
singleWebView.configuration.userContentController.addUserScript(WKUserScript(source: scriptContent, injectionTime: .atDocumentEnd, forMainFrameOnly: true))

let configuration = singleWebView.configuration
[ShowPreviewWindowContoller.textDidChange, ShowPreviewWindowContoller.heightDidChange].forEach {
   configuration.userContentController.add(WeakScriptMessageHandler(delegate: self), name: $0)
}

and added below method :

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
   switch message.name {
   case ShowPreviewWindowContoller.textDidChange:
       guard let body = message.body as? String else { return }
       delegate?.textDidChange(text: body)
   case ShowPreviewWindowContoller.heightDidChange:
       guard let height = message.body as? CGFloat else { return }
       self.height = height > ShowPreviewWindowContoller.defaultHeight ? height + 30 : ShowPreviewWindowContoller.defaultHeight
       delegate?.heightDidChange()
    default:
       break
    }
}

This way of implementation is not saving the edited data.

Also to note here is i don't have "editor" element in the html string. i am not really sure what all elements the html string has as the file is not a constant one, elements can be anything.

Sorry for the long question. Please let me know on how to implement this. Editing WKWebView, saving the edited text back. Also i will be having lot of strings like below, i need to make sure i replace into exact element

<trans-unit id="0000000006" datatype="x-text/xml" restype="string">
    <target>Some Text here</target>
</trans-unit>
<trans-unit id="0000000007" datatype="x-text/xml" restype="string">
    <target>Some Text here</target>
</trans-unit>

I am using below HTML string, Please note i really don't have any control on html, i display what ever user selects.

Edit : Added modified htmlstring

<!doctype html>\n
<html>
   \n
   <head>
      \n
      <meta charset=\"UTF-8\">
      \n<meta name=\"description\" content=\"AppleCare Training authored this course. If you use this course outside the AppleCare Training site, give credit to its author.\">\n<!-- If you are using an interaction to mark the page complete or if it is an AA page, change mark_complete to false -->\n
      <meta name=\"template_version\" content=\"01.05.2015\">
      \n
      <meta name=\"page\" content=\" \" mark_complete=\"false\">
      \n
      <link href=\"https://idesk.corp.apple.com/training/ce/_common/css/sgt.css\" media=\"screen, projection\" rel=\"stylesheet\" type=\"text/css\"/>
      \n<script>\n\tvar url = window.location.href;\n\tvar filename = \"\";\n\tvar is_local = url.substr(0,4);\n\tif(is_local==\"file\" || window.location.hostname === \"localhost\"){\n\t\t//Local Common Folder Location\n\t\tdocument.write(\'\\x3Cscript type=\"text/javascript\" src=\"http://localhost/_common/js/jquery.js\">\\x3C/script>\');\n\t\tdocument.write(\'\\x3Cscript type=\"text/javascript\" src=\"http://localhost/_common/js/ready.js\">\\x3C/script>\');\n\t}else{\n\t\tdocument.write(\'\\x3Cscript type=\"text/javascript\" src=\"/training/ce/_common/js/jquery.js\">\\x3C/script>\');\n\t\tdocument.write(\'\\x3Cscript type=\"text/javascript\" src=\"/training/ce/_common/js/ready.js\">\\x3C/script>\');\n\t}\n</script>\n\n
      <title>
         <target id=2>Key Terms and Concepts</target>
      </title>
      \n
   </head>
   \n\n
   <body class=\"aa\">
      \n\n\t\t\t<!-- BEGIN PAGE CONTENT -->\n\t
      <div id=\"maincontent\">
         \n
         <div id=\"learningcontentdiv\">
            \n
            <div id=\"morelearningcontentdiv\"></div>
            \n
         </div>
         \n\t\t\t  
         <div id=\"content\">
            \n
            <div id=\"close_aa_term\">
               <target id=4>×</target>
            </div>
            \n
            <h2>
               <target id=6>Key Terms and Concepts</target>
            </h2>
            \n
            <p>
               <target id=8>You should already be familiar with all the terms in this module.</target>
               \t  
            </p>
            \n\t  
         </div>
         \n
      </div>
      \n\n<!-- END PAGE CONTENT -->\n
   </body>
   \n
</html>

Solution

  • Tried with this sample code works as expected.

    class ViewController: UIViewController, WKScriptMessageHandler {
    
    var htmlString = """
    
    <!doctype html>\n
    <html>
       \n
       <head>
          \n
          <meta charset=\"UTF-8\">
          \n<meta name=\"description\" content=\"AppleCare Training authored this course. If you use this course outside the AppleCare Training site, give credit to its author.\">\n<!-- If you are using an interaction to mark the page complete or if it is an AA page, change mark_complete to false -->\n
          <meta name=\"template_version\" content=\"01.05.2015\">
          \n
          <meta name=\"page\" content=\" \" mark_complete=\"false\">
          \n
          <link href=\"https://idesk.corp.apple.com/training/ce/_common/css/sgt.css\" media=\"screen, projection\" rel=\"stylesheet\" type=\"text/css\"/>
          \n<script>\n\tvar url = window.location.href;\n\tvar filename = \"\";\n\tvar is_local = url.substr(0,4);\n\tif(is_local==\"file\" || window.location.hostname === \"localhost\"){\n\t\t//Local Common Folder Location\n\t\tdocument.write(\'\\x3Cscript type=\"text/javascript\" src=\"http://localhost/_common/js/jquery.js\">\\x3C/script>\');\n\t\tdocument.write(\'\\x3Cscript type=\"text/javascript\" src=\"http://localhost/_common/js/ready.js\">\\x3C/script>\');\n\t}else{\n\t\tdocument.write(\'\\x3Cscript type=\"text/javascript\" src=\"/training/ce/_common/js/jquery.js\">\\x3C/script>\');\n\t\tdocument.write(\'\\x3Cscript type=\"text/javascript\" src=\"/training/ce/_common/js/ready.js\">\\x3C/script>\');\n\t}\n</script>\n\n
          <title>
             <target id=2>Key Terms and Concepts</target>
          </title>
          \n
       </head>
       \n\n
       <body class=\"aa\">
          \n\n\t\t\t<!-- BEGIN PAGE CONTENT -->\n\t
          <div id=\"maincontent\">
             \n
             <div id=\"learningcontentdiv\">
                \n
                <div id=\"morelearningcontentdiv\"></div>
                \n
             </div>
             \n\t\t\t
             <div id=\"content\">
                \n
                <div id=\"close_aa_term\">
                   <target id=4>×</target>
                </div>
                \n
                <h2>
                   <target id=6>Key Terms and Concepts</target>
                </h2>
                \n
                <p>
                   <target id=8>You should already be familiar with all the terms in this module.</target>
                   \t
                </p>
                \n\t
             </div>
             \n
          </div>
          \n\n<!-- END PAGE CONTENT -->\n
       </body>
       \n
    </html>
    """
    
    var javaScript = """
    var richeditor = {};
    var editor = document.body;
    
    function getActiveDiv() {
        var sel = window.getSelection();
        var range = sel.getRangeAt(0);
        var node = document.createElement('span');
        range.insertNode(node);
        range = range.cloneRange();
        range.selectNodeContents(node);
        range.collapse(false);
        sel.removeAllRanges();
        sel.addRange(range);
        var activeDiv = node.parentNode;
        node.parentNode.removeChild(node);
        return activeDiv;
    }
    
    richeditor.insertText = function(text) {
        editor.innerHTML = text;
        window.webkit.messageHandlers.heightDidChange.postMessage(document.body.offsetHeight);
    }
    
    editor.addEventListener("input", function() {
    var activeElement = getActiveDiv()
    window.webkit.messageHandlers.textDidChange.postMessage("activeElement ID = "+ activeElement.id + " and TEXT = " + activeElement.innerHTML);
    }, false)
    
    document.addEventListener("selectionchange", function() {
        window.webkit.messageHandlers.heightDidChange.postMessage(document.body.offsetHeight);
    }, false);
    
    """
    
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        let config = WKWebViewConfiguration()
        config.userContentController = WKUserContentController()
        config.userContentController.add(self, name: "heightDidChange")
        config.userContentController.add(self, name: "textDidChange")
        config.userContentController.addUserScript(WKUserScript(source: javaScript, injectionTime: .atDocumentEnd, forMainFrameOnly: true))
    
        let webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 400, height: 400), configuration: config)
    
        view.addSubview(webView)
        webView.loadHTMLString(htmlString, baseURL: nil)
        
        webView.navigationDelegate = self
        
      }
    
    
      func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        print(message.name)
        let body = message.body
        print(body)
      }
        
    
    }
    
    extension ViewController : WKNavigationDelegate {
        
        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!){
          webView.evaluateJavaScript("document.body.setAttribute('contentEditable','true')") { (anyObj: Any?, error: Error?) in
             if anyObj != nil{
                 print("AnyObj in jave script : \(anyObj!)")
             }
             if error != nil{
                 print("Error in Java script : \(error!)")
             }
            }
          }
    
    }