Может ли стандартное вспомогательное представление находиться в другом месте в UITableViewCell?

Я хочу, чтобы мой аксессуар находился немного в другом месте, чем обычно. Является ли это возможным? Этот код не действует:

cell.accessoryType =  UITableViewCellAccessoryDisclosureIndicator;
cell.accessoryView.frame = CGRectMake(5.0, 5.0, 5.0, 5.0);

person cannyboy    schedule 20.05.2010    source источник


Ответы (11)


Нет, вы не можете перемещаться туда, где находится дополнительный вид. В качестве альтернативы вы можете добавить подпредставление, подобное следующему:

[cell.contentView addSubview:aView];

Кроме того, при установке свойства accessoryView равным чему-либо значение accessoryType игнорируется.

person rickharrison    schedule 20.05.2010
comment
Благодарю. причина, по которой я хотел использовать индикатор раскрытия, заключалась в том, что он становился белым при выборе строки. похоже, нет способа изменить аксессуарView, как только ячейка выбрана. - person cannyboy; 20.05.2010
comment
Дополнительные представления действительно можно перемещать программно, переопределяя layoutSubviews и настраивая фрейм, как показано в ответе @Incyc. - person gdavis; 24.07.2015

Есть способ переместить accessView по умолчанию, но он довольно хакерский. Поэтому он может перестать работать в один прекрасный день, когда появится новый SDK.

Используйте на свой страх и риск (этот фрагмент кода перемещает любые accessoryView 8 пикселей влево. Вызов [self positionAccessoryView]; из метода -(void)layoutSubviews желаемого подкласса UITableViewCell):

- (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;
}
person Alexey    schedule 27.05.2011
comment
делает в законном для appstore приложений? Кто-нибудь использовал его с одобрением? - person Valerii Pavlov; 25.10.2011
comment
Он не использует какой-либо частный API, так что это законно. Я использовал его, и мое приложение было успешно одобрено. С другой стороны, вы должны понимать, что это тоже не часть публичного API и поэтому может перестать работать в любой момент. - person Alexey; 25.10.2011
comment
Мое приложение тоже прошло проверку. Скрыть взлом :) - person Valerii Pavlov; 19.02.2012
comment
Алексей, спасибо, добавил немного логики в ваш код. Найдите мое решение ниже. - person Tomasz; 05.05.2013
comment
Это отлично. Это сэкономило мне время сегодня вечером. Я признателен за это. - person braden; 18.10.2013
comment
@ Алексей, нет, это не незаконно. Просто некрасиво. - person Cameron Lowell Palmer; 19.03.2014
comment
Я обнаружил странное поведение, если забыл вызвать super layoutSubviews. - person simple_code; 08.03.2018

Я смог изменить рамку вспомогательного представления, просто сделав это в своем подклассе пользовательской ячейки.

CGRect adjustedFrame = self.accessoryView.frame;
adjustedFrame.origin.x += 10.0f;
self.accessoryView.frame = adjustedFrame;
person James Kuang    schedule 24.09.2013
comment
Оно работает. Если кто-то хочет попробовать этот ответ, поместите эти строки кода в метод подкласса layoutSubviews; также не забудьте вызвать [super layoutSubviews] в начале метода layoutSubviews. - person Jonathan F.; 26.03.2015
comment
Для представления аксессуаров по умолчанию accessoryView равно нулю, поэтому вы не отвечаете на вопрос. - person Cœur; 10.08.2017

Другой способ сделать это — встроить свой пользовательский дополнительный вид в другой вид, который установлен как дополнительный вид ячейки, и управлять отступом с помощью фрейма.

Вот пример с представлением изображения в качестве пользовательского вспомогательного вида:

// 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;
person Ole    schedule 15.01.2014

Следуя решению, данному Анной, я попытался лучше обнаружить дополнительный вид, я смотрю на правую сторону ячейки.

Создайте собственный класс, расширяющий UITableViewCell, и добавьте этот метод:

- (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;
            }
        }
    } 
}
person Tomasz    schedule 04.05.2013

Возможно, вам этого будет достаточно:

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;

Таким образом, я добавил отступ справа, поэтому кнопка аксессуара выглядит смещенной влево. Он имеет более широкую область, которая реагирует на прикосновения, это единственный побочный эффект.

person ancajic    schedule 27.06.2013

Приведенные выше ответы не работали для меня под ios 6.1. Поэтому я попытался использовать UIEdgeInsets, потому что DetailDisclosure — это UIButton. И теперь он работает нормально. Вот источник:

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);
    }
}
person larshaeuser    schedule 02.08.2013

Простой способ установить пользовательскую позицию для accessoryView, которая сохраняется в любом статусе ячейки, — это разместить аксессуарView в layoutSubViews:

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.accessoryView.center = CGPointMake($yourX, $yourY);
}
person Kappe    schedule 16.03.2017
comment
Для представления аксессуаров по умолчанию accessoryView равно нулю, поэтому вы не отвечаете на вопрос. - person Cœur; 10.08.2017
comment
@Кер что? : D прочитайте исходный вопрос, ваш комментарий не имеет никакого смысла. - person Kappe; 11.08.2017
comment
попробуйте в objC cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;, затем NSLog(@"%@", cell.accessoryView);, и вы увидите, почему код в вопросе не работает или почему ваш код тоже не будет работать. И это также объясняет два верхних ответа. - person Cœur; 11.08.2017

Я работал с ios5, и решение, данное Алексеем, не работало полностью. Я обнаружил, что когда для таблицы задан тип аксессуара, аксессуарView имеет значение null, поэтому первое «если» не работает. Я немного изменил код:

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;

}

и это решение работает для меня. Как сказал Алексей, я не знаю, что будет с будущими версиями, но по крайней мере в ios 4 работает.

person Ana    schedule 14.08.2012
comment
Спасибо, Ана, я добавил немного логики в ваш код, чтобы найти дополнительный вид, который я смотрю на правую сторону ячейки. - person Tomasz; 05.05.2013

улучшения других ответов

Для Джеймса Куанга, Каппе, accessoryView равно нулю для представления аксессуаров по умолчанию.

Для Матьяна subviews.lastObject — это просто неправильное представление, например UITableViewCellSeparatorView.

Для Алексея, Аны, Томаша перечисление подпредставлений, пока не найдем неизвестное, пока работает. Но это трудоемко и может быть легко сломано в будущих версиях, если, скажем, Apple добавит backgroundAccessoryView.

Для larshaeuser перечисление подпредставлений до тех пор, пока мы не найдем UIButton, является хорошей идеей, но contentEdgeInsets неадекватно заметно изменяет вспомогательное представление.

решение для Swift 3.x и 4.0

Мы будем перечислять и искать последний 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
        }
    }
}

для Swift 4.1 и новее

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
        }
    }
}
person Cœur    schedule 11.08.2017

Вот что я использовал, это избавит от заполнения по умолчанию.

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
}
person Balázs Vincze    schedule 20.03.2020