【问题标题】:Swift Custom UIView Inherited by UILabelUILabel 继承的 Swift 自定义 UIView
【发布时间】:2019-07-16 12:50:41
【问题描述】:

我有一个正在尝试解决的继承问题。通常,我在这里只使用多重继承,但 Swift 并没有真正做到这一点。

自定义 UIView

import UIKit

class ValidationView: UIView {

    var required:Bool = false
    var validRegex:String? = nil

    var requiredLbl:UILabel?

    private var requiredColor:UIColor = UIColor.red
    private var requiredText:String = "*"
    private var requiredFont:UIFont = UIFont.systemFont(ofSize: 16.0, weight: UIFont.Weight.bold)

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.setupValidationViews()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.setupValidationViews()
    }

    private func setupValidationViews() {
        self.requiredLbl = UILabel(frame: CGRect(x: self.frame.width - 30, y: 30, width: 20, height: 20))
        self.styleRequiredLabel()
        self.addSubview(self.requiredLbl!)
    }

    func styleRequiredLabel(color:UIColor?, text:String?, font:UIFont?) {
        self.requiredColor = color ?? self.requiredColor
        self.requiredText = text ?? self.requiredText
        self.requiredFont = font ?? self.requiredFont
        self.styleRequiredLabel()
    }

    private func styleRequiredLabel() {
        self.requiredLbl?.textColor = self.requiredColor
        self.requiredLbl?.text = self.requiredText
        self.requiredLbl?.font = self.requiredFont
    }

}

自定义 UITextField

import Foundation
import UIKit

@IBDesignable open class CustomTextField: UITextField {

    @IBInspectable public var borderWidth: CGFloat = 2.0 {
        didSet {
            layer.borderWidth = borderWidth
        }
    }

    @IBInspectable public var borderColor: UIColor = UIColor.lightGray {
        didSet {
            layer.borderColor = borderColor.cgColor
        }
    }

    @IBInspectable public var cornerRadius: CGFloat = 4.0 {
        didSet {
            layer.cornerRadius = cornerRadius
            layer.masksToBounds = true
        }
    }

}

我希望自定义 UITextField 也成为 ValidationView。我知道我可以做一个协议和扩展,然后让我的 CustomTextField 实现该协议,但这不允许初始化覆盖。我宁愿不必更改实现 ValidationView 的视图的 inits。

这样的事情可以使用@arturdev 回答来完成。我最终得到了这个:

import UIKit

class ValidatableProperties {
    var required:Bool
    var validRegex:String?
    var requiredColor:UIColor
    var requiredText:String
    var requiredFont:UIFont

    init(required:Bool, validRegex:String?, requiredColor:UIColor, requiredText:String, requiredFont:UIFont) {
        self.required = required
        self.validRegex = validRegex
        self.requiredText = requiredText
        self.requiredColor = requiredColor
        self.requiredFont = requiredFont
    }
}

protocol Validatable : UIView {

    var validatableProperties:ValidatableProperties! { get set }
    var requiredLbl:UILabel! { get set }

    func setupValidationDefaults()
    func setupValidationViews(frame:CGRect)
    func styleRequiredLabel(color:UIColor?, text:String?, font:UIFont?)

}

extension Validatable {

    func setupValidationDefaults() {
        let props = ValidatableProperties(required: false, validRegex: nil, requiredColor: UIColor.red, requiredText: "*", requiredFont: UIFont.systemFont(ofSize: 16.0, weight: .bold))
        self.validatableProperties = props
    }

    func setupValidationViews(frame:CGRect) {
        self.requiredLbl = UILabel(frame: CGRect(x: frame.width, y: 0, width: 20, height: 20))
        self.styleRequiredLabel()
        self.addSubview(self.requiredLbl)
    }

    func styleRequiredLabel(color:UIColor?, text:String?, font:UIFont?) {
        self.validatableProperties.requiredColor = color ?? self.validatableProperties.requiredColor
        self.validatableProperties.requiredText = text ?? self.validatableProperties.requiredText
        self.validatableProperties.requiredFont = font ?? self.validatableProperties.requiredFont
        self.styleRequiredLabel()
    }

    private func styleRequiredLabel() {
        self.requiredLbl.textColor = self.validatableProperties.requiredColor
        self.requiredLbl.text = self.validatableProperties.requiredText
        self.requiredLbl.font = self.validatableProperties.requiredFont
    }

}

open class ValidationTextField:UITextField, Validatable {
    var requiredLbl: UILabel!
    var validatableProperties: ValidatableProperties!


    override public init(frame: CGRect) {
        super.init(frame: frame)
        self.setupValidationDefaults()
        self.setupValidationViews(frame: frame)
    }

    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.setupValidationDefaults()
        self.setupValidationViews(frame: self.frame)
    }

}

