I am wondering how did you manage to correctly respect MVC design pattern on your IOS developments with Swift ? What I feel right now is that a view controller mix both the controller part and the view part but it feels wrong right ? To respect MVC, we clearly need to separate the controller and the view. How did you make this ?
Maybe, it's more obvious with other design patterns like MVVM or MVP ?
You can separate your project and create a structure where you have all the logic and models in one place and all the viewControllers in one place.
Like this for example:
Core
Models
Person.swift
Car.swift
Helpers
Extensions.swift
APIHelper.swift
Webservice
Webservice.swift
Controllers
ViewController.swift
SecondViewController.swift
So you basically have all the logics and calculations in your Core and all the views and UI elements in your Controllers. With this way you won´t have to do the same logic code multiple times. You could also create custom views and add them to your Core which you later can call in your Controllers.
Although it might be too broad to answer such a question, I will answer your specific issue about:
view controller mix both the controller part and the view part...
Note that when working an iOS project, it leads you implicitly to apply the MVC pattern. By default, the view controllers represent the Controller part, the .storyboard and .xib files represent the View part and any model objects for encapsulating the data (data templates) represents the Model.
What I feel right now is that a view controller mix both the
controller part and the view part but it feels wrong right ?
The view controller has many responsibilities to be handled, in addition to interprets user actions, it should also have to be the intermediary between view and the model(s), and don't forget about handling the integration with web services... That's the issue of the Massive View Controller.
if you tried to do some researches about solving this issue, you would find many approaches to follow such as applying other structural patterns like MVVM, MVP , VIPER or Clean Architecture or even simpler approaches such as dividing your project files to increase the jobs independency which leads to make it more clear and easy to trace, MVC-N might be a good example.
But for the specific case that are you asking about (mix both the controller part and the view part) to keep it simple: I would recommend to separate the logic of the data representation based on its view, example:
One of the most popular case when building an iOS project is working with table view and its cells, consider the following:
ViewController.swift:
class ViewController: UIViewController {
// ...
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myDataSourceArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCellID") as! MyCustomCell
// ...
return cell
}
}
MyCustomCell.swift:
class MyCustomCell: UITableViewCell {
#IBOutlet weak var lblMessage: UILabel!
}
Now, imagine that -somehow- there is a requirement to change the lblMessage label text color in the cell based on a bunch of complex calculations, for such a case:
Do not do this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCellID") as! MyCustomCell
// do calulations which might needs tens of lines to be achieved, based on that:
cell.lblMessage.textColor = UIColor.red
return cell
}
That leads to make the view controller to be massive and contains alot of jobs, instead do:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCellID") as! MyCustomCell
//...
cell.doCalaulations()
return cell
}
In the cell class:
class MyCustomCell: UITableViewCell {
#IBOutlet weak var lblMessage: UILabel!
func doCalaulations() {
// do calulations which might needs tens of lines to be achieved, based on that:
blMessage.textColor = UIColor.red
}
}
That leads to make the project components to be more encapsulated and the important thing is the view controller does not has to take care of the whole thing. For me, in cases that similar to this one, I would prefer to even make blMessage to be private which guarantees to make it only editable from the owner class (more encapsulation) to handle any needed behavior, thus any view controller should call the class methods instead of direct accessing to its properties and its IBOutlets.
Separate a usual ViewController on two different parts: View and Presenter. View responses only for displaying data and collecting user's interaction. Presenter prepares data for view and process user actions comes from View.
This idea came from Clean Architecture by uncle Bob and realized into VIPER architecture.
View has two protocols:
ViewInput : contains functions for pass data to display like set(labelText: String). This protocol should implement View. Presenter has object of type ViewInput.
ViewOutput : contains functions, which calls when some events happen in view like viewDidLoad() or rowDidSelect(at: IndexPath). This protocol should implement Presenter. View has object of type ViewOutput.
VIPER isn't trivial thing, I spent several days to understand its principles. So read articles and try to implement it in your code. Do not hesitate to ask questions.
Related
I have an app where the user has a list of stocks that they follow (so it can vary/change). When the user clicks on a cell in the list, it opens up a StockViewController() that shows the stock data. I've started to worry that initialing a new ViewController every time is bad practice and causing an increase in memory usage over time (Not even entirely sure if that's true).
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if(indexPath.section == 1) {
let stockVC = StockViewController()
stockVC.parentView = self
stockVC.stock = followingStocks[indexPath.row]
DispatchQueue.main.async {
self.present(stockVC, animated: true, completion: nil)
}
}
}
I have tried attempting to use the same ViewController by reseting the tableview/data inside of it after each use but am having trouble successfully making this a smooth process. Is doing it the above way going to cause issues in my app or should I try to use the same ViewController, below, each time?
class VCManager {
static var stockVC = StockViewController()
static func resetStockVC() {
stockVC.stockData = [StockData]()
stockVC.tableView.reloadData()
}
}
Initializing a new view controller every time is not only fine, but very common. When you dismiss that view controller, the memory for it is automatically deallocated, so you won't be increasing your memory usage overtime. (With the exception that you have a retain cycle in that view controller that could potentially cause a memory leak, but that's a whole different topic of its own).
I agree with Reza. Caching a view controller is uncommon. Creating a new instance of a view controller is really fast und laying out its views depends on the complexity of the view hierarchy.
Creating a new view controller every time you need not only has the advantage of consuming memory only when you need it and as long as you need it but it also removes state handling from it. I.e. you do no longer need to take care of updating your model and that your table view is in sync with the underlying model data aka making sure that your datasource is up to date with the model and tableView.reloadData(). Ideally you'd inject your model array as part of your initializer and store it as a property constant. Using a constant also makes your intent obvious for others that there will be no model changes to worry about.
I'm building an app and trying to use the MVVM pattern. While there's a plethora of information about how to wire up things up for data to flow form the model to the view model to the controller to the view, I'm having a very hard time learning how to do other things while sticking to MVVM principles. One thing I'm really struggling with is setting a relationships between two managed objects after segueing to different view controllers. Let me explain...
When my app starts, it presents the first view controller which is backed by a view model which talks to a newly created NSManagedObject of type LiftEvent. From here, the user can segue to a Settings view controller which also has a view model, and from there they can segue to a table view, which also has a view model, and make a selection that needs to change an NSManaged var property of the model object. This property is a relationship to another NSManagedObject. Here's the flow:
I'm passing the managedObjectContext from the first view controller to the Settings view controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let nav = segue.destinationViewController as? UINavigationController {
let vc = nav.topViewController as! SettingsViewController
vc.dismissalDelegate = self
let moc = viewModel.model.context
vc.moc = moc
}
and then pass it again to the last view controller where the user makes a selection and I want to set the relationship. It works, but I don't like it because I now have to import CoreData in the view controllers I'm passing it to.
The view controller on the far right is a UITableViewController. When the user selects one of the rows, I want to use that selection to grab the corresponding managed object and set it as one of the properties on the object that was created when the app launched. Here's the didSelectRowAtIndexPath method:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let defaultFormula = NSUserDefaults.formula()
guard indexPath.row != defaultFormula.rawValue else { return }
if let newDefaultFormula = FormulaName(rawValue: indexPath.row) {
NSUserDefaults.setDefaultFormulaName(newDefaultFormula)
}
if let selectedFormula = formulasArray[indexPath.row] {
// now what?
}
}
FormulaName is a enum of the possible names. The data source is formulasArray, an array of Formula managed objects. I initialize the array in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
formulasArray = dataManager.fetchSelectableFormulas(moc)
}
I see a few possible solutions in my head but I have no idea which of them, if any, are good choices:
pass the Formula object I picked out of the array back to the first view controller in unwind segues and set the relationship there
somehow get a hold of the LiftEvent from here, set the relationship, then tell the view model of the first view controller that the object has changed.
pass a reference to the LiftEvent object around instead of passing just the managedObjectContext property
I've spent countless hours trying to find examples, lessons, git repositories, SO threads, etc to figure this out that I don't know where to go from here. I'm not asking anyone to write the code for me, just steer me in a good direction and I'd be forever grateful.
I too have had issues finding concrete examples of MVVM in iOS, so i've kind of developed my own approach.
I have a couple recommendations (this is opinion to be clear).
Keep the NSManagedObject context in a CD access singleton for access on the main thread is really helpful. Assuming you're not using multiple databases or doing a bunch of things in the background, (which you can handle as well with the accessor if necessary) you will have 1 context with no context passing.
When using Segues, dependency injection is awesome. Rather than giving your new VC the minimum information it needs to find the data and set things up, simply set it's ViewModel in the Segue and build the VC to work only off of that. This is what makes ViewModels work so well. your View/ViewController doesn't need to to know anything about your model.
When dealing with unwinds, my approach is to simply make a "selectedItem" property in the desired VC and leave the responsibility of what to do to the VC to which it is unwinding. If necessary it can grab that value when the unwind occurs. This allows your choosing VC to operate the same way regardless of the behavior around it.
The documentation for the NSTableViewDataSource protocol says
This method is mandatory
and if you create a new NSTableViewDataSource the compiler asks you to provide this method as well as numberOfRows(in tableView: NSTableView).
So far, so good. And if you provide it, it promptly gets called once for every row... but if you delete it, the table seems to work every bit as well as it does without. There seems to be absolutely no connections in a view-based table to this method: in the NSTableViewDelegate's tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) method, I return new views based on the content array of my datasource, and if I stick a bunch of nonsense data into objectValueFor: row (up to and including returning the same string - my table objects are not strings) is has no effect whatsoever because I'm setting the values of my NSTableViewCells in viewFor tableColumn:.
If I'm handling selection changes (and anything I want to do with objects) by retrieving the corresponding value from the datasource directly (e.g.)
func tableViewSelectionDidChange(_ notification: Notification) {
print(dataSource.allItems[tableView.selectedRow])
}
is there any reason to implement this method? I cannot see any point where that object is actually used, and it feels like an artefact from cell-based tables.
I don't want to break my code by leaving out a necessary method, but since I'm about to try something weird and wonderful with a custom datasource, I also don't want to overcomplicate my code with a method that gets called, but the result of which seems completely meaningless.
If the cell view responds to setObjectValue:, then the table view calls that method and passes in the object value for the row that was obtained from tableView(_:objectValueFor:row:).
NSTableCellView does respond to that method and is a common cell view class. NSTextField does, too; it actually inherits it from NSControl.
In your case, your cell view either doesn't have an objectValue property or, more likely, you're just not using it.
One common configuration is to use NSTableCellView as the cell view and then use bindings to bind the subviews to key paths going through its objectValue property.
I'm a bit lost here:
I created a button acting like a colorPicker: clicking on it shows a collectionView in a popover.
I first did it with a nib fil containing a view + the collectionView (embedded in as scrollView + a clipView).
The stuff works just fine.
As the nib file is very simple (and to improve my coding skills in designing views programmatically), I decided to get rid of the nib file and write the missing part in code.
The thing is, I manage to get the job done except for the content of the collectionView. After deep investigation, it appears that, inside the method:
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem
which is supposed to manage the data source, the method
collectionView.makeItem(withIdentifier: String, for: IndexPath)
doesn't work. In fact, in:
let item = collectionView.makeItem(withIdentifier: ColorPickerPopover.itemIdentifier, for: indexPath)
item is uninitialized, as the debugger says when I step in (not nil, uninitialized). Apparently, the makeItem method never instantiate any collectionViewItem from the subclass I've made.
The identifier is fine and the collectionView.register function is called, just like in the nib version, as both projects are identical in these points. The makeItem function simply doesn't call the loadView method of the NSCollectionViewItem I've subclassed.
Any clue?
Josh
With the collectionView.makeItem(withIdentifier:for:) method, you'll first need to either register the class or the nib file with the collection view:
Using a class
Use register(_:forItemWithIdentifier:) (the first parameter accepts AnyClass?)
collectionView.register(MyCustomCollectionViewItemSubclass.self, forItemWithIdentifier: "SomeId")
Using a Nib file
Use register(_:forItemWithIdentifier:) (the first parameter accepts NSNib?).
let nib = NSNib(nibNamed: "MyCollectionViewItem", bundle: nil)!
collectionView.register(nib, forItemWithIdentifier: "SomeId")
The key thing: On your Nib file, you also have to make sure that you have an NSCollectionViewItem added to the scene. You also have to set the object's class to your subclass in order for it to work (you can set it on the inspector's panel).
Hope this helps!
Is there any way to access the owning UITableView from within a UITableViewCell?
Store a weak reference to the tableView in the cell, which you'd set in -tableView:cellForRowAtIndexPath: of your table's dataSource.
This is better than relying on self.superview to always be exactly the tableView is fragile. Who knows how Apple might re-organize the view hierarchy of UITableView in the future.
Here's a nicer way to do it, which does not rely on any particular UITableView hierarchy. It will work with any future iOS version, provided that UITableView does not change classname altogether. Not only this is extremely unlikely, but if it does happen you will have to retouch your code anyway.
Just import the category below and get your reference with [myCell parentTableView]
#implementation UIView (FindUITableView)
-(UITableView *) parentTableView {
// iterate up the view hierarchy to find the table containing this cell/view
UIView *aView = self.superview;
while(aView != nil) {
if([aView isKindOfClass:[UITableView class]]) {
return (UITableView *)aView;
}
aView = aView.superview;
}
return nil; // this view is not within a tableView
}
#end
// To use it, just import the category and invoke it like so:
UITableView *myTable = [myTableCell parentTableView];
// It can also be used from any subview within a cell, from example
// if you have a UILabel within your cell, you can also do:
UITableView *myTable = [myCellLabel parentTableView];
// NOTE:
// If you invoke this on a cell that is not part of a UITableView yet
// (i.e., on a cell that you just created with [[MyCell alloc] init]),
// then you will obviously get nil in return. You need to invoke this on cells/subviews
// that are already part of a UITableView.
UPDATE
There is some discussion in the comments about whether keeping a weak reference is a better approach. It depends on your circumstances. Traversing the view hierarchy has some small runtime penalty as you are looping until the target UIView is identified. How deep are your views? On the other hand, keeping a reference on every cell has a minimal memory penalty (a weak reference is a pointer after all), and generally adding object relationships where they are not needed is considered a bad OO design practice for many reasons, and should be avoided (see details in the comments below).
More importantly, keeping table references inside cells adds code complexity and can lead to errors, because UITableViewCells are reusable. It is no coincidence that UIKit does not include a cell.parentTable property. If you define your own you must add code to manage it, and if you fail to do so effectively you can introduce memory leaks (i.e., cells live past the lifetime of their table).
Because typically you'll be using the category above when a user interacts with a cell (execute for a single cell), and not when laying-out the table in [tableView:cellForRowAtIndexPath:] (execute for all visible cells), the runtime cost should be insignificant.
Xcode 7 beta, Swift 2.0
This works fine for me, in my opinion it has nothing to do with the hierarchy or whatever. I had no trouble with this approach so far. I've used this for many async callbacks (ex. when an API request is done).
TableViewCell class
class ItemCell: UITableViewCell {
var updateCallback : ((updateList: Bool)-> Void)? //add this extra var
#IBAction func btnDelete_Click(sender: AnyObject) {
let localStorage = LocalStorage()
if let description = lblItemDescription.text
{
//I delete it here, but could be done at other class as well.
localStorage.DeleteItem(description)
}
updateCallback?(updateList : true)
}
}
Inside table view class that implements the DataSource and Delegate
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: ItemCell = self.ItemTableView.dequeueReusableCellWithIdentifier("ItemCell") as! ItemCell!
cell.updateCallback = UpdateCallback //add this extra line
cell.lblItemDescription?.text = self.SomeList[indexPath.row].Description
return cell
}
func UpdateCallback(updateTable : Bool) //add this extra method
{
licensePlatesList = localStorage.LoadNotificationPlates()
LicenseTableView.reloadData()
}
Ofcourse you can put any variable in the updateCallback and change it's function in the tableView accordingly.
Someone might want to tell me if it is save to use though, just to be sure.
You have to add a reference back to the UITableView when you construct the table view cell.
However, almost certainly what you really want is a reference to your UITableViewController... that requires the same thing, set it as a delegate of the cell when you build the cell and hand it to the table view.
An alternate approach if you are wiring up actions is to build the cells in IB, with the table view controller as the files owner - then wire up buttons in the cell to actions in the table view controller. When you load the cell xib with loadNibNamed, pass in the view controller as the owner and the button actions will be wired back to the table view controller.
If you have custom classes for your UITableViewCells, you can add an id type variable in your cell's header, and synthesize the variable. After you set the variable when you load the cell, you are free to do what you please with the tableview or any other higher view without much hassle or overhead.
cell.h
// interface
id root;
// propery
#property (nonatomic, retain) id root;
cell.m
#synthesize root;
tableviewcontroller.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// blah blah, traditional cell declaration
// but before return cell;
cell.root = tableView;
}
Now you can call any of the tableview's methods from within your cell using the root variable. (e.g., [root reloadData]);
Ah, takes me back to the good old days of flash programming.
The two methods in other answers are: (A) store a reference to the table, or (B) walk up the superviews.
I'd always use something like (A) for model objects and (B) for table cells.
Cells
If you are dealing with a UITableViewCell, then AFAIK you must either have the UITableView at hand (say you are in a table delegate method), or are dealing with a visible cell that is in the view hierarchy. Otherwise, you may well be doing something wrong (please note the "may well").
Cells are liberally reused and if you happen to have one that is not visible then the only real reason that cell exists is because of iOS UITableView performance optimization (a slower iOS version would have released and hopefully dealloc'd the cell when it moved off screen) or because you have a specific reference to it.
I guess this is probably the reason that table cells are not endowed with a tableView instance method.
So (B) gives the right result for all iOS's so far, and all future ones until they radically change how views work.
Though in order to avoid writing generalizable code over and over, I'd use this:
+ (id)enclosingViewOfView:(UIView *)view withClass:(Class)returnKindOfClass {
while (view&&![view isKindOfClass:returnKindOfClass]) view=view.superview;
return(view);
}
and a convenience method:
+ (UITableView *)tableForCell:(UITableViewCell *)cell {
return([self enclosingViewOfView:cell.superview withClass:UITableView.class]);
}
(or categories if you like)
BTW, if you are concerned about the effect of a loop with 20 or so iterations of that size on your app performance,.. don't.
Models
If you are talking about the model object that is displayed in the cell, then definitely that model could/should know about its parent model, which may be used to find, or trigger changes in, the table(s) that the cell's model might be displayed in.
This is like (A), but less brittle with future iOS updates (eg one day they might make the UITableViewCell reuse cache exist per reuseidentifier, rather than per reuseidentifier per tableview, on that day all the implementations that use the weak reference method will break).
Th model method would be used for changes to the data displayed in the cell (i.e. model changes) since changes will propagate wherever the model is displayed (eg. some other UIViewController somewhere else in the app, logging, ...)
The cell method would be used for tableview actions, which would likely always be a bad idea if the cell isn't even a subview of a table (though it's your code, go nuts).
Either way, use a unit test rather than assuming that seemingly cleaner code just works when they update iOS.
UITableView *tv = (UITableView *) self.superview.superview;
UITableViewController *vc = (UITableViewController *) tv.dataSource;