Top brick problem I face every time is a passing data between component.
I can separate this problem in few sub-problems:
Do we need to pass data to views? Do we need to change model directly in view?
Can we use some service call directly from views, or we need to pass back all operation to UIViewConrtoller and only then controller will request appropriate service.
Specifying indexes instead of real models.
So the first question demonstrate case when we create UITableViewCell and pass to it data directly. So we now have ability to modify some properties of this data object. Let's say we have PlayListViewController that implement UITableView datasource. If you see bellow I set data model directly into view.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = viewController.theTableView.dequeueReusableCellWithIdentifier(String(SongTableViewCell.self)) as! SongTableViewCell
cell.song = songs[indexPath.row]
return cell
}
Second one for example, when you want to trigger something in your service directly from your SongTableViewCell without using any delegate calls. Let's say in SongTableViewCell I want to play my song and on this cell I have play button. So then it is a simple solution - I can bind cell with UIButton action touch up inside for example and invoke needed operation in my service:
#IBAction func onTappedPlayButton(sender: AnyObject)
{
MusicService.playSong(song)
}
But usually I do another things. I store delegate instance in my cell that pass back any action to a controller and controller decides what to do:
#IBAction func onTappedPlayButton(sender: AnyObject)
{
delegate?.didTappedPlayButton()
}
In this case SongTableViewCell looks like this:
class SongTableViewCell: UITabeViewCell
{
weak var delegate:SongTableViewCellDelegate?
...
#IBAction func onTappedPlayButton(sender: AnyObject)
{
delegate?.didTappedPlayButton(index)
}
}
and my view controller implements this didTappedPlayButton method where it calls MusicService.playSong(song). Here is 3rd problem if we have not pushed model object into UITableviewCell then we need to say somehow to view controller that it needs to play some appropriate song from array. So I use index that I set into the UITableviewCell which is sometimes can tangle other developers. I don't know if it's better to use index or data model. I understood advantage of changeability but index say nothing for developers and data model object says a lot.
I know it's more architecture questions, but maybe we can outline some props and cons of these 3 approaches/problems.
I found a post and it says
Classes=more complexity. Values=less complexity. Not sure it can fit all my subquestion, but maybe values it much better then transit entire class.
Related
I have to create one dynamic form which contains question answer set and questions are different for every user. There are 5 questions out of this user have to answer at least 3 questions. In the set of 5 questions, the first user may get the set question which required 1 textfield 2 drops down and 2 radio buttons in the same way the second user may get 3 textfield 1 drop down and 1 radio button. What approach I should have to follow to achieve this?
I tried to create a table view. In the cell, I specified one label to render a question and one blank view which will be filled later on the basis where the question required text field or radio button or drop down. But with this case, I am not able to maintain which question I get answered because if I make textfield or checkbox's user interaction unable the didselectrowatindexpath method is not getting called even tried to mapping with delegate but this one is also not suitable for me because I have so many cases to manage as an answer field.
There might be different implementations. What I would do is:-
Distinguish the Question Types based on the UI(as per your case)
Add a questionType enum in QuestionModel class
Will create a different cell for each question type
Use that particular cell for each question type
Even in future any new Qustion Type has come, add one more case in enum and create one more cell
My Implementation will go something like this for QuestionModel class:-
Class Question {
enum Type {
case type1, type2 .....
func cellIdentifier() -> String {
switch self {
case type1:
return "type1"
//Handle all cases
}
}
}
var type: Type
}
Controller class will be something like this:-
Class Controller: UIViewController {
var questions = [Question]()
//Table view delegate method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let questionAtIndexPath = questions[indexPath.row]
let cell = tableView.dequeReusableCell(withIdentifier: questionAtIndexPath.type.cellIdentifier(), for: indexPath)
cell.configureWith(questionAtIndexPath)
return cell
}
}
Well, the main task you have is to create a dynamic form where you have different objects depending on the required question. So, do this first:
Create individual tableCells like one containing a label for the question and a textfield for the answer or checkbox's to answer the question. The label will be common for every tableCell.
Connect the required delegates or IBOutlets with the objects
Create closure in table cells and handle the data when user completed answering the question.
Finally save the data collected by the closures call backs in TableViewcellforRowAtIndexpath and use them as per required.
I'm looking for any possible way of passing the result of an networking update to a button in a UITableViewCell as a closure.
I have some UITableViewCells that are products. In these cells, I have an 'Add to Cart' button. I set a buttonTap closure in my UITableViewCell cellforRowAtIndexPath method, setup a touch handler within the cell for the button, and when that handler is called, execute the buttonTap closure. I handle my cart updating on a cart object which lives on the main controller.
The result of the cart update action returns true if they can add more items to their cart. Then, I update the button accordingly. I like this approach because I don't have to deal with delegates and I can keep all of the cart logic itself far far away from the cell; the cell just knows how to make a button enabled/disabled/loading/etc.
/// Buttom tap callback callback.
public typealias Selection = () -> Bool
class MealTableViewCell: UITableViewCell {
var buttonTap: Selection?
// Runs when tapping the button
func didTapAdd() {
if let buttonBlock = buttonTap {
self.button.isLoading = true
// Simulate loading
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
self.button.enabled = buttonBlock()
self.button.isLoading = false
}
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = .... (fetch cell)
cell.buttonTap = {
// returns true if the user can add more items to their cart
return self.cart.update(product: product, quantity: 1)
}
}
My question is that currently, my cart is all local with no API calls. I'm currently switching the cart over to an API driven one, with network calls to add and remove items. That means I can no longer return a BOOL, or return at all, from my cart.update(product:,quantity:) method as it is now an async call.
So, I can do something like rewrite that method signature to be
self.cart.update(product: product, quantity: 1, success: { canAddMore in
// API call succeeded
}, failure: { error in
// fall failed
})
The question is that how can I pass canAddMore to the tableViewCell? If I redefine what Selection means to take in a block that takes a bool as a param, I can't pass that param in from the controller as it would only be passed in when the block is executed on the cell itself.
How can I do something like
cell.buttonTap = {
cell.buttonTap = {
self.cart.update(product: product, quantity: 1), success: { canAddMore in
// !!!! What can I call here to pass canAddMore to the cell.
}, failure: { error in
}
}
}
canAddMore can be any value really, a BOOL is just this example. My big goal is to avoid coupling any knowledge of the cell's loading and buttons to the controller itself. If I use delegates, I would have to have a two way delegate makes the cell a delegate of the controller, and I've always felt that's the sort of wrong direction to approach this. I'm not positive it's really possible to pass the result of a closure back to the cell, but I am hoping there is!
EDIT: The big question I'm really trying to answer is if it is at all possible to pass data back to the cell (or any object) that originally called closure through that closure. There's a million ways to do data modifications in a table view, but that's sort of the main thing I'm trying to address.
I'm also "looking" to avoid storing the canAddMore state (e.g. the quantity remaining for that product) in the main 'products' array that powers the tableview. The initial state is set there, returned from a /products endpoint, but after that, inventory being available or not is returned by the carts API action.
I don't think you want to do what you think you want to do :)
In a nutshell, instead of trying to "talk back" to the cell that called the closure, you probably want to track the "canAddMore" state of each product in your Products data array, and then update the table row(s) when the state changes. So...
User taps "Add to Cart"
Give visual feedback in that row to show that you are processing the tap (gray out the button, or show a spinner, whatever looks good)
Call back to the closure to start the Add-to-cart API call
When the API call returns, update your local Products data to indicate the "canAddMore" state
reload the row(s) in the table to update the Button (make it active, inactive, change the title, whatever)
You almost certainly need to be doing something similar anyway, so the Buttons in each row will be updated when the user scrolls and the cells are reused.
A general approach is to update the cell's content with tableView.reloadRows(at: [indexPath], with: true) in your callback. This will call func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell for the specified cell. Don't forget that this has to be done in the main queue.
I noticed that some of my view controllers have become pretty big and I'd like to avoid the MVC (Massive View Controller).
I found that my view controllers often implement a lot of delegates from other view controllers which I may or may not present at runtime. Also they are often datasources for table- or collectionviews.
Its not to hard to refactor the view controllers and put the datasources and delegates into their own structs or classes and then just have a bunch of lazy optional properties which I can pass around.
My problem is: I'm really bad at naming things and I wonder if there are some best practices on how to name the delegates and datasource objects.
Here are a few examples of delegates and datasources I currently implement in my viewcontroller but which I want to put in their own objects:
UITableViewDataSource
UITableVIewDelegate
UICollectionViewDataSource
UICollectionViewDelegate
ImagePickerDelegate
DatePickerKeyboardDelegate
KeyboardAccessoryToolbarDelegate
AControllerDelegate
BControllerDelegate
CControllerDelegate
And my view controller are named after what they do, followed by Controller (I don't like to name them ViewController because it makes their name even longer):
ImagesController
FoodController
StoreController
AController
BController
CController
Okay so now to an example. AController could push BController onto the navigation stack. AController currently implements the BControllerDelegate but does not actually need to do that. Instead, AController could hold a object which implements the BControllerDelegate and just configures the delegate before it pushes BController and sets BControllers delegate.
But how would I name the object which implements the BControllerDelegate and how would I name the variable which holds this object in my AController?
class AController: UIViewController {
let whatsMyName = WhatsMyName()
}
struct WhatsMyName: BControllerDelegate {}
Update
Yesterday I started refactoring a few of my view controllers. As also suggested by danh (I hope I understood you right), I currently let the view controller still be the delegate of my datasources. Though I now have separate datasource objects and I currently configure everything like so:
struct MyModel {
var name: String
init(name: String) { self.name = name }
}
class MyModelDataSource: NSObject, UITableViewDataSource {
let myModelCollection: [MyModel] = [MyModel(name:"Hello"), MyModel(name:"World")]
init(tableView: UITableView) {
//setup tableview, register cells, set row height, etc.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myModelCollection.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel.text = myModelCollection[indexPath.row].name
return cell
}
}
protocol MyViewControllerDelegate: class {
func myViewController(_ myViewController: MyViewController, picked: MyModel)
}
class MyViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
weak var myViewControllerDelegate: MyViewControllerDelegate?
lazy var myModelDataSource: MyModelDataSource = {
return MyModelDataSource(tableView: self.tableView)
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = myModelDataSource
tableView.delegate = self
if myViewControllerDelegate == nil { delegate = self }
}
}
extension MyViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
myViewControllerDelegate?.myViewController(self, picked: myModelDataSource.myModelCollection[indexPath.row])
}
}
extension MyViewController: MyViewControllerDelegate {
func myViewController(_ myViewController: MyViewController, picked: MyModel) {
//Perform drill down
}
}
extension SomeOtherViewController: MyViewControllerDelegate {
func myViewController(_ myViewController: MyViewController, picked: MyModel) {
//Perform picking of item and dismiss/pop myViewController
}
}
The deal with this is, that it probably still light years away from being perfect. I think actually the viewcontroller could or maybe even sholud still be the delegate and datasource for every tableview (keep in mind that you might have multipe tableview outlets and therefore also multiple datasource objects).
It could then just forward the datasource functions to the appropriate datasource object which is stored as lazy instance variable. That would allow me to have datasources which only have 1 section and "stitch together" multiple datasources into one tableview by using a datasource per section (I hope that you know what I mean).
Also I decided to avoid subclassing the controller for the sake of picking entries as suggested by danh. Instead I set the controller as it's own delegate if no other controller wants to do the job.
This is like a default implementation which basically says "If nobody wants to be my delegate, I'm a drill down controller. Otherwise my delegate will know what to do with me" and though this example only makes use of didSelectRowAt: in reality I delegate often also other things like accessoryTypeFor: and custom functions like when I use the controller as a picker to de-/select multiple entries at once, displaying selected entries with checkmarks an so on.
Hopefully I understood danh correctly and I call the datasources in my viewcontroller after the model they hold together with "DataSource" whereas the DataSource object itself holds a "xxxCollection". Please correct me if I'm wrong. :)
So to sum up, the "naming convention" or "best practice" (as there is probably not a real one besides what we just came up with) is:
Be consistent with the naming
Make the name descriptive and don't violate common language agreements even if it makes the name a little longer (e. g. it's probably better to name it MyVeryOwnSuperDuperCoolViewController rather than MyVeryOwnSuperDuperCoolController if the controller is in fact a UIViewController except if you have very good reasons to do so and if you do, do it consistently)
Name your DataSource after the model it has a collection of (e. g. MyModelDataSource)
The DataSource should have a collection of the model (e. g. var myModelCollection: [MyModel]
I hope I haven't forgotten something. Still interested if somebody has written down something about this topic somewhere which could be considered a best practice or best-practice-suggestion.
I agree with your thinking that reducing vc size by factoring is a good idea, but whether you factor something, and by what means should depend on semantics.
Let me suggest a pattern with this example: Say your app is about driving, and somewhere within it, user must select a car. The view controller in this case might be (ought to be) named CarPickerViewController. Why? "Car" is obvious, "Picker" is a norm borrowed from elsewhere in the SDK culture, and "ViewController" is practically a rule (which you've opted to violate, but at least you've done so deliberately, reasonably, consistently, etc.).
Certainly, the most important noun in this story is a Car, a thing with wheels and a motor, and so on. But there's also an important collective noun, representing a group of zero or more cars. If they were geese, we'd call it a "gaggle", but my rule for nouns that don't have an existing specific collective noun is to add a collective suffix, as in CarCollection.
To recap so far, we have CarPickerViewController, which holds a CarCollection which wraps an array of Car. And certainly, that CarCollection can (should) conform to UITableViewDatasource.
What about the delegate? Here again, imo, we should be driven by semantics, not file arrangement. Probably, only job the delegate has in this example is to learn about selection in the table view. We could, even though it would be poor design, just let the presenting view controller conform to the table view delegate protocol this way:
// in CarPickerViewController.h
#property(weak,nonatomic) id<UITableViewDelegate> delegate;
// in CarPickerViewController.m
- (void)setDelegate:(id<UITableViewDelegate>) delegate {
self.tableView.delegate = delegate;
}
The presenting view controller would then set itself as the delegate, and implement tableView:didSelectRowAtIndexPath:, not crazy, but passing it a tableView? Which tableView? The better answer here is a new protocol, defined by the picker:
// in CarPickerViewController.h
#protocol CarPickerDelegate <NSObject>
- (void)carPicker:(CarPickerViewController *)vc didPickCar:(Car *)car;
#end
// in CarPickerViewController.m (which remains the table view delegate)
- (void)tableView:(UITableView *)didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Car *car = self.carArray[indexPath.row];
[self.delegate carPicker:self didPickCar:car];
}
Should we factor out an another class to be the UITableViewDatasource which does this translation? I say, no way! So to recap again:
CarPickerViewController - conforms to UITableViewDelegate
CarCollection - conforms to UITableViewDatasource
Car - a humble model
Just a little more: Let's say there was a drill-down interface where we could browse car details. I think we'll end up renaming our "picker" class to be more generically, CarViewController. It will have a table view of cars and not much else. A subclass, our original CarPickerViewController, will continue to implement the one method didSelectRowAtIndexPath as it did before, but it's superclass implementation will perform drill-down navigation on the same method.
I could go on, but I fear I've gone on too long. Hope this gives enough sense of an approach that you can apply in your app.
For your above way, you are going to write a lot of boilerplate codes and make it even more complicated.
There is a good practice to organize your Delegate is to use extension in stead of the above way.
For example:
class AController: UIViewController {
}
extension AController: BControllerDataSource {
}
extension AController: CControllerDelegate {
}
You even do not need to put in one file, you can separate it if your ViewController is too long. For example.
AController+BControllerDatasource.swift
AController+CControllerDelegate.swift
It makes ViewController look clean and keep related logic functions in separate groups.
I have an app that allows the user to create categories. A save screen appears with a UITextField. Although, I do not know how to save the user's entry, and allow a new UICollectionViewCell to be added into the current UICollectionView that contains the UITextField words that the user typed. Thanks!! This is also in Swift. This would be used in a UITableView: clothes.name = self.nameTextField.text ,but how would I convert this to a UICollectionView? **clothes is a variable
Your collection view has a data source where you initially have the model for your cells. In your case it's probably an array. The collection view looks at this data source and returns cells based on the entries contained in this data source.
What I am trying to get at is the following:
In order to show the newly created cell, you'll need to update your data source (I'll call it "the blueprint specification" the collection view adheres to) by adding the new cell's "specification" (I think I'm wording this in a more complex fashion than it actually is). Here is an example (If we assume that your data source is an array):
categoryArray.append(CategoryModel(title: yourTextField.text)) // update the data source
After you make changes to the data source you can tell the collection view to reload it's data:
collectionView.reloadData()
That will "parse" the data source to display the cells anew.
Let me know if anything is unclear.
EDIT
Regarding your comment -- given that you use a UINavigationController -- , you can achieve displaying the title like this:
You are probably using this method to go to your next view controller:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let category = categoriesArray[indexPath.row]
let detailVC = DetailViewController()
detailVC.title = category.title // your title
self.navigationController?.pushViewController(detailVC, animated: true, completion: nil)
}
Alternatively, in your DetailViewController you can do:
class DetailViewController : UIViewController{
var category : Category!
override func viewWillAppear(){
super.viewWillAppear()
self.title = self.category.title
}
}
I am displaying data in a collection view, I know how to pass the data on with prepareForSegue function but am trying to have the app determine which segue to use depending on the cell property data. (Each segue goes to a different view controller to display relevant information.)
For e.g.
If the cell.type is equal to "1" then perform segueOne if it is of type "2" then perform segueTwo.
I was trying to do something like this;
func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CollectionViewCell
if cell[indexPath].type = "1" {
performSegueWithIdentifier("showPage1", sender: self)
} else if self.cell[indexPath].type = "2" {
performSegueWithIdentifier("showPage2", sender: self)
} else { println("error when selecting cell to segue") }
}
However with this I get an error;
'CollectionViewCell' does not have a member named Subscript
Has anybody got any ideas ?
Assuming the items in your collection view can be re-arranged (or might be some time in the future), the indexPath will not be sufficient to give you the information which cell was selected. Thus, IMO your idea to give the cell a property is a feasible one.
The easiest "quick and dirty" way is to simply hardcode the segue identifier string into your cell. This is not the best design because you are introducing dependencies between app elements that should know of each other.
class MyCell : UICollectionViewCell {
var segue = "DefaultSegue"
}
Now calling the appropriate segue is really easy in didSelectItemAtIndexPath...
self.performSegueWithIdentifier(cell.segue, sender:cell)
It would of course be preferable to use an enum. Safer, more readable and better maintainable.
enum Segue : String {
case ToInfo = "SegueToInfo"
case ToLogin = "SegueToLogin"
// etc.
}
The ivar for MyCell would now be var : Segue = SomeDefaultValue and you can call it the same way.
BTW: Regarding your original question please note the following: as has been pointed out, you cannot subscript a cell. (UICollectionViewCell is not a Dictionary, so cell["key"] does not make sense.) Also, I am not a fan of dequeueing the cell in more than one place - instead you could call cellForItemAtIndexPath or do the work in that method in the first place, as I have suggested.
You're trying to index into a UICollectionViewCell, but of course that class is not an array, so you can't 'subscript' it.
My suggestion is to refactor your code. Whatever data you're storing in your cell you can presumably get from your data model, because that's where it originally came from. You are probably putting that in your cell in cellForIndexPath.
If that is the case, then there is no reason you can't get the same data from the same place in your func ... shouldSelectItemAtIndexPath ... -> Bool. I'd suggest doing it there. Your cell should only contain the data it needs to properly render itself to the screen.
See if that helps.