但这需要将您希望验证的所有类扩展为它们自己的自定义类,每次都需要覆盖 inits 并调用方法。它可以工作,但并不理想,虽然不完全是反模式继承,但肯定有一些代码味道。

【问题讨论】:

    标签: swift inheritance uiview


    【解决方案1】:

    您应该将ValidationView 设为协议而不是类,并使您的自定义类符合该协议。

    ValidatableView.swift

    import UIKit
    
    fileprivate var requiredColor = UIColor.red
    fileprivate var requiredText  = "*"
    fileprivate var requiredFont  = UIFont.systemFont(ofSize: 16.0, weight: UIFont.Weight.bold)
    
    fileprivate struct AssociatedKeys {
        static var lblKey = "_lblKey_"
    }
    
    protocol ValidatableView: class {
        var required: Bool {get}
        var validRegex: String? {get}
        var requiredLbl: UILabel? {get}
    }
    
    extension ValidatableView where Self: UIView {
        var required: Bool {
            return false
        }
    
        var validRegex: String? {
            return nil
        }
    
        var requiredLbl: UILabel? {
            get {
                return objc_getAssociatedObject(self, &AssociatedKeys.lblKey) as? UILabel
            }
            set {
                objc_setAssociatedObject(self, &AssociatedKeys.lblKey, newValue, .OBJC_ASSOCIATION_RETAIN)
            }
        }
    
        func setupValidation() {
            self.requiredLbl = UILabel(frame: CGRect(x: self.frame.width - 30, y: 30, width: 20, height: 20))
            self.requiredLbl?.autoresizingMask = .flexibleWidth
            self.styleRequiredLabel()
            self.addSubview(self.requiredLbl!)
        }
    
        func styleRequiredLabel(color:UIColor? = requiredColor, text:String? = requiredText, font:UIFont? = requiredFont) {
            self.requiredLbl?.textColor = requiredColor
            self.requiredLbl?.text = requiredText
            self.requiredLbl?.font = requiredFont
        }
    }
    

    CustomTextField.swift

    @IBDesignable open class CustomTextField: UITextField {
    
        @IBInspectable public var borderWidth: CGFloat = 2.0 {
            didSet {
                layer.borderWidth = borderWidth
            }
        }
    
        @IBInspectable public var borderColor: UIColor = UIColor.lightGray {
            didSet {
                layer.borderColor = borderColor.cgColor
            }
        }
    
        @IBInspectable public var cornerRadius: CGFloat = 4.0 {
            didSet {
                layer.cornerRadius = cornerRadius
                layer.masksToBounds = true
            }
        }
    
        public override init(frame: CGRect) {
            super.init(frame: frame)
            setupValidation()
        }
    
        public required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setupValidation()
        }
    }
    
    extension CustomTextField: ValidatableView { //<- Magic line :)
    
    }
    

    【讨论】:

    • 我现在有类似的东西,但我特别好奇这一行:extension ValidatableView where Self: UIView 可以有多个 where 子句吗?像,如果 where 子句不匹配,我是否可以为 3 或 4 个 where 子句设置此填充默认功能,然后使用不同的扩展名?
    • 为了更好地澄清这里protocol Validatable : UIView { //protocol stuff }extension Validatable where Self: UITextField || Self: UITextView || Self: UISwitch { //validation functionality }extension Validatable { //fall through for non-included views }extension UIView : Validatable { //override inits and other methods to make validatable }
    • 不是隐含的,而是明确的。但是你不需要那个,因为所有提到的类都已经继承自 UIView,并且这个扩展只会应用于那些符合该协议的类
    • 我已经调整了我的 OP 以更好地解释我的捕获。我有一个与您所说的类似的设置,但这仍然意味着我需要几行代码和两个覆盖的初始化,用于任何应该可验证的视图类型。我更喜欢的是拥有一个更通用的基于 UIView 的东西,这样我就不必每次都覆盖这些 inits。不过,这确实是一个可行的解决方案,只是在我的干净规模上不是 100%。不过可能有 90%。
    • 好吧,如果您不想在每个自定义类中编写样板代码,您可以在扩展中调配“didMoveToSuperView”方法,并在那里调用 setup 方法。
    【解决方案2】:

    您可以在实例化 CustomTextField 时创建 ValidationView 的实例。

    类似这样的:

    @IBDesignable open class CustomTextField: UITextField {
    
        var validationView: ValidationView
    
        override init(frame: CGRect) {
            super.init(frame: .zero)
            self.validationView = ValidationView()
        }
    
    }
    

    【讨论】:

    • 这不会使 CustomTextField 成为 ValidationView。这意味着我需要将 ValidationView 实例添加为子视图,这实际上不是继承。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-15
    • 2016-10-26
    相关资源
    最近更新 更多