I am using Storyboard to create the UICollectionViewController - CollectionView - Cell(my own DasboardsViewCell) and my customer view (DashBoardView) inside cell. I wired up everything correctly and everything seems to work except when I scroll up and down. I will explain what my understanding is after I debug.
Also I have 2 views inside my DashBoardView custom view which i used "one" as main primary and other as FlipView (e.g. when user taps on cell). Since everything is wired from storyboard I dont have to registerClass for reusing.
1) In my DashboardCustomView (who has 2 other views in it as above) i do this
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
_isPrimary = YES;
//to init my 2 child views and insert to main custom view
[self initSubViewsWithInsert];
}
return self;
}
2) In my DashBoardViewCell class I do this
#synthesize dashBoardView = _dashBoardView;
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
self.backgroundColor = [UIColor colorWithWhite:0.75f alpha:0.6f];
self.layer.borderColor = [UIColor blackColor].CGColor;
self.layer.borderWidth = 2.0f;
self.layer.cornerRadius = 7.0f;
}
return self;
}
// If I dont do this below I get overlapped images and data on cell when scrolling up and down
- (void)prepareForReuse
{
self.dashBoardView.mainContainerView = nil;
self.dashBoardView.flipView = nil;
}
3) Now after this I still see the custom view appears out of order and seems somewhat better than not doing "nil" on my views, but the problem is once I nil out my 2 subviews I have no defined way to reinitialize them when my collectionview controller requeus the cell and start adding my content.
i.e. I do like this cellForItemAtIndexPath
if ([cell isKindOfClass:[DashboardsViewCell class]]) {
DashboardsViewCell *dashCell = (DashboardsViewCell*) cell;
Dashboard* tmpDashboard = self.availableDashBoards[indexPath.item];
[dashCell.dashBoardView addDashboardImageViewAtPoint:tmpDashboard.identifier
usingIndexPath:indexPath];
Last line above is adding some image and text on dashboards main view which is self.dashBoardView.mainContainerView and it has already nil.
Can someone help me understand if there is defined way to do this. Also if you think I am looking at wrong problem
I'm not sure what the issue is exactly and with that code it's a bit hard to tell. If you could post the project somewhere it would be easier. Now that many things are storyboarded, it's harder to fix issues just by posting code :(
But are you using auto layout by any chance?
Thanks for your reply - No Im not using auto layout and I am unable to post my code here as it has grown too big now but here is what I did to solve the problem
1) I realized when cells are being refreshed (dequeue) my logic was such that I was actually maintaining state for certain cell (User taps dashboard-B from A,B,C,D etc) with my own internal state thinking that same cell will be applied to same indexPath. i.e.
a)If user taps on cell 2 - I switch to secondary view and maintains that state in my cell.
b)Now if user scrolls up and down then cells get refreshed and I releases my internal view in prepareForResuse
once i know what i was doing wrong, I have to change my approach and move the logic in controller.
I may sound obscure here but I found that I was wrong in my assumption about cell cellForItemAtIndexPath and what it supposed to do. Once I figured that out i was able to fix my design.
Related
I have a UICollectionView to show some components.
Its vertical scrolling and with only 1 column.
I am adding 2 same objects but with different data to collectionView's datasource at different sections.
And in my cell class my initialisation and update methods are as follow
- (void)commonInitializer {
self.backgroundColor = kVTColorWhite;
self.imageView = [[VTUIKit imageSquareXL2] addToSuperview:self];
self.imageView.f_x = kVTPadding;
self.imageView.backgroundColor = kVTColorBackground;
self.imageView.f_width = [VTAdSpaceDisplayCell cellSize].width - 2*kVTPadding;
self.imageView.f_height = [VTUIKit screenWidth]/3.125;
self.imageView.layer.cornerRadius = 2.0;
self.imageView.layer.masksToBounds = YES;
}
- (void)updateWithModel:(id)model{
self.model = model;
if (self.model.image == nil) {
[self updateEmptyCells];
}
else{
[self.imageView setImageWithUrl:self.model.image optimizedForSize:[self class] cellSize] withPlaceholder:[UIImage imageNamed:#"Placeholder-Image"]];
}
}
But collectionView's didSelectItemAtIndexPath for this cell is being called sometimes and sometimes not, its very weird.
I have also set collectionView's delegate to current class and I am also not adding any gesture recogniser to cell or collectionView.
But still the behaviour is strange and very random, I cant even get a particular scenario to debug.
Any help would be much appreciated.
Thanks
The actual problem is nowhere related to UIImageView or UICollectionViewCell.
Earlier I was using [self.collectionView reloadSections:[INDEX]] for reloading collectionView's sections and that was creating some problems and I dont know why.
But, I changed them to [self.collectionView reloadData] and the issue is gone.
Very strange and weird reason for issue to appear and I have no explanation even though I searched for it.
Anyways, I got my issue resolved so I am posting my solution as an answer if that helps anyone in future.
Thanks
I know this question has been asked before. But no person on the internet had a working and sufficient answer.
EDIT Obviously people don't read questions anymore, on SO. So I'm trying to clarify: I want to remove the SEPARATOR. The separator is neither the space above the section, nor the tableViewHeader or tableViewFooterView. It is only the thin line above (fully from left to right).
I have a grouped UITableView (I don't want to use a plain styled for many other reasons, take it as it is) which has multiple groups.
The first section should not have the separator line on top. Setting the separator style of the tableView is not an option, because I do need the other separators.
Setting the tableViews tableFooterView is something I often read, but it never worked.
I used the tableView with static content before and I was able to remove the separator in -[UITableViewController viewDidLoad] using this:
- (void)viewDidLoad {
[[[self headerTableCell] valueForKey:#"_topSeparatorView"] removeFromSuperView];
}
Since I now had to change the tableView to a dynamic one, the IBOutlet property won't work anymore (obviously).
So I tried everything, -[id tableView:willDisplayCell:atIndexPath:], -[UITableViewCell initWithStyle:reuseIdentifier:, prepareForReuse, awakeFromNib] and some others.
In any case, this separator is nil. So I need a method that gets called when the complete view hierarchy of the cell is setup.
what i get from your situation you have a grouped UITableView you want the first section without separator and you want to keep the separator in the other sections so
remove the separator from the whole tableview from the attributes inspector make Separator : None
create custom UITableviewCell in storyboard for other sections and add View at the end of it with height 1 and width the whole screen (like default separator)
it's maybe not the best idea but this will allow you to have the first section without separator
I faced a similar problem, wanted to remove the last line of the section in grouped table view, I am calling following method in view will appear and on every table reload. This is not the exact answer but problem can be solved by just changing y value of dummy view.
+(void)removeLastSectionSeparatorForTableView:(UITableView *)tableView
{
UIView *oldSeparatorView = [tableView viewWithTag:kTagDummySectionSeparator];
if (oldSeparatorView != nil)
{
[oldSeparatorView removeFromSuperview];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(),
^{
UIView *view = [[UIView alloc] init];
view.tag = kTagDummySectionSeparator;
view.backgroundColor = [UIColor colorWithRed:239.0/255 green:239.0/255 blue:244.0/255 alpha:1.0];//Group table background color
view.frame = CGRectMake(0,
tableView.contentSize.height-40,
tableView.bounds.size.width,
2);
[tableView addSubview:view];
});
}
Maybe this problem is the same as mine before.
Finally, my way to solve this problem: set table view delegate's method (CGFloat)tableView:(UITableView *)tableView heightForHeaderAtSection:(NSInteger)section, then return CGFLOAT_MIN;
add this override function in your Custom Cell Class
override func layoutSubviews() {
super.layoutSubviews()
for subview in subviews where (subview != contentView && abs(subview.frame.width - frame.width) <= 0.1 && subview.frame.height < 2) {
subview.removeFromSuperview()
}
}
I need a custom cell with a label and a switch.
Now, the main problem is that I can't get the switch to display. I have tried several methods, including adding the switch programatically to the cell's accessoryView.
I used the IB, added the switch to the cell, connected the IBOutlet. I also tried to add the switch programatically, in cell's awakeFromNib:
- (void)awakeFromNib {
[super awakeFromNib];
if (!self.fieldSwitch) {
self.fieldSwitch = [[UISwitch alloc] init];
[self.fieldSwitch addTarget:self action:#selector(switchUpdatedValue:) forControlEvents:UIControlEventValueChanged];
self.fieldSwitch.onTintColor = [ColorManager sharedInstance].genericSwitchColor;
self.accessoryView = self.fieldSwitch;
}
}
This has had absolutely no effect; I also tried adding it as a subview to the cell's contentView then calling bringSubviewToFront:. Again, no success.
I checked, and self, accessoryView, fieldSwitch none of them were nil.
Does anyone have any idea what could be so wrong? on a side note, does anyone understand why adding a control from the IB is broken by default?
If you are using size classes you have to set a constraint for the UISwitch. For example if you are using an Any Any size class and you place the UISwitch in the cell it may actually be displaying far off to the right. (I would of posted this as a comment however not enough rep)
I am having a weird issue with using a Xib for UICollectionViewCells. The issue is that an additional view is being created, that seems to be on top of everything, rendering my gestures and IBActions useless.
Here's my setup:
I have a UIViewController with a UICollectionView in it.
In the storyboard, the UICollectionView has a single cell, of class "MyCell", with reuse id "cell"
In cellForItemAtIndexPath, I only return the result of [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath]. I also hardcoded 30 items in numberOfItemsInSection.
That's that. Besides that, I have MyCell.h, MyCell.m and MyCell.xib.
The xib contains a single UIButton, whose Touch Up Inside event is set to call the buttonPressed IBAction. The view itself is set to the class MyCell and the tag is set to 123 (we'll get to why below).
In MyCell.m, I overrode awakeAfterUsingCoder to return [MyCell initView:self]. The definition of initView is at the bottom. But it basically loads the view from the Xib.
That's it.
When I run the app, 30 cells show up, all with their button, but when the button is pressed, nothing happens. After a lot of investigating, I found that there's an extra UIView added inside the cell, which is on top of the button, and it's covering the entire thing.
If I add the for-loop below inside awakeAfterUsingCoder, then the button works again.
- (MyCell *)awakeAfterUsingCoder:(NSCoder *)aDecoder
{
MyCell *cell = [MyCell initView:self];
for(UIView *v in cell.subviews){
if(![v isKindOfClass:[UIButton class]]) [v removeFromSuperview];
}
return cell;
}
My question is: what is that view, and why is it there??? Even though I can get everything to work by removing that view, it feels like a hack.
Thank you! And I can answer any other questions if necessary.
+ (id)initView:(UIView *)viewToInit
{
UIView *viewToReturn;
if(viewToInit.tag != 123){
//If we're in the storyboard codepath
//Initialize from the xib.
//If the code below is failing, we probably forgot the 666 tag :/
viewToReturn = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([viewToInit class])
owner:nil
options:nil] firstObject];
//copy frame, autoresizing, and layour items.
viewToReturn.frame = viewToInit.frame;
viewToReturn.autoresizingMask = viewToInit.autoresizingMask;
viewToReturn.translatesAutoresizingMaskIntoConstraints = viewToInit.translatesAutoresizingMaskIntoConstraints;
for (NSLayoutConstraint *constraint in viewToInit.constraints){
id firstItem = constraint.firstItem;
if (firstItem == viewToInit){
firstItem = viewToReturn;
}
id secondItem = constraint.secondItem;
if (secondItem == viewToInit){
secondItem = viewToReturn;
}
[viewToReturn addConstraint:[NSLayoutConstraint constraintWithItem:firstItem
attribute:constraint.firstAttribute
relatedBy:constraint.relation
toItem:secondItem
attribute:constraint.secondAttribute
multiplier:constraint.multiplier
constant:constraint.constant]];
}
}else{
//otherwise do nothing and just return what was passed in
viewToReturn = viewToInit;
}
return viewToReturn;
}
I've just had this problem myself, and the answer lies in the cell nib file. If you've created a nib and are using the stock UIView contained as a cell, that's where your problem is. This UIView needs to be deleted immediately and replaced with a newly-dragged-on UICollectionViewCell. This will then be your cell, and you can add views to your hearts content without further issues.
OK, so after a long time of having a nasty hack in there to work around this, I've figured it out.
The issue is that the contentView of the UICollectionViewCell is getting inserted on top of my views. It is very weird to me because this doesn't happen with UITableViewCells which also have a content view.
What I did then, was to put everything in a "container" view and then move that container view into the contentView on init, and that took care of the problem.
I still don't understand WHY this is happening. It seems to me that all my views should be added into the contentView (like the table's cells do), but at least I can now work around it.
I've been reading online tutorials on UICollectionView with different layouts. Also looked at a lot of SO Questions on the subject. But it seems what I am looking might be something more simple but I am stuck on how to go forward.
The Goal
I have a UIViewController that is embedded in a UINavigation controller. I am displaying data in a UITableView which includes:1 UIImageView and three UILabels in each cell. The data is fetched from a server and all works nicely.
I then wanted to have a UIButton that, when tapped, would kick off a cool animation that shows the cells transition into a nice grid view.
It suddenly dawned on me that I needed to use a UICollectionView to change between these two cells and ditch the UITableView completely. Tapping the button again, would switch back to the last state (Grid or UITableView style)
The grid cell needs to loose one label - but keep the image.
The problem
I have spent the last two days reading up on UICollectionView and UICollectionViewFlowLayout. I think I could use a Apple's pre-made UICollectionViewFlowLayout and just tweak it a little.
I don't know if I need two custom cells or one cell that changes shape between the two views and how the animations must work.
I'm not looking for the exact code to do this - I just need to know which direction I need to go in and if I need to use two custom cells - and how do I change between the two with animation and not reloading all the data again.
Appreciate any input.
Thanks all.
I finally found a solution that was acceptable to my need. If anyone ever has similar needs - this is how you use two different custom UICollectionViewCell's and how to change between the two different cells / layouts.
First thing is create the customCells in IB - creating the xib
files.
Then set the up as you need
Since my requirement needed the standard flow layout provided by the class UICollectionViewFlowLayout - I just needed to create two custom layouts and tweak them to my needs.
Create two (or more if needed) classes that subclass UICollectionViewFlowLayout
In the implementation - setup the layout as needed. Since I am subclassing the pre-made UICollectionViewFlowLayOut and all I need to do is tweak it - the implementation is pretty simple.
So - for the table view layout I did this:
tableViewFlowLayOut.m
-(id)init
{
self = [super init];
if (self){
self.itemSize = CGSizeMake(320, 80);
self.minimumLineSpacing = 0.1f;
}
return self;
}
This sets each cells width and height to the values I needed. self.minimumLineSpacing sets the spacing between the cells. (Spacing between the cell above / below )
Then for the grid layout:
gridFlowLayOut.m
-(id)init
{
self = [super init];
if (self){
self.itemSize = CGSizeMake(159, 200);
self.minimumInteritemSpacing = 0.1f;
self.minimumLineSpacing = 0.1f;
}
return self;
}
Same as before - however, this time I needed spacing between my cells right edge -
self.minimumInteritemSpacing = 0.1f'
takes care of that.
Right - now to put it all together - in the viewController that has the UICollectionView
viewController.m
// Import the new layouts needed.
#import "GridFlowLayOut.h"
#import "TableViewFlowLayOut.m"
//Create the properties
#property (strong, nonatomic) TableViewFlowLayOut *tableViewLayout;
#property (strong, nonatomic) GridFlowLayOut *grideLayout;
-(void)viewDidLow
{
//Register the two custom collection view cells you created earlier. Make sure you set the correct reuse identifier here.
[self.tradeFeedCollectionView registerNib:[UINib nibWithNibName:#"TableViewCell" bundle:nil] forCellWithReuseIdentifier:#"TableItemCell"];
[self.tradeFeedCollectionView registerNib:[UINib nibWithNibName:#"GridViewCell" bundle:nil] forCellWithReuseIdentifier:#"GridItemCell"];
}
-(void)viewWillAppear
{
//Create the layout objects
self.grideLayout = [[GridFlowLayOut alloc]init];
self.tableViewLayout = [[TableViewFlowLayOut alloc]init];
//Set the first layout to what it should be
[self.tradeFeedCollectionView setCollectionViewLayout:self.tableViewLayout];
}
Right - now to change between the layouts with some animation. This is actually very easy to do and only needs a few lines of code -
I called this code in a button method in viewController.m
-(void)changeViewLayoutButtonPressed
{
//BOOl value to switch between layouts
self.changeLayout = !self.changeLayout;
if (self.changeLayout){
[self.tradeFeedCollectionView setCollectionViewLayout:self.grideLayout animated:YES];
}
else {
[self.tradeFeedCollectionView setCollectionViewLayout:self.tableViewLayout animated:YES];
}
}
And lastly in cellForItemAtIndexPath
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{ static NSString *tableCellIdentifier = #"TableItemCell";
static NSString *gridCellIdentifier = #"GridItemCell";
//BOOL used to detect which layout is active
if (self.gridLayoutActive == NO){
CustomCollectionCellClass *tableItemCell = [collectionView dequeueReusableCellWithReuseIdentifier:tableCellIdentifier forIndexPath:indexPath];
//Setup the cell
}
return tableItemCell;
}else
{
CustomCollectionCellClass *gridItemCell= [collectionView dequeueReusableCellWithReuseIdentifier:gridCellIdentifier forIndexPath:indexPath];
//Setup the cell
}
return gridItemCell;
}
return nil;
}
Of course you will need to conform to the other UICollectionView delegates and setup the remaining stuff.
This actually took me a while to figure out. I really hope it helps others out there.
If anyone wants a demo project - I'll happily create one and upload to GitHub.
For anyone new to UICollectionViews I highly recommend reading Apple's programming guide on the subject - it was this document which lead me to this solution.
Reference:
https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/Introduction/Introduction.html