神刀安全网

Updated: Protocol-Oriented MVVM in Swift 2.0

I’ve fallen in love with Protocol-Oriented Programming (POP), but of course, I’m new to it and still learning. One of my favorite use-cases for POP is with MVVM .

I wrote about it back in August – read it if you’d like to understand the problem more. Since then, I have of course found an even better way of applying POP to MVVM! I gave a few talks on this over the past few months, so I’m excited to finally write it down (I’ll also post the video versions when they come out).

The key was the amazing Mixins and Traits in Swift 2.0 article by @mhollemans . So here is the updated version:

I’m going to use a very simple example. A Settings screen that currently only has one settings – put your app in Minion Mode!, but you can of course extrapolate to multiple settings:

Updated: Protocol-Oriented MVVM in Swift 2.0

The Cell Components

You can split this cell into two components – a label and a switch. Those same components can be used in any other cell or view in your application. And they require the same exact configuration. So why not break out the required configuration into protocols?

Swift

// your label protocol protocol TextPresentable {     var text: String { get }     var textColor: UIColor { get }     var font: UIFont { get } }  // your switch protocol protocol SwitchPresentable {     var switchOn: Bool { get }     var switchColor: UIColor { get }     func onSwitchTogleOn(on: Bool) }
// your label protocol protocol TextPresentable {     var text:String { get }     var textColor:UIColor { get }     var font:UIFont { get } }   // your switch protocol protocol SwitchPresentable {     var switchOn:Bool { get }     var switchColor:UIColor { get }     func onSwitchTogleOn(on: Bool) } 

Let’s say that most of the switches in your app have the same switchColor – you can use protocol extensions to easily set that up:

Swift

extension SwitchPresentable {     var switchColor: UIColor { return .yellowColor() } }
extension SwitchPresentable {     var switchColor:UIColor { return .yellowColor() } } 

However, I recommend being careful with the protocol extensions. Make sure that the same configuration (e.g. color) is used in at least two places before making it the default implementation.

You can also extend the protocol extension configuration to be more specific. For example, let’s say you have a header label that is configured one way (big and bold font and color) vs a subhead label (configured with a smaller font and lighter color) across the app. You can create protocols with default implementation for these!

And of course, this extends to images, text fields, and whatever other components you’re using in your app:

Swift

protocol ImagePresentable {     var imageName: String { get } }  protocol TextFieldPresentable {     var placeholder: String { get }     var text: String { get }          func onTextFieldDidEndEditing(textField: UITextField) }
protocol ImagePresentable {     var imageName:String { get } }   protocol TextFieldPresentable {     var placeholder:String { get }     var text:String { get }          func onTextFieldDidEndEditing(textField: UITextField) } 

The Cell

So now, you can useprotocol composition to require an object that conforms to the new composed protocol to configure the cell:

Swift

//  SwitchWithTextTableViewCell.swift  // protocol composition  // based on the UI components in the cell typealias SwitchWithTextViewPresentable = protocol<TextPresentable, SwitchPresentable>  class SwitchWithTextTableViewCell: UITableViewCell {          @IBOutlet private weak var label: UILabel!     @IBOutlet private weak var switchToggle: UISwitch!          private var delegate: SwitchWithTextViewPresentable?          // configure with something that conforms to the composed protocol     func configure(withPresenter presenter: SwitchWithTextViewPresentable) {         delegate = presenter          // configure the UI components         label.text = presenter.text                  switchToggle.on = presenter.switchOn         switchToggle.onTintColor = presenter.switchColor             }          @IBAction func onSwitchToggle(sender: UISwitch) {        delegate?.onSwitchTogleOn(sender.on)     } }
//  SwitchWithTextTableViewCell.swift   // protocol composition // based on the UI components in the cell typealias SwitchWithTextViewPresentable = protocol<TextPresentable, SwitchPresentable>   class SwitchWithTextTableViewCell:UITableViewCell {          @IBOutletprivateweak var label: UILabel!     @IBOutletprivateweak var switchToggle: UISwitch!          privatevar delegate: SwitchWithTextViewPresentable?          // configure with something that conforms to the composed protocol     func configure(withPresenterpresenter: SwitchWithTextViewPresentable) {         delegate = presenter           // configure the UI components         label.text = presenter.text                  switchToggle.on = presenter.switchOn         switchToggle.onTintColor = presenter.switchColor             }          @IBActionfunc onSwitchToggle(sender: UISwitch) {       delegate?.onSwitchTogleOn(sender.on)     } } 

