NSCollectionViewItem does not display custom view - objective-c

I have a subclass of NSCollectionViewItem, called ZMSDKThumbnailCollectionViewItem. Using this, I want to display a custom view represented by ZMSDKThumbnailVideoItemView.
ZMSDKThumbnailCollectionViewItem is represented by a XIB file. This contains exactly one NSView which's class is set (using Identity Inspector) to ZMSDKThumbnailVideoItemView.
#interface ZMSDKThumbnailCollectionViewItem : NSCollectionViewItem
#property (assign) IBOutlet ZMSDKThumbnailVideoItemView *videoItemView;
#end
In my NSCollectionViewDataSource I create items like this:
- (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath
{
ZMSDKThumbnailCollectionViewItem* item = [collectionView makeItemWithIdentifier:#"ZMSDKThumbnailCollectionViewItem" forIndexPath:indexPath];
ZMSDKThumbnailVideoItemView* thumbnailView = [_thumbnailVideoArray objectAtIndex:indexPath.item];
// IBOutlet videoItemView
item.videoItemView = thumbnailView;
return item;
}
Problem: although _thumbnailVideoArray contains elements, nothing is displayed in the collection view.
When I modify ZMSDKThumbnailCollectionViewItem so that it contains a NSLabel instead of a ZMSDKThumbnailVideoItemView, the items are being displayed properly.
Question: in which manner do I have to create a NSCollectionViewItem to display a custom view? Is it correct to set the class of the containing view in the XIB file to ZMSDKThumbnailVideoItemView like I did?

After all I found the solution.
Instead of changing the class of the item's view, one has to add a sub view to the item's view:
- (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath
{
ZMSDKThumbnailCollectionViewItem* item = [collectionView makeItemWithIdentifier:#"ZMSDKThumbnailCollectionViewItem" forIndexPath:indexPath];
ZoomSDKVideoElement* thumbnailView = [_videoArray objectAtIndex:indexPath.item];
[item.view addSubview:[thumbnailView getVideoView]];
return item;
}

Related

NSCollectionView is drawing NSCollectionViewItems over each other

My NSCollectionView is drawing my NSCollection items over each other.
UPDATE: I have added a sample project
GitHub Sample Project
UPDATE: This has changed somewhat
When the app first launches it looks like this
UPDATE
My current example has two views which are currently in there own nib files, with dedicated NScollectionViewItem objects they are currently the same for testing. I basically have a NSCollectionViewItem which has as it's child a view with the NSTextField in it. With all of the constraints.
For the Collection View it is setup as a Grid Controller, and ideally, I would like to have 1 column.
In order to load it with Data I made my ViewController the NSCollectionViewDataSource, and implemented the - (NSInteger)collectionView:(NSCollectionView *)collectionView numberOfItemsInSection:(NSInteger)section and - (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView
itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath
UPDATED CODE
Full Code included:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[collectionView registerClass:ItemOne.class forItemWithIdentifier:#"Item1"];
[collectionView registerClass:ItemTwo.class forItemWithIdentifier:#"Item2"];
cellArray = [#[#"Item1", #"Item2", #"Item1", #"Item2", #"Item1"] mutableCopy];
}
- (void)setRepresentedObject:(id)representedObject {
[super setRepresentedObject:representedObject];
// Update the view, if already loaded.
}
#pragma mark - NSCollectionViewDatasource -
- (NSInteger)collectionView:(NSCollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section {
// We are going to fake it a little. Since there is only one section
NSLog(#"Section: %ld, count: %ld", (long)section, [cellArray count]);
return [cellArray count];
}
- (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView
itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"IndexPath: %#, Requested one: %ld", indexPath, [indexPath item]);
NSLog(#"Identifier: %#", [cellArray objectAtIndex:[indexPath item]]);
NSCollectionViewItem *theItem = [collectionView makeItemWithIdentifier:[cellArray objectAtIndex:[indexPath item]] forIndexPath:indexPath];
return theItem;
}
UPDATE
The ItemOne and ItemTwo classes are both empty classes, the nib for each has a NSCollectionViewItem which in turn has a view, with label. The View is connected to the NSCollectionViewItem by the view property in NSCollectionViewItem. There are currently no constraints except for the default ones
The NSCollectionView grid is set up as follows:
Layout: Grid Dimensions: Max Rows: 0 Max Columns: 1 Min Item Size:
Width: 250 Height: 150 Max Item Size: Width: 250 Height: 150
This is the code for setting up the whole thing, at this point not tying it to a data source.
It seems that no matter what I change the settings or even changing the CollectionView type to Flow doesn't change anything, it looks the same.
I have been approaching this as an AutoLayout issue because originally there were some auto layout issues, but those have all been resolved.
Any help would be greatly appreciated.
The data array should hold data instead of NSCollectionViewItems. In collectionView:itemForRepresentedObjectAtIndexPath: you call makeItemWithIdentifier:forIndexPath:. Call registerClass:forItemWithIdentifier: or registerNib:forItemWithIdentifier: to register your class or nib.
More info in the documentation of NSCollectionView, collectionView:itemForRepresentedObjectAtIndexPath: and makeItemWithIdentifier:forIndexPath:.
EDIT:
There are two ways to provide a NSCollectionViewItem.
registerClass:forItemWithIdentifier:. When the collection view needs a new item, it instatiates this class. NSCollectionViewItem is a subclass of NSViewController and NSViewController looks for a nib with the same name as the class. The NSCollectionViewItem is the owner of the nib.
registerNib:forItemWithIdentifier:. When the collection view needs a new item, it loads this nib. The NSCollectionViewItem is a top level object in the nib.
You mixed registerClass:forItemWithIdentifier: with a xib for use with registerNib:forItemWithIdentifier:. Use registerNib:forItemWithIdentifier: or fix the xib.
I have it figured out.
And have made a github repo with a working version Working Version of Collection View Sample Application
First thing. Thanks to Willeke's catch of the way the original xib was setup I was able to get the Grid type working. But in the end the Grow view is a better type of view if you can make it do what you want, because it support sections, and distances between views etc. So eventhough I started out wanting to use the Grid type I am going to implement the Grow type in my app.
So I accomplished a single column view using the Grow type.
My Criteria for success are:
That it can support non-uniform view heights (Each custom view can have it's own height)
That there is a single column, and each custom view expands if the view size expands.
Onto the source code:
#interface ViewController ()
#property NSMutableArray *cellArray;
#property (weak) IBOutlet NSCollectionView *collectionView;
#end
#implementation ViewController
#synthesize cellArray;
#synthesize collectionView;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[collectionView registerClass:ItemOne.class forItemWithIdentifier:#"Item1"];
[collectionView registerClass:ItemTwo.class forItemWithIdentifier:#"Item2"];
cellArray = [#[#"Item1", #"Item2", #"Item1", #"Item2", #"Item1"] mutableCopy];
}
- (void)setRepresentedObject:(id)representedObject {
[super setRepresentedObject:representedObject];
// Update the view, if already loaded.
}
#pragma mark - NSCollectionViewDatasource -
- (NSInteger)numberOfSectionsInCollectionView:(NSCollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(NSCollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section {
// We are going to fake it a little. Since there is only one section
NSLog(#"Section: %ld, count: %ld", (long)section, [cellArray count]);
return [cellArray count];
}
- (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView
itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"IndexPath: %#, Requested one: %ld", indexPath, [indexPath item]);
NSLog(#"Identifier: %#", [cellArray objectAtIndex:[indexPath item]]);
NSCollectionViewItem *theItem = [collectionView makeItemWithIdentifier:[cellArray objectAtIndex:[indexPath item]] forIndexPath:indexPath];
theItem.representedObject = [cellArray objectAtIndex:[indexPath item]];
return theItem;
}
#pragma mark - NSCollectionViewDelegate -
- (NSSize)collectionView:(NSCollectionView *)collectionView
layout:(NSCollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"%#", indexPath);
NSSize size = NSMakeSize(438, 150);
NSInteger width = 0;
NSInteger height = 0;
NSString *label = [cellArray objectAtIndex:[indexPath item]];
NSRect collectionFrame = [collectionView frame];
width = collectionFrame.size.width;
// TODO: This needs to be based on the actual value of the view instead of hardcoding a number in.
if ([label isEqualToString:#"Item1"]) {
height = 114;
} else if ([label isEqualToString:#"Item2"]) {
height = 84;
}
size = NSMakeSize(width, height);
return size;
}
#end
And there you have it. The implementation wasn't too bad. Each of the Custom views that show up in the NSCollectionView are defined in there own NSCollectionViewItem and .xib file, so they are easily modifiable.
The only part that is brittle is where I am calculating the height of each view, and it is only brittle because I am being lazy in my implementation in the sample application. In the actual implementation I will dynamically grab them from the actual views, so that they aren't tied to a static number.

Collectionview cell to open new view

I have a collection view with 6 cells in it how do I have each cell open a new viewcontroller when clicked?
In order to achieve your goal you need to edit 2 files:
In the Storyboard you need to add a cell view for each static cell in your Collection View. Just drag as many UICollectionViewCells onto the Collection View. For each cell you need to define a unique reusable identifier (meaning a name like CellType1) in the Attributes Inspector when a cell is selected in the Storyboard's Document Outline. And then control-drag from each cell to the desired target view controller to create a Push segue (provided that your collection view is associated with a UINavigationController).
Create a subclass of UICollectionViewController and assign it in the Storyboard's as the Collection View Controller's class (see the Identity Inspector).
In your UICollectionViewController subclass implement the following methods:
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
// Enter the number of static cells that are present in the Storyboard's collection view:
return 3;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
// Enter the reusable identifiers that are defined for each cell in the Storyboard's collection view:
NSArray *cellIdentifiers = #[#"CellType1", #"CellType2", #"CellType3"];
NSInteger cellIdentifierIndex = indexPath.item;
// Make one identifier the default cell for edge cases (we use CellType1 here):
if (cellIdentifierIndex >= cellIdentifiers.count) cellIdentifierIndex = 0;
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifiers[cellIdentifierIndex] forIndexPath:indexPath];
// Configure the cell …
return cell;
}
I've created a demo app to show a full implementation: https://github.com/widescape/StaticCollectionViewCells

Customized NSTableCellView's IBOutlet is null

I have a NSTableView, its data source and delegate have been set. And I want to customize the cell, so I dragged a view-based cell view from the library. Then I created a class ServiceCell which inherits from NSTableCellView in order to fully control my special cell. After that, I control-drag from the nib file to the cell class to create the IBOutlet properties of the image and text field in the cell.
In the NSTableView's delegate methods, I wrote this:
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
// Get a new ViewCell
ServiceCell *cellView = [tableView makeViewWithIdentifier:#"ServiceCell" owner:self];
NSLog(#"Field = %#", cellView.textField); //which turns out to be null!!!
if( [tableColumn.identifier isEqualToString:#"ServiceColumn"] )
{
cellView.serviceImage.image = nil;
cellView.nameLabel.stringValue = #"Hello";
return cellView;
}
return cellView;
}
As you can see, the text field is null! But makeViewWithIdentifier: has found the cell in Interface Builder and displayed the cell in the app window. I just cannot set it's value, Why?
The problem is you are accessing your textfield but not accessing its textvalue. Try your log statement like this below:-
NSLog(#"Field = %#", cellView.textField.stringValue);
in Table view select cell and give identifier name (same as you are using in code, for your snipped it will be #"ServiceCell"), your code part is right it will work.

Xcode / ObjectiveC - Convert UITableViewController into TableView embedded in a UIViewController

I am fairly new to this Native App dev - I have built an app which contains a UITableViewController to display messages - all works fine - but for styling reasons I need to change it from a TableViewController to a tableview embedded within a viewcontroller.
I have made a view controller containing a table view and relevant linked custom cells / fields and altered the associated header file to -
#interface NotificationsListTVController : UIViewController
but my table methods no longer fire and I'm not sure how to instantiate them?
(code below)
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return self.GPTNotifications.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
static NSString *CellIdentifierRead = #"CellRead";
UITableViewCell *cell;
notifications *n = [self.GPTNotifications objectAtIndex:indexPath.row];
if (n.read == false) {
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
CustomCellRead *cellReadB = (CustomCellRead *)cell;
cellReadB.notifTitle.text = n.notifTitleD;
cellReadB.notifDate.text = n.notifDateD;
cellReadB.notifMsg.text = n.notifMessage;
return cellReadB;
}
else {
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifierRead forIndexPath:indexPath];
CustomCell *cellReadB = (CustomCell *)cell;
cellReadB.notifTitle.text = n.notifTitleD;
cellReadB.notifDate.text = n.notifDateD;
cellReadB.notifMsg.text = n.notifMessage;
return cellReadB;
}
}
Are you setting the delegate and datasource of your tableview to your class?
Something like:
self.myTableView.delegate = self;
self.myTableView.dataSource = self;
When you create a UITableViewController this is done for you, but if you add the table yourself you need to set them.
Also:
#interface NotificationsListTVController : UIViewController <UITableViewDelegate, UITableViewDataSource>
I do it this way in Interface Builder:
Make your TableViewController
Make your ViewController and add a ContainerView to it
Delete the segued embedded ViewController that comes with it
Select the ContainerView and draw a connection from viewDidLoad to your TableViewController
you'll get only once option: embed
Done. Your TableViewController will now get displayed within your ViewController.
Pass whatever Data you need forward from the ViewController to the TableViewController with the embedded Segue.
Make the following changes in NotificationsListTVController.h:
#interface NotificationsListTVController : UIViewController<UITableViewDataSource,UITableViewDelegate>
Also in NotificationsListTVController.m, dont forget to provide these two statements as well.
tableView.delegate=self ;
tableView.dataSource=self;
These are required to set the delegate methods.These two statements need to be provided after initializing your tableView. Like for instance :
tblView = [[UITableView alloc] initWithFrame:CGRectMake(100,200,320,420) style: UITableViewStyleGrouped];
tblView.delegate = self;
tblView.dataSource = self;
[self.view addSubview:tblView];
These methods you are referring to are the delegate methods which cannot be fired directly unlike other ordinary methods.
Hope it Helps!!!

Handling multiple UISwitch controls in a table view without using tag property

I have a table view controller with multiple UISwitch controls in them. I set the delegate to the table view controller with the same action for all switches. I need to be able to determine what switch was changed, so I create an array of strings that contains the name of each switch. The indexes in the array will be put in the tag property of each UISwitch.
However, I'm ready using the tag property for something else, namely to find the right control in the cell in cellForRowAtIndexPath with viewWithTag! (There are several things I need to set within each cell.)
So, am I thinking along the right lines here? I feel I'm rather limited in how I find out exactly which UISwitch changed its value, so I can do something useful with it.
I fixed this by subclassing UISwitch like so:
#interface NamedUISwitch : UISwitch {
NSString *name;
}
It seems elegant (no index arrays required) and the tag property is free to do whatever it wants.
I read that you have to be careful with subclassing in Objective-C, though...
I have written a UISwitch subclass with a block based hander for value change control events which can help when trying to track which switch's value has changed. Ideally, we could do something similar with composition rather than subclassing, but this works well for my needs.
https://gist.github.com/3958325
You can use it like this:
ZUISwitch *mySwitch = [ZUISwitch alloc] init];
[mySwitch onValueChange:^(UISwitch *uiSwitch) {
if (uiSwitch.on) {
// do something
} else {
// do something else
}
}];
You can also use it from a XIB file, by dragging a switch onto your view, and then changing its class to ZUISwitch
You are close in your approach. What I have done in similar situations is create separate UITableViewCell subclasses, set the tag of the UISwitch to be the index.row of the index path, and only use that UITableViewCell subclass in a specific section of the table view. This allows you to use the tag of the cell to uniquely determine what cell has the event without maintaining a separate index list (as it sounds like you are doing).
Because the cell type is unique, you can than easily access the other elements of the cell by creating methods/properties on the UITableViewCell Subclass.
For example:
#interface TableViewToggleCell : UITableViewCell {
IBOutlet UILabel *toggleNameLabel;
IBOutlet UILabel *detailedTextLabel;
IBOutlet UISwitch *toggle;
NSNumber *value;
id owner;
}
#property (nonatomic, retain) UILabel *toggleNameLabel;
#property (nonatomic, retain) UILabel *detailedTextLabel;
#property (nonatomic, retain) UISwitch *toggle;
#property (nonatomic, retain) id owner;
-(void) setLable:(NSString*)aString;
-(void) setValue:(NSNumber*)aNum;
-(NSNumber*)value;
-(void) setTagOnToggle:(NSInteger)aTag;
-(IBAction)toggleValue:(id)sender;
#end
In:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// ... prior iniitalization code for creating cell is assumed
toggleCell.owner = self;
[toggleCell setLable:#"some string value"];
[toggleCell setTagOnToggle:indexPath.row];
toggleCell.owner = self;
return toggleCell;
//... handle cell set up for other cell types as needed
}
Owner is the delegate for the cell and can then be used to initiate actions in your controller. Make sure you connect your UISwitch to the toggleValue Action, so that you can initiate actions in the delegate when the UISwitch changes state:
-(IBAction)toggleValue:(id)sender;
{
BOOL oldValue = [value boolValue];
[value release];
value = [[NSNumber numberWithBool:!oldValue] retain];
[owner performSelector:#selector(someAction:) withObject:toggle];
}
By passing the UISwitch with the method call, you can then access the index path for the cell. You could also bypass the use of the tag property by explicitly having an ivar to store the NSIndexPath of the cell and then passing the whole cell with the method call.
I realize I'm about three years late to the party but I've developed a solution without subclassing that I think is preferable (and simpler). I'm working with the exact same scenario as Thaurin's described scenario.
- (void)toggleSwitch:(id) sender
{
// declare the switch by its type based on the sender element
UISwitch *switchIsPressed = (UISwitch *)sender;
// get the indexPath of the cell containing the switch
NSIndexPath *indexPath = [self indexPathForCellContainingView:switchIsPressed];
// look up the value of the item that is referenced by the switch - this
// is from my datasource for the table view
NSString *elementId = [dataSourceArray objectAtIndex:indexPath.row];
}
Then you want to declare the method shown above, indexPathForCellContainingView. This is a seemingly needless method because it would appear at first glance that all you have to do is identify the switch's superview but there is a difference between the superviews of ios7 and earlier versions, so this handles all:
- (NSIndexPath *)indexPathForCellContainingView:(UIView *)view {
while (view != nil) {
if ([view isKindOfClass:[UITableViewCell class]]) {
return [self.myTableView indexPathForCell:(UITableViewCell *)view];
} else {
view = [view superview];
}
}
return nil;
}