Additional View getting inserted into UICollectionView loaded from Xib - ios

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.

Related

UISwitch not displaying in custom UITableView Cell

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)

How to add an UIView from a XIB file as a subview to another Xib file

I am trying to add a custom UIView that I created in XIB, to my view controller in my main.storyboard as a subview. How can I do this?
matchScrollView (tag 1) is the UIScrollView in view controller in main.storyboard, while matchView (tag 2) is the custom UIView I created in another XIB file.
With the press of a button i want to have the custom UIView added to the UIScrollView as a subview. But how can i actually make it show up on display? I guess i have yet to alloc and init it, along with indicate position and such, but how can i do that? I tried different ways without success. I can create UIViews programmatically, but have yet to find a way to just load the UIView from XIB.
-(IBAction) buttonTapped:(id)sender {
UIScrollView *matchScrollView = (UIScrollView *) [self.view viewWithTag:1];
UIView *matchView = (UIView *) [self.view viewWithTag:2];
[matchScrollView addSubview:matchView];
}
The reason that I am creating my custom UIView in another XIB file instead of directly implementing it on my main.storyboard view controller, is because I want to re-use the same view multiple times. So the UIScrollView has a numerous subviews of UIViews.
I was hoping I could create numerous instances of MatchView and add them all to matchScrollView as subviews.
The issue you are having is completely normal. The way Apple designed it doesn't allow to reuse custom views with their own xib into other xibs.
Lets say you have a custom view named HeaderView with a custom xib named HeaderView.xib. And lets say you want to be able to, in another xib named GlobalView.xib, drag a subview and specify its class to be of type HeaderView expecting it to load that view from HeaderView.xib and insert it inplace. You can do it like this:
A) Make sure File's Owner in HeaderView.xib is set to be HeaderView class.
B) Go to your GlobalView.xib, drag the subview and make it of class HeaderView.
C) In HeaverView.m implement initWithCoder, if after loading the view there aren't subviews means it got loaded from GlobalView, then load it manually from the correct nib, connect the IBOutlets and set the frame and autoresizingmasks if you want to use the GlobalView's frame (this is usually what you want).
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self.subviews.count == 0) { //means view got loaded from GlobalView.xib or other external nib, cause there aren't any subviews
HeaverView *viewFromNib = [[NSBundle mainBundle] loadNibNamed:#"HeaverView" owner:self options:nil].firstObject;
//Now connect IBOutlets
self.myLabel1 = viewFromNib.myLabel1;
self.myLabel2 = viewFromNib.myLabel2;
self.myLabel3 = viewFromNib.myLabel3;
[viewFromNib setFrame:self.bounds];
[viewFromNib setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
[self addSubview:viewFromNib];
}
return self;
}

Stretch UIView over multiple UICollectionViewCells in UIScrollView

I have a UIScrollview with a UICollectionview in it which has multiple custom UICollectionViewCells.
What a I want is to stretch a UIView over multiple cells.
I tried to use: cell.clipToBounds = NO; which is working but when I scroll to left and scroll back the expanded part of the UIView is being cut off again.
I guess it has something to do with dequeuing of the cells which aren't used. Can I assign the expanded part to the new cell so that it won't get deleted or sth?
I had a former problem where Cells got duplicated therefor I had overwritten the method prepareForReuse of my custom UICollectionViewCell-Class.
-(void)prepareForReuse
{
for(id aView in [self.contentView subviews])
{
if ([aView isKindOfClass:[MyCustomUIView class]])
{
[aView removeFromSuperview];
}
}
}
Any advice and help will be thankfully appreciated.
If your cell.clipsToBounds = NO is working, but only the first time, I agree that the problem probably has to do with cell reuse. If you are overriding -prepareForReuse, you probably want to set self.clipsToBounds = NO in there. Then, in -collectionView:cellForItemAtIndexPath:, set clipsToBounds to YES or NO every time as needed.

When in the view lifecycle does UITableViewCell create its outlets/labels/subviews?

I am trying to create a UIView as a subview of a subclassed UITableViewCell. Essentially I want a view which is the same size as the cell, and sits between the cell's contentView and backgroundView.
I imagine that somewhere under the hood (possibly in layoutSubviews), there is a line in UITableViewCell.m something like:
if (self.contentView != nil) {
[self addSubview:self.contentView];
}
If I want to mimic the way Apple does it, where should I put this code in my own custom UITableViewCell subclass?
Also, in my first attempted implementation, the subview is displayed but it has the default cell height of 44px rather than the height I specified in tableView:heightForRowAtIndexPath:. There are also other small bugs that show up, which is why I'd like to try and replicate Apple's implementation rather than try my own semi-functional one.
EDIT: Here's my code so far:
In CustomTableViewCell.h
interface CustomTableViewCell : UITableViewCell
#property (nonatomic, strong) UIView *newSubview;
#end
In CustomTableViewCell.m
- (void)layoutSubviews {
[super layoutSubviews];
if (self.newSubview != nil) {
self.newSubview.autoresizingMask = UIViewAutoresizingFlexibleHeight;
[self insertSubview:self.newSubview aboveSubview:self.backgroundView];
}
}
In tableViewController.m
static NSString *CellIdentifier = #"Cell";
myCustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
UIView *view = [[UIView alloc] initWithFrame:cell.frame];
view.backgroundColor = [UIColor orangeColor];
cell.newSubview = view;
Generally speaking, objects should create their properties in their designated initializer, unless there is an overriding design principle (or a performance/resource issue) which mandates that they create them later.
The designated initializer for UIView (and NSView on OS X) is -initWithFrame:. The designated initializer for UITableView is initWithFrame:style:. Since a view will usually need to have it's visible subviews available immediately upon being added to its superview, it is fine to create and set them in the designated initializer.
-layoutSubviews is intended for updating the layout, which is to say the center, bounds (or frame) and (optionally) transform. Now, because of the ordering of messages. You don't want to create your subviews in -layoutSubviews because that method gets invoked repeatedly during the lifetime of your view, as its parent view's bounds changed and as it gets removed or re-added to its parent view, or as its subviews change.
In the case of UITableView, -layoutSubviews is called every time the table is reloaded.
A typical exception to this rule is the creation of UITableViewCells used as rows of the table, which must be created dynamically.
The -heightForRowAtIndexPath method provides a table view with the amount of space it needs to leave for that row's cell, but doesn't actually cause the cell to be resized. YOu have to set the cell's bounds yourself when creating the cell (or the cell can set its own bounds in -initWithStyle:reuseIdentifier:, if it's designed to be a fixed value). IF your cell's size does not match the table's expectations, you will get gaps or overlaps.
For autoresizing masks to work properly, you must configure the view's initial frame yourself (either in code or in a nib). Autoresizing a view affects how it responds to changes in its parent view's bounds, but does not help in determining the view's initial frame.
Specifically, you must set the initial frames for subviews of your cells (and also ensure sibling subview ordering, etc). This is easier to do in nibs than in code.