The View Model

The View Model is now going to be that object that takes in the Model data, and processes it to conform to the SwitchWithTextViewPresentable protocol for presentation to the user.

Swift

//  MyViewModel.swift  struct MinionModeViewModel: SwitchWithTextViewPresentable { //    This would usually be instantiated with the model //    to be used to derive the information below //    but in this case, my app is pretty static }  // MARK: TextPresentable Conformance extension MinionModeViewModel {     var text: String { return "Minion Mode" }     var textColor: UIColor { return .blackColor() }     var font: UIFont { return .systemFontOfSize(17.0) } }  // MARK: SwitchPresentable Conformance extension MinionModeViewModel {     var switchOn: Bool { return false }     var switchColor: UIColor { return .yellowColor() }          func onSwitchTogleOn(on: Bool) {         if on {             print("The Minions are here to stay!")         } else {             print("The Minions went out to play!")         }     } }
//  MyViewModel.swift   struct MinionModeViewModel:SwitchWithTextViewPresentable { //    This would usually be instantiated with the model //    to be used to derive the information below //    but in this case, my app is pretty static }   // MARK: TextPresentable Conformance extension MinionModeViewModel {     var text:String { return "Minion Mode" }     var textColor:UIColor { return .blackColor() }     var font:UIFont { return .systemFontOfSize(17.0) } }   // MARK: SwitchPresentable Conformance extension MinionModeViewModel {     var switchOn:Bool { return false }     var switchColor:UIColor { return .yellowColor() }          func onSwitchTogleOn(on: Bool) {         if on {             print("The Minions are here to stay!")         } else {             print("The Minions went out to play!")         }     } } 

The View Controller

So now, configuring the Table View Cell is super easy:

Swift

// your label protocol protocol TextPresentable {     var text:String { get }     var textColor:UIColor { get }     var font:UIFont { get } }   // your switch protocol protocol SwitchPresentable {     var switchOn:Bool { get }     var switchColor:UIColor { get }     func onSwitchTogleOn(on: Bool) } 

0

// your label protocol protocol TextPresentable {     var text:String { get }     var textColor:UIColor { get }     var font:UIFont { get } }   // your switch protocol protocol SwitchPresentable {     var switchOn:Bool { get }     var switchColor:UIColor { get }     func onSwitchTogleOn(on: Bool) } 

1

Say goodbye to the Massive View Controller!

Conclusion

For me, the thing that makes something a good pattern is how easy it is to change. After all, “Change is the only Constant” as the famous quote goes. That is especially true in Software Development.

So how does this pattern measure up? Let’s say a product manager wants to add an image to the cell. After adding the UIImageView in my Storyboard and adding it as an @IBOutlet to my cell, the next step would be to simply add the ImagePresentable Protocol to my protocol composition typealias:

Swift

// your label protocol protocol TextPresentable {     var text:String { get }     var textColor:UIColor { get }     var font:UIFont { get } }   // your switch protocol protocol SwitchPresentable {     var switchOn:Bool { get }     var switchColor:UIColor { get }     func onSwitchTogleOn(on: Bool) } 

2

// your label protocol protocol TextPresentable {     var text:String { get }     var textColor:UIColor { get }     var font:UIFont { get } }   // your switch protocol protocol SwitchPresentable {     var switchOn:Bool { get }     var switchColor:UIColor { get }     func onSwitchTogleOn(on: Bool) } 

3

The second step is to just add the additional protocol conformance to the View Model:

Swift

// your label protocol protocol TextPresentable {     var text:String { get }     var textColor:UIColor { get }     var font:UIFont { get } }   // your switch protocol protocol SwitchPresentable {     var switchOn:Bool { get }     var switchColor:UIColor { get }     func onSwitchTogleOn(on: Bool) } 

4

// your label protocol protocol TextPresentable {     var text:String { get }     var textColor:UIColor { get }     var font:UIFont { get } }   // your switch protocol protocol SwitchPresentable {     var switchOn:Bool { get }     var switchColor:UIColor { get }     func onSwitchTogleOn(on: Bool) } 

5

That’s it! Consider how much you’d have to change if you had to add an image to a cell in your app. Protocols make this easy – just add a protocol conformance requirement and have an object conform to it!

For more view protocol examples and implementation, check out the Standard Template Protocols library .

原文  http://natashatherobot.com/updated-protocol-oriented-mvvm-in-swift-2-0/

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Updated: Protocol-Oriented MVVM in Swift 2.0

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