I have a UITableView with UITableViewCells that are swipable. When a cell is swiped, I want a view to be visible (revealed) underneath that cell. Here's the code that I have:
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
_cellBack = [[UIView alloc] initWithFrame:CGRectMake(0, cell.frame.origin.y, cell.frame.size.width, cell.frame.size.height)];
_cellBack.backgroundColor = [UIColor whiteColor];
[self.tableView insertSubview:_cellBack belowSubview:cell];
for (int i = 0; i < [self.tableView subviews].count; i++) {
UIView *v = [[self.tableView subviews] objectAtIndex:i];
if ([v isEqual:_cellBack]) {
NSLog(#"cellBack %d", i);
}
if ([v isEqual:cell]) {
NSLog(#"cell %d", i);
}
}
In the for loop, I check to see if the views' indexes are as I expect, and indeed they are; _cellBack has an index that is one less than cell's index.
When I replace the insertSubview:belowSubview: call with insertSubview:aboveSubview:, it works fine (albeit with the white UIView showing up above the swiped cell), so it's not a matter of not allocating _cellBack properly. I've also tried insertSubview:atIndex: and that didn't work either..
What could be causing this?
Thanks!
To make the SideSwipeTableView example work on ios7 & previous, simply change the line to
[cell.superview insertSubview:self.sideSwipeView belowSubview:cell];
Anything else pushes the back view on top of the cell and you don't get any animation effect, animation happening behind your background cell! To check this change the offset of the cell to less than the full screen to check that your cell is still on the top and background one is really below. From:
cell.frame = CGRectMake(direction == UISwipeGestureRecognizerDirectionRight ? cellFrame.size.width : -cellFrame.size.width, cellFrame.origin.y, cellFrame.size.width, cellFrame.size.height);
to
cell.frame = CGRectMake(direction == UISwipeGestureRecognizerDirectionRight ? cellFrame.size.width/2 : -cellFrame.size.width/2, cellFrame.origin.y, cellFrame.size.width, cellFrame.size.height);
UITableView's already have the ability to put things behind cells. It's the backgroundView of the cell. Why don't you try that instead so you would do the following:
cell.selectedBackgroundView = _cellBack;
So it turns out that, if I add it to another view, the code works as expected.
Here's where I added it:
_cellBack = [[UIView alloc] initWithFrame:CGRectMake(0, cell.contentView.frame.origin.y, cell.frame.size.width, cell.frame.size.height)];
_cellBack.backgroundColor = [UIColor whiteColor];
[cell insertSubview:_cellBack belowSubview:cell.contentView];
I'm still not sure why the code in the question doesn't work. The SideSwipeTableView from this github account has code similar to what I had before and it works fine.
Related
I'm currently working on an iPhone app that's doing some strange things with a UIScrollView inside a UITableView. This is my first foray into iPhone dev, so I'm sure it's something silly I'm missing.
In each UITableViewCell I am putting in a basic UITableViewCell. In that UITableViewCell is a Label and a UIScrollView.
The label and scrollview is setup and working properly, but when it first displays it is offset about 30 pixels down on the y axis than it should be, or is positioned by the XIB/NIB. I am not moving it around manually. The label shows up in the right spot. at 0,0. The UIScrollView should be showing up at 0,22 but is showing up closer to 0,40.
When I swipe to scroll the containing UITableView, then all the UIScrollViews will show up in the right spot assuming that when the UITableView scrolled that UITableViewCell went offscreen.
Here is the code for the UITableView.cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"GalleryRowCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
[cell.layer setMasksToBounds:TRUE];
[cell.layer setCornerRadius:10.0];
Contagion *c = (Contagion *)[self.dataSet objectAtIndex:indexPath.row];
GalleryRowViewController *subView = [[GalleryRowViewController alloc] initWithContagion:c];
[cell.contentView addSubview:subView.view];
subView.contagionName.text = c.name;
subView.contagion = c;
return cell;
}
Here is the code for my GalleryRowViewController.viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
self.imageScroll.delegate = self;
[self.imageScroll setBackgroundColor:[UIColor blackColor]];
[self.imageScroll setCanCancelContentTouches:NO];
self.imageScroll.indicatorStyle = UIScrollViewIndicatorStyleWhite;
self.imageScroll.clipsToBounds = NO;
self.imageScroll.scrollEnabled = YES;
self.imageScroll.pagingEnabled = NO;
NSInteger x = 0;
CGFloat xPos = 0;
for (x=0;x<=10;x++) {
UIImage *image = [UIImage imageNamed:#"57-icon.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[imageView setBackgroundColor:[UIColor yellowColor]];
CGRect rect = imageView.frame;
rect.size.height = 70;
rect.size.width = image.size.width;
rect.origin.x = xPos;
rect.origin.y = 5;
imageView.frame = rect;
[self.imageScroll addSubview:imageView];
xPos += imageView.frame.size.width+5;
}
[self.imageScroll setContentSize:CGSizeMake(xPos, [self.imageScroll bounds].size.height)];
}
--- EDIT FOR IMAGES ---
After App Loads: http://img809.imageshack.us/img809/4576/screenshot20110927at427.png
After Scrolling the rows offscreen and back: http://img690.imageshack.us/img690/9461/screenshot20110927at428.png
Well, as my previous response was at too low a level, let me take another shot at it.
First, I just noticed the core problem that you're using a viewcontroller for each cell. To quote Apple, " "A single view controller typically manages the views associated with a single screen’s worth of content." That would also get rid of your XIB (just manually configuring your scrollview), which I bet will get rid of your problem.
To proceed, your main choice is whether to create a ContagionTableViewCell class or not as suggested by Scott.
If so, following the Elements example, create a subclass of UITableViewCell ContagionTableViewCell with properties of a scrollView, a labelview and a contagion. Like they use a custom setter for the element, use one for the contagion, so that whenever it is assigned, it also updates the cells label (and associated pictures).
Move your imageScroll code from GalleryRowViewController.viewDidLoad into the ContagionTableViewCell init code. Put the image code into a new routine, which will be called from the contagion setter.
If NOT, then move the GalleryRowView Controller code into your UITableView. I suggest you take a look at cellForRowAtIndexPath in Apple's tableViewSuite, the fourth example on subviews. In particular, it shows this pattern of separating the creation of a cell (when you need a brand new one) vs configuring the cell (when reusing it). As you have 10 imageViews inside your scrollView, you'll have to decide whether to delete all those (and/or the scrollview), or just reach inside and update their images when a cell is reused.
Can you post a screenshot. Its a bit hard to visualize what you are describing. I'm not sure how you are computing y origin to be 22.
As a side note I believe its cleaner to do this by creatint your own TableViewCell subclass and use that instead of the default UITableViewCell. There is an example called Elements which shows how to do this properly: http://developer.apple.com/library/ios/#samplecode/TheElements/Introduction/Intro.html
Well, I don't know it's the cause of your problem, but you've definitely got an issue. Note that every time you are asked for a cell, you're adding the galleryRow subview. When a cell goes off-screen, it's put on the reusableCell queue. Then you're asked for another cell; you get it from the queue, it still has the old galleryRow subview, and now you add another one; so that's not good. You should either reuse or delete the old one.
Finally, why are you using UITableViewCellStyleSubtitle, and then not using any of the default fields in that UITableView?
I am new to iPhone development and I am currently working on a simple RSS reader app. The problem I am having is that I need to reposition the textLabel inside the UITableViewCells. I have tried setFrame or setCenter but it doesn't do anything. Does anyone know what I need to do inside the tableView:cellForRowAtIndexPath: method to reposition the textLabel at the top of the cell (x = 0, y = 0)?
Thank you
PS: The UITableViewCell is referenced by a variable called cell. I have tried [cell setFrame:CGRectMake(0, 0, 320, 20)] with no success.
You can create a subclass for UITableViewCell and customize de textLabel frame. See that answer: Labels aligning in UITableViewCell. It's works perfectly to me.
It's my subclass
#import "UITableViewCellFixed.h"
#implementation UITableViewCellFixed
- (void) layoutSubviews {
[super layoutSubviews];
self.textLabel.frame = CGRectMake(0, 0, 320, 20);
}
#end
It's my UITableViewControllerClass:
UITableViewCellFixed *cell = (UITableViewCellFixed *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCellFixed alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
You may try indentationLevel, separatorInset and other content indentation properties of UITableViewCell object.
Seems I solved my own problem. Here's some code, in case someone runs into the same problem:
UILabel *ttitle = [[[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 20)] autorelease];
ttitle.font = [UIFont boldSystemFontOfSize:13];
ttitle.textColor = [UIColor blackColor];
ttitle.textAlignment = UITextAlignmentLeft;
[ttitle setText:[[stories objectAtIndex: storyIndex] objectForKey: #"title"]];
[cell.contentView addSubview:ttitle];
The idea is to create your own label object, because the textLabel is automatically positioned and can't be moved around.
Cheers.
The reason the original poster's code doesn't work is that it appears that the frame of the textLabel is set after the UITableViewCell has been returned from your delegate method.
I noticed that I can successfully alter many properties of the textLabel, such as the text alignment, color, font, etc, but altering the frame has no effect and when I print the frame to the debugger later (like on select), the frame isn't what I set. Therefore, I conclude that the UIKit framework is altering the frame of the textLabel after it is returned from the delegate method. No doubt this is likely done because Apple engineers wanted to make sure that your text was drawn to the screen, so they measure it and alter the frame so that it will fit. They probably figured that people such as ourselves who wanted to alter the position of the text would be able to do so by subclassing, or simply adding another UILabel (or whatever) as a subview. A novice developer might have a very hard time if his or her text didn't show up in the label or was truncated because they didn't adjust the frame.
In my case, I wanted the text to be center horizontally, to be a specific color/font/size, and to be slightly higher vertically in the cell. Being too lazy to subclass this, I first tried altering the frame. When that didn't work, I tried googling the answer (found this post).
My final solution was to set the numberOfLines property to 0 and add some trailing carriage returns to my text. Now THAT is lazy.
In Swift 3 it would be
override func layoutSubviews() {
super.layoutSubviews()
self.textLabel?.frame.origin.x = 50
}
Before describing the problem, let me first point out that this is a distinct issue from this question.
The Problem
This screenshot was taken with a break set at tableView:didSelectRowAtIndexPath:, and as you can see in the simulator (far right of the image), there's a single-pixel blue line at the bottom of the selected cell. This is not the design asked for by the client, nor is it how this app used to behave: there should be no separator, even on selection.
How I Got Here
I'd initially designed this table view using custom UITableViewCell classes with corresponding nib (.xib) files and had no trouble with selections: the separator was hidden as desired. Predictably, scrolling was sluggish due to all the overhead from the view hierarchy, so I reworked the custom cells to use Loren Brichter's fast scrolling solution. Now scrolling is much faster, but I can't get rid of the separator for the life of me.
What I've tried
At the time of the screenshot above...
the table view has "Separator [None]" in IB.
the UIViewController that contains the table view has this line in viewDid Load: self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
As you can see in the screenshot, I inserted some unused lines to prove that separatorStyle is set as desired. Other testing confirms that tableView and self.tableView are equivalent pointers at that same breakpoint.
I've also tried setting tableView.separatorColor to black and to clear, all with the same result: the cells look right until a selection is made.
Manjunath: Here's the code I'm using to draw alternate backgrounds depending on whether the cell's been touched or not. You can see the difference—which is less subtle when animated—in the screenshot.
if(self.highlighted) {
textColor = [UIColor blackColor];
UIImage *bg = [UIImage imageNamed:#"image-cell-background_highlighted.png"];
[bg drawAtPoint:CGPointMake(0.0, 1.0)];
}
else {
UIImage *bg = [UIImage imageNamed:#"image-cell-background.png"];
[bg drawAtPoint:CGPointMake(0.0, 0.0)];
}
This gets called in UIImageCell.m in drawContentView:, a method inherited from Mr. Brichter's ABTableViewCell super class.
Chris,
Delving into ABTableViewCell, I see:
- (void)setFrame:(CGRect)f
{
[super setFrame:f];
CGRect b = [self bounds];
b.size.height -= 1; // leave room for the seperator line
[contentView setFrame:b];
}
Since the height of the cell is one pixel shorter than the actual cell, when the cell gets selected, that one-pixel line will bleed through in the color of the selection color. It may look like it's the separator, but it is actually the selection color.
To test, try to change that line above to be two pixels or more shorter to see what happens.
Update:
By making this change to the FastScrollingExample project's -rootViewController:
- (void)viewDidLoad
{
self.title = #"Fast Scrolling Example";
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
[super viewDidLoad];
}
and commenting out:
// if(self.selected)
// {
// backgroundColor = [UIColor clearColor];
// textColor = [UIColor whiteColor];
// }
//
in -drawContentView to mimic what would happen if you didn't have the selection color showing through, then I get a screen shot like this:
alt text http://files.me.com/mahboud/7k656q
Look familiar?
How would you get around this? If you don't need to select cells, then disable cell selection. Otherwise, if you are selecting cells, then you should make the rect larger so the default selection color doesn't show through when you paint with your own selection color in -drawConentRect.
Try this:
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = NSLocalizedString(#"Cell",#"");
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (nil == cell)
{
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
return cell;
}
I added the interfaceOrientation to my app. It works fine concerning the views. Some of the table-cells I defined by CGRects to position the text in the cell. In portrait-mode the cell is 300px long, in landscape-mode 420px. I use the following code to change the CGRects depending the orientation:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (self.interfaceOrientation == UIDeviceOrientationPortrait) {
NSString *currentLanguage = [[NSString alloc] initWithContentsOfFile:[NSHomeDirectory() stringByAppendingPathComponent:#"/Documents/sprache.txt"]];
static NSString *TableViewTableCellIdentifier = #"TableViewTableCellIdentifier";
UITableViewCell *cell = (UITableViewCell *) [tableView dequeueReusableCellWithIdentifier:TableViewTableCellIdentifier];
CGRect cellRect = CGRectMake(0, 0, 300, 175);
cell.backgroundColor = [UIColor darkGrayColor];
cell = [[[UITableViewCell alloc] initWithFrame:cellRect reuseIdentifier:TableViewTableCellIdentifier] autorelease];
CGRect keyLabelRect = CGRectMake(0, 5, 5, 20);
UILabel *keyLabel = [[UILabel alloc]initWithFrame:keyLabelRect];
keyLabel.tag = 100; //.........
} else {
NSString *currentLanguage =
[[NSString alloc] initWithContentsOfFile:[NSHomeDirectory() stringByAppendingPathComponent:#"/Documents/sprache.txt"]];
static NSString *TableViewTableCellIdentifier = #"TableViewTableCellIdentifier";
UITableViewCell *cell = (UITableViewCell *) [tableView dequeueReusableCellWithIdentifier:TableViewTableCellIdentifier];
CGRect cellRect = CGRectMake(0, 0, 450, 175);
cell.backgroundColor = [UIColor darkGrayColor];
cell = [[[UITableViewCell alloc] initWithFrame:cellRect reuseIdentifier:TableViewTableCellIdentifier] autorelease];
CGRect keyLabelRect = CGRectMake(0, 5, 5, 20);
UILabel *keyLabel = [[UILabel alloc] //.....
}
}
My problem is, when the table is visible and the orientation is changed, I need to scroll to see the new "layout". How can I manage to "reload" the view after changing the orientation?
First of all, this will leak like mad. You create cells and dequeue them but you never release them. You just create an entirely new cell every time a cell is requested. There is no reason to recreate a cell if you dequeue and vice versa. More importantly, your creating as many cells as you have rows in your logical table. That will eat all your memory very quickly.
When you dequeue a cell, you need to check if it's frame is the right size and reuse it if it is. If it is not, then you need to release the cell and create another one.
Second, a table will not ask for new cells until you scroll the existing cells off the screen. This is why your cells do not change until you scroll. It's the expected behavior of a tableview.
My standing recommendation for any moderately complex view is to use different view-controller/view pairs for each orientation. It seems like more work but usually I find it it actually takes less and it's easier to manage. In this case, having two separate tables will probably save you a lot of grief.
I'm pretty sure that you do not need to specify frame during default UITableViewCell construction. Usually frame size is handled by UITableView itself.
But if you wish you can send setNeedsLayout and/or setNeedsDisplay message to a tableView to force it update cell layout (in a orientation handler).
I have a UITableView with reorderable rows and I'm using the standard UITableViewCell.text property to display text. When I tap Edit, move a row, tap Done, then tap the row, the built-in UILabel turns completely white (text and background) and opaque, and the blue shade to the cell doesn't show behind it. What gives? Is there something I should be doing that I'm not? I have a hacky fix, but I want the real McCoy.
Here is how to reproduce it:
Starting with the standard "Navigation-Based Application" template in the iPhone OS 2.2.1 SDK:
Open RootViewController.m
Uncomment viewDidLoad, and enable the Edit button:
- (void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
Specify that the table has a few cells:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 4;
}
In tableView:cellForRowAtIndexPath:, add a line to set the text property of a cell, and therefore to use the built-in UILabel subview:
// Set up the cell...
cell.text = #"Test";
To enable reordering, uncomment tableView:moveRowAtIndexPath:toIndexPath:. The default implementation is blank, which is fine in this case since the template doesn't include a data model.
Configure the project for the Simulator, OS 2.2.1, Build and Go. When the app comes up, tap Edit, then slide any row to a new position, tap Done, and then tap each row one at a time. Usually a tap will select a row, turn it blue, and turn its text white. But a tap on the row that you just moved does that and leaves the UILabel's background color as white. The result is a confusing white open space with blue strips on the edges. Oddly enough, after the first bogus tap, another tap appears to correct the problem.
So far I have found a hack that fixes it, but I'm not happy with it. It works by ensuring that the built-in UILabel is non-opaque and that it has no background color, immediately upon selection.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// hacky bugfix: when a row is reordered and then selected, the UILabel displays all crappy
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
for (UIView *view in cell.contentView.subviews) {
if ([[view class] isSubclassOfClass:[UILabel class]]) {
((UILabel *) view).backgroundColor = nil;
view.opaque = NO;
}
}
// regular stuff: only flash the selection, don't leave it blue forever
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
This appears to work, but I don't expect it to be a good idea forever. What is the Right Way to fix this?
This looks like a bug in UITableView's rendering, and you should file a Radar bug report on it. It's like the cells don't get refreshed properly after the move.
One way to work around this for now is to not use the built-in label, but roll your own in the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
CGRect frame = cell.contentView.bounds;
frame.origin.x = frame.origin.x + 10.0f;
UILabel *textLabel = [[UILabel alloc] initWithFrame:frame];
[textLabel setAutoresizingMask:UIViewAutoresizingFlexibleRightMargin];
textLabel.tag = 1;
textLabel.textAlignment = UITextAlignmentLeft;
textLabel.backgroundColor = [UIColor clearColor];
textLabel.textColor = [UIColor blackColor];
textLabel.font = [UIFont boldSystemFontOfSize:20.0];
textLabel.numberOfLines = 1;
textLabel.highlightedTextColor = [UIColor whiteColor];
[cell.contentView addSubview:textLabel];
[textLabel release];
}
UILabel *textLabel = (UILabel *)[cell viewWithTag:1];
textLabel.text = #"Test";
return cell;
}
I tried this, and it doesn't exhibit the same sort of white blank rectangle you see with the built-in label. However, adding another non-opaque view to the table cell might not be the best for overall rendering performance.
I don't know how major of a glitch this is, because Apple doesn't want you to persist a selection highlight on a table row (they've been enforcing this lately during the review process). You're supposed to place a checkmark or move on to the next level in the navigation hierarchy with a selection, at which point this white box would only be on the screen for a fraction of a second.
The trick in the solution from Brad appears to be:
textLabel.backgroundColor = [UIColor clearColor];
If you leave the background as the default you still get the problem even when you roll your own cells UITableViewCells.
The reason I left it as the default is because the documentation says it is less computationally costly to use opaque backgrounds. Ideally I wouldn't want to use [UIColor clearColor] to fix this bug.
Maybe a completely custom painted cell would somehow fix it. I haven't tried those before though.
Does anyone else have a solution for this?
Thanks for the info, I was searching how to erase the background color from a UILabel.
I used the following line:
textLabel.backgroundColor = [UIColor clearColor];
and worked perfectly!!!
thanks
Alejandra :)
Selections aren't meant to be shown for extended periods! (We got knocked on this for several of our apps)
??? That means Apple would not approve their own Calendar app on iPhone! When you go to edit the start and end times of the event, the start time is selected indefinitely, it only changes once the user taps to the next field.