2017-07-05 36 views
0

我在Swift中創建動態視圖時遇到問題。但是,這個問題並沒有直接關係到Swift本身,而是一個面向對象的編程問題。如何以良好的OO設計以編程方式創建動態視圖

問題是我需要能夠動態添加其他視圖元素到視圖。而且我不確定我是否正確地做了。我的解決方案對我來說似乎過分。

爲了解決這個問題,我認爲裝飾模式是一個很好的選擇。除了更好地控制流程外,我還介紹了模板方法模式。

我有許多類定義默認的外觀和感覺在某些視圖控件,如標籤,TextFields和按鈕。下面你可以看到它是如何的一個大概的想法。

這裏是我的代碼:

class ViewElement{ 
// this class inherits from default UIKit elemnts and provides default UI view 
} 
// default cell is the cell that implements default elements layout and margings, etc 
class DefaultCell: UITableViewCell { 
    let mainStack: UIViewStack 
    func addElement(ViewElement) 
} 

class BlueCell: DefaultCell { 

    let textField1: TextField 
    let label : Label 
    let button: Button 

    init(){ 
     textField = TextField() 
     label = Label() 
     button = Button() 

     addElement(textField) 
     addElement(label) 
     addElement(button) 
    } 
} 

這裏是tableViewDataSource實施

class BlueTable: UITableViewDataSource { 
... 
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 

    var cell: = dequeue the cell 

    if cell == nil { 
     cell = BlueCell(with everything I want to pass to the constructor) 
    } 

    // then I check for the condition 

    switch weather { 
     case good: 

      labelOne 
      labelTwo 
      buttonOne 

      cell.addElement(labelOne) 
      cell.addElement(labelTwo) 
      cell.addElement(buttonOne) 

     case bad: 
      // idem  
      cell.addView(badWeatherView) 
    } 
    return cell 
    } 

} 

正如你所看到的,條件的數量越多,越大我的switch語句。

由於我需要訪問我在條件中分配的其他元素,例如回調,點擊事件等,還有一個事實,即通過addElement方法添加了條件中的那些元素,這意味着那些元素將被添加到mainStack的底部。

爲了有超過元素添加到堆棧的方式,我決定去與下面的解決方案控制:模板方法模式

protocol OrderableElements { 
    func top() 
    func middle() 
    func bottom() 
} 

extension OrderableElements { 
    func render() { 
     top() 
     middle() 
     bottom() 
    } 
} 

現在BLUECELL實現協議,看起來像這樣

class BlueCell: DefaultCell, OrderableElements { 

    init(){ 
     textField = TextField() 
     label = Label() 
     button = Button() 
    } 

    func top() { 
     addElement(textField) 
    } 

    func middle() { 
     addElement(label) 
    } 

    func bottom(){ 
     addElement(button) 
    } 
} 

的tabledatasource類,然後將如下所示:

class BlueTable: UITableViewDataSource { 
... 
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 

    var cell: = dequeue the cell 

    if cell == nil { 
     cell = BlueCell(with everything I want to pass to the constructor) 
    } 

    // then I check for the condition 

    switch weather { 
     case good: 

      labelOne 
      labelTwo 
      buttonOne 

      cell.addElement(labelOne) 
      cell.addElement(labelTwo) 
      cell.addElement(buttonOne) 

     case bad: 
      // idem 
      cell.addView(badWeatherView) 
    } 

    ... 
    **cell.render()** 
    return cell 

    } 
} 

現在,因爲我需要BLUECELL的範圍過程中添加新的視圖元素在特定位置或更好說,在某些時刻,我介紹了裝飾的細胞,就像這樣:

class CellDecorator: OrderableElements { 
    var cell: BlueCell 
    init(cell: BlueCell){ 
     self.cell = cell 
    } 

    func top() { 
     self.cell.top() 
    } 

    func middle(){ 
     self.cell.middle() 
    } 

    func bottom(){ 
     self.cell.bottom() 
    } 

    func getCell() { 
     return self.cell 
    } 

} 

下面是具體的實現

class GoodWeatherDecorator: CellDecorator { 

    let goodLabel 
    let goodTextField 
    let goodButton 

    override top() { 
     super.top() 
     addElement(goodLabel) 
    } 

    override middle(){ 
     super.middle() 
     addElement(goodTextfield) 
    } 

    override bottom(){ 
     super.bottom() 
     addElement(goodButton) 
    } 

} 

