{"id":119,"date":"2022-06-12T18:44:32","date_gmt":"2022-06-12T18:44:32","guid":{"rendered":"https:\/\/icab.de\/blog\/?p=119"},"modified":"2022-11-24T14:16:35","modified_gmt":"2022-11-24T14:16:35","slug":"customize-the-contextual-menu-of-wkwebview-on-macos","status":"publish","type":"post","link":"https:\/\/icab.de\/blog\/2022\/06\/12\/customize-the-contextual-menu-of-wkwebview-on-macos\/","title":{"rendered":"Customize the contextual menu of WKWebView on macOS"},"content":{"rendered":"\n<p>Under iOS the WKWebView class provides a delegate method which allows to customize the contextual menu of the web engine (the menu which opens when long-pressing a link). Unfortunately under macOS the WKWebView does not provide such a method for its contextual menu. This article explains how you can customize the contextual menu of WKWebView under macOS as well. It\u2019s not that obvious how to do so, but it can be done.<\/p>\n\n\n\n<p>Because the WKWebView API for macOS itself does not provide anything at all which deals with the contextual menu, we need to look elsewhere. WKWebView is a subclass of an NSView on the Mac and the NSView class has methods to deal with a contextual menu. These are &nbsp; &nbsp; <\/p>\n\n\n\n<p class=\"has-background has-small-font-size\" style=\"background-color:#f9f2f4\"><code><br>&nbsp;&nbsp;<strong>open<\/strong> <strong>func<\/strong> willOpenMenu(<strong>_<\/strong> menu: NSMenu, with event: NSEvent)<br><br><\/code><\/p>\n\n\n\n<p>which is called before the contextual menu of the view will open and<\/p>\n\n\n\n<p class=\"has-background has-small-font-size\" style=\"background-color:#f9f2f4\"><code><br>&nbsp;&nbsp;<strong>open<\/strong> <strong>func<\/strong> didCloseMenu(<strong>_<\/strong> menu: NSMenu, with event: NSEvent)<br><br><\/code><\/p>\n\n\n\n<p>which is called after the contextual menu has closed.<\/p>\n\n\n\n<p>So in order to customize the contextual menu of WKWebView, we could simply subclass WKWebView and override these methods in order to modify this menu. In the \u201e<strong>willOpenMenu<\/strong>\u201c method we can add, modify or remove menu items and in \u201e<strong>didCloseMenu<\/strong>\u201c we would reset anything that needs to be reverted back to normal.<\/p>\n\n\n\n<p>In \u201e<strong>willOpenMenu<\/strong>\u201c the parameter \u201emenu\u201c represents the contextual menu, so we can easily inspect all the available menu items, remove what we don\u2019t need and add all the new menu items we want to have in the menu. This sounds easy, but there are a few issues. <\/p>\n\n\n\n<p>Because Apple does not officially provide a way to customize the contextual menu, nothing is officially documented. So some simple reverse engineering is required to find out the meaning of the default menu items and their actions.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The first thing we can find out is that the \u201e<strong>identifier<\/strong>\u201c property of the menu items is very clear about the meaning, and these identifiers look very stable &#8211; they haven\u2019t changed between different macOS releases. <\/li>\n\n\n\n<li>The second thing we can find out is that if a link is clicked, we find menu items with the ID \u201e<strong>WKMenuItemIdentifierOpenLinkInNewWindow<\/strong>\u201c and \u201e<strong>WKMenuItemIdentifierDownloadLinkedFile<\/strong>\u201c. If an Image was clicked we find \u201e<strong>WKMenuItemIdentifierOpenImageInNewWindow<\/strong>\u201c and \u201e<strong>WKMenuItemIdentifierDownloadImage<\/strong>\u201c and for videos \u201e<strong>WKMenuItemIdentifierOpenMediaInNewWindow<\/strong>\u201c and \u201e<strong>WKMenuItemIdentifierDownloadMedia<\/strong>\u201c<\/li>\n\n\n\n<li>Another thing we can find out is that the menu and its items do not contain any information about the context, e.g. the link, image or other element on which the contextual menu was opened is totally unknown.<\/li>\n<\/ul>\n\n\n\n<p>The first point makes it easy to remove all the menu items which deal with features we do not want to provide in out App. We just need to check the identifiers and remove items whose identifier match these features.<\/p>\n\n\n\n<p>But the latter point is a problem, because if we want to add custom menu items which deal with the current context (the clicked link, image, etc), we need to know about this context. <\/p>\n\n\n\n<p>So how we can solve this issue? The solution I\u2019ve found is the following: <\/p>\n\n\n\n<p>Because the context is unknown but the context can only be a link, an image, a frame or a video (something that can be clicked and which has a URL) and for all these contexts there\u2019s already a default menu item which opens the context object in a new window, why not simply use these existing menu items and then intercept the default action for the menu item and replace it with our custom action. All the default actions to open an object in a new window will call the following method of the standard navigation delegate of WKWebView:<\/p>\n\n\n\n<p class=\"has-background has-small-font-size\" style=\"background-color:#f9f2f4\"><code><br>&nbsp;&nbsp;<strong>func<\/strong> webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -&gt; WKWebView?<br><br> <\/code><\/p>\n\n\n\n<p>And this delegate method is getting the required context via the navigationAction parameter, which means the URL of the object. The delegte method itself and its parameters are unable to provide any information about our custom action of the menu item, so we have to find another way to get this information there.<\/p>\n\n\n\n<p>We already have overridden the WKWebView class in order to intercept the contexual menu, therefore we could simply add another property to this subclass where we save the custom action for the selected contextual menu item. In the navigation delegate method above it\u2019s then easy to check this property and use it to decide wether to continue with the default action (creating a new window) or continue with the custom action.<\/p>\n\n\n\n<p>We now have everything we need to implement our own custom menu items for the contextual menu. <\/p>\n\n\n\n<p>At first we implement an enum for all our custom actions:<\/p>\n\n\n\n<p class=\"has-background has-small-font-size\" style=\"background-color:#f9f2f4\"><code><br>\n&nbsp;&nbsp;enum ContextualMenuAction {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;case openInNewTab<br>\n&nbsp;&nbsp;&nbsp;&nbsp;case addToBookmarks<br>\n&nbsp;&nbsp;&nbsp;&nbsp;\/\/ add any other actions you want to have<br>\n&nbsp;&nbsp;}<br><br><\/code><\/p>\n\n\n\n<p>Now we implement the subclass for the WKWebView, including the property <strong>contextualMenuAction<\/strong> which will hold the custom action that was selected from within the contextual menu. This property is declared as optional, so it will be nil if a default menu item is selected.<\/p>\n\n\n\n<p class=\"has-background has-small-font-size\" style=\"background-color:#f9f2f4\"><code><br>\n&nbsp;&nbsp;class MyWebView: WKWebView {<br><br>\n&nbsp;&nbsp;&nbsp;&nbsp;var contextualMenuAction: ContextualMenuAction?<br><br>\n<\/code><\/p>\n\n\n\n<p>Now we override the <strong>willOpenMenu<\/strong> method, which will be called when the contextual menu opens and where we modify the menu according to our needs.<\/p>\n\n\n\n<p class=\"has-background has-small-font-size\" style=\"background-color:#f9f2f4\"><code><br>\n&nbsp;&nbsp;override func willOpenMenu(_ menu: NSMenu, with event: NSEvent) {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;super.willOpenMenu(menu, with: event)<br>\n&nbsp;&nbsp;&nbsp;&nbsp;<br>\n&nbsp;&nbsp;&nbsp;&nbsp;var items = menu.items<br>\n&nbsp;&nbsp;&nbsp;&nbsp;<br>\n&nbsp;&nbsp;&nbsp;&nbsp;\/\/ In our example we don't need Download options, so we remove these menu items<br>\n&nbsp;&nbsp;&nbsp;&nbsp;for idx in (0..&lt;items.count).reversed() {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if let id = items[idx].identifier?.rawValue {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if id == \"WKMenuItemIdentifierDownloadLinkedFile\" || <br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id == \"WKMenuItemIdentifierDownloadImage\" || <br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id == \"WKMenuItemIdentifierDownloadMedia\" {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;items.remove(at:idx)<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>\n&nbsp;&nbsp;&nbsp;&nbsp;}<br>\n&nbsp;&nbsp;&nbsp;&nbsp;<br>\n&nbsp;&nbsp;&nbsp;&nbsp;\/\/ For all default menu items which open a new Window, we add custom menu items<br>\n&nbsp;&nbsp;&nbsp;&nbsp;\/\/ to open the object in a new Tab and to add them to the bookmarks.<br>\n&nbsp;&nbsp;&nbsp;&nbsp;for idx in (0..&lt;items.count).reversed() {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if let id = items[idx].identifier?.rawValue {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if id == \"WKMenuItemIdentifierOpenLinkInNewWindow\" || <br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id == \"WKMenuItemIdentifierOpenImageInNewWindow\" || <br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id == \"WKMenuItemIdentifierOpenMediaInNewWindow\" || <br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;id == \"WKMenuItemIdentifierOpenFrameInNewWindow\" {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let object:String<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if id == \"WKMenuItemIdentifierOpenLinkInNewWindow\" {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;object = \"Link\"<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} else if id == \"WKMenuItemIdentifierOpenImageInNewWindow\" {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;object = \"Image\"<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} else if id == \"WKMenuItemIdentifierOpenMediaInNewWindow\" {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;object = \"Video\"<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} else {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;object = \"Frame\"<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let action = #selector(processMenuItem(_:))<br><br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let title = \"Open \\(object) in new Tab\"<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let tabMenuItem = NSMenuItem(title:title, action:action, keyEquivalent:\"\")<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tabMenuItem.identifier = NSUserInterfaceItemIdentifier(\"openInNewTab\")<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tabMenuItem.target = self<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tabMenuItem.representedObject = items[idx]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;items.insert(tabMenuItem, at: idx+1)<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let title = \"Add \\(object) to Bookmarks\"<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let bookmarkMenuItem = NSMenuItem(title:title, action:action, keyEquivalent:\"\")<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bookmarkMenuItem.identifier = NSUserInterfaceItemIdentifier(\"addToBookmarks\")<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bookmarkMenuItem.target = self<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bookmarkMenuItem.representedObject = items[idx]<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;items.insert(bookmarkMenuItem, at: idx+2)<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>\n&nbsp;&nbsp;&nbsp;&nbsp;}<br>\n&nbsp;&nbsp;&nbsp;&nbsp;<br>   \n&nbsp;&nbsp;&nbsp;&nbsp;menu.items = items<br>\n&nbsp;&nbsp;}<br><br>\n<\/code><\/p>\n\n\n\n<p>The first action is to call <strong>super<\/strong> so that the WKWebView can do everything it needs to do for the menu. Then the menu items will be modified to our needs. In this example all the download items will be removed from the contextual menu. The second step is to look for the default menu items which open an object (link, image video, frame) in a new window and then create a new custom menu item which is supposed to open this object in a new tab and a second menu item which is supposed to save the object into the bookmark (this article doesn\u2019t cover how to do this, it\u2019s just an example how to add such menu items and later how to detect and process the selection of these menu items). The new custom menu items use the \u201e<strong>representedObject<\/strong>\u201c property to store the default menu item which is needed later when the user has selected our custom menu items. Our new custom menu items are then inserted after the original menu item within the contextual menu. Now the macOS will show the contextual menu with all our customizations.<\/p>\n\n\n\n<p>The method  which is called when selecting our custom menu items is implemented next.<\/p>\n\n\n\n<p class=\"has-background has-small-font-size\" style=\"background-color:#f9f2f4\"><code><br>\n&nbsp;&nbsp;@objc func processMenuItem(_ menuItem: NSMenuItem) {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;self.contextualMenuAction = nil<br><br>\n&nbsp;&nbsp;&nbsp;&nbsp;if let originalMenu = menuItem.representedObject as? NSMenuItem {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if menuItem.identifier?.rawValue == \"openInNewTab\" {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.contextualMenuAction = .openInNewTab<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} else if menuItem.identifier?.rawValue == \"addToBookmarks\" {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.contextualMenuAction = .addToBookmarks<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if let action = originalMenu.action {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;originalMenu.target?.perform(action, with: originalMenu)<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>\n&nbsp;&nbsp;&nbsp;&nbsp;}<br>\n&nbsp;&nbsp;}<br><br>\n<\/code><\/p>\n\n\n\n<p>At first the property <strong>contextualMenuAction<\/strong> is initialized to nil. Then we check if the property <strong>representedObject<\/strong> contains a NSMenuItem, in which case this menu item is the original default menu item and we use its target and action to trigger its default action. But before that, we need to set the property <strong>contextualMenuAction<\/strong>  to the action that is bound to our custom menu item, so when the navigation delegate of WKWebView is called to create a new window, we can check <strong>contextualMenuAction<\/strong> to find out the action that is really supposed to be triggered.<\/p>\n\n\n\n<p>And finally we override <strong>didCloseMenu<\/strong> to make sure that the property <strong>contextualMenuAction<\/strong> is reset to nil after the menu has closed. The navigation delegate to create new windows can be also called by WKWebView without having any contextual menu involved (for example if JavaScript code of the web site creates new windows), therefore this property can\u2019t be allowed to keep its last value until the contextual menu is used again. <\/p>\n\n\n\n<p class=\"has-background has-small-font-size\" style=\"background-color:#f9f2f4\"><code><br>\n&nbsp;&nbsp;override func didCloseMenu(_ menu: NSMenu, with event: NSEvent?) {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;super.didCloseMenu(menu, with: event)<br><br>\n\n&nbsp;&nbsp;&nbsp;&nbsp;DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.contextualMenuAction = nil<br>\n&nbsp;&nbsp;&nbsp;&nbsp;}<br>\n&nbsp;&nbsp;}<br><br><\/code><\/p>\n\n\n\n<p>It is important set <strong>contextualMenuAction<\/strong> to nil after a delay, because the contextual menu action is processed asynchronously. Which means the menu can close before the action is actually triggered and the navigation delegate method is called. Theoretically you can also clear this property within the navigation delegate method, but in practice it can happen that this method is not called if something goes wrong while processing the menu action (like there\u2019s no network connection, the URL is invalid etc). Therefore it\u2019s better to clear the property after closing the menu here.<\/p>\n\n\n\n<p>And as the final action we need to implement the navigation delegate method of WKWebView  which is called to create a new window:<\/p>\n\n\n\n<p class=\"has-background has-small-font-size\" style=\"background-color:#f9f2f4\"><code><br>\n&nbsp;&nbsp;func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration,<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for navigationAction: WKNavigationAction,<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;windowFeatures: WKWindowFeatures) -&gt; WKWebView? {<br><br>\n&nbsp;&nbsp;&nbsp;&nbsp;if let customAction = (webView as? MyWebView)?.contextualMenuAction {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let url = navigationAction.request.url<br>\n\t\t<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if customAction == .openInNewTab {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;createNewTab(for:url)<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} else if customAction == .addToBookmarks {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;addBookmark(url:url)<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return nil<br>\n\t\t<br>\n&nbsp;&nbsp;&nbsp;&nbsp;} else {<br>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return createWebView(with:configuration)<br>\n&nbsp;&nbsp;&nbsp;&nbsp;}<br>\n&nbsp;&nbsp;}<br><br><\/code><\/p>\n\n\n\n<p>First we check if the &nbsp;property <strong>contextualMenuAction<\/strong> is not nil. In this case one of the custom actions must be performed. The value of this property tells us, which action this is. Otherwise the default action (creating a new window) must be performed. <\/p>\n\n\n\n<p>You see that it\u2019s not too complicated to implement a custom contextual menu for WKWebView on the Mac.  Basically we need to \u201ehijack\u201c one of the existing default menu items to let the WKWebEngine pass the context (the URL of the object for which the contextual menu was opened) to the App which would be otherwise inaccessible. The solution is not totally \u201eclean\u201c because we have to use undocumented identfiers of the default menu items, but because these are simple strings you can\u2019t break anything. The worst thing that can happen is that these identifiers change in future macOS releases and then the custom menu items will be missing unless you add the new identifieres as well. But this is very unlikely.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Under iOS the WKWebView class provides a delegate method which allows to customize the contextual menu of the web engine (the menu which opens when long-pressing a link). Unfortunately under macOS the WKWebView does not provide such a method for its contextual menu. This article explains how you can customize the contextual menu of WKWebView [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[9,10,6,8],"class_list":["post-119","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-contextual-menu","tag-macos","tag-swift","tag-wkwebview"],"_links":{"self":[{"href":"https:\/\/icab.de\/blog\/wp-json\/wp\/v2\/posts\/119","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/icab.de\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/icab.de\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/icab.de\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/icab.de\/blog\/wp-json\/wp\/v2\/comments?post=119"}],"version-history":[{"count":16,"href":"https:\/\/icab.de\/blog\/wp-json\/wp\/v2\/posts\/119\/revisions"}],"predecessor-version":[{"id":142,"href":"https:\/\/icab.de\/blog\/wp-json\/wp\/v2\/posts\/119\/revisions\/142"}],"wp:attachment":[{"href":"https:\/\/icab.de\/blog\/wp-json\/wp\/v2\/media?parent=119"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/icab.de\/blog\/wp-json\/wp\/v2\/categories?post=119"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/icab.de\/blog\/wp-json\/wp\/v2\/tags?post=119"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}