11

UISwipeGestureRecognizer我正在构建简单的主题引擎,并希望有一个扩展UIViewController

这是我的代码:

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)
    }
}

当然编译器找不到#selector(Self.switchCurrentTheme),因为它不是通过@objc指令公开的。是否可以将此行为添加到我的扩展程序中?

更新: Theme是一个 Swift 枚举,所以我不能在协议@objc前面添加Themeable

4

4 回答 4

19

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)
    }
}
于 2016-07-08T21:36:12.933 回答
1

我找到了解决方案。可能不是完美的,但它有效。由于我无法定义Themeable协议,@objc因为它仅使用 Swiftenum我决定将我想调用的方法移动到“父”协议并将此协议定义为@objc. 它似乎有效,但我真的不喜欢它老实说......

@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)
    }
}
于 2016-04-15T10:36:22.827 回答
0

您是否考虑过创建一个包装器来让您从 @objc 调用非 @objc 函数?

@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)
    }
}
于 2016-04-15T11:32:20.970 回答
0

这是一个类似的用例,您可以通过选择器调用方法,而无需像在 swift 中那样使用 dynamic 关键字来使用 @objc。通过这样做,您将指示编译器隐式使用动态调度。

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
    }
}
于 2019-12-19T09:01:56.697 回答