I have a problem with create new UIMenuItem and assigning it a selector. Problem is it automatically call its selector without tapping it.
This is my code:
let customMenuItem1 = UIMenuItem(title: "Salvează", action: Selector(showNote()))
menuController.menuItems = NSArray(array: [customMenuItem1]) as? [UIMenuItem]
This is the method for appearance of menuitem:
override func canPerformAction(action: Selector,withSender sender: AnyObject?) -> Bool
{
if action == Selector(showNote())
{
return super.canPerformAction(action, withSender: sender)
}
return false
}
Thanks all.
In first 2 line of code exist mistake in swift:
let customMenuItem1 = UIMenuItem(title: "Salvează", action: Selector(showNote()))
menuController.menuItems = NSArray(array: [customMenuItem1]) as? [UIMenuItem]
at hear we have Selector on method this Selector mean he will be aumatically call method without wait for user to tap and for resolving this problem only we can put like this
let customMenuItem1 = UIMenuItem(title: "Salvează", action: #selector(RulesDetailViewController.showNote))
menuController.menuItems = NSArray(array: [customMenuItem1]) as? [UIMenuItem]
because #selector this parameters wait for touch and event for users.
Related
Below is my code, I found when click menu "pasteAndGo", two log strings are printed: 1. paste and go show 2.paste and go clicked. My requirement is when the menu is shown, log "paste and go show" is shown. When it is clicked, log "paste and go clicked" is shown.
class MyTextField: UITextField {
private func Init() {
let menuController: UIMenuController = UIMenuController.shared
menuController.isMenuVisible = true
let pasteAndGoMenuItem: UIMenuItem = UIMenuItem(title: "pasteAndGo", action: #selector(pasteAndGo(sender:)))
let myMenuItems: NSArray = [pasteAndGoMenuItem]
menuController.menuItems = myMenuItems as? [UIMenuItem]
}
#objc private func pasteAndGo(sender: UIMenuItem) {
Print("paste and go clicked")
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
let pasteboard = UIPasteboard.general
if action == #selector(pasteAndGo) {
if pasteboard.url != nil {
Print("paste and go show")
return true
} else {
return false
}
}
return super.canPerformAction(action, withSender: sender)
}
}
Your code works as implemented:
In the instant you press your pasteAndGo menu item, the UIKit framework calls canPerformAction to ask whether it is allowed to execute the action or not. Here, you print "paste and go show"
Since you return true, your action pasteAndGo(sender:) is executed and prints "paste and go clicked"
To react on the menu item being shown, you'll have to register to the notification center with the UIMenuController.willShowMenuNotification, like this:
// create a property
var token: NSObjectProtocol?
// then add observer
self.token = NotificationCenter.default.addObserver(forName: UIMenuController.willShowMenuNotification, object: nil, queue: .main)
{ _ in
print ("paste and go show")
}
and don't forget to unsubscribe (NotificationCenter.default.removeObserver) once your viewcontroller gets dismissed.
if let t = self.token {
NotificationCenter.default.removeObserver(t)
}
Update
You could also do so (without properties) in Init
// in Init
var token = NotificationCenter.default.addObserver(forName: UIMenuController.willShowMenuNotification, object: nil, queue: .main)
{ _ in
print ("paste and go show")
NotificationCenter.default.removeObserver(token)
}
I'm programmatically adding a UITapGestureRecognizer to one of my views:
let gesture = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(modelObj:myModelObj)))
self.imageView.addGestureRecognizer(gesture)
func handleTap(modelObj: Model) {
// Doing stuff with model object here
}
The first problem I encountered was "Argument of '#selector' does not refer to an '#Objc' method, property, or initializer.
Cool, so I added #objc to the handleTap signature:
#objc func handleTap(modelObj: Model) {
// Doing stuff with model object here
}
Now I'm getting the error "Method cannot be marked #objc because the type of the parameter cannot be represented in Objective-C.
It's just an image of the map of a building, with some pin images indicating the location of points of interest. When the user taps one of these pins I'd like to know which point of interest they tapped, and I have a model object which describes these points of interest. I use this model object to give the pin image it's coordinates on the map so I thought it would have been easy for me to just send the object to the gesture handler.
It looks like you're misunderstanding a couple of things.
When using target/action, the function signature has to have a certain form…
func doSomething()
or
func doSomething(sender: Any)
or
func doSomething(sender: Any, forEvent event: UIEvent)
where…
The sender parameter is the control object sending the action message.
In your case, the sender is the UITapGestureRecognizer
Also, #selector() should contain the func signature, and does NOT include passed parameters. So for…
func handleTap(sender: UIGestureRecognizer) {
}
you should have…
let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
Assuming the func and the gesture are within a view controller, of which modelObj is a property / ivar, there's no need to pass it with the gesture recogniser, you can just refer to it in handleTap
Step 1: create the custom object of the sender.
step 2: add properties you want to change in that a custom object of the sender
step 3: typecast the sender in receiving function to a custom object and access those properties
For eg:
on click of the button if you want to send the string or any custom object then
step 1: create
class CustomButton : UIButton {
var name : String = ""
var customObject : Any? = nil
var customObject2 : Any? = nil
convenience init(name: String, object: Any) {
self.init()
self.name = name
self.customObject = object
}
}
step 2-a: set the custom class in the storyboard as well
step 2-b: Create IBOutlet of that button with a custom class as follows
#IBOutlet weak var btnFullRemote: CustomButton!
step 3: add properties you want to change in that a custom object of the sender
btnFullRemote.name = "Nik"
btnFullRemote.customObject = customObject
btnFullRemote.customObject2 = customObject2
btnFullRemote.addTarget(self, action: #selector(self.btnFullRemote(_:)), for: .touchUpInside)
step 4: typecast the sender in receiving function to a custom object and access those properties
#objc public func btnFullRemote(_ sender: Any) {
var name : String = (sender as! CustomButton).name as? String
var customObject : customObject = (sender as! CustomButton).customObject as? customObject
var customObject2 : customObject2 = (sender as! CustomButton).customObject2 as? customObject2
}
Swift 5.0 iOS 13
I concur a great answer by Ninad. Here is my 2 cents, the same and yet different technique; a minimal version.
Create a custom class, throw a enum to keep/make the code as maintainable as possible.
enum Vs: String {
case pulse = "pulse"
case precision = "precision"
}
class customTap: UITapGestureRecognizer {
var cutomTag: String?
}
Use it, making sure you set the custom variable into the bargin. Using a simple label here, note the last line, important labels are not normally interactive.
let precisionTap = customTap(target: self, action: #selector(VC.actionB(sender:)))
precisionTap.customTag = Vs.precision.rawValue
precisionLabel.addGestureRecognizer(precisionTap)
precisionLabel.isUserInteractionEnabled = true
And setup the action using it, note I wanted to use the pure enum, but it isn't supported by Objective C, so we go with a basic type, String in this case.
#objc func actionB(sender: Any) {
// important to cast your sender to your cuatom class so you can extract your special setting.
let tag = customTag as? customTap
switch tag?.sender {
case Vs.pulse.rawValue:
// code
case Vs.precision.rawValue:
// code
default:
break
}
}
And there you have it.
cell.btn.tag = indexPath.row //setting tag
cell.btn.addTarget(self, action: #selector(showAlert(_ :)), for: .touchUpInside)
#objc func showAlert(_ sender: UIButton){
print("sender.tag is : \(sender.tag)")// getting tag's value
}
Just create a custom class of UITapGestureRecognizer =>
import UIKit
class OtherUserProfileTapGestureRecognizer: UITapGestureRecognizer {
let userModel: OtherUserModel
init(target: AnyObject, action: Selector, userModel: OtherUserModel) {
self.userModel = userModel
super.init(target: target, action: action)
}
}
And then create UIImageView extension =>
import UIKit
extension UIImageView {
func gotoOtherUserProfile(otherUserModel: OtherUserModel) {
isUserInteractionEnabled = true
let gestureRecognizer = OtherUserProfileTapGestureRecognizer(target: self, action: #selector(self.didTapOtherUserImage(_:)), otherUserModel: otherUserModel)
addGestureRecognizer(gestureRecognizer)
}
#objc internal func didTapOtherUserImage(_ recognizer: OtherUserProfileTapGestureRecognizer) {
Router.shared.gotoOtherUserProfile(otherUserModel: recognizer.otherUserModel)
}
}
Now use it like =>
self.userImageView.gotoOtherUserProfile(otherUserModel: OtherUserModel)
You can use an UIAction instead:
self.imageView.addAction(UIAction(identifier: UIAction.Identifier("imageClick")) { [weak self] action in
self?.handleTap(modelObj)
}, for: .touchUpInside)
that may be a terrible practice but I simply add whatever I want to restore to
button.restorationIdentifier = urlString
and
#objc func openRelatedFact(_ sender: Any) {
if let button = sender as? UIButton, let stringURL = factButton.restorationIdentifier, let url = URL(string: stringURL) {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:])
}
}
}
Hello Guys im currently lost on this error, its my first time dealing with IOS and With Swift and although i have been able to use 1 gesture recognizer for a view i need to set multiples ones for any object ( component.control has an AnyObject type ), and when i run it and press the Button ( passed as an AnyObject Type) i get just this error in the appDelegate.swift file
line 1: class AppDelegate: UIResponder, UIApplicationDelegate{ //Thread 1: EXC_BAD_ACCESS (code=1,address=0x1)
any help will be apreciated! thx!
here is what i have
override func viewDidLoad() {
super.viewDidLoad()
//gestures
let gesture_tap = UITapGestureRecognizer(target: self, action: #selector(ComponentDetailViewController.ctrl_tapped(_:event:)));
let gesture_pinch = UIPinchGestureRecognizer(target: self, action: #selector(ComponentDetailViewController.ctrl_pinched(_:event:)));
let gesture_swipe = UISwipeGestureRecognizer(target:self, action: #selector(ComponentDetailViewController.ctrl_swiped(_:event:)));
let gesture_longPress = UILongPressGestureRecognizer(target: self, action: #selector(ComponentDetailViewController.ctrl_longPressed(_:event:)));
let gesture_rotate = UIRotationGestureRecognizer(target:self,action: #selector(ComponentDetailViewController.ctrl_rotated(_:event:)));
let gesture_pan = UIPanGestureRecognizer(target:self, action: #selector(ComponentDetailViewController.ctrl_panned(_:event:)));
let ctrl = component?.control;
ctrl!.addGestureRecognizer(gesture_tap);
ctrl!.addGestureRecognizer(gesture_pinch);
ctrl!.addGestureRecognizer(gesture_swipe);
ctrl!.addGestureRecognizer(gesture_longPress);
ctrl!.addGestureRecognizer(gesture_rotate);
ctrl!.addGestureRecognizer(gesture_pan);
gesture_tap.delegate = self;
gesture_pinch.delegate = self;
gesture_swipe.delegate = self;
gesture_longPress.delegate = self;
gesture_rotate.delegate = self;
gesture_pan.delegate = self;
component?.control = ctrl as? UIView;
//component?.control!.userInteractionEnabled = true;
//component?.control!.addGestureRecognizer(tap);
viewDisplayComponent.addSubview((component?.control)! as! UIView);
}
//Gesture methods
func ctrl_tapped(ctrl: AnyObject, event:UIEvent){
setMessage("TO_tapped");
}
func ctrl_pinched(ctrl: AnyObject, event:UIEvent){
setMessage("TO_pinchedWithArgs")
}
func ctrl_swiped(ctrl: AnyObject, event:UIEvent){
setMessage("TO_swipedWithArgs");
}
func ctrl_longPressed(ctrl: AnyObject, event:UIEvent){
setMessage("TO_longPressedWithArgs");
}
func ctrl_rotated(ctrl: AnyObject, event:UIEvent){
//logTextView.text += "Rotated";
}
func ctrl_panned(ctrl: AnyObject, event:UIEvent){
setMessage("TO_pannedWithArgs");
}
According to the docs:
The action methods invoked must conform to one of the following
signatures:
(void)handleGesture;
(void)handleGesture:(UIGestureRecognizer *)gestureRecognizer;
Swift translation:
handleGesture(gestureRecognizer: UIGestureRecognizer)
Yet, all your action methods have signatures with two arguments. I am unsure how you were able to get one gesture recognizer to work.
i get just this error in the appDelegate.swift file:
line 1: class AppDelegate: UIResponder, UIApplicationDelegate{
//Thread 1: EXC_BAD_ACCESS (code=1,address=0x1)
What about the top of the output in Xcode's debug area, i.e. where print() statements are output?
I wanted to be able to call this function from two places: When I finish editing a text field, I want to add a new webView when there are none in a stackView, and I also want to be able to use a barButtonItem to do so.
I'm having two problems. when the bar button calls this function, the parameter 'url', becomes an object, type UIBarButtonItem. when it's called from textFieldShouldReturn, it properly comes in as an NSURL. if the user doesn't type anything in the address field, and hits enter, a blank NSURL comes in, and the default value is not used. (i'd like it to be)
what should the call look like from the textfieldShouldReturn function, so that a blank will trigger the default?
how do i handle the fact that either my function or the button will call the function, and why does my named parameter 'url' become what i guess would be 'sender?'
override func viewDidLoad() {
super.viewDidLoad()
setDefaultTitle()
let add = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: #selector(ViewController.addWebView))
let delete = UIBarButtonItem(barButtonSystemItem: .Trash, target: self, action: #selector(ViewController.deleteWebView))
navigationItem.rightBarButtonItems = [delete, add]
}
func addWebView(url: NSURL = NSURL(string: "https://www.google.com")!) {
let webView = UIWebView()
webView.delegate = self
stackView .addArrangedSubview(webView)
webView.loadRequest(NSURLRequest(URL: url))
webView.layer.borderColor = UIColor.blueColor().CGColor
selectWebView(webView)
let recognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.webViewTapped))
recognizer.delegate = self
webView.addGestureRecognizer(recognizer)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
if let webView = activeWebView, address = addressBar.text {
if let url = NSURL(string: address) {
webView.loadRequest(NSURLRequest(URL: url))
}
} else if stackView.arrangedSubviews.count == 0 {
let address = NSURL(string: addressBar.text!)!
addWebView(address)
}
textField.resignFirstResponder()
return true
}
That's right that you are getting sender object which is actually UIBarButtonItem. Have you heard about Target-Action Cocoa pattern? If no, you can read more here:
https://developer.apple.com/library/ios/documentation/General/Conceptual/Devpedia-CocoaApp/TargetAction.html
Especially relevant section to you is "An Action Method Must Have a Certain Form".
Consider to introduce addWebView overload:
func addWebView(sender: NSObject) {
addWebView(url: NSURL(string: "https://www.google.com")!)
}
private func addWebView(url: NSURL) {
//left as is ...
}
Here is update per Dave's comments.
Have to use different name for actual implementation method. Otherwise Swift compiler is failed to resolve the assigned selector name.
Useful code, which demonstrates the problem is attached below:
class Notifier: NSObject {
private var _target: NSObject!
private var _action: Selector!
func addObserver(target: NSObject, action: Selector) {
_target = target
_action = action
}
func invokeMethod() {
guard let t = _target else {
print("target must be set")
return
}
guard let a = _action else {
print("action must be set")
return
}
if t.respondsToSelector(a) {
t.performSelector(a, withObject: self)
}
}
}
class Observer: NSObject {
func subscribe(notifier: Notifier) {
notifier.addObserver(self, action: #selector(Observer.callback))
}
func callback(sender: NSObject) {
callbackImpl(NSURL(string: "https://www.google.com")!)
}
private func callbackImpl(url: NSURL) {
print("url\(url)")
}
}
//client's code
let n = Notifier()
let o = Observer()
o.subscribe(n)
n.invokeMethod()
I have UITextView on which I want to add highlight as custom menu item. I have registered to following notification UIMenuControllerWillShowMenuNotification.
The method for the notification is something like this:
if textIsHighlighted {
let highlightMenuItem = UIMenuItem(title: "Highlight", action: Selector("highlightText"))
UIMenuController.sharedMenuController().menuItems = [highlightMenuItem]
}
else {
let highlightMenuItem = UIMenuItem(title: "Dehighlight", action: Selector("highlightText"))
UIMenuController.sharedMenuController().menuItems = [highlightMenuItem]
}
Although the first time the menucontroller fails to update even though it executes the part of code. It shows the last value. Where should I write this part of code as I feel that during willShow menuController it's already created and thus fails to update.
Hopefully you've solved this by now, but I've just figured this one out myself:
Other answers have said you can update the menu items by adding it when the UIMenuControllerWillShowMenuNotification is called, but this wasn't working for me (iOS 9, Swift 2).
Instead I implemented the UITextView delegate method: textViewDidChangeSelection and set the relevant menu items there:
func textViewDidChangeSelection(textView: UITextView) {
if self.currentSelectionIsInHighlightedRange() {
self.setUpUnhighlightMenuItem()
} else {
self.setUpHighlightMenuItem()
}
}
private func currentSelectionIsInHighlightedRange() -> Bool {
let allHighlightedRanges = self.document.highlightedRanges()
let selectedTextRange = self.documentView.textView.selectedRange
for range in allHighlightedRanges {
let intersectionRange = NSIntersectionRange(range, selectedTextRange)
if intersectionRange.length > 0 {
return true
}
}
return false
}