如何从任何 ViewController 轻松显示活动指示器?

pml*_*mlk 5 ios swift

编写具有一些网络活动的应用程序 我发现自己一遍又一遍地为多个视图控制器编写相同的代码只是为了显示活动指示器。

class SomeViewController: UIViewController {
    let indicator: UIActivityIndicatorView = UIActivityIndicatorView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))

    override func viewDidLoad() {
        super.viewDidLoad()

        // customize indicator
        self.indicator.layer.cornerRadius = 10
        self.indicator.center = self.view.center
        self.indicator.hidesWhenStopped = true
        self.indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.whiteLarge
        self.indicator.backgroundColor = UIColor(red: 1/255, green: 1/255, blue: 1/255, alpha: 0.5)
    }
    // MARK: - Acitivity Indicator
    func startIndicatingActivity() {
        DispatchQueue.main.async {
            self.view.addSubview(self.indicator)
            self.indicator.startAnimating()
        }
    }

    func stopIndicatingActivity() {
        DispatchQueue.main.async {
            self.indicator.stopAnimating()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在同一个SomeViewController类中,我可以按如下方式使用它:

@IBAction func startHeavyNetworkStuffButtonPressed(_ sender: UIButton) {
    startIndicatingActivity()
    doHeavyNetworkStuff() { success in
        // heavy networking has finished
        stopIndicatingActivity()
    }
}
Run Code Online (Sandbox Code Playgroud)

只要我只需要在单个视图控制器中显示活动指示器,这就可以正常工作。但是,为每个需要此功能的视图控制器一遍又一遍地执行此操作很乏味。因为我讨厌一遍又一遍地编写相同的代码,所以我正在寻找一种解决方案,我可以在任何视图控制器中简单地调用startIndicatingActivity()(和stopIndicatingActivity()分别调用 )。

第 0 个想法 - 扩展

我明显的第一个想法是为这个UIViewController类编写一个扩展。UIActivityIndicatorView但是,由于我需要存储 的实例,因此Extensions may not contain stored properties出现错误。

第一个想法 - 子类化

接下来:子类化UIViewController。这适用于任何简单的视图控制器。但是,如果我需要相同的功能 a MyCustomTableViewController,我将再次需要首先从子类化UITableViewController复制/粘贴现有​​代码

我的问题

在避免复制/粘贴大量代码的同时,是否有一种优雅的方法可以在任何视图控制器中调用startIndicatingActivity()/ stopIndicatingActivity()?我假设一个优雅的解决方案将涉及extensionprotocol或某种形式的多重继承的方式。

pml*_*mlk 5

这个SO线程是解决方案!事实证明,毕竟一种方法可以使用 anextension模拟属性来解决这个问题。

为感兴趣的读者发布完整的解决方案:

扩展 UIViewController

extension UIViewController {
    // see ObjectAssociation<T> class below
    private static let association = ObjectAssociation<UIActivityIndicatorView>()

    var indicator: UIActivityIndicatorView {
        set { UIViewController.association[self] = newValue }
        get {
            if let indicator = UIViewController.association[self] {
                return indicator
            } else {
                UIViewController.association[self] = UIActivityIndicatorView.customIndicator(at: self.view.center)
                return UIViewController.association[self]!
            }
        }
    }

    // MARK: - Acitivity Indicator
    public func startIndicatingActivity() {
        DispatchQueue.main.async {
            self.view.addSubview(self.indicator)
            self.indicator.startAnimating()
            //UIApplication.shared.beginIgnoringInteractionEvents() // if desired
        }
    }

    public func stopIndicatingActivity() {
        DispatchQueue.main.async {
            self.indicator.stopAnimating()
            //UIApplication.shared.endIgnoringInteractionEvents()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

从上述SO 线程借用代码

// source: /sf/ask/1779874631/
public final class ObjectAssociation<T: AnyObject> {

    private let policy: objc_AssociationPolicy

    /// - Parameter policy: An association policy that will be used when linking objects.
    public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) {

        self.policy = policy
    }

    /// Accesses associated object.
    /// - Parameter index: An object whose associated object is to be accessed.
    public subscript(index: AnyObject) -> T? {

        get { return objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T? }
        set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) }
    }
}
Run Code Online (Sandbox Code Playgroud)

为了完整性

我还UIActivityIndicatorView使用自定义的静态函数扩展了该类。

extension UIActivityIndicatorView {
    public static func customIndicator(at center: CGPoint) -> UIActivityIndicatorView {
        let indicator = UIActivityIndicatorView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
        indicator.layer.cornerRadius = 10
        indicator.center = center
        indicator.hidesWhenStopped = true
        indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.whiteLarge
        indicator.backgroundColor = UIColor(red: 1/255, green: 1/255, blue: 1/255, alpha: 0.5)
        return indicator
    }
}
Run Code Online (Sandbox Code Playgroud)