【问题标题】:Swift constrained Protocol for a subclass and superclass子类和超类的 Swift 约束协议
【发布时间】:2019-12-26 12:16:33
【问题描述】:

我想为 UIViewCntroller 和 UIView 实现自己的 HUD,所以我这样做了:

protocol ViewHudProtocol {
    func showLoadingView()
    func hideLoadingView()
}

extension ViewHudProtocol where Self: UIView {

    func showLoadingView() { //Show HUD by adding a custom UIView to self.}
    }

    func hideLoadingView() {
    }
}

现在我可以轻松地在任何 UIView 上采用 ViewHudProtocol 来调用 showLoadingViewhideLoadingView。问题是我想为 UIViewController 使用相同的协议,所以我这样做了:

extension ViewHudProtocol where Self: UIViewController {

    func showLoadingView() {
        self.view.showLoadingView() //Error: UIView has no member showLoadingView
    }

    func hideLoadingView() {
        self.view.hideLoadingView() //Error: UIView has no member hideLoadingView
    }
}

我同意 UIView 尚未采用协议的错误。所以我这样做了:

extension UIView: ViewHudProtocol {}

而且它有效。有一个更好的方法吗?我的意思是用ViewHudProtocol 扩展每个视图感觉不对,因为并非所有人都会使用它。如果我可以做类似的事情,“如果 UIViewController 需要它,则只为 UIView 隐式采用 ViewHudProtocol。否则,您可以在需要时在任何 UIView 上手动采用 ViewHUDProtocol。”

