I have an ImageView, say imageViewA, in a UICollectionViewCell, say ComicStripViewCellA.xib. I need to reposition imageViewA if a condition is true. I can access imageViewA or change its alpha value in collectionView:cellForItemAtIndexPath, but cannot reposition it if this is the first time that this viewCell is dequeued.
But if this viewCell is already in the pool, I can reposition it the next time when collectionView:cellForItemAtIndexPath is executed.
-(UICollectionView *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
id <ComicStripViewCell> viewCell = [collectionView dequeueReusableCellWithReuseIdentifier:#"comicStripCell" forIndexPath:indexPath];
if (condition) {
viewCell.imageViewA.alpha = 0.5; // Always work.
[viewCell reposition:YES]; // Only work if viewCell is already in the pool.
}
return (UICollectionView *)viewCell;
}
The following routine is in ComicStripViewCellA.m:
-(void)reposition:(BOOL)flag
{
if (flag) {
CGPoint center = _imageViewA.center;
center.x -= 60;
_imageViewA.center = center;
}
}
I guess it has something to do with the view cell initialization not complete when the reposition routine is run, but I am not sure.
Any help is appreciated!
Thanks!
I've been experimenting around, it would appear that the UICollectionViewCell outlets can't be repositioned programmatically (or at least I couldn't get them to reposition, no matter what). One possible solution, however, is to have a simple UIView as the only outlet, and to add subviews to that outlet programmatically, in your case the UIImageView. Once you do that, you can freely reposition any subview in your UIView outlet. I hope this helps!
Related
I am learning about UITableview on iOS and following a course online. I get the table showing fine, but the images on my cells are not all the way to the left (whereas the instructor's ones are). Here is a screenshot of the cells in question:
I don't want that gap, I want the images to be positioned right at the beggining of the cell, all the way to the left. I have done some research and it seems Apple has changed the default look of the cells between ios6 and ios7 so that now the images in cells show a little gap at the left. To get rid of it, I have tried UIEdgeInsets:
[tableView setSeparatorInset:UIEdgeInsetsZero];
and that's not working. I also have tried this approach:
cell.imageView.frame = CGRectMake( 0, 0, 50, 55 );
Nothing happens. So how would I go about it? Thanks
edit-------------------------------------------------------------------------------------------------------------------------------
Still not have found the answer to this. The solutions posted here don't work. I found this piece of code:
self.tableView.contentInset = UIEdgeInsetsMake(0, -50, 0, 0);
Which besides completely puzzling me (as the parameter affected should be the y?) I thought solved the issue by making the image on the cell appear all the way to the left, until I realised it only moved the whole view to the left (as I should have expected I guess) leaving an equal gap on the other side of the screen. All I want is for my images in the cells to appear all the way to the left of the cell as it used to be the case on previous ios. Thanks
It happens because default table content offset from left is 15, you should change it with 0.
See this once, you get idea Remove empty space before cells in UITableView
If you create custom cells. UITableViewCell have owner imageView. Change title of image in your cell.
If you use default cell, use custom cell with constraint Leading space = 0.
It is better not use default imageView of the cell. Drag and drop UIImageView from objective library, create a custom table view cell (Child class of UITableViewCell) then create and outlet of the image view just dragged.
The spacing in the UITableViewCell is because of the default TRUE returned by shouldIndentWhileEditingRowAtIndexPath method of UITableViewDelegate.
I was able to reproduce your problem by the below scenario:
UITableView is in editable mode:
self.tableView.editing = true
And you have implemented:
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewCellEditingStyleNone;
}
To correct your code:
If you do not want to set Editing Style then you can turn off the editing mode by
self.tableView.editing = false
and remove editingStyleForRowAtIndexPath.
Else if you need editing mode then set the appropiate Editing style(UITableViewCellEditingStyleDeleteor UITableViewCellEditingStyleInsert) or simply turn the indentation off.
- (BOOL)tableView:(UITableView *)tableView
shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath {
return FALSE;
}
You must create a custom cell, by adding a new class as a subclass of UITableViewCell. then you can design cell with autolayout and constraints which will resolve the issue.
there is a another concrete way to achieve this by creating subclass uitableviewcell (custom class).
steps to follow
create a class subclass of UITableViewCell.
in .h file create properties and outlets of UI components.
go to storyboard and add table view cell inside the tableview.
now add UI components like: imageview or button etc and set the x, y values according to.
make class of custom cell your className using identity inspector see image.
connect all outlets of UI components.
use below code uitableview
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *MyIdentifier = #"uniqueIdentifire";
yourCustomClassForCell *cell = (yourCustomClassForCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil){
cell = [[yourCustomClassForCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier];
}
cell.imageView.image = [imageAry objectAtIndex:indexPath.row];
}
Dont forget to give identifire by selecting your cell using storyboard Attribute inspector uniqueIdentifire to identifire property see image.
Also you can give some vertical space between cells by just to add this below code (Method only) inside customeCellClass.
- (void)setFrame:(CGRect)frame { // method to insert gap between table view cell
frame.origin.y += 6;
frame.size.height -= 2 * 6;
[super setFrame:frame];
}
You can not really change the frame of the inbuilt subviews of uitableviewcell like imageview, accessoryview. But if you create a custom tableviewcell class(even if you do not add any other subelement to it), you can change the frame of the inbuilt imageview by overriding the layoutSubviews method inside the UITableViewCell. I have tried it and it works.
#import "TableViewCell.h"
#implementation TableViewCell
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
-(void) layoutSubviews{
[super layoutSubviews];
CGRect frame = self.imageView.frame;
frame.origin.x = 0;
self.imageView.frame = frame;
}
#end
I have a collection view cell with a number of subviews. One of the subviews I want to remove altogether so the adjacent subview can expand to fill that space via constraints.
This constraint configuration set up within the prototype cell in storyboard, and the subview is already in place in the prototype, ready to be removed after the cell has been instantiated.
My UICollectionViewCell subclass has a setter that does this:
- (void)setThing:(NSString *)aThing
{
if (!aThing)
{
[self.thingContainerView removeFromSuperview];
return;
}
// proceed if aThing exists
And when the cell is setup:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"mycellid" forIndexPath:indexPath];
cell.aThing = nil; //this should trigger removal of one of the cell's subviews
return cell;
}
The problem is when the UICollectionViewCell is first loaded, the subview has not been removed. Once I've scrolled the cell offscreen and returned to it, the subview has been removed as expected. So I'm assuming there's some sort of issue with the cell not being fully laid out when I'm setting the property the first time around.
How to get this to work?
I don't think the view has been laid out at that point so the subview cannot be removed. Try removing it in a different method such as;
- (void) didMoveToSuperview
{
if (!self.aThing)
{
[self.thingContainerView removeFromSuperview];
}
}
This will be overridden in your collection view cell.
There might be a more appropriate method to call this in but this should work.
Inside the reusable view of my cell, I have a UIView.
Then, I have this method in the controller
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// .. set value for CGFloat backgroundHeight
[cell addSubview:cell.backgroundView];
CGRect f = cell.backgroundView.frame;
f.size.height = backgroundHeight;
cell.backgroundView.frame = f;
}
But the UIView's height remains the same as specified in the Layout Rectangle.
What should I try next?
Your problem here lies in the fact that you are attempting to use the cell's backgroundView.
Firstly, you cannot add the cell's backgroundView as a subview. You simply assign a UIView to it with :
cell.backgroundView = yourView;
Secondly, if you read the docs, it clearly states :
Use this property to assign a custom background view to the cell. The background view is placed behind the content view and its frame is automatically adjusted so that it fills the bounds of the cell.
This means, no matter what frame you try to set for the backgroundView it will automatically adjust and fill the entire cell. Now, I haven't actually tried it, but you might be able to override this by subclassing. Though, i'll mention here, I am unsure.
Back to your problem, if you really want a UIView that you can control, you will need to create a UIView and then add it as a subview. Using the cell's backgroundView is not the solution.
It just seems like useless, what you'r approaching with the UICollectionViewCell's backgroundView .
By the Doc
backgroundView The view that provides the background appearance.
#property (nonatomic, retain) UIView *backgroundView; Discussion The
view (if any) in this property is positioned underneath all of the
other content and sized automatically to fill the entire bounds of the
collection view. The background view does not scroll with the
collection view’s other content. The collection view maintains a
strong reference to the background view object.
This property is nil by default, which displays the background color
of the collection view.
the backgroundView is just nothing but the cell, so what you'r upto do is doesn't effect . seems like directly changing the Cell's height.
the best solution is to just ignore the backgroundView property all
together. Instead, make the collection view’s background clear, and
implement your own View; just throw a view behind the
collection view.
Kindly check this blog, this would be helpful for you.
You can manage the Layout height With sizeForItemAtIndexPath
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
return CGSizeMake(view.frame.size.width, view.frame.size.height);
}
here you can manage spacing
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
return UIEdgeInsetsMake(5,5,0,5);
}
Controlling UICollectionViewCells is just the same as UITableViewCell. What you need to do is create a UICollectionViewCell subclass. These can seem confusing to start with but are really pretty simple to set up.
The first thing is to add any additional properties you might need like additional UIImageViews, UILabels etc. Now, we need to make sure all objects are instantiated but only once so this happens in
- (id)initWithFrame:(CGRect)frame
As well as instantiating an adding subviews to self.contentView set any global or default properties such as font, color etc. You can't set a frame relative to self.contentView yet though because self.contentView has zero size until layoutSubviews.
Now, create a method:
-(void)layoutSubviews
{
[super layout subviews];
...
[self.backgroundView setFrame: myFrame]; // in this case
}
The [super layoutSubviews] is important to set self.contentView's frame from the delegate cell layout methods. This method is called every time the cell comes into view or changes in any way (which is often). What you need to do now is set the various frames of things based on self.contentView.frame or self.frame. Also, you can set any conditional properties like hiding icons depending on a state etc.
To answer the question, you do not need to add self.backgroundView because it is already there. What you do need to do is set the frame in layoutSubviews as above but you need a UICollectionViewCell subclass in order to do that.
To use the custom cell you just need to include your new .h file and swap UICollectionViewCell for your new classname in the
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
method like:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
cell.backgroundColor = [UIColor redColor];
return cell;
}
Do it like below, as you can not modify only height in frame you have to define new frame using CGRectMake function, after this you will also require to change the cell height also otherwise your view will be displayed in that much portion only.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// .. set value for CGFloat backgroundHeight
[cell addSubview:cell.backgroundView];
CGRect frame = CGRectMake(cell.backgroundView.frame.origin.x, cell.backgroundView.frame.origin.y, cell.backgroundView.frame.size.width, backgroundHeight);
cell.backgroundView.frame = frame;
}
I'm having an issue,
I have a simple UICollectionView with a static 200 cells that load images from Flickr.
my CellForItemAtIndexPath looks like this:
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
if(![[cell.subviews objectAtIndex:0] isKindOfClass:[PFImageView class]])
{
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
PFImageView *imageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell];
[cell addSubview:imageView];
}
return cell;
}
PFImageView is a subclass of UIImageView that loads a Flickr photo URL on a background thread and then updates it's own image on the main thread - this works fine.
The logic is really simple - I create a cell if there isn't one dequeueable.
If the cell (which I'm expecting to be dequeued and already have a PFImageView) doesn't have a PFImageView, I alloc and init an imageView for the cell and add it as a subview of the cell.
Thus I expect if the cell has been dequeued it should already have a PFImageView as a subview and as we should not get into the if statement to create a new imageView and kick off a new photo download request
Instead what I see is that the cells at the top and bottom of the UICollectionView that 'go off screen' momentarily - when they come back on screen they are not being reused and seemingly a new cell is created and the picture refreshed.
1) How can I achieve a static image once the cell has been created (i.e. not refreshing when the cell goes slightly off screen.
2) Why are the cells not being reused?
Many thanks for your time.
John
UICollectionView will reuse cells for maximum efficiency. It does not guarantee any particular reuse or population strategies. Anecdotally, it seems to place and remove cells based on integer power of two regions — e.g. on a non-retina iPad it might divide your scroll area up into regions of 1024x1024 and then populate and depopulate each of those regions as they transition into and out of the visible area. However you should not predicate any expectations on its exact behaviour.
In addition, your use of collection view cells is incorrect. See the documentation. A cell explicitly has at least two subviews — backgroundView and contentView. So if you add a subview it will be at index 2 at the absolute least and, in reality, the index will be undefined. In any case you should add subviews to contentView, not to the cell itself.
The most normal way of doing what you're doing would be to create a custom UICollectionView subclass that inherently has a PFImageView within it.
I see several potential issues:
You are looking specifically at index 0 of the cell for the child class that you are adding. The UICollectionViewCell may have other views as children, so you can't just assume that the only (or first) child is the one you added.
I don't see that you are calling registerClass:forCellWithReuseIdentifier: or registerNib:forCellWithReuseIdentifier:, one of which is required for proper use of dequeue (https://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionViewCell_class/Reference/Reference.html).
You are only setting the URL of the PFImageView in the case that you have to construct the PFImageView. The idea with dequeuing reusable views is that you will only construct a small subset of the views needed, and the UITableView will recycle them as they move offscreen. You need to reset the value for the indexPath that is being requested, even when you don't construct the new content.
If your case is as simple as you describe, you can probably get away with adding your PFImageView to the contentView property of your dequeued UICollectionView.
In your controller:
// solve problem 2
[self.collectionView registerClass:[UICollectionViewCell class] forReuseIdentifer:#"FlickrCell"];
In collectionView:cellForItemAtIndexPath
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
// solve problem 1 by looking in the contentView for your subview (and looping instead of assuming at 0)
PFImageView *pfImageView = nil;
for (UIView *subview in cell.contentView.subviews)
{
if ([subview isKindOfClass:[PFImageView class]])
{
pfImageView = (PFImageView *)subview;
break;
}
}
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
if (pfImageView == nil)
{
// No PFImageView, create one
// note the use of contentView!
pfImageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.contentView.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell.contentView];
[cell.contentView addSubview:pfImageView];
}
else
{
// Already have recycled view.
// need to reset the url for the pfImageView. (Problem 3)
// not sure what PFImageView looks like so this is an e.g. I'd probably remove the
// URL loading from the ctr above and instead have a function that loads the
// image. Then, you could do this outside of the if, regardless of whether you had
// to alloc the child view or not.
[pfImageView loadImageWithUrl:staticPhotoURL];
// if you really only have 200 static images, you might consider caching all of them
}
return cell;
For less simple cases (e.g. where I want to visually lay out the cell, or where I have multiple children in the content), I typically customize my UICollectionViewCell's using Interface Builder.
Create a subclass of UICollectionViewCell in the project (In your case, call it PFImageCell).
Add an IBOutlet property to that subclass for the view I want to change in initialization (In your case, a UIImageView).
#property (nonatomic, assign) IBOutlet UIImageView *image;
In Interface Builder, create a prototype cell for the UITableView.
In the properties sheet for that prototype cell, identify the UICollectionViewCell subclass as the class.
Give the prototype cell an identifier (the reuse identifier) in the property sheet.
Add the view child in interface builder to the prototype cell (here, a UIImageView).
Use IB to map the IBOutlet property to the added UIImageView
Then, on dequeue in cellForRowAtIndexPath, cast the dequeued result to the subclass (PFImageCell) and set the value of the IBOutlet property instance. Here, you'd load the proper image for your UIImageView.
I am not sure if the cell is being re-used or not. It may be being reused but the subview may not be there. My suggestion would be to create a PFImageViewCollectionViewCell Class (sub class of UICollectionViewCell) and register it as the CollectionView Cell and try. That's how I do and would do if I need a subview inside a cell.
Try adding a tag on this particular UIImageView
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static int photoViewTag = 54353532;
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
PFImageView *photoView = [cell.contentView viewWithTag:photoViewTag];
// Create a view
//
if (!photoView) {
photoView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell];
imageView.tag = photoViewTag;
[cell.contentView addSubview:imageView];
}
// Update the current view
//
else {
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
photoView.imageURL = staticPhotoURL;
}
return cell;
}
I would really recommend to create your own UICollectionViewCell subclass though.
EDIT: Also, note that I used the contentView property instead of adding it directly to the cell.
I try to make UICollectionView with cells, that intersect and partially overlay each other as it is done at screenshot:
This layout was reached by setting
self.minimumLineSpacing = -100;
at my UICollectionViewFlowLayout subclass.
When I scroll down, everything is OK. I see what I want. But when I scroll up, I see another behaviour, not like I expected:
So my question is: how can I make my layout look as at the first screen regardless scroll view direction.
Note: I have to support both iOS 6 and 7.
Thanks very much for any advices and any help.
Hmm, interesting. Since the collection view recycles cells, they are continuously added to and removed from the view hierarchy as they move on and off the screen. That being said, it stands to reason and when they are re-added to the view, they are simply added as subviews meaning that when a cell gets recycled, it now has the highest z-index of all of the cells.
One fairly pain-free way to rectify this would be to manually adjust the z position of each cell to be incrementally higher with the index path. That way, lower (y) cells will always appear above (z) the cells above (y) them.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellID = #"CELLID";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellID forIndexPath:indexPath];
if (cell.layer.zPosition != indexPath.row) {
[cell.layer setZPosition:indexPath.row];
}
return cell;
}
Found another sollution to solve this problem. We need to use UICollectionViewFlowLayout subclass.
#interface MyFlowLayout : UICollectionViewFlowLayout
#end
#implementation MyFlowLayout
- (void)prepareLayout {
[super prepareLayout];
// This allows us to make intersection and overlapping
self.minimumLineSpacing = -100;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *layoutAttributes = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *currentLayoutAttributes in layoutAttributes) {
// Change zIndex allows us to change not only visible position, but logic too
currentLayoutAttributes.zIndex = currentLayoutAttributes.indexPath.row;
}
return layoutAttributes;
}
#end
Hope that helps someone else.