2017-09-05 100 views
2

我试图做...在基类中使用泛型函数

在我的应用我有很多看起来很像带着一帮自定义功能(在突出改变颜色的表单字段ECT )。

我想创建一个包装类,它抽象所有这些代码,然后从中继承来实现我不同的输入类型,如日期输入和文本输入。

继承的类只需要为它的类型设置正确的输入控件。

我已经试过

这更像是伪代码。我一直在尝试几个小时,但我只是不明白如何实现我所需要的

我想我从一个基类开始,这需要定义一个对输入控件的引用,以及每个方法的一些方法将覆盖,如能够设置或获得当前值

class BaseInput<T>: UIView { 
    let label = UILabel() 
    let control: T 

    ... A bunch of methods for layout and stuff ... 

    func setControlValue(_ value: U) { 
     print("I'm not a real input yet, so i can't do that") 
    } 
} 

然后,我创建一个日期输入的继承类。它使用一个基本的标签控制,并在内部将使用的UIDatePicker设定值

class DateInput: BaseInput<UILabel> { 

    override init() { 
     self.control = UILabel() 
    }  

    override func setControlValue(_ value: Date) { 
     MyGlobalDateFormatter.string(format:value) 
    } 

} 

,另一个用于文本输入字段

class TextInput: BaseInput<UITextField> { 

    override init() { 
     self.control = UITextField() 
    }  

    override func setControlValue(_ value: String) { 
     control.textLabel!.text = value 
    } 

} 

什么,我最终要的是能力初始化一个输入组件,并且MyInput.control属性为该特定输入的正确类,并且setControlValue方法接受正确类型的数据(即字符串,整数或日期,具体取决于控件的类型)

我相信这可以使用泛型的解决,但我真的很难理解如何。如果任何人都能指出我会朝着正确的方向发展,那将是很棒的。

注意:我不期望或不希望任何人为我编写所有代码。伪代码足以让我全力以赴。

尝试1:

protocol CustomControl { 
    associatedtype Control 
    associatedtype Value 

    var control : Control { get } 
    var label : UILabel { get } 

    func setControlValue(_ value: Value) 
} 

class AbstractInputField: UIView { 
    // This class contains all the setup 
    // for the label and wrapping UI View 
} 

class TextInputField: AbstractInputField, CustomControl { 
    typealias Control = UITextField 
    typealias Value = String 

    let control = UITextField() 

    func setControlValue(_ value: String) { 
     control.text = value 
    } 
} 

class DateInputField: AbstractInputField, CustomControl { 
    typealias Control = UILabel 
    typealias Value = String 

    let control = UILabel() 

    private let picker = UIDatePicker() 

    func setControlValue(_ value: Date) { 
     control.text = GlobalDateFormatter.string(from: value) 
    } 

    .. Also in this class it's a bunch of date picker methods .. 
} 

在其他地方,如果我做的:

override func viewDidLoad() { 

    let firstInput = makeControl("text") 
    firstInput.label.text = "First name" 
    firstInput.setControlValue(myUser.first_name) 

    let dobInput = makeControl("date") 
    dobInput.label.text = "Date of birth" 
    dobInput.setControlValue(myUser.dob) 

} 

func makeControl(controlType: String) -> CustomControl { 
    // Im using strings just for testing, i'd probably make this an enum or something 
    if controlType == "text" { 
     return TextInputField() 
    } else { 
     return DateInputField() 
    } 
} 

我得到的错误:'协议 'CustomControl' 只能作为一种通用的约束,因为它有自我或相关的类型要求

我最终试图实现的是一个非常简单的API来输入我可以设置标签文本,并设置输入值。我的应用程序的其余部分不关心它是文本字段,textview还是完整的自定义输入类型。我的应用程序想要使用协议(或某种基类)来说明它具有这些方法的属性。

也许我很愚蠢。

回答

2

我会建议使用协议。

protocol CustomControl { 
    associatedtype Control 
    associatedtype Value 

    var control: Control { get } 
    func setControlValue(_ value: Value) 
} 

,然后创建自定义类符合这个协议型

