NSCollectionViewItem never instantiate - swift

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!

Related

Move hidden UICollectionViewCell seamlessly with a custom UICollectionViewLayout

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.

How to respect MVC in Swift?

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.

UITableViewController Static Cells File Configuration (Swift)

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.

Purpose of tableView(_:objectValueFor:row:)

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.

Copy NSTableView row greyed out

I would like to add copy (Command + C) functionality to my NSTableView for a row (or multiple rows) that the user has selected. Based on what I read the copy functionality in the Menu Item should be active automatically if you implement copy in your TableView Controller Delegate.
I've tried adding in the NSTableViewController (is the delegate and data source of the table view) the following things with no luck, the copy menu item still appears greyed out:
override func copy() -> AnyObject {
print("TEST: copytriggered") /Here i would copy to clipboard the selected rows/
return true
}
2.
Conforming to NSCopying, implementing copyWithZone...
3.
Trying 1 and 2 in the window controller and app delegate.
Adding func tableView(_ tableView: NSTableView,
pasteboardWriterForRow row: Int) -> NSPasteboardWriting? in the TableView data source.
I have no clue what to do at this point, any help appreciated.
Thanks,
Marc
Just got the answer, publishing here for future reference.
You are confusing the Copy edit action method with the methods for making a duplicate of an object. The NSCopying protocol and the copyWithZone() method are for the latter, enabling an object to create a copy of itself. Likewise, the copy() method is just a convenience cover method for copyWithZone().
The action method for the Copy edit menu item is subtly different. It's "func copy(_ sender: AnyObject?)". That's the general form for all action methods, taking a sender argument and not returning anything. You need to implement that method.
Finally, tableView(_:pasteboardWriterForRow:) is generally only used for drag-and-drop, when the user drags a row.