I have a UICollectionView controller embedded inside a navigation controller. The collectionView lists projects and each cell is supposed to segue to a ProjectDetail screen.
I simply cannot get the segue to trigger. If I simply drop a button on the nav bar and hook up a segue to the detail, it works. But triggering from my CollectionView cell doesn't.
Here is what the storyboard looks like: http://cl.ly/RfcM I do have a segue hooked up from the CollectionViewCell to the ProjectDetailViewController
Here's the relevant code inside my ProjectDetailViewController:
#interface ProjectCollectionViewController () {
NSArray *feedPhotos;
Projects *projects;
}
#end
#implementation ProjectCollectionViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.collectionView registerClass:[FeedViewCell class] forCellWithReuseIdentifier:#"cell"];
[self loadData];
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"selected %d", indexPath.row);
Project *project = [projects getProject:indexPath.row];
NSLog(#"project = %#", project);
}
- (void)loadData {
[self.projectLoader loadFeed:self.username
onSuccess:^(Projects *loadedProjects) {
NSLog(#"view did load on success : projects %#", loadedProjects);
projects = loadedProjects;
[self.collectionView reloadData];
}
onFailure:^(NSError *error) {
[self handleConnectionError:error];
}];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return projects.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"cell";
FeedViewCell *cell = (FeedViewCell *) [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
cell.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:1.0 alpha:1.0];
UIImageView *cellImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
Project *project = [projects getProject:indexPath.row];
NSString *imageUrl = [project coverPhotoUrl:200 forHeight:200];
NSLog(#"imageurl =>%#", imageUrl);
if (imageUrl) {
[cellImageView setImageWithURL:[NSURL URLWithString:imageUrl]];
}
[cell addSubview:cellImageView];
cell.imageView = cellImageView;
return cell;
}
I'm guessing the problem is somewhere in how I'm hooking up the Cells to the CollectionView.
Any help would be greatly appreciated!
You cannot create segues directly from cells in a storyboard because the collectionview is populated dynamically through the data source. You should use the collectionView:didSelectItemAtIndexPath: and perform the segue programatically using performSegueWithIdentifier:sender:. Something like this:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
[self performSegueWithIdentifier:#"MySegueIdentifier" sender:self];
}
where MySegueIdentifier is the identifier of the segue defined in storyboard.
TLDR: FOR A STORYBOARD, do not call registerClass:forCellWithReuseIdentifier:. It overrides what the storyboard sets up for the cell (including how segues are handled):
How to set a UILabel in UICollectionViewCell
Brief setup
Used a storyboard
Created a new collection view controller using the Xcode template,
setting it as a subclass of UICollectionViewController.
Initially used the default UICollectionViewCell, adding a UILabel
programmatically.
The generated UICollectionViewController code registered the cell in viewDidLoad:
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:reuseIdentifier];
First Issue:
The prepareForSegue:sender: event was not firing, which brought me to this answer .
I implemented the UICollectionViewDelegate and collectionView:didSelectItemAtIndexPath: event, then called the segue programmatically.
This fixed my first issue.
Second Issue: I switched to a custom cell containing one label. After hooking everything up, the cell label was not displaying.
After some digging, I found a solution contained in the link at the top of my answer.
Third Issue and Solution: I removed the registerClass:forCellWithReuseIdentifier: line. When I ran my app, the label appeared correctly, but when I tapped a cell, it called the prepareForSegue:sender event twice. By removing the registerClass:forCellWithReuseIdentifier line, the cell was processing cell touches directly, without the need of the delegate method. This is how I expected the storyboard to work. I deleted the collectionView:didSelectItemAtIndexPath: event, which resolved the double-firing of prepareForSegue:sender:. If you are using a storyboard, do not register the cell class. It overwrites what storyboard sets up.
Have you made your CollectionView Cell's connection in Triggered Segues on selection?
You can also trigger a segue programatically using
[self performSegueWithIdentifier:#"segueIdentifier" sender:nil]
in
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
Equivalent Swift code for similar question.
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier(#"TargetSegway", sender: self)
}
Make sure, in case if your cell has other overlapping views, "User Interaction Enabled" is unchecked (you can find this option, under attribute inspector View/Interaction). Otherwise, your Tap Gesture is consumed by the overlapping view, didSelectItemAtIndexPath may not be called.
Related
I have created a View Controller with a Navigation bar and UiCollectionView. UI Collection View contains custom UICollectionViewCell. Navigation bar contains two UIBarButton items, one is on the left corner - prepared segue to previous page and other item is on the right corner - arranged to delete cell(s) in the UI CollectionView as show in the picture below:
Main Screen
Now I want to remove the selected UICollectionViewCell when UIBarButtonItem in the right corner, is tapped.
This how my cellForItemAtIndexPath method look like:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath{
self.GlobalIndexPath = indexPath;
MessagesCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"messagesCell" forIndexPath:indexPath];
cell.MessageHeading.text = [self.Message_Heading objectAtIndex:indexPath.row];
cell.MessageSubject.text = [self.Message_Subject objectAtIndex:indexPath.row];
cell.MessageContent.text = [self.Message_Details objectAtIndex:indexPath.row];
[cell.Checkbox setHidden:YES];
[cell.Checkbox setChecked:NO];
}
I have tried a solution like Declaring Indexpath as global variable and use it in the button event as below:
#property (strong,nonatomic) NSIndexPath *GlobalIndexPath;
some other code .......
//When Bin Icon(UIBarButtonItem) Clicked
- (IBAction)DeleteMessages:(id)sender {
[self.view makeToast:#"You clicked delete button !"];
NSIndexPath *indexPath = [self.MessageCollectionView.indexPathsForVisibleItems objectAtIndex:0] ;
BOOL created = YES;
// how to get desired selected cell here to delete
MessagesCollectionViewCell *cell = [self.MessageCollectionView cellForItemAtIndexPath:self.GlobalIndexPath];
if([cell.Checkbox isHidden])
{
[cell setHidden:YES];
}
else{
[cell.Checkbox setChecked:NO];
[cell.Checkbox setHidden:YES];
}
}
It's not worked.
For showing the UICollectionViewCell selected as checked, i'm using #Chris Chris Vasselli's solution
Please help me with this. Thanks in Advance.
There are a few steps. First, determine the selected indexPath, but don't assume there is a selection when the method is run....
// in your button method
NSArray *selection = [self.MessageCollectionView indexPathsForSelectedItems];
if (selection.count) {
NSIndexPath *indexPath = selection[0];
[self removeItemAtIndexPath:indexPath];
}
There are two more steps to remove items from a collection view: remove them from your datasource, and tell the view it has changed.
- (void)removeItemAtIndexPath:(NSIndexPath *)indexPath {
// if your arrays are mutable...
[self.Message_Heading removeObjectAtIndex:indexPath.row];
// OR, if the arrays are immutable
NSMutableArray *tempMsgHeading = [self.Message_Heading mutableCopy];
[tempMsgHeading removeObjectAtIndex:indexPath.row];
self.Message_Heading = tempMsgHeading;
// ...
Do one or the other above for each datasource array. The last step is to inform the collection view that the datasource has changed, and it must update itself. There are a few ways to do this. The simplest is:
// ...
[self.MessageCollectionView reloadData];
OR, a little more elegantly:
[self.MessageCollectionView deleteItemsAtIndexPaths:#[indexPath]];
} // end of removeItemAtIndexPath
I have a custom UITableViewCell in which I have two UIViews . I want to change the BackgroundColor radius and some more properties of UIViews.
But I am unable to do so.
Here is my setUp.
Step 1:
Create A Custom Cell with XIB.
Step 2: Entered the Cell Identifer name in XIB for CustomCell.
Step 3: Instantiated NIB in viewDidLoad
UINib *nib = [UINib nibWithNibName:#"CustomCell" bundle:nil];
[[self mTableView] registerNib:nib forCellReuseIdentifier:#"CustomCell"];
Step 4: Cell For Row At Index Method:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Create an instance of ItemCell
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CustomCell"];
[cell updateCellView];
return cell;
}
Step 5: Checked Twice the Outlet Connections all are Fine.
Step 6: Custom Cell Class:
#import "TransportCell.h"
#implementation TransportCell
- (void)awakeFromNib {
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)updateCellView
{
self.backgroundView.backgroundColor = [UIColor redColor];
}
#end
This code has no effect on my Cell View.
I debug the code. When I logged the backgroundView I get nil when updateCellView method get called:
Here is my CustomCell's Xib:
I have to change the Properties of Inner UIView.(Blue in color)
Instead of calling the method in the cellForRowAtIndexPath try this:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Create an instance of ItemCell
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CustomCell"];
return cell;
}
and then in your custom method
- (void)updateCellView
{
self.backgroundView.backgroundColor = [UIColor redColor];
// reload the table view
[self.tableView reloadData];
}
you need to add [ tableview reloadData ]
in method update cell view
Ideally all the info needed to render the cell should come from either the data source or other events. For example, if you are changing the border-color it should mean that either some entity (say user) has performed an event or something has changed in the data-source like a value passed its threshold.
The change in data-source or those events should either trigger refreshing the whole table:
-(void) reloadData
or some cells:
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
Only then the cells will get updated.
you are changing the self.backgroundview.backgroundColor
Default it is nil for cells in UITableViewStylePlain, and non-nil for UITableViewStyleGrouped.
The 'backgroundView' will be added as a subview behind all other views.
#property (nonatomic, strong, nullable) UIView *backgroundView;
try this method in TransportCell, it's swift code make same for objective c
override func layoutSubviews()
{
self.backgroundView.backgroundColor = [UIColor redColor];
}
Use this:
self.contentView.backgroundColor = [UIColor redColor];
Hope this helps!
I have some components in UICollectionViewCell and i can access all inside the cellForItemAtIndexPath. I need to access the component in a custom method,
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
// UIButton
cell.btn1.frame =CGRectMake(380,100, 150, 40);
[cell.btn1 addTarget:self action:#selector(btnClicked) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
In custom method,
-(void)btnClicked{
MycollectionclassView *cell =[[MycollectionclassView alloc]init];
cell.btn1.backgroundColor =[UIColor greenColor];
}
Here I cannot change the color of the button.
You must create a protocol and create a IBAction to a method. When your action method is called, you can notify the protocol and its screen that has the UICollectionView protocol will receive this event.
On your cell header file.h define the protocol:
#protocol YourCellNamelDelegate <NSObject>
#required
/**
* Notifies that current ITEM was clicked on icon.
*
* #param cell The current cell that was clicked.
*/
-(void)bookmarkClicked:(YourCellName *)cell; // OR without argument
#interface YourCellName : UITableViewCell
/**
* Reference to 'YourCellNameDelegate' delegate.
*/
#property (assign,nonatomic) id<YourCellNamelDelegate>delegate;
...
On your cell file.m
...
- (IBAction)bookmarkClickAction:(id)sender {
// change or update screen element here ...
if (self.delegate) {
[self.delegate bookmarkClicked:self];
}
}
On your ViewController implement the your protocol
#interface SMGListViewController () <YourCellNameDelegate>
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// get your cell here, for example in my case:
SMGCityGuideCollectionViewCell *cell;
cell = (SMGCityGuideCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
SMGTouristInfo *touristInfo = [self.listTouristInfo objectAtIndex:indexPath.row];
[cell setTouristInfoCell:touristInfo];
// IMPORTANT HERE *************************
cell.delegate = self;
// IMPORTANT HERE *************************
return cell;
}
...
#pragma mark <SMGPlaceCellDelegate>
-(void)bookmarkClicked:(SMGPlaceCell *)cell {
// notify events here ...
NSIndexPath *index = [self.tableViewPlaces indexPathForCell:cell];
[self.tableViewPlaces reloadRowsAtIndexPaths:#[index] withRowAnimation:UITableViewRowAnimationAutomatic];
}
I hope it helps! :)
To make things clear , when cellForItemAtIndexPath gets called it is the time that your cells get laid in the view , in your custom method you are just creating a cell object but this object does not reference the objects that is laid out in your collection view , so your just changing the color of a newly created object that does not reference anything .
In the Button Click function pass arguments as Sender , by which in the function call you can access the collectionView cell as Sender.SuperView .
You can also find the indexpath from the collectionviewcell now.
Now if you want to update a UI of the Collection view cell , you will have to do it in the cellforItemAtIndexPath function. which can be called by [collectionview reloadData].
So I would suggest to keep some flag variable in the data source to check if you have to update the UI or not.
I'm not using storyboard, everything is done by code..
and when I scroll the UICollectionView.. after it reusing correctly..some cells..
than it happen :
-the cell initWithFrame is being call
-new gray hair appear on my head.
I read other q/a and check maybe it's something with threads but all the reloadData is on the main thread.
any directions ?
I have no idea what's your code, so I'll propose how do I do it:
// somewhere in eg viewDidLoad
[self.collectionView setDelegate:self];
[self.collectionView setDataSource:self];
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:CellId];
And later the delegate:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = cell = [collectionView dequeueReusableCellWithReuseIdentifier:MyCellId forIndexPath:indexPath];
// do something with your cell, set title or anything
return cell;
}
There is another possibility. Your cell, as it's reusable, will have already saved previous properties. So if you did something like this:
if (iCanAddGrayHair) {
[cell.imageView setImage:[UIImage imageNamed:#"hair"]]
}
Then do notice, that new cell will still have this image set! You need to add:
else {
[cell.imageView setImage:nil];
}
To reset it from previous state. You can also override prepareForReuse: method of UICollectionViewCell class to reset the values (don't forget to call super).
I have an array of NSStrings, one UILabel & a UICollectionView.
My Question:
I want the array's count to determine how many UICollectionViewCell's there are.
Each UICollectionViewCell contains a button. Upon click, I want this button to cause the data in the array that corresponds to the UICollectionViewCell's number to be displayed in the label.
For example, if the user clicks on the 13th UICollectionViewCell's button, then the 13th NSString in the array would become the UILabel's text.
What I have done:
I have made my own subclass of UICollectionViewCell for the nib file that I use for all of the UICollectionViewCells, & connected the button to the .h file as a IBAction. I have also imported the MainViewController.h, which is the one that contains the array property that stores the NSStrings.
When I edit the code in the UICollectionViewCell's action, I cannot access the array property. The button does work - I placed an NSLog in the IBAction's method, which does work.
I have searched through tens of other answers on SO, but none answer my specific question. I can update this with samples of my code if requested.
I have made my own subclass of UICollectionViewCell for the nib file
that I use for all of the UICollectionViewCells, and connected the
button to the .h file as a IBAction.
If you connect the IBAction to the subclass of collectionViewCell you would need to create a delegate to make the touch event available in the viewController where you are displaying the data.
One easy tweak is to add the button the collectionViewCell, connect it's IBOutlet to the cell. But not IBAction. In the cellForRowAtIndexPath: add an eventHandler for button in that viewController containing collectionView.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
//Dequeue your cell
[cell.button addTarget:self
action:#selector(collectionViewCellButtonPressed:)
forControlEvents:UIControlEventTouchUpInside];
return cell;
}
- (IBAction)collectionViewCellButtonPressed:(UIButton *)button{
//Acccess the cell
UICollectionViewCell *cell = button.superView.superView;
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
NSString *title = self.strings[indexPath.row];
self.someLabel.text = title;
}
Please try like this..
In YourCollectionViewCell.h
Create an IBOutlet not IBAction called button for the UIButton that you added to the xib. Remember you should connect the outlet to the cell object not to the file owner in the xib.
MainViewController.m
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
cell.button.tag = indexPath.row;
[cell.button addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
-(void)buttonPressed:(UIButton*)sender
{
NSLog(#"%d : %#",sender.tag,[array objectAtIndex:sender.tag]);
self.textLabel.text = [array objectAtIndex:sender.tag];
}
Edit- Handle multiple sections
-(void)buttonPressed:(UIButton*)sender
{
NSIndexPath *indexPath = [self.collectionView indexPathForCell: (UICollectionViewCell *)sender.superview.superview];
NSLog(#"Section : %d Row: %d",indexPath.section,indexPath.row);
if (0 == indexPath.section) {
self.textLabel.text = [firstArray objectAtIndex:indexPath.row];
}
else if(1 == indexPath.section)
{
self.textLabel.text = [secondArray objectAtIndex:indexPath.row];
}
}
When I edit the code in the UICollectionViewCell's action, I cannot access the array property.
That's because you connected the button action to the "wrong" object. It needs to be connected to the MainViewController (or whoever it is that does have access to the array property).
You are going to have several tasks to perform:
Receive the button action message.
Access the array (the model for the data).
Throw a switch saying which cell should now have its label showing.
Tell the collection view to reloadData, thus refreshing the cells.
All those tasks should most conveniently belong to one object. I am presuming that this is MainViewController (and thus I am presuming that MainViewController is the delegate/datasource of the collection view).