class CustomTextField: CustomControl { 
    typealias Control = UITextField 
    typealias Value = String 

    let control: UITextField 

    init() { 
     self.control = UITextField() 
    } 

    func setControlValue(_ value: String) { 
     control.text = value 
    } 
} 

编辑:

class CustomLabel: CustomControl { 
    typealias Control = UILabel 
    typealias Value = String 

    let control: UILabel 

    init() { 
     self.control = UILabel() 
    } 

    func setControlValue(_ value: String) { 
     control.text = value 
    } 
} 

编辑2:替代做法

protocol HasSettableValue: class { 
    associatedtype Value 
    var customValue: Value { get set } 
} 

protocol IsInitializable { 
    init() 
} 

extension UITextField: IsInitializable {} 
extension UITextField: HasSettableValue { 
    typealias Value = String? 
    var customValue: String? { 
     get { 
      return text 
     } 
     set { 
      text = newValue 
     } 
    } 
} 

class BaseClass<T> where T: HasSettableValue, T: IsInitializable { 
    let control: T 

    init() { 
     self.control = T() 
    } 

    func setControlValue(_ value: T.Value) { 
     control.customValue = value 
    } 
} 

class CustomTextField: BaseClass<UITextField> { 

} 

let customTextField = CustomTextField() 
customTextField.setControlValue("foo") 
print(customTextField.control) // prints <UITextField: 0x...; frame = (0 0; 0 0); text = 'foo'; opaque = NO; layer = <CALayer: 0x...>> 

最终更新:

协议具有关联类型的问题是你不能用它们来声明变量。你总是需要指定具体的实现。

我想我找到了一个解决方案适合您的需要:

enum ControlType { 
    case textField, datePicker 
} 

enum ControlValueType { 
    case text(text: String) 
    case date(date: Date) 

    var string: String { 
     switch self { 
     case .text(text: let text): 
      return text 
     case .date(date: let date): 
      // apply custom format 
      return "\(date)" 
     } 
    } 

    var date: Date { 
     switch self { 
     case .date(date: let date): 
      return date 
     default: 
      preconditionFailure("`date` can be only used with `.date` value type") 
     } 
    } 
} 

protocol Control: class { 
    var controlValue: ControlValueType { get set } 
} 

class TextInputField: Control { 
    private let textField = UITextField() 

    var controlValue: ControlValueType { 
     get { 
      return .text(text: textField.text ?? "") 
     } 
     set { 
      textField.text = newValue.string 
     } 
    } 
} 

class DateInputField: Control { 
    private let picker = UIDatePicker() 

    var controlValue: ControlValueType { 
     get { 
      return .date(date: picker.date) 
     } 
     set { 
      picker.date = newValue.date 
     } 
    } 
} 

func createControl(ofType type: ControlType) -> Control { 
    switch type { 
    case .textField: 
     return TextInputField() 
    case .datePicker: 
     return DateInputField() 
    } 
} 

let customDatePicker = createControl(ofType: .datePicker) 
customDatePicker.controlValue = .date(date: Date()) 
print(customDatePicker.controlValue.string) // prints 2017-09-06 10:47:22 +0000 

let customTextFiled = createControl(ofType: .textField) 
customTextFiled.controlValue = .text(text: "Awesome text") 
print(customTextFiled.controlValue.string) // prints Awesome text 

我希望这有助于。 PS:你试图实现的目标不是iOS中很常见的模式,我知道。

+0

有趣。谢谢你,我会快速玩这个:) – TRG

+0

在这种情况下你会如何处理BaseClass?理想情况下,我可以在同一个实例上使用'input.control'和'input.label'来分别获取标签或控件。 我应该在'BaseInputField'上实现'CustomControl',并让控件和值类型像'UILabel'&'String'一样泛型并在其他类中重写? 我想另一种选择是根据我想访问的内容将我的实例投射到'BaseInputField'或'CustomControl',但这看起来不是正确的选项。 – TRG

+0

我真的不明白你想达到什么目的。当日期输入和文本输入字段可以有标签或文本字段时,我会创建两个符合'CustomControl'协议的不同类。 –