Consider the following code, which prints "working" when a button is pressed:
protocol MyClassDelegate: class {
func foo()
}
class MyClass {
weak var delegate: MyClassDelegate?
func foo() {
delegate?.foo()
}
let button: UIButton = {
button.addTarget(self, action: #selector(foo), for: .touchUpInside)
return button
}()
}
class MyViewController { ... }
extension MyViewController: MyClassDelegate {
func foo() {
print("working")
}
}
When I try adding a parameter to MyClassDelegate's foo method, "working" stops printing (meaning the button stops working?). I.e.:
protocol MyClassDelegate: class {
func foo(_ str: String)
}
class MyClass {
weak var delegate: MyClassDelegate?
func foo() {
delegate?.foo("working")
}
let button: UIButton = {
button.addTarget(self, action: #selector(foo), for: .touchUpInside)
return button
}()
}
class MyViewController { ... }
extension MyViewController: MyClassDelegate {
func foo(_ str: String) {
print(str)
}
}
How can I get the second version of the code with the parameter to work? Thanks.
You are calling a wrong method on the delegate . Your MyClassDelegate doesn't have method named showDetails() . Call your delegate method this way:
func foo() {
delegate?.foo("working")
}
The problem was that button needs to be a declared with lazy var rather than with let.
Did you do that: cell.delegate = self in your ViewController?
Related
I needed to delegate an click action for my UIView class to my UIViewController class since swift does not support multiple class inheritance. So i wanted it such that once a button is clicked on my subview, a function in my ViewController class is called. Am using protocol delegate to achieve this but on the click of my button it does not work for me as the function does not get called. Please help me out. Code snippet would be largely appreciated.
ViewController
var categoryItem: CategoryItem! = CategoryItem() //Category Item
private func setupExplore() {
//assign delegate of category item to controller
self.categoryItem.delegate = self
}
//function to be called
extension BrowseViewController: ExploreDelegate {
func categoryClicked(category: ProductCategory) {
print("clicked")
let categoryView = ProductByCategoryView()
categoryView.category = category
categoryView.modalPresentationStyle = .overCurrentContext
self.navigationController?.pushViewController(categoryView, animated: true)
}
}
Explore.swift (subview)
import UIKit
protocol ExploreDelegate:UIViewController {
func categoryClicked(category: ProductCategory)
}
class Explore: UIView {
var delegate: ExploreDelegate?
class CategoryItem: UIView {
var delegate: ExploreDelegate?
var category: ProductCategory? {
didSet {
self.configure()
}
}
var tapped: ((_ category: ProductCategory?) -> Void)?
func configure() {
self.layer.cornerRadius = 6
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.categoryTapped)))
self.layoutIfNeeded()
}
#objc func categoryTapped(_ sender: UIGestureRecognizer) {
delegate?.categoryClicked(category: ProductCategory.everything)
self.tapped?(self.category)
}
}
I'm building simple theme engine and would like have an extension which adds UISwipeGestureRecognizer to UIViewController
Here is my code:
protocol Themeable {
func themeDidUpdate(currentTheme: Theme) -> Void
}
extension Themeable where Self: UIViewController {
func switchCurrentTheme() {
Theme.switchTheme()
themeDidUpdate(Theme.currentTheme)
}
func addSwitchThemeGestureRecognizer() {
let gestureRecognizer = UISwipeGestureRecognizer(target: self, action:#selector(Self.switchCurrentTheme))
gestureRecognizer.direction = .Down
gestureRecognizer.numberOfTouchesRequired = 2
self.view.addGestureRecognizer(gestureRecognizer)
}
}
Of course compiler can't find #selector(Self.switchCurrentTheme) as it isn't exposed via #objc directive. Is it possible to add this behaviour to my extension?
UPDATE: Theme is a Swift enum, so I can't add #objc in front of Themeable protocol
The cleanest, working solution I could come up with was to define a private extension on UIViewController with the method in question. By limiting the scope to private, access to this method is isolated to within the source file where the protocol is defined in. Here's what it looks like:
protocol Themeable {
func themeDidUpdate(currentTheme: Theme) -> Void
}
fileprivate extension UIViewController {
#objc func switchCurrentTheme() {
guard let themeableSelf = self as? Themeable else {
return
}
Theme.switchTheme()
themeableSelf.themeDidUpdate(Theme.currentTheme)
}
}
extension Themeable where Self: UIViewController {
func addSwitchThemeGestureRecognizer() {
let gestureRecognizer = UISwipeGestureRecognizer(target: self, action:#selector(switchCurrentTheme))
gestureRecognizer.direction = .Down
gestureRecognizer.numberOfTouchesRequired = 2
self.view.addGestureRecognizer(gestureRecognizer)
}
}
I found a solution. May be not the perfect one, but it works.
As I can't define Themeable protocol as #objc because it uses Swift-only enum I decided to move method I want to call to "parent" protocol and define this protocol as #objc. It seems like it works but I don't really like it to be honest...
#objc protocol ThemeSwitcher {
func switchCurrentTheme()
}
protocol Themeable: ThemeSwitcher {
func themeDidUpdate(currentTheme: Theme) -> Void
}
extension Themeable where Self: UIViewController {
func switchCurrentTheme() {
Theme.switchTheme()
themeDidUpdate(Theme.currentTheme)
}
func addSwitchThemeGestureRecognizer() {
let gestureRecognizer = UISwipeGestureRecognizer(target: self, action:#selector(switchCurrentTheme))
gestureRecognizer.direction = .Down
gestureRecognizer.numberOfTouchesRequired = 2
self.view.addGestureRecognizer(gestureRecognizer)
}
}
Have you considered creating a wrapper to let you call your non-#objc function from an #objc one?
#objc class Wrapper: NSObject {
let themeable: Themeable
init(themeable: Themeable) {
self.themeable = themeable
}
func switchCurrentTheme() {
Theme.switchTheme()
themeable.themeDidUpdate(Theme.currentTheme)
}
}
protocol Themeable {
func themeDidUpdate(currentTheme: Theme) -> Void
}
extension Themeable where Self: UIViewController {
func addSwitchThemeGestureRecognizer() {
let wrapper = Wrapper(themeable: self)
let gestureRecognizer = UISwipeGestureRecognizer(target: wrapper, action:#selector(Wrapper.switchCurrentTheme))
gestureRecognizer.direction = .Down
gestureRecognizer.numberOfTouchesRequired = 2
self.view.addGestureRecognizer(gestureRecognizer)
}
}
Here is a similar use-case, you can call a method through a selector without using #objc as in swift by using the dynamic keyword. By doing so, you are instructing the compiler to use dynamic dispatch implicitly.
import UIKit
protocol Refreshable: class {
dynamic func refreshTableData()
var tableView: UITableView! {get set}
}
extension Refreshable where Self: UIViewController {
func addRefreshControl() {
tableView.insertSubview(refreshControl, at: 0)
}
var refreshControl: UIRefreshControl {
get {
let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
if let control = _refreshControl[tmpAddress] as? UIRefreshControl {
return control
} else {
let control = UIRefreshControl()
control.addTarget(self, action: Selector(("refreshTableData")), for: .valueChanged)
_refreshControl[tmpAddress] = control
return control
}
}
}
}
fileprivate var _refreshControl = [String: AnyObject]()
class ViewController: UIViewController: Refreshable {
#IBOutlet weak var tableView: UITableView! {
didSet {
addRefreshControl()
}
}
func refreshTableData() {
// Perform some stuff
}
}
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'm trying to use delegation and property observers together to know when a property changes. I setup the protocol but I'm not sure how to use property observers.
I have a class called GridView that is being added to DetailViewController. GridView has an array of ints called rowValues. I would like to observe rowValues from DetailViewController.
GridView.swift
protocol gridViewDelegate {
func rowValueChanged(value: [Int])
}
class GridView: UIView {
var rowValues = [0,0,0,0,0]
var delegate: gridViewDelegate?
func updateRowValue() {
rowValues[0] = 1
}
}
DetailViewController.swift
class DetailViewController: UIViewController, gridViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
var grid = GridView(frame: view.frame)
grid.delegate = self
view.addSubview(grid)
}
func rowValueChanged(value: [Int]) {
println(value)
}
}
Probably this is the syntax you are looking for:
class GridView: UIView {
var rowValues: [Int] = [0,0,0,0,0] {
didSet {
if let theDelegate = self.delegate {
theDelegate.rowValueChanged(rowValues)
}
}
}
var delegate: gridViewDelegate?
}