I am creating a UIView programmatically in static method and want to add UITapGestureRecognizer which will call another static method in my Helper class.
Helper.swift :
static func showLoadingPopUp(frame: CGRect) -> UIView {
let transView = UIView.init(frame: frame!)
let tapGesture = UITapGestureRecognizer(target: self, action: "transViewTapped:")
transView.addGestureRecognizer(tapGesture)
return transView
}
static func transViewTapped(gesture: UITapGestureRecognizer) {
print("Oh Tapped!!!")
}
But it ends up with the following message probably because of the static nature of my method. Also Helper.swift is simple swift class (not UIView or UIViewController)
Error:
Unrecognized selector +[JaClassified.Helper transViewTapped:]
If your class is not a descendant of NSObject, you may need to bridge the static func to Objective-C (since old style Objective-C selector works on NSObjects).
So, in your case, you could either declare you Helper class as
class Helper: NSObject {
...
}
or, you can bridge your transViewTapped: to Objective-C by prefix it with an #objc
#objc static func transViewTapped(gesture: UITapGestureRecognizer) {
...
}
Hope it helps.
Related
This question already has an answer here:
UILongPressGestureRecognizer not calling its target method
(1 answer)
Closed 1 year ago.
I was wondering, why UITapGestureRecognizer doesn't work, if we make it become the member variable of a class?
Not working. hideKeyboard is not called when tapped
class TabInfoSettingsCell: UICollectionViewCell {
private let hideKeyboardTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard))
override func awakeFromNib() {
super.awakeFromNib()
self.isUserInteractionEnabled = true
self.addGestureRecognizer(hideKeyboardTapGestureRecognizer)
}
#objc private func hideKeyboard() {
print("hide keyboard")
}
Working
class TabInfoSettingsCell: UICollectionViewCell {
override func awakeFromNib() {
super.awakeFromNib()
self.isUserInteractionEnabled = true
let hideKeyboardTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard))
self.addGestureRecognizer(hideKeyboardTapGestureRecognizer)
}
#objc private func hideKeyboard() {
print("hide keyboard")
}
Do you have idea what is the reason behind, on why UITapGestureRecognizer does not work if we make it become the member variable of a class?
Property initializers are run before the class initializer. This means that self (the instance of the class in this case) is not available before class init (at the time of property initialization).
If you make it a lazy var instead, it will be executed later in the lifecycle after init has been run and it will work as expected.
I've got a protocol extension it used to work perfectly before swift 2.2.
Now I have a warning that tells me to use the new #selector, but if I add it
no method declared with Objective-C Selector.
I tried to reproduce the issue in this few lines of code, that can be easily copy and paste also into playground
protocol Tappable {
func addTapGestureRecognizer()
func tapGestureDetected(gesture:UITapGestureRecognizer)
}
extension Tappable where Self: UIView {
func addTapGestureRecognizer() {
let gesture = UITapGestureRecognizer(target: self, action:#selector(Tappable.tapGestureDetected(_:)))
addGestureRecognizer(gesture)
}
}
class TapView: UIView, Tappable {
func tapGestureDetected(gesture:UITapGestureRecognizer) {
print("Tapped")
}
}
There is also a suggestion to append to that method in the protocol #objc, but if I do it asks me also to add it to the class that implements it, but once I add the class doesn't conform to the protocol anymore, because it doesn't seems to see the implementation in the protocol extension.
How can I implement this correctly?
I had a similar problem. here is what I did.
Marked the protocol as #objc.
Marked any methods I extended with a default behavior as optional.
Then used Self. in the #selector.
#objc public protocol UpdatableUserInterfaceType {
optional func startUpdateUITimer()
optional var updateInterval: NSTimeInterval { get }
func updateUI(notif: NSTimer)
}
public extension UpdatableUserInterfaceType where Self: ViewController {
var updateUITimer: NSTimer {
return NSTimer.scheduledTimerWithTimeInterval(updateInterval, target: self, selector: #selector(Self.updateUI(_:)), userInfo: nil, repeats: true)
}
func startUpdateUITimer() {
print(updateUITimer)
}
var updateInterval: NSTimeInterval {
return 60.0
}
}
You can create a property which is a Selector... Example:
protocol Tappable {
var selector: Selector { get }
func addTapGestureRecognizer()
}
extension Tappable where Self: UIView {
func addTapGestureRecognizer() {
let gesture = UITapGestureRecognizer(target: self, action: selector)
addGestureRecognizer(gesture)
}
}
class TapView: UIView, Tappable {
var selector = #selector(TapView.tapGestureDetected(_:))
func tapGestureDetected(gesture:UITapGestureRecognizer) {
print("Tapped")
}
}
The error stops to show and it is not more necessary to set your protocol and class with the #objc decorator.
This solution is not the most elegant, but looks ok until now.
This answer is quite similar to Bruno Hecktheuers, but instead of having everyone that wants to conform to the "Tappable" protocol implement the variable "selector", we choose to pass it as a parameter to the addTapGestureRecognizer function:
protocol Tappable {
func addTapGestureRecognizer(selector selector: Selector)
func tapGestureDetected(gesture:UITapGestureRecognizer)
}
extension Tappable where Self: UIView {
func addTapGestureRecognizer(selector selector: Selector)
let gesture = UITapGestureRecognizer(target: self, action: selector)
addGestureRecognizer(gesture)
}
}
class TapView: UIView, Tappable {
func tapGestureDetected(gesture:UITapGestureRecognizer) {
print("Tapped")
}
}
and then just pass the selector wherever it is used:
addTapGestureRecognizer(selector: #selector(self.tapGestureDetected(_:)))
This way we avoid having the ones implementing this protocol having to implement the selector variable and we also avoid having to mark everyone using this protocol with "#objc". Feels like this approach is less bloated.
Here is a working example using Swift 3. It uses a standard Swift protocol without the need for any #objc decorations and a private extension to define the callback function.
protocol PlayButtonPlayable {
// be sure to call addPlayButtonRecognizer from viewDidLoad or later in the display cycle
func addPlayButtonRecognizer()
func handlePlayButton(_ sender: UITapGestureRecognizer)
}
fileprivate extension UIViewController {
#objc func _handlePlayButton(_ sender: UITapGestureRecognizer) {
if let playable = self as? PlayButtonPlayable {
playable.handlePlayButton(sender)
}
}
}
fileprivate extension Selector {
static let playTapped =
#selector(UIViewController._handlePlayButton(_:))
}
extension PlayButtonPlayable where Self: UIViewController {
func addPlayButtonRecognizer() {
let playButtonRecognizer = UITapGestureRecognizer(target: self, action: .playTapped)
playButtonRecognizer.allowedPressTypes = [ NSNumber(value: UIPressType.playPause.rawValue as Int) ]
view.addGestureRecognizer(playButtonRecognizer)
}
}
I happened to see this in the side bar, I recently had this same issue.. Unfortunately, due to Objective-C runtime limitations you cannot use #objc on protocol extensions, I believe this issue was closed early this year.
The issue arises because the extension is added after the conformance of the protocol, therefor there is no way to guarantee that conformance to the protocol is met. That said, it is possible to call a method as a selector from anything that subclasses NSObject and conforms to the protocol. This is most often done with delegation.
This implies you could create an empty wrapper subclass that conforms to the protocol and use the wrapper to call its methods from the protocol that are defined in the wrapper, any other undefined methods from the protocol can be passed to the delegate. There are other similar solutions that use a private extension of a concrete class such as UIViewController and define a method that calls the protocol method but these are also tied to a particular class and not a default implementation of a particular class that happens to conform to the protocol.
Realize that you are trying to implement a default implementation of a protocol function that uses another of it's own protocol functions to define a value for it's own implementation. whew!
Protocol:
public protocol CustomViewDelegate {
func update()
func nonDelegatedMethod()
}
View:
Use a delegate, and define a wrapper method to safely unwrap the delegate’s method.
class CustomView: UIView {
let updateButton: UIButton = {
let button = UIButton(frame: CGRect(origin: CGPoint(x: 50, y: 50), size: CGSize(width: 150, height: 50)))
button.backgroundColor = UIColor.lightGray
button.addTarget(self, action: #selector(doDelegateMethod), for: .touchUpInside)
return button
}()
var delegate:CustomViewDelegate?
required init?(coder aDecoder: NSCoder) {
fatalError("Pew pew, Aghh!")
}
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(updateButton)
}
#objc func doDelegateMethod() {
if delegate != nil {
delegate!.update()
} else {
print("Gottfried: I wanted to be a brain surgeon, but I had a bad habit of dropping things")
}
}
}
ViewController:
Conform the View Controller to the view’s delegate: and implement the protocol’s method.
class ViewController: UIViewController, CustomViewDelegate {
let customView = CustomView(frame: CGRect(origin: CGPoint(x: 100, y: 100), size: CGSize(width: 200, height: 200)))
override func viewDidLoad() {
super.viewDidLoad()
customView.backgroundColor = UIColor.red
customView.delegate = self //if delegate is not set, the app will not crash
self.view.addSubview(customView)
}
// Protocol -> UIView Button Action -> View Controller's Method
func update() {
print("Delegating work from View that Conforms to CustomViewDelegate to View Controller")
}
//Protocol > View Controller's Required Implementation
func nonDelegatedMethod() {
//Do something else
}
}
Note that the view controller only had to conform to the delegate and did not set the selector of some property of the view, this separates the view (and it's protocol) from view controller.
You already have a UIView named TapView that inherits from UIView and Tappable so your implementation could be:
Protocol:
protocol TappableViewDelegate {
func tapGestureDetected(gesture:UITapGestureRecognizer)
}
TappableView:
class TappableView: UIView {
var delegate:TappableViewDelegate?
required init?(coder aDecoder: NSCoder) {
fatalError("Pew pew, Aghh!")
}
override init(frame: CGRect) {
super.init(frame: frame)
let gesture = UITapGestureRecognizer(target: self, action: #selector(doDelegateMethod(gesture:)))
addGestureRecognizer(gesture)
}
#objc func doDelegateMethod(gesture:UITapGestureRecognizer) {
if delegate != nil {
delegate!.tapGestureDetected(gesture: gesture)
} else {
print("Gottfried: I wanted to be a brain surgeon, but I had a bad habit of dropping things")
}
}
}
ViewController:
class ViewController: UIViewController, TappableViewDelegate {
let tapView = TappableView(frame: CGRect(origin: CGPoint(x: 100, y: 100), size: CGSize(width: 200, height: 200)))
override func viewDidLoad() {
super.viewDidLoad()
tapView.backgroundColor = UIColor.red
tapView.delegate = self
self.view.addSubview(tapView)
}
func tapGestureDetected(gesture: UITapGestureRecognizer) {
print("User did tap")
}
}
In the creation of a swift iOS app, I needed to handle the event of a UIButton press outside of the parent view controller, so I created a (very simple) protocol to delegate that responsibility to a different class:
import UIKit
protocol MyButtonProtocol {
func buttonPressed(sender: UIButton)
}
However, when I try to addTarget to a UIButton with that protocol, I get this error: Cannot convert value of type 'MyButtonProtocol' to expected argument type 'AnyObject?'. Shouldn't anything be able to be converted to AnyObject?? Here is my main code:
import UIKit
class MyView: UIView {
var delegate: MyButtonProtocol
var button: UIButton
init(delegate: MyButtonProtocol) {
self.delegate = delegate
button = UIButton()
//... formatting ...
super.init(frame: CGRect())
button.addTarget(delegate, action: "buttonPressed:", forControlEvents: .TouchUpInside)
addSubview(button)
//... more formatting ...
}
}
Thanks in advance.
AnyObject is the protocol to which all classes conform.
To define a protocol which can only adopted by classes, add
: class to the definition:
protocol MyButtonProtocol : class {
func buttonPressed(sender: UIButton)
}
Without that modification,
var delegate: MyButtonProtocol
can be a struct or enum type and that is not convertible to AnyObject.
//i hope it will work
import UIKit
class MyView: UIView {
var delegate: MyButtonProtocol
var button: UIButton
init(delegate: MyButtonProtocol) {
self.delegate = delegate
button = UIButton()
//... formatting ...
super.init(frame: CGRect())
button.addTarget(delegate, action: Selector("buttonPressed:") forControlEvents: .TouchUpInside)
addSubview(button)
//... more formatting ...
}
}
I have a set of view controllers which will have a Menu bar button. I created a protocol for those viewControllers to adopt. Also, I've extended the protocol to add default functionalities.
My protocol looks like,
protocol CenterViewControllerProtocol: class {
var containerDelegate: ContainerViewControllerProtocol? { get set }
func setupMenuBarButton()
}
And, the extension looks like so,
extension CenterViewControllerProtocol where Self: UIViewController {
func setupMenuBarButton() {
let barButton = UIBarButtonItem(title: "Menu", style: .Done, target: self, action: "menuTapped")
navigationItem.leftBarButtonItem = barButton
}
func menuTapped() {
containerDelegate?.toggleSideMenu()
}
}
My viewController adopts the protocol -
class MapViewController: UIViewController, CenterViewControllerProtocol {
weak var containerDelegate: ContainerViewControllerProtocol?
override func viewDidLoad() {
super.viewDidLoad()
setupMenuBarButton()
}
}
I got the button to display nicely, but when I click on it, the app crashes with
[AppName.MapViewController menuTapped]: unrecognized selector sent to instance 0x7fb8fb6ae650
If I implement the method inside the ViewController, it works fine. But I'd be duplicating the code in all viewControllers which conform to the protocol.
Anything I'm doing wrong here?
Thanks in advance.
It seems like using protocol extensions are not supported at this point in time. According to fluidsonic's answer here:
In any case all functions you intend to use via selector should be marked with dynamic or #objc. If this results in an error that #objc cannot be used in this context, then what you are trying to do is simply not supported."
In your example, I think one way around this would be to create a subclass of UIBarButtonItem that calls a block whenever it is tapped. Then you could call containerDelegate?.toggleSideMenu() inside that block.
This compiles but crash also in Xcode7.3 Beta so finally you should use a ugly super class as target of the action, that i suppose that it's what you and me are trying to avoid.
This is an old question but I also ran into the same issue and came up with a solution which may not be perfect but it's the only way I could think of.
Apparently even in Swift 3, it's not possible to set a target-action to your protocol extension. But you can achieve the desired functionality without implementing your func menuTapped() method in all your ViewControllers that conforms to your protocol.
first let's add new methods to your protocol
protocol CenterViewControllerProtocol: class {
var containerDelegate: ContainerViewControllerProtocol? { get set }
//implemented in extension
func setupMenuBarButton()
func menuTapped()
//must implement in your VC
func menuTappedInVC()
}
Now change your extention like this
extension CenterViewControllerProtocol where Self: UIViewController {
func setupMenuBarButton() {
let barButton = UIBarButtonItem(title: "Menu", style: .Done, target: self, action: "menuTappedInVC")
navigationItem.leftBarButtonItem = barButton
}
func menuTapped() {
containerDelegate?.toggleSideMenu()
}
}
Notice now button's action is "menuTappedInVC" in your extension, not "menuTapped" . And every ViewController that conforms to CenterViewControllerProtocol must implement this method.
In your ViewController,
class MapViewController: UIViewController, CenterViewControllerProtocol {
weak var containerDelegate: ContainerViewControllerProtocol?
override func viewDidLoad() {
super.viewDidLoad()
setupMenuBarButton()
}
func menuTappedInVC()
{
self.menuTapped()
}
All you have to do is implement menuTappedInVC() method in your VC and that will be your target-action method. Within that you can delegate that task back tomenuTapped which is already implemented in your protocol extension.
I think you can wrap Target-Action to make Closure from them and then use it in similar way I have used Target-Action for UIGestureRecognizer
protocol SomeProtocol {
func addTouchDetection(for view: UIView)
}
extension SomeProtocol {
func addTouchDetection(for view: UIView) {
let tapGestureRecognizer = UITapGestureRecognizer(callback: { recognizer in
// recognizer.view
})
view.addGestureRecognizer(tapGestureRecognizer)
}
}
// MARK: - IMPORTAN EXTENSION TO ENABLE HANDLING GESTURE RECOGNIZER TARGET-ACTIONS AS CALLBACKS
extension UIGestureRecognizer {
public convenience init(callback: #escaping (_ recognizer: UIGestureRecognizer) -> ()) {
let wrapper = CallbackWrapper(callback)
self.init(target: wrapper, action: #selector(CallbackWrapper.callCallback(_:)))
// retaint callback wrapper
let key = UnsafeMutablePointer<Int8>.allocate(capacity: 1);
objc_setAssociatedObject(self, key, wrapper, .OBJC_ASSOCIATION_RETAIN)
}
class CallbackWrapper {
var callback : (_ recognizer: UIGestureRecognizer) -> ();
init(_ callback: #escaping (_ recognizer: UIGestureRecognizer) -> ()) {
self.callback = callback;
}
#objc public func callCallback(_ recognizer: UIGestureRecognizer) {
self.callback(recognizer);
}
}
}
I am calling a class function from my ViewController class like this:
Buttons.set_cornerRadius(10)
I have another .swift file where I have the function declared:
class Buttons {
class func set_cornerRadius(radius: CGFloat) {
ViewController().someButton.layer.cornerRadius = radius
}
}
When I'm trying to run this it throws the error: "Unexpectedly found nil while unwrapping an optional Value".
I checked the Storyboard-IBOutlet connections already. Everything is connected right.
If I call the method in the same class like this, everything works:
class ViewController: UIViewController {
#IBOutlet weak var someButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
set_cornerRadius(10)
}
func set_cornerRadius(radius: CGFloat) {
someButton.layer.cornerRadius = radius
}
}
How can I fix this? What am I doing wrong/not understanding right?
Thanks in advance.
You access a generic ViewController, but need to use an existing UIView. Do something like this:
class Test: UIViewController {
class func set_cornerRadius(yourView: UIView, radius: CGFloat) {
yourView.layer.cornerRadius = radius
}
}
That way, you pass the UIView you want to set the corner-radius.
You extend your ViewController class like so:
extension ViewController {
func set_cornerRadius(radius: CGFloat) {
someButton.layer.cornerRadius = radius
}
}
Now you can call this method in your original ViewController file using: set_cornerRadius(someValue) in your viewDidLoad or wherever you want. You can put this extension in a different file.