When i pass a static/class method in selector param of UIAccessibilityCustomAction, it is not getting triggered.
The same method works for gesture recognizers/add targets functions
The custom action is set and announced properly. So there's no problem in set up of that. But when i double tap staticTest is not triggered.
If i pass instance method to it, it works.
Code set-up which is not working:
// does not work
newView.accessibilityCustomActions?.append(
UIAccessibilityCustomAction(
name: "staticTest test action",
target: ViewController.self,
selector: #selector(ViewController.staticTest)))
Code Sample:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let newView = UIView(frame: CGRect(x: 20, y: 20, width: 300, height: 300))
newView.backgroundColor = UIColor.red
view.isUserInteractionEnabled = true
view.addSubview(newView)
newView.isAccessibilityElement = true
// works
newView.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(instanceTest)))
// works
newView.accessibilityCustomActions = [
UIAccessibilityCustomAction(name: "instanceTest test action", target: self, selector: #selector(instanceTest))
]
// works
newView.addGestureRecognizer(
UITapGestureRecognizer(
target: ViewController.self,
action: #selector(ViewController.staticTest)))
// does not work
newView.accessibilityCustomActions?.append(
UIAccessibilityCustomAction(
name: "staticTest test action",
target: ViewController.self,
selector: #selector(ViewController.staticTest)))
}
#objc static func staticTest() -> Bool {
print("staticTest")
return true
}
#objc func instanceTest() -> Bool {
print("InstanceTest")
return true
}
}
I tried your code and it was difficult to believe the observation. Then I checked the apple docs and here is my analysis.
Even though apple documentation is silent on the use of static/class function as selector. It's kind of indirectly mentioned in the wordings.
https://developer.apple.com/documentation/uikit/uiaccessibilitycustomaction/1620499-init
For UIAccessibilityCustomAction:
target:The object that performs the action.
selector:The selector of target to call when you want to perform the action.
In your code, target is the ViewController object but it's difficult to say that selector belongs to this object as it's a static/class function. Actually, swift doesn't even allow to call static function using an object.
Having said that, it's debatable why static function works with gestureRecogniser.
https://developer.apple.com/documentation/uikit/uigesturerecognizer/1624211-init
The defintion of target is slightly different here.
target: An object that is the recipient of action messages sent by the receiver when it recognizes a gesture.
action: A selector that identifies the method implemented by the target to handle the gesture recognized by the receiver.
So, your code matches the target definiton completely as self is the object which should receive the gesture. But action definition is not completely matched as the target(which is an object and not a class) actually does not implement the static function.
As per my understanding, static function needs to be avoided as selector based on the documentation.
Related
So I was making a to-do list app, and I had this variable inside VIEWDIDLOAD:
var edit = UIBarButtonItem (title: "Edit", style: .plain, target: self, action: #selector(editfunc))
I wanted that when someone finishes editing, the button changes from 'edit' to 'done' so I wrote this:
#objc func editfunc () {
table.isEditing = !table.isEditing
if table.isEditing == true {
edit.title = "Done"
}
else {
edit.title = "Edit"
}
}
It didn't work. I googled it and it seems like the solution was to write 'var edit' outside of the viewdidload func, and also to use 'lazy' before it. It worked, but I still didn't understand why? What's the difference between defining properities inside and outside viewdidload and why did it work only when I used 'lazy'm, a
It's as Lorem ipsum commented that it's about the scope of the variable.
But to answer your second question about the lazy property is used on variables where you want to use self as Lazy waits for init to be run. self is still available as a property, but as init has not been run yet, you are basically asserting nil to the target property
I set up a right navigation button like so inviewWillAppear in . a class ChatMessageViewController..
let button2 = UIBarButtonItem(image: nil, style: .plain, target: self, action: #selector(blockPressed(sender:)))
button2.title = "Block"
self.navigationItem.rightBarButtonItem = button2
Now on the click of blockPressed another shared function is called like so...
#objc fileprivate func blockPressed(sender: UIButton) {
XMPPConfig.shared.blockUser(userJID: theUserJID!) //XMPPConfig is another class having some common functions and delegate methods.
}
(This function basically blocks a certain user like blocking a whatsapp user. Once blocking happens, certain delegate methods are called. One such delegate method after which I change the Block button is given as follows..)
func xmppBlocking(_ sender: XMPPBlocking!, didBlockJID xmppJID: XMPPJID!) {
print("successfully blocked!")
ChatMessageViewController.shared.setupUnBlock()
}
Doing this also properly calls setupUnBlock() function in ChatMessageViewController like so...
func setupUnBlock() {
if XMPPConfig.shared.sectionGroupsFlag == false {
let button2 = UIBarButtonItem(image: nil, style: .plain, target: self, action: #selector(unblockPressed(sender:)))
button2.title = "Unblock"
self.navigationItem.rightBarButtonItem = button2
}
}
But the button title still remains unchanged...i.e. it is still "Block"..what could be the reason for this...?
You should initialize your UINavigationBarButton using another initializer:
let right = UIBarButtonItem(title: "Some title", style: .plain, target: self, action: #selector(rightNavBarButtonPressed))
You are using UIBarButtonItem(image:... instead.
I tried to duplicate your code and apparently everything works.
So, the problem may be due to three possible problems, as far as i can see.
it may be thread problem. This is unlikley unless you have not turned on the thread checker in xcode.. But inside both unblock and block codes, enter
print(Thread.current)
to see both blocks of codes in setting the rightbarButton is in main thread. If not, you should know how to solve them.
It may be view update problem, unlikely still, but worth a try.. So in your block of codes where you are adding rightBarButton, add one more line of code.
self.navigationController?.viewIfLoaded?.setNeedsLayout()
to update the navigationController's view after you have set the rightBarButtonItem.
The most likely problem in my opinion is that, you have been doubling calling block barButtonItem codes. This means when you have called unblock barButtonItem setup codes, you accidentally called the block barButtonItem too.
To debug this is easy, you just put a statement in each of block of the codes, to see if they appear together.
Okay, this sounds like an easy problem but I'm going crazy trying to solve it.
I'm using the Model View Controller design in my code.
I have a view which has an image view. I add a gesture recognizer to the image view as follows:
let closeSelector: Selector = "closeView:"
let gestureRecognizer = UITapGestureRecognizer(target: self, action: closeSelector)
gestureRecognizer.numberOfTapsRequired = 1
escapeIcon.addGestureRecognizer(gestureRecognizer)
now when the image or "escape icon" as i've defined it, is tapped, the selector 'closeView" function is called.
However, this function is only called IF I define the closeView function within my view file.
But.. I need to call this function in my view controller file since from within the view controller file there are some additional properties and methods that I want to change.
Any help?
In UITapGestureRecognizer(target: self, action: closeSelector), the target is the class that the selector is in. If you have a reference to parentViewController or similar, you can just say UITapGestureRecognizer(target: parentViewController, action: closeSelector).
I have a custom UIView (called GridView) that I initialize and then add to a ViewController (DetailViewController). GridView contains several UIButtons and I would like to know in DetailViewController when those buttons are touched. I'm new to Swift and am wondering what is the best pattern to use to get those events?
If you want to do this with notifications, use 1:
func postNotificationName(_ notificationName: String,
object notificationSender: AnyObject?)
in the method that is triggered by your button. Then, in your DetailViewController, add a listener when it is initialized with 2:
func addObserver(_ notificationObserver: AnyObject,
selector notificationSelector: Selector,
name notificationName: String?,
object notificationSender: AnyObject?)
Both functions can be called from NSNotificationCenter.defaultCenter().
Another method would be to add callbacks which you connect once you initialize the GridView in your DetailViewController. A callback is essentially a closure:
var callback : (() -> Void)?
which you can instantiate when needed, e.g.
// In DetailViewController initialization
gridView = GridView()
gridView.callback = { self.doSomething() }
In GridView you can trigger the callback like this:
func onButton()
{
callback?()
}
The callback will only execute, if unwrapping succeeds. Please ensure, that you have read Automatic Reference Counting, because these constructs may lead to strong reference cycles.
What's the difference? You can connect the callback only once (at least with the method I've showed here), but when it triggers, the receiver immediately executes its code. For notifications, you can have multiple receivers but there is some delay in event delivery.
Lets assume your GridView implementation is like as follows:
class GridView : UIView {
// Initializing buttons
let button1:UIButton = UIButton(...)
let button2:UIButton = UIButton(...)
// ...
// Adding buttons to view
self.addSubview(button1)
self.addSubview(button2)
// ...
}
Now, we will add selector methods which will be called when a button is touched. Lets assume implementation of your view controller is like as follows:
class DetailViewController : UIViewController {
let myView:GridView = GridView(...)
myView.button1.addTarget(self, action: "actionForButton1:", forControlEvents: UIControlEvents.TouchUpInside)
myView.button2.addTarget(self, action: "actionForButton2:", forControlEvents: UIControlEvents.TouchUpInside)
// ...
func actionForButton1(sender: UIButton!) {
// Your actions when button 1 is pressed
}
// ... Selectors for other buttons
}
I have to say that my example approach is not a good example for encapsulation principles of Object-Oriented Programming, but I have written like this because you are new to Swift and this code is easy to understand. If you want to prevent duplicate codes such as writing different selectors for each button and if you want to set properties of your view as private to prevent access from "outside" like I just did in DetailViewController, there are much much better solutions. I hope it just helps you!
I think you better create a class called GridView that is inherited from the UIView. Then, you can connect all you UI element with you class as IBOutlet or whatever using tag something like that. Later on, you can ask the instance of GridView in DetailViewController so that you can connect as IBAction.
Encapsulation is one of the principles of OOP.
In objective-c I define my action methods in .m file. So they become private(sort of) and other classes (view controllers) can't see them.
In swift I tried to do it. But app crashed with unrecognized selector sent to instance error. Here is the action source object.
let rememberSwitch = UISwitch()
rememberSwitch.addTarget(self, action: "rememberMeChanged:", forControlEvents: .ValueChanged)
and here is the action method.
private func rememberMeChanged(rememberSwitch: UISwitch) {
}
Is there another way to make this method hidden from other classes other than define it as private?