【问题讨论】:

    标签: swift protocols swift-protocols


    【解决方案1】:

    我将通过以下方法解决这个问题,使用 associatedtype,仅为所需的视图和/或控制器定义(在 Xcode 11.2 / swift 5.1 中测试):

    protocol ViewHudProtocol {
        associatedtype Content : ViewHudProtocol
    
        var content: Self.Content { get }
    
        func showLoadingView()
        func hideLoadingView()
    }
    
    extension ViewHudProtocol where Self: UIView {
    
        var content: some ViewHudProtocol {
            return self
        }
    
        func showLoadingView() { //Show HUD by adding a custom UIView to self.}
        }
    
        func hideLoadingView() {
        }
    }
    
    extension ViewHudProtocol where Self: UIViewController {
    
        func showLoadingView() {
            self.content.showLoadingView() //NO Error
        }
    
        func hideLoadingView() {
            self.content.hideLoadingView() //NO Error
        }
    }
    
    //Usage
    extension UITableView: ViewHudProtocol { // only for specific view
    }
    
    extension UITableViewController: ViewHudProtocol { // only for specific controller
        var content: some ViewHudProtocol {
            return self.tableView
        }
    }
    

    【讨论】:

    • 恭喜,非常聪明的解决方案。
    【解决方案2】:

    问题

    因此,只有当UIViewController.view 属性符合ViewHudProtocol 时,您才希望将UIViewController 的符合性限制为协议ViewHudProtocol

    恐怕这是不可能的。

    了解问题

    让我们更好地了解您的问题

    您有 2 种类型(UIView 和 UIViewController),并且您想要添加到相同的功能中

    func showLoadingView()
    func hideLoadingView()
    

    米克·韦斯特教我们什么

    这种情况在某种程度上类似于米克韦斯特在开发托尼霍克斯系列赛期间米克韦斯特所面临的情况,其文章Evolve your hierarchy 中描述了一个优雅的解决方案。

    解决方案

    我们可以将这种方法应用于您的问题,这就是解决方案

    struct HudViewComponent {
    
        let view: UIView
        private let hud: UIView
    
        init(view: UIView) {
            self.view = view
            self.hud = UIView(frame: view.frame)
            self.hud.isHidden = true
            self.view.addSubview(hud)
        }
    
        func showLoadingView() {
            self.hud.isHidden = false
        }
    
        func hideLoadingView() {
            self.hud.isHidden = true
        }
    
    }
    
    protocol HasHudViewComponent {
        var hidViewComponent: HudViewComponent { get }
    }
    
    extension HasHudViewComponent {
        func showLoadingView() {
            hidViewComponent.showLoadingView()
        }
        func hideLoadingView() {
            hidViewComponent.hideLoadingView()
        }
    }
    
    

    就是这样,现在您可以将 hud 功能添加到任何符合 HasHudViewComponent 的 Type。

    class SomeView: UIView, HasHudViewComponent {
        lazy var hidViewComponent: HudViewComponent = { return HudViewComponent(view: self) }()
    }
    

    class MyViewController: UIViewController, HasHudViewComponent {
        lazy var hidViewComponent: HudViewComponent = { return HudViewComponent(view: self.view) }()
    }
    

    注意事项

    如您所见,我们的想法是从组件的角度进行思考。 您使用您的 hud 功能构建一个组件 (HudViewComponent)。该组件只要求最低要求:它需要一个 UIView。

    接下来定义HasHudViewComponent,它声明当前类型有一个 HudViewComponent 属性。

    最后,您可以将您的 hud 功能添加到具有视图(UIView、UIViewController、...)的任何类型,只需使您的类型符合 HasHudViewComponent

    注意事项

    您提出了一个有趣的问题,我知道这并不能 100% 回答您的需求,但从实际的角度来看,它应该为您提供了一个工具来满足您的需求。

    【讨论】:

      【解决方案3】:

      我会采用这种方法:

      1. 创建一个 UIView 类,
      2. 设置视图
      3. 声明一个共享对象。
      4. 显示视图的函数
      5. 删除视图的函数。然后在视图控制器中调用它为 IndicatorView.shared.show()IndicatorView.shared.hide()

        import Foundation
        import UIKit
        import Lottie
        
        class IndicatorView : UIView {
        
        static let shared = IndicatorView()
        
        var loadingAnimation : AnimationView = {
            let lottieView = AnimationView()
            lottieView.translatesAutoresizingMaskIntoConstraints = false
            lottieView.layer.masksToBounds = true
            return lottieView
        }()
        
        var loadingLabel : UILabel = {
            let label = UILabel()
            label.textColor = .white
            label.translatesAutoresizingMaskIntoConstraints = false
            label.font = UIFont(name: "SegoeUI", size: 12)
            return label
        }()
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            translatesAutoresizingMaskIntoConstraints = false
        }
        
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        public func show() {
            setupLoadingView()
        
            self.alpha = 0
            UIView.animate(withDuration: 0.5, animations: {
                self.isHidden = false
                self.alpha = 1
            }, completion: nil)
            applyLottieAnimation()
        }
        
        public func hide() {
            self.alpha = 1
                  UIView.animate(withDuration: 0.5, animations: {
                      self.alpha = 0
                  }, completion: { _ in
                      self.isHidden = true
                      self.removeFromSuperview()
                  })    }
        
        
        private func setupLoadingView() {
            let controller = UIApplication.shared.keyWindow!.rootViewController!
            controller.view.addSubview(self)
        
            //setup your views here
        
            self.setNeedsLayout()
            self.reloadInputViews()
        }
        
        }
        

      【讨论】:

        【解决方案4】:

        对于这种特殊情况,装饰器会更好地工作,并产生更好的设计:

        final class HUDDecorator {
            private let view: UIView
        
            init(_ view: UIView) {
                self.view = view
            }
        
            func showLoadingView() {
                // add the spinner
            }
        
            func hideLoadingView() {
                // remove the spinner
            }
        }
        

        使用装饰器就像为它声明一个属性一样简单:

        class MyViewController: UIViewController {
            lazy var hudDecorator = HUDDecorator(view)
        }
        

        这将允许任何控制器通过简单地公开此属性来决定是否需要支持显示/隐藏加载视图。

        对于简单的任务,比如增强 UI 组件的外观,协议的侵入性太大,并且它们的缺点是强制某个类的所有视图都公开协议功能,而装饰器方法允许您决定要使用哪些视图实例接收功能。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-04-30
          • 2017-10-12
          • 1970-01-01
          • 2015-06-26
          • 1970-01-01
          相关资源
          最近更新 更多