I've been hunting around for ways to setup a UICollectionView for an iOS app. Google only turns up a few blogs with posts about what it is, not how it works. Then of course, there's the Apple documentation which is helpful, but doesn't provide as much information as I'd like to be able to setup a UICollectionView.
How can one setup a UICollectionView?
The uicollectionview class is almost identical to the uitableview Class. They share many of the same methods and functions. And if the methods / functions are different, most of the time it's just a matter of swapping out "row" for "cell" and vice versa. However there are a few methods that don't exist on UICollectionView that do on UITableView. First though, I'll explain how to setup a UICollectionView:
Begin by adding your UICollectionView to a current ViewController, or creating a new UICollectionViewController. The steps aren't that much different for the view and controller.
If you're using the View and not the ViewController, make sure that the Delegate and DataSource of the CollectionView is the view controller it's on. Also make sure to add the Delegate and DataSource to your header file: <UICollectionViewDataSource, UICollectionViewDelegate>
Next, make sure to include these three methods in your view controller's class:
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
These are the only required methods. The first tells the collection view the number of sections it should have. This should return an integer value. The second method gets the number of cells in each section. Again, this should return an integer value. The last method populates each cell using the data given (usually from an NSArray). This last method should return a CollectionViewCell. If you set breakpoints on this method, you'll notice that it is called once for every cell defined in the numberOfItemsInSection method.
UICollectionViews provide advanced animation methods and allow cells to be deselected and selected (similar to apps like Pages when in 'Edit' mode). However, to my knowledge, UICollectionViews do not provide features such as "swipe to delete" or and kind of disclosure indicator.
UICollectionViews also allow you to create custom cells using xib (AKA nib) files, this allows for some very advanced-looking and unique interfaces without lots of complicated code.
Sadly, UICollectionView is only supported in iOS 6 and up. There are a few projects available such as PSTCollectionView which adds support for CollectionViews in iOS 4.3+, but I haven't figured out how to use them. In my case, when the view loads I just check if the UICollectionView Class is available and if it isn't then I load a Table instead.
Here is a link to Apple's official documentation on Collection Views. You might also want to check out this tutorial.
I created a step-by-step tutorial for setting up UICollectionViews with custom layouts. Hopefully it helps some people to get familiar with the API.
Related
My iPad and iPhone interfaces use a UICollectionView and a UITableView respectively. In each case, there is a lot of commonality:
each has the same number of sections (hard-coded)
each section has the same number of respective rows/items (derived from the same data source)
each has identical cell content (these are custom views built using auto layout so are suitable for both cases)
each has identical section headers and footers (again, custom content)
each can respond to certain notifications in the same way (e.g. when new data is received, refresh the data source) but with custom parts also (reload the UITableView vs reload the UICollectionView)
each will present the same controllers via cell selection, though the UITableView will push the new controller and the UICollectionView will use a popover.
I am using a shared parent class to cater to some of this. This approach seems especially well suited to the data requirements - the parent class builds the data and is responsible for maintaining it. I use two subclasses to present the data - one with a UITableView and the other UICollectionView.
The presentation side is a little less clean. To take the simplest example, when the table/collection view needs to know the number of sections, in each case I am relying on a customized method in the parent:
return [super sectionsCount]
This allows me to set many values only once and have both views updated.
Then comes the part that is working poorest. Again, to simplify, consider the header view for the first section. In both cases, this should be identical. I have a custom UIView subclass that I want to use, with a couple of properties that will be set the same. The problem here is that the re-usable header for a table view section expects a UITableViewHeaderFooterView and the counterpart for a collection view expects a UICollectionReusableView. So to accommodate this, I'm having to create subclasses of these simply to hold the header view I want in both. So in summary:
What I want: UITableView and UICollectionView should use the same UIView subclass as the header for section. The custom properties of that view should be set identically in each case.
What I am having to do:
Build the required UIView subclass for the header.
Build a UITableViewHeaderFooterView subclass that holds one such header. It's init does nothing more than add a header view.
Build a UICollectionReusableView subclass that equally does nothing more than add a header view.
When the collection view needs a header, create an instance of the UITableViewHeaderFooterView subclass. Set its properties.
When the table view needs a header, create an instance of the UITableViewHeaderFooterView subclass. Set its properties.
Once I have to do this for footers, and especially cells, things are getting kinda icky. I have three times the classes I should need, and I'm repeating all of my code for setting custom properties of the view.
How can I best re-use this logic between the UICollectionView and UITableView?
You can just create stock UICollectionReusableViews, UITableViewHeaderFooterViews, etc. and add your custom view as a subview. If you need to access the custom view later, you can set its tag property and use [view viewWithTag:].
Ok, so I have this issue where I need to get access to the headers/footers displayed in a UITableView. Moreover, I need to do this from a subclass of UITableView (so I can't simply assign tags to the view from the UITableView Delegate). UITableView does keep an array of visible headers and footers but it provides no access to those arrays even to the subclass, which I personally think is asinine.
I need this so that I can provide a custom drag-n'-drop insertion/move user interface. I'm trying to get it to work almost exactly like Apple's own rearranging interface, but of course, with my own custom implementation (like the ability to drag from another table). Everything works perfectly except for the headers/footers.
At the moment I'm trying to hack it by iterating through all the subviews of UITableView. I'm pretty sure that the only subviews of UITableView is: backgroundView, UITableViewCells, and Headers/Footers. So I just need to determine which are UITableViewCells (very easy), which is the background view (also easy) and which are headers/footers (not so easy). I'm pretty sure I can do it this way but it's definitely a hack and I'd rather find a better way if possible.
UPDATE
I'm overriding the - (void) setDelegate:(id<UITableViewDelegate>)delegate method and checking to see if the delegate responds to the appropriate selectors to generate headers/footers (and set BOOL's appropriately). This is making it a lot easier to determine what a particular subview is since the progression of header -> cells -> footer is predictable.
You say you can't use UITableView delegate methods, but did you consider letting the UITableView subclass object be its own delegate? Create a separate property on the subclass called realDelegate and pass any other delegate calls through to that.
I'm still struggling with the view-based NSOutlineView in my little Cocoa application. I'm trying model my OutlineView after the finder one. When the Finder OutlineView loses focus (e.g. clicking any folder on the right side), the selected row (e.g. Desktop) stays selected with the bright blue gradient and does not change to the inactive blue-grey gradient.
I'd like to duplicate this behaviour in my application.
In a not view-based OutlineView I was able to subclass NSOutlineView and reimplement (void)highlightSelectionInClipRect:(NSRect)clipRect, so that each highlighted row could be supplied with the bright blue background image.
However, now with my view-based OutlineView (set to SourceList style) this method apparently is not even called. I've even implemented (id)_highlightColorForCell:(NSCell *)cell to return nil, as some sites suggest, but that doesn't help either.
Any hints on how I can set the highlight gradient in the view-based OutlineView?
Are you doing any custom drawing that could be messing with things? As far as I can tell all the selection drawing is handled for you normally, check out the TableViewPlayground example (not Source-list style by default but that's an easy change to the XIB).
But failing that, according to the Mac OS X 10.7 doc entry on highlightSelectionInClipRect:
Note: This method should not be subclassed or overridden for a view-base table view.
Instead, row drawing customization should be done by subclassing NSTableRowView.
So I think (I haven't tried any of this) like you'd want to subclass NSTableRowView, override drawSelectionInRect: (there's an example in TableViewPlayground, and draw your selection. You could check for the app being active with [NSApp active] or maybe use the self.emphasized property like the example does.
You'd then return one of your custom NSTableRowViews in the NSOutlineViewDelegate protocol method (10.7 only!): (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
Hope this works/helps!
I should note that TableViewPlayground example uses the outlineView:viewForTableColumn:item: delegate method by default and does everything with NSTableViewCells, but if you add the rowViewForItem method I mention above it is called. So I'm guessing you could use it to return a view for each row in it's entirety.
Thanks, with your hint I was able to solve the problem quite easily. I subclassed NSTableRowView and overwrote -(BOOL) isEmphasized to always return true.
I then implemented -(NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item in my OutlineViews delegate to return a item specific instance of my subclass by calling ClbTableRowView *result = [outlineView makeViewWithIdentifier:identifier owner:self];
Edit: Besides that, there also seems to be a pretty hidden way of using the custom NSTableRowView subclass by dropping a new NSView Object into the the OutlineView in Interface Builder. Then set the views class to your subclass and give it a userinterface item identifier of "NSTableViewRowViewKey", according to the Apple documentation.
I have a UITableView which gets loaded with data from a MutableArray. The thing is, within this array I have many different attributes, therefore I would like to enter these attributes into a custom cell programmatically.
Also I would like to include a Slider within the Cell so there will need to be some sort of addition there.
You say you've looked at tutorials, but have you checked out the "UITableView construction, drawing and management (revisited)" post on http://cocoawithlove.com - this pretty much covers the full custom UITableViewCell gamut, including loading a custom view from a NIB.
Irrespective, one potential approach is to create your own custom cell that expends the UITableViewCell and simply add a UISlider property which you'd then set up within the - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier method and add it as a sub view via [[self contentView] addSubview:yourUISlider];, etc.
However, I'd be tempted to have a good look at the http://cocoawithlove.com article, and download the provided project as this will show you the available options in full.
I'm trying to set up very basic drag and drop for my NSTableView. The table view has a single column (with a custom cell). The column is bound to an NSArrayController, and the array controller's content array is bound to an NSArray on my controller object. The data displays fine in the table. I connected the dataSource and delegate outlets of the table view to my controller object, and then implemented these methods:
- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard
{
NSLog(#"dragging");
return YES;
}
- (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id <NSDraggingInfo>)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)op
{
return NSDragOperationEvery;
}
- (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id <NSDraggingInfo>)info
row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation
{
return YES;
}
I also registered the drag types in -awakeFromNib:
#define MyDragType #"MyDragType"
- (void)awakeFromNib
{
[super awakeFromNib];
[_myTable registerForDraggedTypes:[NSArray arrayWithObjects:MyDragType, nil]];
}
The problem is that the -tableView:writeRowsWithIndexes:toPasteboard: method is never called. I've looked at a bunch of examples and I can't figure out anything I'm doing wrong. Could the problem be that I'm using a custom cell? Is there something I'm supposed to override in the cell subclass to enable this functionality?
EDIT: Confirmed. Switching the custom cell for a regular NSTextFieldCell made dragging work. Now, how do I make drag and drop work with my custom cell?
I was banging my head against the wall in search of a more elegant solution to the same problem, and then I came across this:
http://www.wooji-juice.com/blog/cocoa-10-bindings.html
For instance, if you want to support drag-and-drop from a table, you need to set up a data source for it — even if you’re using bindings to supply the actual data, you can set a data source on it, and Cocoa needs one to handle the tableView:writeRowsWithIndexes:toPasteboard and related messages.
Yep. If you've got everything bound to your array controller, what you can do is to have the array controller implement the necessary drag/drop functions and then set the table view's data source to the array controller with setDataSource:.
I fixed the issue. There seems to be an issue with using bindings with custom NSCells in a table view. Switching to the traditional NSTableViewDataSource methods rather than bindings and an array controller solved it.
What works for me is to call initTextCell rather than init or initImageCell within the initializer of my custom cell (in my case, init). It doesn't seem to matter whether the superclass is NSCell or NSActionCell. Also, I have binding, and dragging still works.
That should be sufficient to allow the drag to start. Are you sure you've connected the delegate methods?
I ran into this problem, your custom cell needs to extend NSActionCell not NSCell if you want drag and drop to work properly. There is probably something you could implement in NSCell that would make it all work too, but I didn't dig any further after switching to NSActionCell. At least, that fixed the issue for me.
I ran into the problem. I have a NSCell subclass, and I did implement the tableView:writeRowsWithIndexes:toPasteboard and the dataSource was set for the NSTableView. Dragging would not work.
If I set the cell type in my init for the custom subclass
self.type = NSTextCellType;
Then I get dragging. If I don't, it defaults to NSNullCellType and dragging doesn't work. I'm guessing the people who got it working by using another subclass NSTextFieldCell works because the cell type is different.
I also observe the similar issue, NStableView drag & drop is not working. I have 4 column in my tableview and two of them are custom cells. Dragging for non-custom cell is working fine even though its working on cell separator as well however its not working with custom cells.
My custom cell was subclassed from NSButtonCell which was causing the
issue. So as suggested, I changed my parent class from NSButtonCell to
NSActionCell. Now, dragging is working perfectly.
It works with both NSCell as well as NSActionCell however I required action on my cell so used NSActionCell.