I am working on a PR for react-native-webview to add custom menu items to the RNCWebView (which is a subclass of WKWebView). It works in that it adds the additional options but "Copy | Look Up | Share..." always appear. Even if canPerformAction returns NO for every action (note that canPerformAction is not returning true/YES for copy)
I've come across so many other posts such as:
WKWebView and UIMenuController
Removing Copy, Look Up, and Share from UIMenuController
Custom Cut, Copy & Paste operations for WKWebView
And countless other ones that just suggest using CSS to hide the entire menu. I am not trying to hide the whole menu, but just "Copy, Look Up and Share" while letting my custom ones remain while using WKWebView.
My thought was that there was some other class up the responder chain that is still setting them to true, but I've tried extending and overriding as many as I know about and still those options show up:
extension UIView {
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
extension UITextView {
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
extension UIImageView{
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
extension UIScrollView{
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
extension UISlider{
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
extension UIWebView{
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
#objc private extension UIResponder {
func swizzle_canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
None of that works. Those three options still appear no matter what. Does anyone know what class could responding and adding these options? This seems like a common question but all the posts I find are either unanswered or wildly out of date.
// WKWebView subclass //
import WebKit
import UIKit
class MyWebView: WKWebView {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
// View controller //
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
// MARK: - Variables
var helloMenu = UIMenuItem()
// MARK: - IBOutlet
#IBOutlet weak var myWebView: MyWebView!
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
myWebView.navigationDelegate = self
helloMenu = UIMenuItem(title: "Hello", action: #selector(sayHello))
UIMenuController.shared.menuItems = [helloMenu]
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let urlStr = "https://www.google.com/"
if let url = URL(string: urlStr) {
let request = URLRequest(url: url)
myWebView.load(request)
}
}
// MARK: - Custom menu
#objc func sayHello() {
print("Hello")
}
}
First, sub-class WKWebView, removing all menu items.
Make an IBOutlet object of WKWebView and change its class name.
Add your custom menu item to your view controller.
Related
I am trying to detect when a string is getting dragged onto the NSStatusItem. Here is the implementation of my code in AppDelegate:
func applicationDidFinishLaunching(_ aNotification: Notification) {
if let button = statusItem.button {
button.image = NSImage(named:NSImage.Name("StatusBarButtonImage"))
button.action = #selector(menubarAction(_:))
button.sendAction(on: [.leftMouseUp, .rightMouseUp])
button.window?.registerForDraggedTypes([.string ])
button.window?.delegate = self
}
}
And here is my NSWindow delegate calls:
extension AppDelegate:NSWindowDelegate{
func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation{
NSApplication.shared.activate(ignoringOtherApps: true )
return .copy
}
func performDragOperation(_ sender: NSDraggingInfo) -> Bool{
NSApplication.shared.activate(ignoringOtherApps: true )
return true
}
}
However these delegates do not get called when a string is dragged onto the NSStatusItem. I know that drag has been detected as:
applicationDidBecomeActive(_ notification: Notification)
gets called. Any suggestions why my delegate is not getting called.
Thanks
Reza
draggingEntered and performDragOperation are methods of protocol NSDraggingDestination. Swift doesn't call a protocol method if the class doesn't adopt the protocol. AppDelegate must adopt NSDraggingDestination.
extension AppDelegate: NSWindowDelegate {
}
extension AppDelegate: NSDraggingDestination {
func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation{
NSApplication.shared.activate(ignoringOtherApps: true )
return .copy
}
func performDragOperation(_ sender: NSDraggingInfo) -> Bool{
NSApplication.shared.activate(ignoringOtherApps: true )
return true
}
}
Is there a way to only allow the new translate option of ios15 in a custom UITextView? See the following picture to know what I mean with translate option:
Translate option
I know about the canPerformAction method but there is no translate option:
class CustomUITextField: UITextField {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(UIResponderStandardEditActions.paste(_:)) {
return false
}
return super.canPerformAction(action, withSender: sender)
}
}
I've read several offered solutions for different versions of Swift.
What I cannot see is how to implement the extensions--if that's even the best way to go about it.
I'm sure there is an obvious method here that was expected to be known first, but I'm not seeing it. I've added this extension and none of my text fields are affected.
extension UITextField {
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return action == #selector(UIResponderStandardEditActions.cut) || action == #selector(UIResponderStandardEditActions.copy)
}
}
You can not override a class method using an extension.
from the docs "NOTE Extensions can add new functionality to a type, but they cannot override existing functionality."
What you need is to subclass UITextField and override your methods there:
To only disable paste functionality:
class TextField: UITextField {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(UIResponderStandardEditActions.paste) {
return false
}
return super.canPerformAction(action, withSender: sender)
}
}
Usage:
let textField = TextField(frame: CGRect(x: 50, y: 120, width: 200, height: 50))
textField.borderStyle = .roundedRect
view.addSubview(textField)
To allow only copy and cut:
class TextField: UITextField {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
[#selector(UIResponderStandardEditActions.cut),
#selector(UIResponderStandardEditActions.copy)].contains(action)
}
}
Siwft 5
// class TextField: UITextField
extension UITextField {
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return action == #selector(UIResponderStandardEditActions.cut) || action == #selector(UIResponderStandardEditActions.copy)
}
}
I'm new to rx and curious about a question: What's the replacement of override in Rx?
For codes I have read about rx, a button is configured like:
override func viewDidLoad() {
super.viewDidLoad()
updateConversation()
self.naviAvatar.rx.tap
.debug("naviAvatar tap")
.subscribe(onNext: { _ in
print("didTapNaviAvatar")
})
.disposed(by: disposeBag)
}
and it works perfect.
However I meet a question that in a subclass I want to silent the button and I don't know how to achieve in rx.
In my previous code, I have following codes:
class A: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapNaviAvatar(_:)))
self.naviAvatar.addGestureRecognizer(tapRecognizer)
}
#objc func didTapNaviAvatar(_ sender: Any) {
print("didTapNaviAvatar")
}
//...
}
class B: A {
// Silent the method, do nothing.
override func didTapNaviAvatar(_ sender: Any) {}
//...
}
I came up with an idea that I can reconfigure the naviAvatar in B's viewDidLoad method. But what if I have number of codes(like 20 lines, including mapping, filtering, configuring) about the button's behavior but I just want to change only one line(like just override the button title on touch down)?
Any helps would be appreciated.
Weak self isn't needed because there is no delay or async method fired on a tap action. So you can just pass the method and it will trigger the method overridden in class B.
class A: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
naviAvatar.rx.tap
.debug("naviAvatar tap")
.subscribe(onNext: didTapNaviMoreButton)
.disposed(by: disposeBag)
}
#objc func didTapNaviAvatar(_ sender: Any) {
print("didTapNaviAvatar")
}
}
class A: B {
override func didTapNaviAvatar(_ sender: Any) {}
}
Say, I have a label show : Loading...
problem: When return from VC(2). The label is not hidden.
How to hide it when return from VC(2) and dont hide it when in navigating to VC(2) and show the message : Loading....
in VC(1)
#IBOutlet weak var lbLoadingMsg
In viewDidLoad() {
lbLoadingMsg.hidden = true
}
-2-- turn it on when prepare to navigate to VC(2)
override func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool
{
--code--
lbLoadingMsg.hidden = false
}
Override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!){
}
You can use NSNotificationCenter for that.
Follow this simple steps:
1.In your VC(2) add this code into your button from where you are going back:
#IBAction func goBack(sender: AnyObject) {
NSNotificationCenter.defaultCenter().postNotificationName("hide", object: nil)
self.dismissViewControllerAnimated(true, completion: nil)
}
2.In your First View add this code into viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "hideLabel:", name:"hide", object: nil)
}
now this method will call this function:
func hideLabel(notification: NSNotification){
self.lbLoadingMsg.hidden = true
}
And this will hide your label in first view when ever goBack button will pressed from first view.
Hope this will help you.
Write this in VC2
,
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var identifier = segue.identifier
if(identifier! == "yourIdentifier"){
var vc1:VC1 = segue.destinationViewController as! VC1
vc1.lbLoadingMsg.hidden = true
}
}
func viewDidAppear(_ animated: Bool) {
lbLoadingMsg.hidden = true
}
Move
lbLoadingMsg.hidden = true
line from viewDidLoad to viewDidAppear. I think most quicker way.