最終實現cellForRowAt方法看起來象下面這樣:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 

    var cell: = dequeue the cell 

    if cell == nil { 
     cell = BlueCell(with everything I want to pass to the constructor) 
    } 

    // then I check for the condition 

    var decoratedCell = CellDecorator(cell: cell) 

    switch weather { 

     case good: 
      decoratedCell = GoodWeatherDecorator(cell: cell) 
     case bad: 
      decoratedCell = BadWeatherDecorator(cell: cell) 

    } 

    decoratedCell.configure() // <------------ here is the configure call 
    cell = decoratedCell.getCell() // <------- here I get cell from the decorator 

    return cell 

    } 

} 

現在我明白,我的第執行的e裝飾器模式不是100%有效的,因爲我沒有從BlueCell類繼承,例如。但那並不會讓我感到困擾。困擾我的事情是,我認爲解決這個問題的方法有點矯枉過正。

所有的作品都是正確的,但我可以幫助解決這個微不足道的問題。

您認爲如何?你將如何解決這類問題?在advace

感謝

回答

1

既然你只顯示兩種類型的細胞和您的解決方案實際上沒有擺脫switch語句中,我會說,你的解決方案算作「矯枉過正」。

你沒有顯示它,但似乎你有一個Weather枚舉。我假設......

enum Weather: String { 
    case good 
    case bad 
} 

在表視圖的數據源,我的目標將是有這樣的事情:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
    let weather = weathers[indexPath.row] 
    let cell = tableView.dequeueReusableCell(withIdentifier: weather.rawValue, for: indexPath) as! ConfigurableCell 
    cell.configure(with: weather) 
    return cell as! UITableViewCell 
} 

爲了實現上述目標,我想有幾個細胞在故事板文件中使用不同的標識符進行佈局。我會在我的代碼中爲每種類型的單元格設置一個子類,它們全都符合ConfigurableCell協議。

protocol ConfigurableCell { 
    func configure(with weather: Weather) 
} 

如果不能符合你的Weather枚舉String類型,您將需要一個switch語句天氣對象轉換爲字符串標識,但除此之外,沒有switch語句必要的。

+0

嗨,謝謝你的回答。我應該提到,我將有15個額外的視圖組件(元素)應該添加到此容器中。 我同意你的建議,移動容器視圖內的條件,並決定顯示哪些附加元素。但再一次,因爲即使隱藏在tableViewDataSource中,我的條件仍然會有很大的差異,幾乎無法管理。 – DS888

+0

所有更多的理由這樣做,因爲我在這個答案建議。 –

0

您應該關注Daniel T.answer

但是,這是我在自己的項目中使用的建議升級。

而不是僅僅使用

protocol ConfigurableCell { 
    func configure(with weather: Weather) 
} 

我用這個在許多不同的場景中重用的目的。

protocol Configurable { 
    associatedtype Initializables 

    func configure(_ model: Initializables) -> Self 
} 

實施例用例

的UIViewController

class SomeViewController: UIViewController { 
    var someIntProperty: Int? 
    ... 
} 

extension SomeViewController: Configurable { 
    struct Initializables { 
     let someIntProperty: Int? 
    } 

    func configure(_ model: SomeViewController.Initializables) -> Self { 
     self.someIntProperty = model.someIntProperty 
     return self 
    } 
} 

// on some other part of the code. 
let someViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() as! SomeViewController 
_ = someViewController.configure(SomeViewController.Initializables(someIntProperty: 100)) 

的UITableViewCell

class SomeTableViewCell: UITableViewCell { 
    var someIntProperty: Int? 
    var someStringProperty: Int? 
    ... 
} 

extension SomeTableViewCell: Configurable { 
    struct Initializables { 
     let someIntProperty: Int? 
     let someStringProperty: Int? 
    } 

    func configure(_ model: SomeTableViewCell.Initializables) -> Self { 
     self.someIntProperty = model.someIntProperty 
     self.someStringProperty = model.someStringProperty 
     return self 
    } 
} 

// on cellForRow 
let cell = tableView.dequeueReusableCell(withIdentifier: "SomeTableViewCell", for: indexPath) as! SomeTableViewCell 
return cell.configure(SomeTableViewCell.Initializables(someIntProperty: 100, someStringProperty: "SomeString")) 

注:

正如你可以看到它非常可重用,易於使用和實現。下面是使用時產生的代碼可能很長的代碼configure