Road to MVC: the case of Settings Table View Controller
$begingroup$
Now that I know that MVC can help do better code, I want to make my SettingsTableViewController
class conform to it.
SettingsTableViewController
is a subclass of UITableViewController
. It is linked to a storyboard scene that contains a UITableView
with two cells (grouped tableView style, prototype cells). The first cell has a right detail style (textLabel and detailTextLabel), the second cell has a custom style and displays a UIPickerView
inside of its contentView.
When it appears on screen, SettingsTableViewController
displays only the first cell. When I click on this cell, the second cell containing a UIPickerView
appears. If I click again on the first cell, the second cell disappears.
The detailtextLabel of the first row displays an integer saved in NSUserDefaults
. The pickerView of the second cell is linked to an array of integers. When I select a row in the pickerView, it saves the row's related integer in NSUserDefaults
and updates the detailtextLabel of the first cell.
The image below may help understand the way it works:
Before knowing the concept of MVC, I was able to write the following code to make the previous explanation work:
class SettingsTableViewController: UITableViewController, UIPickerViewDelegate, UIPickerViewDataSource {
let itemsArray = [5, 10, 15]
var pickerIndexPath: NSIndexPath? //acts like a Bool and allows to hide or show cell with identifier "pickerCell"
var numberOfItems: Int {
get {
return NSUserDefaults.standardUserDefaults().integerForKey("NumberOfItems")
}
set {
NSUserDefaults.standardUserDefaults().setInteger(newValue, forKey: "NumberOfItems")
NSUserDefaults.standardUserDefaults().synchronize()
}
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Settings"
//Init numberOfItems
if numberOfItems == 0 {
numberOfItems = 5
}
//Autoset cells height (iOS8)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//If pickerIndexPath is nil, show only one cell
return pickerIndexPath == nil ? 1 : 2
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell!
if indexPath.row == 0 {
cell = tableView.dequeueReusableCellWithIdentifier("RightDetailCell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = "Items"
cell.detailTextLabel?.text = "(numberOfItems)"
} else {
cell = tableView.dequeueReusableCellWithIdentifier("PickerCell", forIndexPath: indexPath) as UITableViewCell
cell.selectionStyle = .None
//Set pickerView inside cell
let picker = cell.viewWithTag(1) as UIPickerView
picker.delegate = self
picker.dataSource = self
//Set the middle row in picker according to numberOfItems
if let index = find(itemsArray, numberOfItems) {
picker.selectRow(index, inComponent: 0, animated: false)
}
}
return cell
}
override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
switch indexPath {
case NSIndexPath(forRow: 1, inSection: 0):
return nil
default:
return indexPath
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if indexPath == NSIndexPath(forRow: 0, inSection: 0) {
//Show or hide pickerCell
if pickerIndexPath == nil {
tableView.beginUpdates()
pickerIndexPath = NSIndexPath(forRow: 1, inSection: 0)
tableView.insertRowsAtIndexPaths([pickerIndexPath!], withRowAnimation: .Fade)
tableView.endUpdates()
} else {
tableView.beginUpdates()
tableView.deleteRowsAtIndexPaths([pickerIndexPath!], withRowAnimation: .Fade)
pickerIndexPath = nil
tableView.endUpdates()
}
}
}
//MARK: UIPickerViewDataSource
func numberOfComponentsInPickerView(_: UIPickerView) -> Int {
return 1
}
func pickerView(_: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return itemsArray.count
}
//MARK: UIPickerViewDelegate
func pickerView(_: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return "(itemsArray[row]) items"
}
func pickerView(_: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if pickerIndexPath != nil {
//save new value in NSUserDefaults
numberOfItems = itemsArray[row]
//update first cell
let index = NSIndexPath(forRow: pickerIndexPath!.row - 1, inSection: 0)
tableView.reloadRowsAtIndexPaths([index], withRowAnimation: .Fade)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Then, in order to conform to MVC design pattern, I replaced the previous code with the following:
SettingsTableViewController:
class SettingsTableViewController: UITableViewController {
let dataSource = DataSource()
override func viewDidLoad() {
super.viewDidLoad()
title = "Settings"
//Autoset cells height (iOS8)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//If pickerIndexPath is nil, show only one cell
return dataSource.pickerIndexPath == nil ? 1 : 2
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("LabelCell", forIndexPath: indexPath) as LabelCell
cell.dataSource = dataSource
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("PickerCell", forIndexPath: indexPath) as PickerCell
cell.dataSource = dataSource
return cell
}
}
override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
switch indexPath {
case NSIndexPath(forRow: 1, inSection: 0):
return nil
default:
return indexPath
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if indexPath == NSIndexPath(forRow: 0, inSection: 0) {
//Show or hide pickerCell
tableView.beginUpdates()
if dataSource.pickerIndexPath == nil {
dataSource.pickerIndexPath = NSIndexPath(forRow: indexPath.row + 1, inSection: 0)
tableView.insertRowsAtIndexPaths([dataSource.pickerIndexPath!], withRowAnimation: .Fade)
} else {
tableView.deleteRowsAtIndexPaths([dataSource.pickerIndexPath!], withRowAnimation: .Fade)
dataSource.pickerIndexPath = nil
}
tableView.endUpdates()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
LabelCell:
//Global context variable
private var observerContext = 0
class LabelCell: UITableViewCell {
var dataSource: DataSource? {
willSet {
disconnectFromModel()
}
didSet {
connectToModel()
update()
}
}
private func disconnectFromModel() {
dataSource?.removeObserver(self, forKeyPath: "numberOfItems", context: &observerContext)
}
private func connectToModel() {
dataSource?.addObserver(self, forKeyPath: "numberOfItems", options: NSKeyValueObservingOptions(), context: &observerContext)
}
override func observeValueForKeyPath(keyPath: String!, ofObject object: AnyObject!, change: [NSObject : AnyObject]!, context: UnsafeMutablePointer<Void>) {
if context == &observerContext {
update()
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
private func update() {
if let model = dataSource {
textLabel?.text = "Items"
detailTextLabel?.text = "(model.numberOfItems)"
}
}
deinit {
disconnectFromModel()
}
}
PickerCell:
class PickerCell: UITableViewCell, UIPickerViewDelegate, UIPickerViewDataSource {
@IBOutlet weak var picker: UIPickerView!
var dataSource: DataSource? {
didSet {
update()
}
}
override func awakeFromNib() {
super.awakeFromNib()
selectionStyle = .None
picker.delegate = self
picker.dataSource = self
}
func update() {
if let dataSource = dataSource {
if let index = find(dataSource.itemsArray, dataSource.inspectionPref) {
picker.selectRow(index, inComponent: 0, animated: false)
}
}
}
//MARK: UIPickerViewDataSource
func numberOfComponentsInPickerView(_: UIPickerView) -> Int {
return 1
}
func pickerView(_: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return dataSource!.itemsArray.count
}
//MARK: UIPickerViewDelegate
func pickerView(_: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return "(dataSource!.itemsArray[row]) items"
}
func pickerView(_: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
dataSource!.numberOfItems = dataSource!.itemsArray[row]
}
}
DataSource:
class DataSource: NSObject {
let itemsArray = [5, 10, 15]
var pickerIndexPath: NSIndexPath? //allows to hide or show cell with identifier "pickerCell"
dynamic var numberOfItems: Int {
get {
return NSUserDefaults.standardUserDefaults().integerForKey("NumberOfItems")
}
set {
NSUserDefaults.standardUserDefaults().setInteger(newValue, forKey: "NumberOfItems")
NSUserDefaults.standardUserDefaults().synchronize()
}
}
override init() {
super.init()
//Init numberOfItems at first launch
if numberOfItems == 0 {
numberOfItems = 5
}
}
}
I have several questions about this new code. As it is my first attempt with MVC, I wonder if it is a real MVC design pattern code. I also ask myself if it is a complete/correct MVC code: is there anything left to do/modify in order to fully conform to MVC? Furthermore, I've read that passing a model to a view (here, a cell) is not recommended. Thus, what would be the way to make a cell interact with the model without passing the model to it?
design-patterns mvc ios swift
$endgroup$
bumped to the homepage by Community♦ 39 mins ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
add a comment |
$begingroup$
Now that I know that MVC can help do better code, I want to make my SettingsTableViewController
class conform to it.
SettingsTableViewController
is a subclass of UITableViewController
. It is linked to a storyboard scene that contains a UITableView
with two cells (grouped tableView style, prototype cells). The first cell has a right detail style (textLabel and detailTextLabel), the second cell has a custom style and displays a UIPickerView
inside of its contentView.
When it appears on screen, SettingsTableViewController
displays only the first cell. When I click on this cell, the second cell containing a UIPickerView
appears. If I click again on the first cell, the second cell disappears.
The detailtextLabel of the first row displays an integer saved in NSUserDefaults
. The pickerView of the second cell is linked to an array of integers. When I select a row in the pickerView, it saves the row's related integer in NSUserDefaults
and updates the detailtextLabel of the first cell.
The image below may help understand the way it works:
Before knowing the concept of MVC, I was able to write the following code to make the previous explanation work:
class SettingsTableViewController: UITableViewController, UIPickerViewDelegate, UIPickerViewDataSource {
let itemsArray = [5, 10, 15]
var pickerIndexPath: NSIndexPath? //acts like a Bool and allows to hide or show cell with identifier "pickerCell"
var numberOfItems: Int {
get {
return NSUserDefaults.standardUserDefaults().integerForKey("NumberOfItems")
}
set {
NSUserDefaults.standardUserDefaults().setInteger(newValue, forKey: "NumberOfItems")
NSUserDefaults.standardUserDefaults().synchronize()
}
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Settings"
//Init numberOfItems
if numberOfItems == 0 {
numberOfItems = 5
}
//Autoset cells height (iOS8)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//If pickerIndexPath is nil, show only one cell
return pickerIndexPath == nil ? 1 : 2
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell!
if indexPath.row == 0 {
cell = tableView.dequeueReusableCellWithIdentifier("RightDetailCell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = "Items"
cell.detailTextLabel?.text = "(numberOfItems)"
} else {
cell = tableView.dequeueReusableCellWithIdentifier("PickerCell", forIndexPath: indexPath) as UITableViewCell
cell.selectionStyle = .None
//Set pickerView inside cell
let picker = cell.viewWithTag(1) as UIPickerView
picker.delegate = self
picker.dataSource = self
//Set the middle row in picker according to numberOfItems
if let index = find(itemsArray, numberOfItems) {
picker.selectRow(index, inComponent: 0, animated: false)
}
}
return cell
}
override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
switch indexPath {
case NSIndexPath(forRow: 1, inSection: 0):
return nil
default:
return indexPath
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if indexPath == NSIndexPath(forRow: 0, inSection: 0) {
//Show or hide pickerCell
if pickerIndexPath == nil {
tableView.beginUpdates()
pickerIndexPath = NSIndexPath(forRow: 1, inSection: 0)
tableView.insertRowsAtIndexPaths([pickerIndexPath!], withRowAnimation: .Fade)
tableView.endUpdates()
} else {
tableView.beginUpdates()
tableView.deleteRowsAtIndexPaths([pickerIndexPath!], withRowAnimation: .Fade)
pickerIndexPath = nil
tableView.endUpdates()
}
}
}
//MARK: UIPickerViewDataSource
func numberOfComponentsInPickerView(_: UIPickerView) -> Int {
return 1
}
func pickerView(_: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return itemsArray.count
}
//MARK: UIPickerViewDelegate
func pickerView(_: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return "(itemsArray[row]) items"
}
func pickerView(_: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if pickerIndexPath != nil {
//save new value in NSUserDefaults
numberOfItems = itemsArray[row]
//update first cell
let index = NSIndexPath(forRow: pickerIndexPath!.row - 1, inSection: 0)
tableView.reloadRowsAtIndexPaths([index], withRowAnimation: .Fade)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Then, in order to conform to MVC design pattern, I replaced the previous code with the following:
SettingsTableViewController:
class SettingsTableViewController: UITableViewController {
let dataSource = DataSource()
override func viewDidLoad() {
super.viewDidLoad()
title = "Settings"
//Autoset cells height (iOS8)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//If pickerIndexPath is nil, show only one cell
return dataSource.pickerIndexPath == nil ? 1 : 2
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("LabelCell", forIndexPath: indexPath) as LabelCell
cell.dataSource = dataSource
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("PickerCell", forIndexPath: indexPath) as PickerCell
cell.dataSource = dataSource
return cell
}
}
override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
switch indexPath {
case NSIndexPath(forRow: 1, inSection: 0):
return nil
default:
return indexPath
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if indexPath == NSIndexPath(forRow: 0, inSection: 0) {
//Show or hide pickerCell
tableView.beginUpdates()
if dataSource.pickerIndexPath == nil {
dataSource.pickerIndexPath = NSIndexPath(forRow: indexPath.row + 1, inSection: 0)
tableView.insertRowsAtIndexPaths([dataSource.pickerIndexPath!], withRowAnimation: .Fade)
} else {
tableView.deleteRowsAtIndexPaths([dataSource.pickerIndexPath!], withRowAnimation: .Fade)
dataSource.pickerIndexPath = nil
}
tableView.endUpdates()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
LabelCell:
//Global context variable
private var observerContext = 0
class LabelCell: UITableViewCell {
var dataSource: DataSource? {
willSet {
disconnectFromModel()
}
didSet {
connectToModel()
update()
}
}
private func disconnectFromModel() {
dataSource?.removeObserver(self, forKeyPath: "numberOfItems", context: &observerContext)
}
private func connectToModel() {
dataSource?.addObserver(self, forKeyPath: "numberOfItems", options: NSKeyValueObservingOptions(), context: &observerContext)
}
override func observeValueForKeyPath(keyPath: String!, ofObject object: AnyObject!, change: [NSObject : AnyObject]!, context: UnsafeMutablePointer<Void>) {
if context == &observerContext {
update()
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
private func update() {
if let model = dataSource {
textLabel?.text = "Items"
detailTextLabel?.text = "(model.numberOfItems)"
}
}
deinit {
disconnectFromModel()
}
}
PickerCell:
class PickerCell: UITableViewCell, UIPickerViewDelegate, UIPickerViewDataSource {
@IBOutlet weak var picker: UIPickerView!
var dataSource: DataSource? {
didSet {
update()
}
}
override func awakeFromNib() {
super.awakeFromNib()
selectionStyle = .None
picker.delegate = self
picker.dataSource = self
}
func update() {
if let dataSource = dataSource {
if let index = find(dataSource.itemsArray, dataSource.inspectionPref) {
picker.selectRow(index, inComponent: 0, animated: false)
}
}
}
//MARK: UIPickerViewDataSource
func numberOfComponentsInPickerView(_: UIPickerView) -> Int {
return 1
}
func pickerView(_: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return dataSource!.itemsArray.count
}
//MARK: UIPickerViewDelegate
func pickerView(_: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return "(dataSource!.itemsArray[row]) items"
}
func pickerView(_: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
dataSource!.numberOfItems = dataSource!.itemsArray[row]
}
}
DataSource:
class DataSource: NSObject {
let itemsArray = [5, 10, 15]
var pickerIndexPath: NSIndexPath? //allows to hide or show cell with identifier "pickerCell"
dynamic var numberOfItems: Int {
get {
return NSUserDefaults.standardUserDefaults().integerForKey("NumberOfItems")
}
set {
NSUserDefaults.standardUserDefaults().setInteger(newValue, forKey: "NumberOfItems")
NSUserDefaults.standardUserDefaults().synchronize()
}
}
override init() {
super.init()
//Init numberOfItems at first launch
if numberOfItems == 0 {
numberOfItems = 5
}
}
}
I have several questions about this new code. As it is my first attempt with MVC, I wonder if it is a real MVC design pattern code. I also ask myself if it is a complete/correct MVC code: is there anything left to do/modify in order to fully conform to MVC? Furthermore, I've read that passing a model to a view (here, a cell) is not recommended. Thus, what would be the way to make a cell interact with the model without passing the model to it?
design-patterns mvc ios swift
$endgroup$
bumped to the homepage by Community♦ 39 mins ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
1
$begingroup$
About passing a model to the view; usually it's recommended to pass a ViewModel instead of a DataModel. Essentially, they're simple models that only have data needed by the view. You would strip fields that are not needed, and you can compose multiple data models into one view model, specific to your view. At least, that's how I understand it.
$endgroup$
– Ivo Coumans
Sep 29 '14 at 13:37
$begingroup$
Thanks for your comment. However, I can hardly translate it to real code. The truth is that it's easy to find theoretical answers about MVC and MVVM but really hard to find practical answers. Since Swift has been launched, I've only been able to find two MVC/MVVM concrete explanations for it (the Rob Mayoff's answer to a previous question and a Natasha The Robot blog post). Would you give a try for this question?
$endgroup$
– user53113
Sep 29 '14 at 14:05
$begingroup$
Unfortunately I'm not familiar with Swift at all, which is why I only left a comment, hoping to give you a little advice.
$endgroup$
– Ivo Coumans
Sep 29 '14 at 14:19
$begingroup$
@IvoCoumans viewmodel is equivalent of controller in the pattern of MVVM, it's definitely different from model, so I don't agree with your point of passing some view model to controller.
$endgroup$
– zinking
Oct 6 '14 at 1:58
add a comment |
$begingroup$
Now that I know that MVC can help do better code, I want to make my SettingsTableViewController
class conform to it.
SettingsTableViewController
is a subclass of UITableViewController
. It is linked to a storyboard scene that contains a UITableView
with two cells (grouped tableView style, prototype cells). The first cell has a right detail style (textLabel and detailTextLabel), the second cell has a custom style and displays a UIPickerView
inside of its contentView.
When it appears on screen, SettingsTableViewController
displays only the first cell. When I click on this cell, the second cell containing a UIPickerView
appears. If I click again on the first cell, the second cell disappears.
The detailtextLabel of the first row displays an integer saved in NSUserDefaults
. The pickerView of the second cell is linked to an array of integers. When I select a row in the pickerView, it saves the row's related integer in NSUserDefaults
and updates the detailtextLabel of the first cell.
The image below may help understand the way it works:
Before knowing the concept of MVC, I was able to write the following code to make the previous explanation work:
class SettingsTableViewController: UITableViewController, UIPickerViewDelegate, UIPickerViewDataSource {
let itemsArray = [5, 10, 15]
var pickerIndexPath: NSIndexPath? //acts like a Bool and allows to hide or show cell with identifier "pickerCell"
var numberOfItems: Int {
get {
return NSUserDefaults.standardUserDefaults().integerForKey("NumberOfItems")
}
set {
NSUserDefaults.standardUserDefaults().setInteger(newValue, forKey: "NumberOfItems")
NSUserDefaults.standardUserDefaults().synchronize()
}
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Settings"
//Init numberOfItems
if numberOfItems == 0 {
numberOfItems = 5
}
//Autoset cells height (iOS8)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//If pickerIndexPath is nil, show only one cell
return pickerIndexPath == nil ? 1 : 2
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell!
if indexPath.row == 0 {
cell = tableView.dequeueReusableCellWithIdentifier("RightDetailCell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = "Items"
cell.detailTextLabel?.text = "(numberOfItems)"
} else {
cell = tableView.dequeueReusableCellWithIdentifier("PickerCell", forIndexPath: indexPath) as UITableViewCell
cell.selectionStyle = .None
//Set pickerView inside cell
let picker = cell.viewWithTag(1) as UIPickerView
picker.delegate = self
picker.dataSource = self
//Set the middle row in picker according to numberOfItems
if let index = find(itemsArray, numberOfItems) {
picker.selectRow(index, inComponent: 0, animated: false)
}
}
return cell
}
override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
switch indexPath {
case NSIndexPath(forRow: 1, inSection: 0):
return nil
default:
return indexPath
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if indexPath == NSIndexPath(forRow: 0, inSection: 0) {
//Show or hide pickerCell
if pickerIndexPath == nil {
tableView.beginUpdates()
pickerIndexPath = NSIndexPath(forRow: 1, inSection: 0)
tableView.insertRowsAtIndexPaths([pickerIndexPath!], withRowAnimation: .Fade)
tableView.endUpdates()
} else {
tableView.beginUpdates()
tableView.deleteRowsAtIndexPaths([pickerIndexPath!], withRowAnimation: .Fade)
pickerIndexPath = nil
tableView.endUpdates()
}
}
}
//MARK: UIPickerViewDataSource
func numberOfComponentsInPickerView(_: UIPickerView) -> Int {
return 1
}
func pickerView(_: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return itemsArray.count
}
//MARK: UIPickerViewDelegate
func pickerView(_: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return "(itemsArray[row]) items"
}
func pickerView(_: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if pickerIndexPath != nil {
//save new value in NSUserDefaults
numberOfItems = itemsArray[row]
//update first cell
let index = NSIndexPath(forRow: pickerIndexPath!.row - 1, inSection: 0)
tableView.reloadRowsAtIndexPaths([index], withRowAnimation: .Fade)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Then, in order to conform to MVC design pattern, I replaced the previous code with the following:
SettingsTableViewController:
class SettingsTableViewController: UITableViewController {
let dataSource = DataSource()
override func viewDidLoad() {
super.viewDidLoad()
title = "Settings"
//Autoset cells height (iOS8)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//If pickerIndexPath is nil, show only one cell
return dataSource.pickerIndexPath == nil ? 1 : 2
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("LabelCell", forIndexPath: indexPath) as LabelCell
cell.dataSource = dataSource
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("PickerCell", forIndexPath: indexPath) as PickerCell
cell.dataSource = dataSource
return cell
}
}
override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
switch indexPath {
case NSIndexPath(forRow: 1, inSection: 0):
return nil
default:
return indexPath
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if indexPath == NSIndexPath(forRow: 0, inSection: 0) {
//Show or hide pickerCell
tableView.beginUpdates()
if dataSource.pickerIndexPath == nil {
dataSource.pickerIndexPath = NSIndexPath(forRow: indexPath.row + 1, inSection: 0)
tableView.insertRowsAtIndexPaths([dataSource.pickerIndexPath!], withRowAnimation: .Fade)
} else {
tableView.deleteRowsAtIndexPaths([dataSource.pickerIndexPath!], withRowAnimation: .Fade)
dataSource.pickerIndexPath = nil
}
tableView.endUpdates()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
LabelCell:
//Global context variable
private var observerContext = 0
class LabelCell: UITableViewCell {
var dataSource: DataSource? {
willSet {
disconnectFromModel()
}
didSet {
connectToModel()
update()
}
}
private func disconnectFromModel() {
dataSource?.removeObserver(self, forKeyPath: "numberOfItems", context: &observerContext)
}
private func connectToModel() {
dataSource?.addObserver(self, forKeyPath: "numberOfItems", options: NSKeyValueObservingOptions(), context: &observerContext)
}
override func observeValueForKeyPath(keyPath: String!, ofObject object: AnyObject!, change: [NSObject : AnyObject]!, context: UnsafeMutablePointer<Void>) {
if context == &observerContext {
update()
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
private func update() {
if let model = dataSource {
textLabel?.text = "Items"
detailTextLabel?.text = "(model.numberOfItems)"
}
}
deinit {
disconnectFromModel()
}
}
PickerCell:
class PickerCell: UITableViewCell, UIPickerViewDelegate, UIPickerViewDataSource {
@IBOutlet weak var picker: UIPickerView!
var dataSource: DataSource? {
didSet {
update()
}
}
override func awakeFromNib() {
super.awakeFromNib()
selectionStyle = .None
picker.delegate = self
picker.dataSource = self
}
func update() {
if let dataSource = dataSource {
if let index = find(dataSource.itemsArray, dataSource.inspectionPref) {
picker.selectRow(index, inComponent: 0, animated: false)
}
}
}
//MARK: UIPickerViewDataSource
func numberOfComponentsInPickerView(_: UIPickerView) -> Int {
return 1
}
func pickerView(_: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return dataSource!.itemsArray.count
}
//MARK: UIPickerViewDelegate
func pickerView(_: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return "(dataSource!.itemsArray[row]) items"
}
func pickerView(_: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
dataSource!.numberOfItems = dataSource!.itemsArray[row]
}
}
DataSource:
class DataSource: NSObject {
let itemsArray = [5, 10, 15]
var pickerIndexPath: NSIndexPath? //allows to hide or show cell with identifier "pickerCell"
dynamic var numberOfItems: Int {
get {
return NSUserDefaults.standardUserDefaults().integerForKey("NumberOfItems")
}
set {
NSUserDefaults.standardUserDefaults().setInteger(newValue, forKey: "NumberOfItems")
NSUserDefaults.standardUserDefaults().synchronize()
}
}
override init() {
super.init()
//Init numberOfItems at first launch
if numberOfItems == 0 {
numberOfItems = 5
}
}
}
I have several questions about this new code. As it is my first attempt with MVC, I wonder if it is a real MVC design pattern code. I also ask myself if it is a complete/correct MVC code: is there anything left to do/modify in order to fully conform to MVC? Furthermore, I've read that passing a model to a view (here, a cell) is not recommended. Thus, what would be the way to make a cell interact with the model without passing the model to it?
design-patterns mvc ios swift
$endgroup$
Now that I know that MVC can help do better code, I want to make my SettingsTableViewController
class conform to it.
SettingsTableViewController
is a subclass of UITableViewController
. It is linked to a storyboard scene that contains a UITableView
with two cells (grouped tableView style, prototype cells). The first cell has a right detail style (textLabel and detailTextLabel), the second cell has a custom style and displays a UIPickerView
inside of its contentView.
When it appears on screen, SettingsTableViewController
displays only the first cell. When I click on this cell, the second cell containing a UIPickerView
appears. If I click again on the first cell, the second cell disappears.
The detailtextLabel of the first row displays an integer saved in NSUserDefaults
. The pickerView of the second cell is linked to an array of integers. When I select a row in the pickerView, it saves the row's related integer in NSUserDefaults
and updates the detailtextLabel of the first cell.
The image below may help understand the way it works:
Before knowing the concept of MVC, I was able to write the following code to make the previous explanation work:
class SettingsTableViewController: UITableViewController, UIPickerViewDelegate, UIPickerViewDataSource {
let itemsArray = [5, 10, 15]
var pickerIndexPath: NSIndexPath? //acts like a Bool and allows to hide or show cell with identifier "pickerCell"
var numberOfItems: Int {
get {
return NSUserDefaults.standardUserDefaults().integerForKey("NumberOfItems")
}
set {
NSUserDefaults.standardUserDefaults().setInteger(newValue, forKey: "NumberOfItems")
NSUserDefaults.standardUserDefaults().synchronize()
}
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Settings"
//Init numberOfItems
if numberOfItems == 0 {
numberOfItems = 5
}
//Autoset cells height (iOS8)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//If pickerIndexPath is nil, show only one cell
return pickerIndexPath == nil ? 1 : 2
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell!
if indexPath.row == 0 {
cell = tableView.dequeueReusableCellWithIdentifier("RightDetailCell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = "Items"
cell.detailTextLabel?.text = "(numberOfItems)"
} else {
cell = tableView.dequeueReusableCellWithIdentifier("PickerCell", forIndexPath: indexPath) as UITableViewCell
cell.selectionStyle = .None
//Set pickerView inside cell
let picker = cell.viewWithTag(1) as UIPickerView
picker.delegate = self
picker.dataSource = self
//Set the middle row in picker according to numberOfItems
if let index = find(itemsArray, numberOfItems) {
picker.selectRow(index, inComponent: 0, animated: false)
}
}
return cell
}
override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
switch indexPath {
case NSIndexPath(forRow: 1, inSection: 0):
return nil
default:
return indexPath
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if indexPath == NSIndexPath(forRow: 0, inSection: 0) {
//Show or hide pickerCell
if pickerIndexPath == nil {
tableView.beginUpdates()
pickerIndexPath = NSIndexPath(forRow: 1, inSection: 0)
tableView.insertRowsAtIndexPaths([pickerIndexPath!], withRowAnimation: .Fade)
tableView.endUpdates()
} else {
tableView.beginUpdates()
tableView.deleteRowsAtIndexPaths([pickerIndexPath!], withRowAnimation: .Fade)
pickerIndexPath = nil
tableView.endUpdates()
}
}
}
//MARK: UIPickerViewDataSource
func numberOfComponentsInPickerView(_: UIPickerView) -> Int {
return 1
}
func pickerView(_: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return itemsArray.count
}
//MARK: UIPickerViewDelegate
func pickerView(_: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return "(itemsArray[row]) items"
}
func pickerView(_: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if pickerIndexPath != nil {
//save new value in NSUserDefaults
numberOfItems = itemsArray[row]
//update first cell
let index = NSIndexPath(forRow: pickerIndexPath!.row - 1, inSection: 0)
tableView.reloadRowsAtIndexPaths([index], withRowAnimation: .Fade)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Then, in order to conform to MVC design pattern, I replaced the previous code with the following:
SettingsTableViewController:
class SettingsTableViewController: UITableViewController {
let dataSource = DataSource()
override func viewDidLoad() {
super.viewDidLoad()
title = "Settings"
//Autoset cells height (iOS8)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//If pickerIndexPath is nil, show only one cell
return dataSource.pickerIndexPath == nil ? 1 : 2
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("LabelCell", forIndexPath: indexPath) as LabelCell
cell.dataSource = dataSource
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("PickerCell", forIndexPath: indexPath) as PickerCell
cell.dataSource = dataSource
return cell
}
}
override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
switch indexPath {
case NSIndexPath(forRow: 1, inSection: 0):
return nil
default:
return indexPath
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if indexPath == NSIndexPath(forRow: 0, inSection: 0) {
//Show or hide pickerCell
tableView.beginUpdates()
if dataSource.pickerIndexPath == nil {
dataSource.pickerIndexPath = NSIndexPath(forRow: indexPath.row + 1, inSection: 0)
tableView.insertRowsAtIndexPaths([dataSource.pickerIndexPath!], withRowAnimation: .Fade)
} else {
tableView.deleteRowsAtIndexPaths([dataSource.pickerIndexPath!], withRowAnimation: .Fade)
dataSource.pickerIndexPath = nil
}
tableView.endUpdates()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
LabelCell:
//Global context variable
private var observerContext = 0
class LabelCell: UITableViewCell {
var dataSource: DataSource? {
willSet {
disconnectFromModel()
}
didSet {
connectToModel()
update()
}
}
private func disconnectFromModel() {
dataSource?.removeObserver(self, forKeyPath: "numberOfItems", context: &observerContext)
}
private func connectToModel() {
dataSource?.addObserver(self, forKeyPath: "numberOfItems", options: NSKeyValueObservingOptions(), context: &observerContext)
}
override func observeValueForKeyPath(keyPath: String!, ofObject object: AnyObject!, change: [NSObject : AnyObject]!, context: UnsafeMutablePointer<Void>) {
if context == &observerContext {
update()
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
private func update() {
if let model = dataSource {
textLabel?.text = "Items"
detailTextLabel?.text = "(model.numberOfItems)"
}
}
deinit {
disconnectFromModel()
}
}
PickerCell:
class PickerCell: UITableViewCell, UIPickerViewDelegate, UIPickerViewDataSource {
@IBOutlet weak var picker: UIPickerView!
var dataSource: DataSource? {
didSet {
update()
}
}
override func awakeFromNib() {
super.awakeFromNib()
selectionStyle = .None
picker.delegate = self
picker.dataSource = self
}
func update() {
if let dataSource = dataSource {
if let index = find(dataSource.itemsArray, dataSource.inspectionPref) {
picker.selectRow(index, inComponent: 0, animated: false)
}
}
}
//MARK: UIPickerViewDataSource
func numberOfComponentsInPickerView(_: UIPickerView) -> Int {
return 1
}
func pickerView(_: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return dataSource!.itemsArray.count
}
//MARK: UIPickerViewDelegate
func pickerView(_: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
return "(dataSource!.itemsArray[row]) items"
}
func pickerView(_: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
dataSource!.numberOfItems = dataSource!.itemsArray[row]
}
}
DataSource:
class DataSource: NSObject {
let itemsArray = [5, 10, 15]
var pickerIndexPath: NSIndexPath? //allows to hide or show cell with identifier "pickerCell"
dynamic var numberOfItems: Int {
get {
return NSUserDefaults.standardUserDefaults().integerForKey("NumberOfItems")
}
set {
NSUserDefaults.standardUserDefaults().setInteger(newValue, forKey: "NumberOfItems")
NSUserDefaults.standardUserDefaults().synchronize()
}
}
override init() {
super.init()
//Init numberOfItems at first launch
if numberOfItems == 0 {
numberOfItems = 5
}
}
}
I have several questions about this new code. As it is my first attempt with MVC, I wonder if it is a real MVC design pattern code. I also ask myself if it is a complete/correct MVC code: is there anything left to do/modify in order to fully conform to MVC? Furthermore, I've read that passing a model to a view (here, a cell) is not recommended. Thus, what would be the way to make a cell interact with the model without passing the model to it?
design-patterns mvc ios swift
design-patterns mvc ios swift
edited Apr 13 '17 at 12:40
Community♦
1
1
asked Sep 21 '14 at 15:24
user53113
bumped to the homepage by Community♦ 39 mins ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
bumped to the homepage by Community♦ 39 mins ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
1
$begingroup$
About passing a model to the view; usually it's recommended to pass a ViewModel instead of a DataModel. Essentially, they're simple models that only have data needed by the view. You would strip fields that are not needed, and you can compose multiple data models into one view model, specific to your view. At least, that's how I understand it.
$endgroup$
– Ivo Coumans
Sep 29 '14 at 13:37
$begingroup$
Thanks for your comment. However, I can hardly translate it to real code. The truth is that it's easy to find theoretical answers about MVC and MVVM but really hard to find practical answers. Since Swift has been launched, I've only been able to find two MVC/MVVM concrete explanations for it (the Rob Mayoff's answer to a previous question and a Natasha The Robot blog post). Would you give a try for this question?
$endgroup$
– user53113
Sep 29 '14 at 14:05
$begingroup$
Unfortunately I'm not familiar with Swift at all, which is why I only left a comment, hoping to give you a little advice.
$endgroup$
– Ivo Coumans
Sep 29 '14 at 14:19
$begingroup$
@IvoCoumans viewmodel is equivalent of controller in the pattern of MVVM, it's definitely different from model, so I don't agree with your point of passing some view model to controller.
$endgroup$
– zinking
Oct 6 '14 at 1:58
add a comment |
1
$begingroup$
About passing a model to the view; usually it's recommended to pass a ViewModel instead of a DataModel. Essentially, they're simple models that only have data needed by the view. You would strip fields that are not needed, and you can compose multiple data models into one view model, specific to your view. At least, that's how I understand it.
$endgroup$
– Ivo Coumans
Sep 29 '14 at 13:37
$begingroup$
Thanks for your comment. However, I can hardly translate it to real code. The truth is that it's easy to find theoretical answers about MVC and MVVM but really hard to find practical answers. Since Swift has been launched, I've only been able to find two MVC/MVVM concrete explanations for it (the Rob Mayoff's answer to a previous question and a Natasha The Robot blog post). Would you give a try for this question?
$endgroup$
– user53113
Sep 29 '14 at 14:05
$begingroup$
Unfortunately I'm not familiar with Swift at all, which is why I only left a comment, hoping to give you a little advice.
$endgroup$
– Ivo Coumans
Sep 29 '14 at 14:19
$begingroup$
@IvoCoumans viewmodel is equivalent of controller in the pattern of MVVM, it's definitely different from model, so I don't agree with your point of passing some view model to controller.
$endgroup$
– zinking
Oct 6 '14 at 1:58
1
1
$begingroup$
About passing a model to the view; usually it's recommended to pass a ViewModel instead of a DataModel. Essentially, they're simple models that only have data needed by the view. You would strip fields that are not needed, and you can compose multiple data models into one view model, specific to your view. At least, that's how I understand it.
$endgroup$
– Ivo Coumans
Sep 29 '14 at 13:37
$begingroup$
About passing a model to the view; usually it's recommended to pass a ViewModel instead of a DataModel. Essentially, they're simple models that only have data needed by the view. You would strip fields that are not needed, and you can compose multiple data models into one view model, specific to your view. At least, that's how I understand it.
$endgroup$
– Ivo Coumans
Sep 29 '14 at 13:37
$begingroup$
Thanks for your comment. However, I can hardly translate it to real code. The truth is that it's easy to find theoretical answers about MVC and MVVM but really hard to find practical answers. Since Swift has been launched, I've only been able to find two MVC/MVVM concrete explanations for it (the Rob Mayoff's answer to a previous question and a Natasha The Robot blog post). Would you give a try for this question?
$endgroup$
– user53113
Sep 29 '14 at 14:05
$begingroup$
Thanks for your comment. However, I can hardly translate it to real code. The truth is that it's easy to find theoretical answers about MVC and MVVM but really hard to find practical answers. Since Swift has been launched, I've only been able to find two MVC/MVVM concrete explanations for it (the Rob Mayoff's answer to a previous question and a Natasha The Robot blog post). Would you give a try for this question?
$endgroup$
– user53113
Sep 29 '14 at 14:05
$begingroup$
Unfortunately I'm not familiar with Swift at all, which is why I only left a comment, hoping to give you a little advice.
$endgroup$
– Ivo Coumans
Sep 29 '14 at 14:19
$begingroup$
Unfortunately I'm not familiar with Swift at all, which is why I only left a comment, hoping to give you a little advice.
$endgroup$
– Ivo Coumans
Sep 29 '14 at 14:19
$begingroup$
@IvoCoumans viewmodel is equivalent of controller in the pattern of MVVM, it's definitely different from model, so I don't agree with your point of passing some view model to controller.
$endgroup$
– zinking
Oct 6 '14 at 1:58
$begingroup$
@IvoCoumans viewmodel is equivalent of controller in the pattern of MVVM, it's definitely different from model, so I don't agree with your point of passing some view model to controller.
$endgroup$
– zinking
Oct 6 '14 at 1:58
add a comment |
1 Answer
1
active
oldest
votes
$begingroup$
Not familiar with Swift at all. but still want to add some comments.
the primary goal of design pattern of "MVC" is to separate the aspects involved, so that extension and modification can be done easily. so yes MVC definitely involves "MODEL" "VIEW" "CONTROLLER". but as you might know it has several mutations: MVVM,MTV(in the case of Django framework). The key is along the directions of separation you made something like that, but I myself don't get bureaucratic about that, as long as I think the refactoring made the whole process much clear, I am okay with my "MVC".
In the case of your refactoring, I am seeing a bit chunk of logic separated into controllers, views, models. which is good enough for me in terms of "MVC".
$endgroup$
$begingroup$
I generally go follow this wisdom,<jiggliemon> MVD, Model View Don't ask
$endgroup$
– megawac
Oct 6 '14 at 4:25
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f63484%2froad-to-mvc-the-case-of-settings-table-view-controller%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
Not familiar with Swift at all. but still want to add some comments.
the primary goal of design pattern of "MVC" is to separate the aspects involved, so that extension and modification can be done easily. so yes MVC definitely involves "MODEL" "VIEW" "CONTROLLER". but as you might know it has several mutations: MVVM,MTV(in the case of Django framework). The key is along the directions of separation you made something like that, but I myself don't get bureaucratic about that, as long as I think the refactoring made the whole process much clear, I am okay with my "MVC".
In the case of your refactoring, I am seeing a bit chunk of logic separated into controllers, views, models. which is good enough for me in terms of "MVC".
$endgroup$
$begingroup$
I generally go follow this wisdom,<jiggliemon> MVD, Model View Don't ask
$endgroup$
– megawac
Oct 6 '14 at 4:25
add a comment |
$begingroup$
Not familiar with Swift at all. but still want to add some comments.
the primary goal of design pattern of "MVC" is to separate the aspects involved, so that extension and modification can be done easily. so yes MVC definitely involves "MODEL" "VIEW" "CONTROLLER". but as you might know it has several mutations: MVVM,MTV(in the case of Django framework). The key is along the directions of separation you made something like that, but I myself don't get bureaucratic about that, as long as I think the refactoring made the whole process much clear, I am okay with my "MVC".
In the case of your refactoring, I am seeing a bit chunk of logic separated into controllers, views, models. which is good enough for me in terms of "MVC".
$endgroup$
$begingroup$
I generally go follow this wisdom,<jiggliemon> MVD, Model View Don't ask
$endgroup$
– megawac
Oct 6 '14 at 4:25
add a comment |
$begingroup$
Not familiar with Swift at all. but still want to add some comments.
the primary goal of design pattern of "MVC" is to separate the aspects involved, so that extension and modification can be done easily. so yes MVC definitely involves "MODEL" "VIEW" "CONTROLLER". but as you might know it has several mutations: MVVM,MTV(in the case of Django framework). The key is along the directions of separation you made something like that, but I myself don't get bureaucratic about that, as long as I think the refactoring made the whole process much clear, I am okay with my "MVC".
In the case of your refactoring, I am seeing a bit chunk of logic separated into controllers, views, models. which is good enough for me in terms of "MVC".
$endgroup$
Not familiar with Swift at all. but still want to add some comments.
the primary goal of design pattern of "MVC" is to separate the aspects involved, so that extension and modification can be done easily. so yes MVC definitely involves "MODEL" "VIEW" "CONTROLLER". but as you might know it has several mutations: MVVM,MTV(in the case of Django framework). The key is along the directions of separation you made something like that, but I myself don't get bureaucratic about that, as long as I think the refactoring made the whole process much clear, I am okay with my "MVC".
In the case of your refactoring, I am seeing a bit chunk of logic separated into controllers, views, models. which is good enough for me in terms of "MVC".
answered Oct 6 '14 at 2:04
zinkingzinking
1176
1176
$begingroup$
I generally go follow this wisdom,<jiggliemon> MVD, Model View Don't ask
$endgroup$
– megawac
Oct 6 '14 at 4:25
add a comment |
$begingroup$
I generally go follow this wisdom,<jiggliemon> MVD, Model View Don't ask
$endgroup$
– megawac
Oct 6 '14 at 4:25
$begingroup$
I generally go follow this wisdom,
<jiggliemon> MVD, Model View Don't ask
$endgroup$
– megawac
Oct 6 '14 at 4:25
$begingroup$
I generally go follow this wisdom,
<jiggliemon> MVD, Model View Don't ask
$endgroup$
– megawac
Oct 6 '14 at 4:25
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f63484%2froad-to-mvc-the-case-of-settings-table-view-controller%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
$begingroup$
About passing a model to the view; usually it's recommended to pass a ViewModel instead of a DataModel. Essentially, they're simple models that only have data needed by the view. You would strip fields that are not needed, and you can compose multiple data models into one view model, specific to your view. At least, that's how I understand it.
$endgroup$
– Ivo Coumans
Sep 29 '14 at 13:37
$begingroup$
Thanks for your comment. However, I can hardly translate it to real code. The truth is that it's easy to find theoretical answers about MVC and MVVM but really hard to find practical answers. Since Swift has been launched, I've only been able to find two MVC/MVVM concrete explanations for it (the Rob Mayoff's answer to a previous question and a Natasha The Robot blog post). Would you give a try for this question?
$endgroup$
– user53113
Sep 29 '14 at 14:05
$begingroup$
Unfortunately I'm not familiar with Swift at all, which is why I only left a comment, hoping to give you a little advice.
$endgroup$
– Ivo Coumans
Sep 29 '14 at 14:19
$begingroup$
@IvoCoumans viewmodel is equivalent of controller in the pattern of MVVM, it's definitely different from model, so I don't agree with your point of passing some view model to controller.
$endgroup$
– zinking
Oct 6 '14 at 1:58