UITableView in self.view - ios

I'm trying to customize all my tableViews using this code but Xcode is giving me an error.
I have 2 tableViews and I need to find the tableView != self.tableView.
How can I do that?
for(UITableView *tableView in [self.view subviews]) {
if (![tableView isEqual:self.tableView]) {
tableView.separatorInset = UIEdgeInsetsMake(0, 58, 0, 0);
}
}

You need to use like below
for(UIView * view in [self.view subviews]) {
if ([view isKindOfClass:[UITableView class]]) {
UITableView * tblView = (UITableView *) view;
tblView.separatorInset = UIEdgeInsetsMake(0, 58, 0, 0);
}
}
I hope it helps you.

I suspect the error you are getting is something to do with the fact you are assuming every View you are getting back from subviews you are passing into a UITableView in UITableView *tableView in [self.view subviews] but not every view that is returned is going to be a UITableView and it is bad practice to assume that they will be. A good idea would be to change the UITableView to UIView because most if not all UI elements are subclassed from UIView. Once you have done that you will need to check that it is an instance of UITableView and if it is you can start casting view to a UITableView. See the revised code below for a better understanding.
// the issue in your code is that you are getting views back and you are assuming it is
// a UITableView (So I bet that the error is something do with this)
for(UIView *view in [[self view] subviews]) {
// Then we will want to check that it is an instance UITableView
// because not everything will be, the initial View will not be
// an instance of UITableView
if([view isKindOfClass:[UITableView class]]) {
UITableView *tb = view
// Right so we know we have a UITableView
// Now we want to check that it not equal to self.tableView as your code
// indicates you want
if(![tb sEqual:[self tableView]]) {
tb.separatorInset = UIEdgeInsetsMake(0, 58, 0, 0);
}
}
}
UPDATE
Thank you for providing the error
-[UIView setSeparatorInset:]: unrecognized selector sent to instance
The reason for this is because you will be getting a UIView returned in your for loop this is way we have the check to make sure that the view returned is of class UITableView and if it is we start casting to it.

Related

Add subview to a custom class

I have a UITextField that I want to create a custom class on. So I created a file with a subclass of UITextField. Next, in the custom class, I want to implement a tableView. Kind of like a auto-complete textField.
I started creating it, and added the tableView like this:
[self addSubview:self.tableView];
When I run the app, the tableView is in the textField, so I can only see part of the tableView. How can I add it as a subview so I can see the full tableView?
This is what you are looking for
https://github.com/gaurvw/MPGTextField
This uitextfield subclass does what you want - it's builed for 'search' feature.
If you still want to use your own,
add tableview not to uitextfield itself, but like
[[self superview] addSubview:tableViewController.tableView];
EDIT:
you can set frame as:
CGRect frameForPresentation = [self frame];
frameForPresentation.origin.y += self.frame.size.height;
frameForPresentation.size.height = 200;
[tableViewController.tableView setFrame:frameForPresentation];
The way to add subview to uitextfield is to overload layoutSubviews method and init your tableview there:
- (void)layoutSubviews
{
[super layoutSubviews];
if (!self.tableview.superview)
{
[self setupView];
}
}
This will add the tableView as the subView of the textField.
self.tableView.frame = CGRectMake(0, CGRectGetHeight(self.bounds), CGRectGetWidth(self.bounds), YOUR_TABLE_HEIGHT);
[self addSubview:self.tableView];
self.clipsToBounds = NO;
However, a better way is to make the tableView as the textField's superView's subView, that is, the textField and the tableView should be siblings.

How to remove Views from UITableView custom row on PrepareForReuse?

I have a UITable view with a random number of UIImageView in every Single row. In the costruction of the single row, I've used [self addSubView: xxx] and a for cycle to add every UIImageView i need to add (my model has an array of URL). But now I've noticed that when the UITableView reuse the rows and it doesn't clean the UIImageViews added.
I've tied to manually remove them in method onPrepareForReuse as in the code:
if(_messageContentsFrames != nil){
for(NVChatMessageContent *singleContent in _messageContentsFrames){
[singleContent removeFromSuperview];
}
}
But it gives me error. How can i completely reset the view when it is going to be reused?
You can remove these imageViews in -tableView:cellForRowAtIndexPath: method. That always worked for me.
By the way, do you really need to reuse cells if you make such work with it. Wouldn't it be easier to create new cell?
Or it would be better not to remove imageViews, but reconfigure them on reusing and remove those views, that cell doesn't need.
Option 1. Set tag to your UIImageView (use: imageView.tag = 123456) and then in tableView:cellForRowAtIndexPath: you should get the specific view using:
id imageView = [self.view viewWithTag:yourInteger];
[imageView removeFromSuperview];
Option 2. Use:
for( UIView *view in cell.subviews) {
if ([view isKindOfClass:[UIImageView class]]) {
[view removeFromSuperView];
}
}

