34

I want my accessory to be in a slightly different place than normal. Is it possible? This code has no effect:

cell.accessoryType =  UITableViewCellAccessoryDisclosureIndicator;
cell.accessoryView.frame = CGRectMake(5.0, 5.0, 5.0, 5.0);
Cœur
  • 37,241
  • 25
  • 195
  • 267
cannyboy
  • 24,180
  • 40
  • 146
  • 252

11 Answers11

30

No, you cannot move where the accessory view is. As an alternative you can add a subview like the following;

[cell.contentView addSubview:aView];

Also, by setting the accessoryView property equal to something, the accessoryType value is ignored.

rickharrison
  • 4,867
  • 4
  • 35
  • 40
  • thanks. the reason I wanted to use the disclosure indicator was that it changed to white when the row was selected. there does not seem to be a way to change the accessoryView as soon as the cell is selected. – cannyboy May 20 '10 at 17:45
  • 2
    Accessory views can indeed be moved programmatically by overriding layoutSubviews and adjusting the frame as shown in @Incyc's answer. – gdavis Jul 23 '15 at 23:00
23

There is a way to move default accessoryView, but it's pretty hacky. So it might stop working one day when a new SDK arrives.

Use at your own risk (this code snippet moves any accessoryView 8 pixels to the left. Call [self positionAccessoryView]; from inside the -(void)layoutSubviews method of the desired UITableViewCell subclass):

- (void)layoutSubviews {
    [super layoutSubviews];
    [self positionAccessoryView];
}

- (void)positionAccessoryView {
    UIView *accessory = nil;
    if (self.accessoryView) {
        accessory = self.accessoryView;
    } else if (self.accessoryType != UITableViewCellAccessoryNone) {
        for (UIView *subview in self.subviews) {
            if (subview != self.textLabel &&
                subview != self.detailTextLabel &&
                subview != self.backgroundView &&
                subview != self.contentView &&
                subview != self.selectedBackgroundView &&
                subview != self.imageView &&
                [subview isKindOfClass:[UIButton class]]) {
                accessory = subview;
                break;
            }
        }
    }

    CGRect r = accessory.frame;
    r.origin.x -= 8;
    accessory.frame = r;
}
Logan
  • 52,262
  • 20
  • 99
  • 128
Alexey
  • 7,262
  • 4
  • 48
  • 68
16

I was able to change the accessory view's frame by simply doing this in my custom cell subclass.

CGRect adjustedFrame = self.accessoryView.frame;
adjustedFrame.origin.x += 10.0f;
self.accessoryView.frame = adjustedFrame;
James Kuang
  • 10,710
  • 4
  • 28
  • 38
  • 10
    It works. If someone wants to try this answer, please put these lines of code in the subclass `layoutSubviews` method ; also don't forget to call `[super layoutSubviews]` in the `layoutSubviews` method's beginning. – Jonathan F. Mar 26 '15 at 14:07
  • 2
    For default accessory view, `accessoryView` is nil, so you're not answering the question. – Cœur Aug 10 '17 at 10:24
3

Another way to do this is to embed your custom accessory view in another view, that is set as the cell's accessory view and control the padding using the frame.

Here is an example with an image view as custom accessory view:

// Use insets to define the padding on each side within the wrapper view
UIEdgeInsets insets = UIEdgeInsetsMake(24, 0, 0, 0);

// Create custom accessory view, in this case an image view
UIImage *customImage = [UIImage imageNamed:@"customImage.png"];
UIImageView *accessoryView = [[UIImageView alloc] initWithImage:customImage];

// Create wrapper view with size that takes the insets into account 
UIView *accessoryWrapperView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, customImage.size.width+insets.left+insets.right, customImage.size.height+insets.top+insets.bottom)];

// Add custom accessory view into wrapper view
[accessoryWrapperView addSubview:accessoryView];

// Use inset's left and top values to position the custom accessory view inside the wrapper view
accessoryView.frame = CGRectMake(insets.left, insets.top, customImage.size.width, customImage.size.height);

// Set accessory view of cell (in this case this code is called from within the cell)
self.accessoryView = accessoryWrapperView;
Ole
  • 603
  • 1
  • 6
  • 4
2

Following the solution given by Ana I tried to better detect the accessory view, I look on the right side of the cell.

Create a custom class that extends UITableViewCell and add this method:

- (void)layoutSubviews {
    [super layoutSubviews];

    if (self.accessoryType != UITableViewCellAccessoryNone) {
        float estimatedAccesoryX = MAX(self.textLabel.frame.origin.x + self.textLabel.frame.size.width, self.detailTextLabel.frame.origin.x + self.detailTextLabel.frame.size.width);

        for (UIView *subview in self.subviews) {
            if (subview != self.textLabel &&
                subview != self.detailTextLabel &&
                subview != self.backgroundView &&
                subview != self.contentView &&
                subview != self.selectedBackgroundView &&
                subview != self.imageView &&
                subview.frame.origin.x > estimatedAccesoryX) {

                // This subview should be the accessory view, change its frame
                frame = subview.frame;
                frame.origin.x -= 10;
                subview.frame = frame;
                break;
            }
        }
    } 
}
Tomasz
  • 3,476
  • 1
  • 22
  • 13
