Been searching for this for quite a while, and I still haven't found a way to do it.
I'd like to reproduce the section headers of the iPhone's Spotlight into a UITableView.
"Regular" table view headers stay visible at the top of a section when you scroll, as we all know. But there is one kind of header that I've never seen elsewhere than in the Spotlight page of Springboard: there, the headers span the whole height of the section and are not stuck on the top of the section, but on the left side.
How the heck is that achieved?
Good question. I made a little experiment. It almost looks like the view from spotlight. But it's lacking one import feature. The lower "image" doesn't push the upper image to the top if they collide.
I doubt that there is a built in solution without the use of private frameworks.
I achieved this:
As you can see the two header images at the top overlap. They don't push each other like normal headers to. And I had to deactivate the cell separators. If I would use them they would appear at the whole cell. So you have to draw them yourself. Not a big deal.
But I have no idea how I could fix the overlapping.
Here is the code that made this happen:
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 1;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView *contentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.bounds.size.width, 44)];
contentView.backgroundColor = [UIColor lightGrayColor];
UIImageView *imageView = [[[UIImageView alloc] initWithFrame:CGRectMake(5, 5, 34, 34)] autorelease];
imageView.image = [UIImage imageNamed:[NSString stringWithFormat:#"%d", section]];;
[contentView addSubview:imageView];
return [contentView autorelease];
}
Okay I have done something similar to what the music app and spotlight search does.
I have not subclassed UITableView, I have just tracked the sections through it's scrollViewDidScroll method, and added the header views to the left of the tableView (so you will have to put the tableview to the right in your viewController's view, which means you can't use UITableViewController).
this method should be called in the scrollViewDidScroll, ViewDidLoad, and in didRotateFromInterfaceOrientation: (if you support rotation)
keep in mind that you will have to make space in the left equal to the size of the headers in any interface orientation that you support.
-(void)updateHeadersLocation
{
for (int sectionNumber = 0; sectionNumber != [self numberOfSectionsInTableView:self.tableView]; sectionNumber++)
{
// get the rect of the section from the tableview, and convert it to it's superview's coordinates
CGRect rect = [self.tableView convertRect:[self.tableView rectForSection:sectionNumber] toView:[self.tableView superview]];
// get the intersection between the section's rect and the view's rect, this will help in knowing what portion of the section is showing
CGRect intersection = CGRectIntersection(rect, self.tableView.frame);
CGRect viewFrame = CGRectZero; // we will start off with zero
viewFrame.size = [self headerSize]; // let's set the size
viewFrame.origin.x = [self headerXOrigin];
/*
three cases:
1. the section's origin is still showing -> header view will follow the origin
2. the section's origin isn't showing but some part of the section still shows -> header view will stick to the top
3. the part of the section that's showing is not sufficient for the view's height -> will move the header view up
*/
if (rect.origin.y >= self.tableView.frame.origin.y)
{
// case 1
viewFrame.origin.y = rect.origin.y;
}
else
{
if (intersection.size.height >= viewFrame.size.height)
{
// case 2
viewFrame.origin.y = self.tableView.frame.origin.y;
}
else
{
// case 3
viewFrame.origin.y = self.tableView.frame.origin.y + intersection.size.height - viewFrame.size.height;
}
}
UIView* view = [self.headerViewsDictionary objectForKey:[NSString stringWithFormat:#"%i", sectionNumber]];
// check if the header view is needed
if (intersection.size.height == 0)
{
// not needed, remove it
if (view)
{
[view removeFromSuperview];
[self.headerViewsDictionary removeObjectForKey:[NSString stringWithFormat:#"%i", sectionNumber]];
view = nil;
}
}
else if(!view)
{
// needed, but not available, create it and add it as a subview
view = [self headerViewForSection:sectionNumber];
if (!self.headerViewsDictionary && view)
self.headerViewsDictionary = [NSMutableDictionary dictionary];
if (view)
{
[self.headerViewsDictionary setValue:view forKey:[NSString stringWithFormat:#"%i", sectionNumber]];
[self.view addSubview:view];
}
}
[view setFrame:viewFrame];
}
}
also we need to declare a property that would keep the views that are visible:
#property (nonatomic, strong) NSMutableDictionary* headerViewsDictionary;
these methods return the size and X axis offset of the header views:
-(CGSize)headerSize
{
return CGSizeMake(44.0f, 44.0f);
}
-(CGFloat)headerXOrigin
{
return 10.0f;
}
I have Built the code so that any header view that's not needed gets removed, so we need a method that would return the view whenever needed:
-(UIView*)headerViewForSection:(NSInteger)index
{
UIImageView* view = [[UIImageView alloc] init];
if (index % 2)
{
[view setImage:[UIImage imageNamed:#"call"]];
}
else
{
[view setImage:[UIImage imageNamed:#"mail"]];
}
return view;
}
here's how it will look :
How it will look in lanscape, I have used contraints to give 44px in the left of the tableView
hope this helps :).
Good news: See answer of How to achieve an advanced table view header
result http://i.minus.com/jyea3I5qbUdoQ.png
Related
I used UITableviewcellEditingstyleDelete to show button for user click on it to show delete button (it's the same way you can see on email app, when user click edit and then click on button to show the delete button). It's work fine in ios6 but when I build my app on device which have ios 7, the delete button is disappear, but when you tap in the delete button's area it's also can delete. The prolem is user cannot see the delete button (The button which have red color provide by OS).
My code is:
- (UITableViewCellEditingStyle)tableView:(UITableView *)aTableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Detemine if it's in editing mode
if (self.editing)
{
return UITableViewCellEditingStyleDelete;
}
return UITableViewCellEditingStyleNone;
}
please help me to find the solution, I'm not know much with iOS7 enviroment.Thanks!
Thanks all for your advice, and this solution can solved it, I make it into custom cell
UIImageView* backgroundImage;
//Variable for animation delete button
// Keep the ContactView in normal state
BOOL bFirstEditingCell;
BOOL bShownRedMinus;
BOOL bShownDeleteButton;
CGRect frameOfContactViewInNormal;
CGPoint centerOfCellInNormal;
UITableViewCellAccessoryType accessoryTypeInNormal;
//
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super initWithCoder:decoder])
{
super.backgroundColor = [UIColor clearColor];
self.selectionStyle = UITableViewCellSelectionStyleNone;
backgroundImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"row_gradient" ]];
self.backgroundView = backgroundImage;
bShownDeleteButton = false;
bShownRedMinus = false;
bFirstEditingCell = true;
accessoryTypeInNormal = UITableViewCellAccessoryNone;
}
return self;
}
- (void)willTransitionToState:(UITableViewCellStateMask)state {
[super willTransitionToState:state];
if (!isOS7()) {
return;
}
for (UIView *subview in self.subviews) {
//Keep normal value of contact view and cell
if (bFirstEditingCell ) {
frameOfContactViewInNormal = self->contactView.frame;
frameOfContactViewInNormal.size.width = kContactViewWidth;
centerOfCellInNormal = subview.center;
bFirstEditingCell = false;
}
if (state == UITableViewCellStateDefaultMask) {
self.backgroundView = backgroundImage;
subview.center = centerOfCellInNormal;
//Set for position of speed dial image
CGRect f = frameOfContactViewInNormal;
if (bShownRedMinus) {
f.size.width -= kRedMinusButtonWidth;
}
self->contactView.frame = f;
bShownDeleteButton = false;
self.accessoryType = accessoryTypeInNormal;
}
else if (state == UITableViewCellStateShowingDeleteConfirmationMask)
{
float sectionIndexWidth = 0.0;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
sectionIndexWidth = 30.0;
}
else {
sectionIndexWidth = 15.0;
}
CGPoint center = centerOfCellInNormal;
subview.center = CGPointMake(center.x - sectionIndexWidth, center.y);
self.backgroundView = nil;
//Set width of contact name
UIView* view = [subview.subviews objectAtIndex: 0];
CGRect f = view.frame;
f.origin.x = (kDeleteButtonWidth + sectionIndexWidth);
view.frame = f;
f = frameOfContactViewInNormal;
f.size.width = self.frame.size.width - (kDeleteButtonWidth + sectionIndexWidth);
self->contactView.frame = f;
bShownDeleteButton = true;
bShownRedMinus = false;
accessoryTypeInNormal = self.accessoryType;
self.accessoryType = UITableViewCellAccessoryNone;
}
else if (state == UITableViewCellStateShowingEditControlMask) {
CGRect f = frameOfContactViewInNormal;
f.size.width -= 5;
self->contactView.frame = f;
bShownRedMinus = true;
}
else if (state == 3) { //State for clicking red minus button
CGRect f = frameOfContactViewInNormal;
f.size.width += kRedMinusButtonWidth;
self->contactView.frame = f;
self.backgroundView = nil;
}
}
}
It looks like one of the Bug from iOS 7. For some reason the backgroundView is moved by iOS over the delete button. You can work-around this by sub classing your backgroundView and implementing the setFrame function of your derived view like this :
UITableViewCell delete button gets covered up.
It may also happen when the accesoryView is specified and the editingAccessoryView is nil. Detailed explanation of this issue and solution is mentioned here :
UITableViewCell content overlaps delete button when in editing mode in iOS7.
You do not need to check if the tableView is being edited...just implement:
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewCellEditingStyleDelete;
}
Best way to remove this problem is that add an image in cell and set it in Backside.
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"bgImg.png"]];
imageView.frame = CGRectMake(0, 0, 320, yourCustomCell.frame.size.height);
[yourCustomCell addSubview:imageView];
[yourCustomCell sendSubviewToBack:imageView];
If your text would overlap the delete button then implement Autolayout. It'll manage it in better way.
One more case can be generate that is cellSelectionStyle would highlight with default color. You can set highlight color as follows
yourCustomCell.selectionStyle = UITableViewCellSelectionStyleNone;
Set your table cell's selection style to UITableViewCellSelectionStyleNone. This will remove the blue background highlighting or other. Then, to make the text label or contentview highlighting work the way you want, use this method in yourCustomCell.m class.
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
if (highlighted)
self.contentView.backgroundColor = [UIColor greenColor];
else
self.contentView.backgroundColor = [UIColor clearColor];
}
I hope it will help you to understand.
Simpler Workaround
Assuming an iOS7 only app, with a technique similar to that linked in the post by Vin above, I believe the approach here: https://stackoverflow.com/a/19416870/535054 is going to be cleaner.
In this approach, you don't need to subclass your backgroundView, which could be different for different cells.
Place the code in the answer I've linked to above, in the root of your custom table cell hierarchy, and all of your table cells (that inherit from it), get the fix whenever they use the backgroundView or selectedBackgroundView properties.
Copy the solution from this gist: https://gist.github.com/idStar/7018104
An easy way to solve this problem is to make the delete confirmation button view as a front view. It can be done by implementing the delegate method layoutSubviews in customtableviewcell.m file.
In my case I solved it by just adding the following code into customtableviewcell.m file. It may be little different according to how the views are placed in your cell. But surely it will give you an idea about how to solve the problem.
- (void)layoutSubviews // called when a interface orientation occur ie. in our case when the '-' button clicks
{
[super layoutSubviews];
for (UIView *subview in self.subviews) { // Loop through all the main views of cell
// Check the view is DeleteConfirmationView or not, if yes bring it into the front
if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellDeleteConfirmationView"]) {
[self bringSubviewToFront:subview];// code for bring the view into the front of all subviews
}
}
}
I have a UITableView where each cell contains one UIView in its contentView, let's call it V.
V also has subViews, one UIImageView and UILabel.UIImage is just a white rounded rectangle
I want my cell (along with UIImageView) to expand and shrink when selected. I added some code to didSelectRowAtIndexPath and heightForRowAtIndexPath methods.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSNumber * key = [NSNumber numberWithInt:indexPath.row];
UITableViewCell *cell = [cells objectAtIndex:indexPath.row];
UIView * v = [[[cell contentView] subviews] objectAtIndex:0];
UIImageView * image = [[v subviews] objectAtIndex:0];
_tappedIndex = [key intValue];
_expanded = [_tappedCells objectForKey:key] == nil;
NSLog(#"view's old params: %#", [GGStackPanel printFrameParams:v]); //prints out the frame params
NSLog(#"image's old params: %#", [GGStackPanel printFrameParams:image]);
if (_expanded)
{
[_tappedCells setObject:key forKey:key];
v.frame = CGRectMake(v.frame.origin.x, v.frame.origin.y, v.frame.size.width, v.frame.size.height*1.5);
image.frame = CGRectMake(image.frame.origin.x, image.frame.origin.y, image.frame.size.width, v.frame.size.height + 73);
}
else
{
[_tappedCells removeObjectForKey:key];
v.frame = CGRectMake(v.frame.origin.x, v.frame.origin.y, v.frame.size.width, v.frame.size.height/1.5 );
image.frame = CGRectMake(image.frame.origin.x, image.frame.origin.y, image.frame.size.width, v.frame.size.height - 113);
}
NSLog(#"view's new params: %#", [GGStackPanel printFrameParams:v]);
NSLog(#"image's new params: %#", [GGStackPanel printFrameParams:image]);
[tableView beginUpdates];
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tv heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *v = [_cells objectAtIndex:indexPath.row];
CGFloat height = v.frame.size.height;
if (indexPath.row == _tappedIndex)
{
if (_expanded)
{
height = v.frame.size.height * 1.5;
}
else
{
height = v.frame.size.height/1.5;
}
}
return height;
}
The cell frame and V's are expanding, I logged their frame parameters. But the imageView always stays the same, even if you change its frame at each cell selection. Its frame changes while in didSelectRowAtIndexPath method, but when you tap on the same cell again, it says that its frame hasn't change from last time.
If I put another UIView instead of an imageView, it expands and shrinks with its parent View and cell. Thus, I came up with really odd solution: cut the rounded rectangle, leave narrow top and bottom parts as imageView. Then put a blank UIView in the middle, with same color as a rectangle. Now my "rectangle" is expanding, because the blank UIView expands and those two imageViews are shifting up and down. I don't like this solution, because UI gets messed up if you turn your phone to landscape mode, or try to expand a cell from landscape mode and go back to portrait.
Any comments, suggestions?
UIImage view has the same size as its image if you use initWithImage:. I assume that you're adding the image in IB, so I'm guessing that IB uses that method to fill the image view. If you can't add the image after the expansion (with setImage which will cause the image to fill the size of the image view), then I would just use a UIView with rounded corners. You don't need to do the slicing, just use a layer with rounded corners. This is what I've done to make a cell have a grouped look, by putting a rounded white rectangle inside a cell. This is the init method for the subview:
-(id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
self.backgroundColor = [UIColor whiteColor];
CALayer *bgLayer = self.layer;
[bgLayer setMasksToBounds:YES];
[bgLayer setCornerRadius:8];
}
return self;
}
Be sure to import the QuartzCore framework if you do this.
I've a UIScrollView of size (320,160). I'm adding some UIImageView into it, which are of size (213,160). The first UImageView starting from 54 (x) and so on, I've added a space of 5.0 in between each UIImageView. I've also enabled pagingEnable in IB & in coding. What my problem is its not properly working as per its property! When I scroll it should show me UIImageViews in each single page instead it showing me something like see screenshot I want output something like this see output screenshot
Where I'm doing wrong? I also having function of< (previous) & > (next) there to show images. I've asked one question yesterday which was I accepted however my requirement is little change and it won't become my solution. see my question.
Is there any special property that I've to set, or some logic I should implement? All examples I've checked and tried but I find that my requirement is some special. Point me! Thanks.
EDITED:
- (void) setImages
{
CGFloat contentOffset = 0.0f;
for (int i=0; i<[arrImgUrls count]; i++)
{
CGRect imageViewFrame = CGRectMake(contentOffset, 0.0f, 213, scrollImages.frame.size.height);
AsyncImageView *asyncImageView = [[AsyncImageView alloc] initWithFrame:imageViewFrame];
[asyncImageView.layer setMasksToBounds:YES];
NSString *urlImage = [arrImgUrls objectAtIndex:i];
NSURL *url = [NSURL URLWithString:urlImage];
[asyncImageView loadImageFromURL:url];
[scrollImages addSubview:asyncImageView];
contentOffset += asyncImageView.frame.size.width+increment;
[asyncImageView release];
}
scrollImages.contentSize = CGSizeMake([arrImgUrls count] * scrollImages.frame.size.width, scrollImages.frame.size.height);
scrollImages.pagingEnabled = YES;
scrollImages.clipsToBounds = NO;
}
-(IBAction)prevImage:(id)sender
{
_currentImage--;
[btnNext setEnabled:YES];
if (_currentImage==0)
{
[btnPrev setEnabled:NO];
[scrollImages setContentOffset:CGPointMake((_currentImage*imageWidth), 0) animated:YES];
return;
}
NSLog(#"previous:mult %d inc %d current %d",_currentImage*imageWidth,increment*_currentImage,_currentImage);
int nextImage=_currentImage+2;
[scrollImages setContentOffset:CGPointMake((((_currentImage*imageWidth)-(increment*_currentImage)))+(nextImage*increment), 0) animated:YES];
}
-(IBAction)nextImage:(id)sender
{
_currentImage++;
NSLog(#"next:mult %d inc %d current %d",_currentImage*imageWidth,increment*_currentImage,_currentImage);
[scrollImages setContentOffset:CGPointMake((_currentImage*imageWidth)+(increment*_currentImage), 0) animated:YES];
[btnPrev setEnabled:YES];
if (_imageCount-1 == _currentImage)
{
[btnNext setEnabled:NO];
}
}
Paging scroll views alway page multiples of their frame size. So in your example paging is always +320.
This behavior is good if you have content portions matching the frame of the scroll view.
What you have to do, is giving your scroll view a width of 213 and set its clipsToBounds property to NO.
After that your scroll view pages exactly how you want and you see what's left and right outside the frame.
Additionally you have to do a trick to make this left and right area delegate touches to the scroll view.
It's greatly explained in this answer.
You are forgetting to set scrollview content size.
make sure that you have set the content size to fit N number of images.
if you want to scrollview to scroll for 10 images with an image on each page
set scrollView contentSize as
[scrollView setContentSize:CGSizeMake(320 * 10,200)];
anybody know how to resize the dimmed black overly, once you clicked the search bar ?
i having problem when i clicked cancelled the tableview will expend then animated to disappear.
i using this to resize my result tableview.
-(void)searchDisplayController:(UISearchDisplayController *)controller didShowSearchResultsTableView:(UITableView *)tableView {
tableView.frame =fTableView.frame;//CGRectMake(26, 100, 280, 310); //fTableView.frame;
tableView.backgroundColor = [UIColor colorWithRed:243.0/255.0 green:236.0/255.0 blue:212.0/255.0 alpha:1];
}
when clicked on the search bar, gray overlay are full instead of my defined size.
when clicked cancel button, the view will expend back.
I combined several answers in order to move the dimmed overlay frame.
1: override UISearchDisplayController class
#interface MySearchController : UISearchDisplayController
2: override setActive function
- (void)setActive:(BOOL)visible animated:(BOOL)animated
{
[super setActive: visible animated: animated];
//move the dimming part down
for (UIView *subview in self.searchContentsController.view.subviews) {
//NSLog(#"%#", NSStringFromClass([subview class]));
if ([subview isKindOfClass:NSClassFromString(#"UISearchDisplayControllerContainerView")])
{
CGRect frame = subview.frame;
frame.origin.y += 10;
subview.frame = frame;
}
}
}
3: change the xib/storyboard Search Display Controller from UISearchDisplayController to
MySearchController
I thought the searchDisplayController owned a seperate tableview, so my guess is that you would need to resize that one.
Something along the lines of: <yourSearchViewController>.view.frame =self.tableView.frame;
or if you don't have it as class variable, in a method which receives it as argument, eg:
-(void)searchDisplayController:(UISearchDisplayController *)controller didShowSearchResultsTableView:(UITableView *)tableView {
controller.view.frame = self.tableView.frame;
tableView.backgroundColor = [UIColor colorWithRed:243.0/255.0 green:236.0/255.0 blue:212.0/255.0 alpha:1];
}
Alternativily you might want to subclass it and override its view properties locally.
Hope this helps!
The UISearchDisplayController does owns its own tableview which not as easy to tame.
I came across something like this and am still looking for a better solution.
-(void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller
{
[controller.searchResultsTableView setDelegate:self];
CGFloat gr = 12.0;
controller.searchResultsTableView.backgroundColor = [UIColor colorWithRed:gr green:gr blue:gr alpha:0.0];
[controller.searchResultsTableView setSeparatorStyle:UITableViewCellSelectionStyleNone];
CGRect searchTableFrame = CGRectMake(7, 105, 305, 292);
[controller.searchResultsTableView setFrame:searchTableFrame];
}
The above code does sets the background to transparent but seems to silently ignore the frame size.
EDIT:SOLVED
I found the robust solution to this here.
This saved my life.
The UISwitch on my device: Switch Image with the bottom pixels cut off http://gorgando.com/uiswitch.jpg
The UISwitch on the simulator: Good UISwitch http://gorgando.com/uiswitch-good.png
As you can see, the bottom pixels are cut off on the device, but not on the simulator. I have tried just about everything I can think of, but nothing has fixed the problem.
Some of the things I've tried:
Changing the UISwitch's frame's height
Changing the UICell's height
Changing the UICell's contentView's height
Adding the UISwitch to the UICell rather than the UICell's contentView
Here is the relevant code:
This is in the viewDidLoad of the uiTableViewController:
UISwitch *sw = [[UISwitch alloc] init];
self.contactedSwitch = sw;
[sw release];
self.contactedSwitch = [UISwitch switchWithLeftText:#"YES" andRight:#"NO"];
self.contactedSwitch.center = CGPointMake(230, 22);
self.contactedSwitch.on = [self.contact.contacted boolValue];
This is where the switchWithLeftText:andRight method comes from:
#import "UISwitch-Extended.h"
#define TAG_OFFSET 900
#implementation UISwitch (tagged)
- (void) spelunkAndTag: (UIView *) aView withCount:(int *) count
{
for (UIView *subview in [aView subviews])
{
if ([subview isKindOfClass:[UILabel class]])
{
*count += 1;
[subview setTag:(TAG_OFFSET + *count)];
}
else
[self spelunkAndTag:subview withCount:count];
}
}
- (UILabel *) label1
{
return (UILabel *) [self viewWithTag:TAG_OFFSET + 1];
}
- (UILabel *) label2
{
return (UILabel *) [self viewWithTag:TAG_OFFSET + 2];
}
+ (UISwitch *) switchWithLeftText: (NSString *) tag1 andRight: (NSString *) tag2
{
UISwitch *switchView = [[UISwitch alloc] initWithFrame:CGRectMake(0, 0, 94, 27)];
int labelCount = 0;
[switchView spelunkAndTag:switchView withCount:&labelCount];
if (labelCount == 2)
{
[switchView.label1 setText:tag1];
[switchView.label2 setText:tag2];
}
return [switchView autorelease];
}
#end
This is where I add the UISwitch to my tableviewcell:
[[contactedCell contentView] addSubview:self.contactedSwitch];
Thanks so much!
[Update] I thought the tableviewcell's might be the problem, so I added these UISwitches to a regular UIView to see how they looked. I have the exact same problem where they look alright in the simulator and the bottom is chopped in the device. So bizarre!
I had the same problem when programatically creating a UISwitch and changing its frame's origin- it turned out to be a half-pixel issue.
A key to noticing half-pixel bugs is: do the unwanted artifacts appear differently on the simulator vs device?
Your solution of placing the UISwitch with Interface Builder fixes this- you can also make sure to floor() your new coordinates if you are setting the UISwitch's frame's origin x/y.
UISwitch *mySwitch = [[UISwitch alloc] initWithFrame(CGRectZero)];
CGRect switchFrame = mySwitch.frame;
// move frame up without pixel fractions
switchFrame.origin.y = floor((cell.contentView.frame.size.height - switchFrame.size.height) / 2);
mySwitch.frame = switchFrame;
[cell.contentView addSubview:mySwitch];
In the original poster's case, the height of a uiswitch is an odd number (27 pixels), so setting the center to 22 splits the height into 13.5. The UISwitch's origin's y coordinate becomes 22-13.5 = 8.5 pixels. Either don't move a UISwitch by setting its center, or floor the coordinate, or use a fraction in the call to CGPointMake(230,22.5).
Another way to track this type of bug is to grep through Interface Builder looking for ".5" coordinates. I have found that sometimes over-tweaking the placement of UI elements in Interface Builder introduces this bug.
This is caused of the UISwich's top.
You can use UISwich.origin.y = ceil((height - UISwich.size.height)/2)
to fix it.
Never figured out exactly what was wrong, but I ended up creating the UISwitches in IB and got it to work beautifully that way. Very weird that it works in IB and not programmatically when I am basically doing the exact same thing.