Labels are lingering in my reusable table cells

I have a table whose cells contain labels. Whenever I dequeue a reusable cell, the old labels are still lingering on it. I was able to remove them with this:
for(int a=[[newcell subviews]count]-1; a>=0;a--)
{
if([[[[newcell subviews]objectAtIndex:a]class] isSubclassOfClass:[UILabel class]])
{
[[[newcell subviews] objectAtIndex:a] removeFromSuperview];
}
}
But when I select the cell, I can see the old text on top of the new. I tried this:
[[newcell.selectedBackgroundView subviews] makeObjectsPerformSelector: #selector(removeFromSuperview)];
[[newcell.backgroundView subviews] makeObjectsPerformSelector: #selector(removeFromSuperview)];
But it didn't work. How can I make the old labels disappear from the selected cell as well as the regular view of the cell?
Subclass UITableViewCell (if you aren't already). Override prepareForReuse and remove the labels there. Might work
This kind of problem tends to happen when you add subviews to your cells in cellForRowAtIndexPath: regardless of whether it's being dequeued or newly created. As a result, you end up creating a new subview each time the row is reused, and the old subviews accumulate.
What you instead want to do is to use the same subview each time, but just set the relevant attributes (e.g., labels or color) each time. Check out the answers to How do I clear a cell completely when I reuse it? to see some possible approaches.
I kinda did what Yuji suggested. Instead of putting in new labels on each iteration, I checked whether the cell contained labels and then either edited the labels if they were there or put them in if they weren't. Code goes like this:
if([[newcell.contentView subviews] count]>=2 && [[[[newcell.contentView subviews] objectAtIndex:0]class] isSubclassOfClass:[UILabel class]] &&
[[[[newcell.contentView subviews] objectAtIndex:1]class] isSubclassOfClass:[UILabel class]])
{
//change the text of the labels
}
else
{
//add the labels to the cell
}

Resources