2

Maybe this will be sufficient for you:

UIImageView* accessoryImageView = [[UIImageView alloc] initWithFrame:
        CGRectMake(0, 0, accessoryImage.size.width + MARGIN_RIGHT, accessoryImage.size.height)];
accessoryImageView.contentMode = UIViewContentModeLeft;
accessoryImageView.image = accessoryImage;

self.accessoryView = accessoryImageView;

This way I added padding to the right, so accessory button looks shifted to the left. It has a wider area that responds to touches, that is the only side-effect.

AndroC
  • 4,758
  • 2
  • 46
  • 69
2

The above answers didn't work for me under ios 6.1. So I tried to use UIEdgeInsets, because the DetailDisclosure is a UIButton. And it works fine now. Here the source:

if (cell.accessoryType == UITableViewCellAccessoryDetailDisclosureButton) {
    UIView* defaultAccessoryView = [cell.subviews lastObject];
    if ([defaultAccessoryView isKindOfClass:[UIButton class]]){
        UIButton *bt = (UIButton*)defaultAccessoryView;            
        bt.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 10);
    }
}
larshaeuser
  • 368
  • 2
  • 7
2

The simple way to set a custom position for the accessoryView that is persisted in any cell status is to layout the accessoryView in layoutSubViews:

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.accessoryView.center = CGPointMake($yourX, $yourY);
}
Kappe
  • 9,217
  • 2
  • 29
  • 41
  • 1
    For default accessory view, `accessoryView` is nil, so you're not answering the question. – Cœur Aug 10 '17 at 10:23
  • 1
    @Cœur what? :D read the original question, your comment doesn't make any sense. – Kappe Aug 11 '17 at 11:21
  • try it, in objC, `cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;` then `NSLog(@"%@", cell.accessoryView);` and you'll see why the code in the question was not working or why your code isn't going to work either. And that also explain the top two answers. – Cœur Aug 11 '17 at 11:44
1

I was working with the ios5 and the solution given by Alexey was not working entirely. I discovered that when an accessoryType is set on a table, the accessoryView is null so the first "if" was not working. I have changed a the code just a little:

if (self.accessoryType != UITableViewCellAccessoryNone) {
    UIView* defaultAccessoryView = nil;

    for (UIView* subview in self.subviews) {
        if (subview != self.textLabel && 
            subview != self.detailTextLabel && 
            subview != self.backgroundView && 
            subview != self.contentView &&
            subview != self.selectedBackgroundView &&
            subview != self.imageView &&
            subview != self.explanationButton && // Own button
            subview.frame.origin.x > 0 // Assumption: the checkmark will always have an x position over 0. 
            ) {
            defaultAccessoryView = subview;
            break;
        }
    }
    r = defaultAccessoryView.frame;
    r.origin.x -= 8;
    defaultAccessoryView.frame = r;

}

and this solution is working for me. As Alexey said, I don't know what is going to happen with future versions but at least in ios 4 is working.

Ana
  • 89
  • 5
  • 1
    Thanks Ana, I've added a bit more of logic to you code, to find the accessory view I look on the right side of the cell. – Tomasz May 04 '13 at 20:14
1

improvements on other answers

For James Kuang, Kappe, accessoryView is nil for default accessory view.

For Matjan, subviews.lastObject is easily the wrong view, like an UITableViewCellSeparatorView.

For Alexey, Ana, Tomasz, enumerating the subviews until we find an unknown one works for now. But it's laborious and could be easily broken in future versions if, let say, Apple adds a backgroundAccessoryView.

For larshaeuser, enumerating the subviews until we find a UIButton is good idea, but contentEdgeInsets is not adequately visibly changing the accessory view.

solution for Swift 3.x and 4.0

We will enumerate and look for the last UIButton.

class AccessoryTableViewCell: UITableViewCell {
    override func layoutSubviews() {
        super.layoutSubviews()
        if let lastButton = subviews.reversed().lazy.flatMap({ $0 as? UIButton }).first {
            // This subview should be the accessory view, change its origin
            lastButton.frame.origin.x = bounds.size.width - lastButton.frame.size.width - 5
        }
    }
}

for Swift 4.1 and newer

class AccessoryTableViewCell: UITableViewCell {
    override func layoutSubviews() {
        super.layoutSubviews()
        // https://stackoverflow.com/a/45625959/1033581
        if let lastButton = subviews.reversed().lazy.compactMap({ $0 as? UIButton }).first {
            // This subview should be the accessory view, change its origin
            lastButton.frame.origin.x = bounds.size.width - lastButton.frame.size.width - 5
        }
    }
}
Community
  • 1
  • 1
Cœur
  • 37,241
  • 25
  • 195
  • 267
0

Here is what I used, this will get rid of the default padding.

override func layoutSubviews() {
    super.layoutSubviews()

    // Remove the accessory view's default padding.
    accessoryView!.frame.origin.x = bounds.width - accessoryView!.bounds.width - safeAreaInsets.right
    contentView.frame.size.width = bounds.width - safeAreaInsets.left - safeAreaInsets.right - accessoryView!.bounds.width
}
Balázs Vincze
  • 3,550
  • 5
  • 29
  • 60