I have created a table view with static cells and it looks good in the main storyboard but when I run the application the contents of the table view is empty. I think this is because the numberOfSections and numberOfRowsInSections functions are resetting the table view to 0. When I remove these functions the table view appears as desired.
My questions is am I okay to leave these functions out and are there any other functions I should include to conform to good coding standards?
If you use static cells, you can remove both methods or change values to return the correct number of sections and methods.
You can also define all the content of cells in the storyboard or override the method :
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
It depends on what you are looking for.
Related
I implemented a custom mechanism to perform reordering using drag and drop in a UICollectionView, using a UILongPressGestureRecognizer.
I have a very simple UICollectionView with a custom UICollectionViewLayout.
My layout class is not a subclass of UICollectionViewFlowLayout.
Here is how it looks like:
When I start lifting a cell to begin the drag, I update a dropProposalIndexPath property in my custom layout object so it knows which cell needs to be hidden.
My custom UICollectionViewLayout subclass returns a UICollectionViewLayoutAttributes with the alpha property set to 0 for the cell which corresponds to dropProposalIndexPath like so:
if self.dropProposalIndexPath == indexPath {
itemLayoutAttributes.alpha = 0
}
It makes the lifted cell hidden thus creating a "hole" in the UICollectionView.
When the user moves its finger, I update the dropProposalIndexPath property of my custom UICollectionViewLayout subclass and invalidate its layout, then move the cell that acts as a hole at the new indexPath:
// Inform layout of the indexPath of the dropProposal
self.reorderableLayout?.dropProposalIndexPath = dropProposalIndexPath
// Move the cell
collectionView.moveItem(at: draggingIndexPath, to: dropProposalIndexPath)
Here is how it looks:
Pretty bad huh?
I implemented the exact same logic in a demo project using a subclass of UICollectionViewFlowLayout and the transition is seamless:
I guess I'm doing something wrong or forget to do something in my UICollectionViewLayout subclass.
I can post more code if needed.
Please note that using the built-in drag & drop mechanism (introduced with iOS 11) to perform the reordering is not an option, I want to use my custom implementation for various reasons.
In case someone encounters the same issue, the solution was to implement the following method in your UICollectionViewLayout subclass:
func initialLayoutAttributesForAppearingItem(at: IndexPath) -> UICollectionViewLayoutAttributes?
You then return a UICollectionViewLayoutAttributes with the alpha property set to 0 in the initialLayoutAttributesForAppearingItem function if the itemIndexPath parameter matches the indexPath of your dragging cell.
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.
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!
I had the suggestion to use function textDidChange() to perform code inside this function automatically. It is working. It is in my Controller class.
Now I am going to use this approach for creating automatically, inside of textDidChange(), a dictionary variable from text, when user is typing in NSTextView. Then, I need this dictionary for the NSTableView functions, to display a Table in application corresponding to the text typed by user.
Question is how to make accessible this dictionary from textDidChange(_:) function for the NSTableView functions like
numberOfRowsInTableView(tableView: NSTableView)
and
tableView(tableView: NSTableView, viewForTableColumn tableColumn:
NSTableColumn?, row: Int) -> NSView?
All in the same Controller class.
I am not sure if same dictionary will be your datasource for the tabelview .But for this method you can simply call reloaddata function of tableview to after updating your tableview datasource .
I have a tableview that is populated using a Parse query. However, all of the cells are loaded with the first object from the query results array. For example, if the query returns [User1,User2] the two cells in the tableview will show "User1". What am I doing wrong?
Here's a gist of the file:
https://gist.github.com/jtansley/2329c6fa4baa63f48ee2
Hopefully the issue is obvious to experienced programmers.
Thanks!
After reading your code, I think you don't know that findObjectsInBackgroundWithBlock is a function run in another thread but main thread.
Therefore, in func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell , your change in UI would be wrong.
One more things, to improve your app performance, you shouldn't query in cellForRowAtIndexPath! Do your queries in viewDidLoad instead. Fill your database on Parse to an array, then load it inside cellForRowAtIndexPath function.
I couldn't code for you since I'm hanging out with my gf. ;)
Hope this help.