Remove Subview from Superview

I am really stuck in this issue for quite a long time
I am trying to add a UIControl (which is a UIView in the end) to a UITableViewCell that i have subclasses in my own class (i made a custom cell)
on swipe, i create my UIControl class and add it to myself (the cell), so far so good. Here is the code
[self addSubview:_statusView];
However, i am adding a target action to my UIControl in the custom cell, so that the cell can handle when the UIControl says that he has recognized a touchDownEvent.
[self.statusView addTarget:self action:#selector(resetAll:) forControlEvents:UIControlEventTouchDown];
And here is what i want to do in the action, I want to remove that UIControl from self.subviews (the cell's subviews), so i set the action method to be like this
- (void)resetAll:(id)sender
{
for (UIView *view in self.subviews) {
if ([view isKindOfClass:[StatusView class]]) {
[view removeFromSuperview];
}
}
}
Can someone point out whats wrong in this code? because i can't really figure out why the view that gets added to the cell does't get removed. It seem to me the the subviews property doesn't ever contain my UIControl that i add.
- (void)resetAll:(id)sender
{
for (UIView *view in self.view.subviews) {
if ([view isKindOfClass:[StatusView class]]) {
[view removeFromSuperview];
}
}
}
or
- (void)resetAll:(id)sender
{
[sender removeFromSuperview];
}
UITableViewCell internally does some manipulations with its view hierarchy. You should add subviews not to the cell itself, but to its contentView, as stated in the docs:
If you want to go beyond the predefined styles, you can add subviews
to the contentView property of the cell.
So you have to replace
[self addSubview:_statusView];
with
[self.contentView addSubview:_statusView];
And then iterate on subviews of the contentView:
for (UIView *view in self.contentView.subviews) {
if ([view isKindOfClass:[StatusView class]]) {
[view removeFromSuperview];
}
}

UICollectionView not removing old cells after scroll

I have a UICollectionView with a grid of images. When you tap on one, it opens up the grid and shows a subview with some details. Like this:
I open up the grid in my UICollectionViewLayout by adjusting the UICollectionViewLayoutAttributes and setting a translation on the transform3D property for all cells below the current row of the selected item. This works really nicely, and is a much better animation and a simpler approach than my first attempt at inserting another cell into the grid which is a different size to the others.
Anyway... it works most of the time, but then after continued use I see old images on the collection view. They are like ghost cells. I can't click them, it's like they haven't been removed from the collection view properly, and they sit on top of the cells preventing taps and just being a nuisance. Like this:
Any ideas why these cells are doing this?
EDIT:
I'd like to add, I think it only happens when i scroll the collection view really fast. I've written my own UICollectionViewFlowLayout replacement to test if it still happens. It does.
EDIT 2:
The 3d transforms or layout have nothing to do with this. It must be a bug in UICollectionView. I can exploit by just scrolling really fast, letting come to a standstill and then querying the views that are on screen. There are often double the number of cells, but they are hidden as they are stacked on top of each other. My implementation above reveals them because of the translation i do.
This can really hurt performance too.
See my answer for a solution.
My second edit of my question details why this is happenening, and here is my workaround. It's not bullet proof, but it works in my case, and if you experience somethign similar you could tweak my solution:
- (void) removeNaughtyLingeringCells {
// 1. Find the visible cells
NSArray *visibleCells = self.collectionView.visibleCells;
//NSLog(#"We have %i visible cells", visibleCells.count);
// 2. Find the visible rect of the collection view on screen now
CGRect visibleRect;
visibleRect.origin = self.collectionView.contentOffset;
visibleRect.size = self.collectionView.bounds.size;
//NSLog(#"Rect %#", NSStringFromCGRect(visibleRect));
// 3. Find the subviews that shouldn't be there and remove them
//NSLog(#"We have %i subviews", self.collectionView.subviews.count);
for (UIView *aView in [self.collectionView subviews]) {
if ([aView isKindOfClass:UICollectionViewCell.class]) {
CGPoint origin = aView.frame.origin;
if(CGRectContainsPoint(visibleRect, origin)) {
if (![visibleCells containsObject:aView]) {
[aView removeFromSuperview];
}
}
}
}
//NSLog(#"%i views shouldn't be there", viewsShouldntBeThere.count);
// 4. Refresh the collection view display
[self.collectionView setNeedsDisplay];
}
and
- (void) scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (!decelerate) {
[self removeNaughtyLingeringCells];
}
}
- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[self removeNaughtyLingeringCells];
}
A quick further comment to bandejapaisa's: under iOS 6 only, I found that UICollectionView also had a habit of bungling animated transitions. The original cells would remain where they were, copies would be made and then the copies would be animated. Usually on top of the originals but not always. So a simple bounds test wasn't sufficient.
I therefore wrote a custom subclass of UICollectionView that does the following:
- (void)didAddSubview:(UIView *)subview
{
[super didAddSubview:subview];
//
// iOS 6 contains a bug whereby it fails to remove subviews, ever as far as I can make out.
// This is a workaround for that. So, if this is iOS 6...
//
if(![UIViewController instancesRespondToSelector:#selector(automaticallyAdjustsScrollViewInsets)])
{
// ... then we'll want to wait until visibleCells has definitely been updated ...
dispatch_async(dispatch_get_main_queue(),
^{
// ... then we'll manually remove anything that's a sub of UICollectionViewCell
// and isn't currently listed as a visible cell
NSArray *visibleCells = self.visibleCells;
for(UIView *view in self.subviews)
{
if([view isKindOfClass:[UICollectionViewCell class]] && ![visibleCells containsObject:view])
[view removeFromSuperview];
}
});
}
}
Obviously it's a shame that 'is this iOS 6' test can't be a little more direct but it's hidden off in a category in my actual code.
A Swift UICollectionView extension version of bandejapaisa's answer:
extension UICollectionView {
func removeNaughtyLingeringCells() {
// 1. Find the visible cells
let visibleCells = self.visibleCells()
//NSLog("We have %i visible cells", visibleCells.count)
// 2. Find the visible rect of the collection view on screen now
let visibleRect = CGRectOffset(bounds, contentOffset.x, contentOffset.y)
//NSLog("Rect %#", NSStringFromCGRect(visibleRect))
// 3. Find the subviews that shouldn't be there and remove them
//NSLog("We have %i subviews", subviews.count)
for aView in subviews {
if let aCollectionViewCell = aView as? UICollectionViewCell {
let origin = aView.frame.origin
if (CGRectContainsPoint(visibleRect, origin)) {
if (!visibleCells.contains(aCollectionViewCell)) {
aView.removeFromSuperview()
}
}
}
}
// 4. Refresh the collection view display
setNeedsDisplay()
}
}

How to make UITable header stick to the top of the screen when using Storyboards?

I've made header using this post: Table Header Views in StoryBoards
But i am unable to make header stick (fix) to the top of the screen while scrolling.
How can that be achieved?
P.S. Table style is plain, and when i tired to overload viewForHeaderInSection and returned outlet for header view, it just got worse.
Thanks in advance!
Here's the code which I believe makes the header view stick to the table top (that is not it's default behavior):
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGRect rect = self.tableHeaderView.frame;
rect.origin.y = MIN(0, self.tableView.contentOffset.y);
self.tableHeaderView.frame = rect;
}
An alternative is to have header view for the first section. You can't use any subview at viewForHeaderInSection, you have to return there the view which is not yet at views hierarchy. The advantage of the method is that the section header view is designed to be at the table top, the drawback is that you might want to have headers for sections in the feature, it will break the solution.
I think it's better for you to use a UIViewController, instead of a UITableViewController in the storyboard, to achieve this. Just put a header in the top of the view, in the storyboard, and put a UITableView under the header.
I was able to do it (at least in iOS 6) like this, don't know why this thing works:)
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if(self.headerManualView == nil) {
//allocate the view if it doesn't exist yet
headerManualView = [[UIView alloc] init];
//create the button
UITextField *txtField = [[UITextField alloc] initWithFrame:CGRectMake(10, 3, 250, 44)];
//the button should be as big as a table view cell
txtField.borderStyle = UITextBorderStyleRoundedRect;
//set action of the button
//[txtField addTarget:self action:#selector(removeAction:) forControlEvents:UIControlEventTouchUpInside];
//add the button to the view
[headerManualView addSubview:txtField];
}
//return the view for the footer
return headerManualView;
}

Resources