Как иметь UIView как круг с радиусом угла с Xcode 8

У меня проблема с настройкой радиуса угла в представлении, потому что с момента обновления Xcode до 8 кадры представлений в большинстве случаев устанавливаются на 1000x1000 вместо соответствующего размера.

Существует аналогичный вопрос здесь, но я хотел бы добавить немного больше информации в надежде, что кто-то найдет ответ или обходной путь.

В одном из экземпляров у меня есть ячейка таблицы, которая имеет представление изображения, созданное в раскадровке. Его ширина и высота устанавливаются как постоянные с ограничением (до 95). Радиус угла установлен в layoutSubviews, который работает сейчас:

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.myImageView.layer.cornerRadius = self.myImageView.frame.size.width/2.0f;
}

Итак, я немного поиграл с пробной версией и ошибкой, и ситуация кажется немного лучше, если я попытаюсь запустить макет после того, как изображение будет установлено (изображение будет извлечено с удаленного сервера), вызвав:

self.myImageView.image = image;
[self.myImageView setNeedsLayout];
[self setNeedsLayout];
[self layoutIfNeeded];

Тем не менее это не работает в большинстве случаев. Теперь смешная часть:

У меня есть случай этого контроллера представления на панели вкладок, где, когда я нажимаю вкладку с контроллером целевого представления, будет немедленно вызван вызов подматрицы макета (из UIApplicationMain), где размер представления изображения равен 1000x1000 но как только изображение установлено, и я вызываю методы компоновки, то подпрограммы компоновки снова вызываются с правильным размером изображения размером 95х95, получая правильный результат.

Вторая ситуация - нажать контроллер вида того же типа, где теперь макет сначала не вызывается, единственный вызов выполняется после того, как изображение установлено, а макет принудительно. Результат снова имеет вид изображения размером 1000x1000, устанавливая радиус угла до 500, и изображение не видно.

Итак, есть ли хорошее решение для исправления этих странных значений фрейма? У меня такая же проблема для нескольких контроллеров представлений, ячеек таблицы. Все это использовалось для работы до обновления.

Есть ли у кого-нибудь идеи, что является источником этой проблемы? Является ли это из раскадровки или какой-то настройки миграции или это какая-то ошибка макета, представленная с этой версией программного обеспечения?

Теперь я также добавил пробуждение от nib:

- (void)awakeFromNib
{
    [super awakeFromNib];

    [self.myImageView setNeedsLayout];
    [self setNeedsLayout];
    [self layoutIfNeeded];
}

Что теперь заставляет макет происходить как минимум 2 раза в моем случае, а во второй раз правильные кадры. Тем не менее это далеко не ответ, это ужасно, и я хотел бы просить его, так как мне нужно будет использовать его во всех ячейках для каждого из изображений, кнопок.

Ответ 1

Можете ли вы просто обновить свой метод до

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.myImageView.layer.cornerRadius = self.myImageView.frame.size.width/2.0f;
    self.myImageView.clipsToBounds = TRUE;
}

и попробуйте одну попытку.

Coz в одном из моих случаев, я пропустил настройку clipsToBounds = TRUE;, и это не делало его круглым.

Ответ 2

Я думаю, что переопределение layoutSubviews, указанное в вопросе, относится к пользовательскому подклассу UITableViewCell.

Проблема здесь, вероятно, в том, что myImageView не является прямым поднабором ячейки.

Важно понимать, что вызов ячейки в [super layoutSubviews] устанавливает только кадры ячеек direct. Перед возвратом он не доходит до иерархии представлений. Обычно ячейка имеет только одно прямое подчинение: contentView. Таким образом, вызов [super layoutSubviews] просто устанавливает кадр представления содержимого и затем возвращается. Попытка установить self.myImageView.layer.cornerRadius происходит слишком рано, потому что self.myImageView.frame еще не обновлен.

Позже, после возврата метода cell layoutSubviews, UIKit отправит сообщение layoutSubviews в представление контента, и в это время представление контента установит собственные прямые рамки подкадров. То есть, когда будет установлен кадр myImageView.

Самое простое исправление для этого - просто сделать подкласс UIImageView и переопределить его layoutSubviews, чтобы установить свой собственный радиус угла.

@interface CircleImageView: UIImageView
@end

@implementation CircleImageView

- (void)layoutSubviews {
    [super layoutSubviews];
    self.layer.cornerRadius = self.bounds.size.width / 2;
}

@end

UPDATE

Matic Oblak спрашивает (в комментарии): "Это то, что вы написали здесь testable"?

Да, это можно проверить. Здесь один из способов проверить это: создать иерархию представлений с корневым представлением, представление контейнера внутри корневого представления и вид листа в представлении контейнера:

иерархия представления

Переопределить layoutSubviews в корневом представлении и представление контейнера для печати фреймов просмотров:

// RootView.m

@implementation RootView {
    IBOutlet UIView *_containerView;
    IBOutlet UIView *_leafView;
}

- (void)layoutSubviews {
    NSLog(@"%s before super: self.frame=%@ _containerView.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_containerView.frame), NSStringFromCGRect(_leafView.frame));
    [super layoutSubviews];
    NSLog(@"%s after super: self.frame=%@ _containerView.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_containerView.frame), NSStringFromCGRect(_leafView.frame));
}

@end

// ContainerView.m

@implementation ContainerView {
    IBOutlet UIView *_leafView;
}

- (void)layoutSubviews {
    NSLog(@"%s before super: self.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_leafView.frame));
    [super layoutSubviews];
    NSLog(@"%s after super: self.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_leafView.frame));
}

@end

Для хорошей меры, давайте также видеть, когда запускаются контроллер вида viewWillLayoutSubviews и viewDidLayoutSubviews:

// ViewController.m

@implementation ViewController

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
    NSLog(@"%s", __FUNCTION__);
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    NSLog(@"%s", __FUNCTION__);
}

@end

Наконец, запустите его на устройстве или симуляции с другим размером, чем в раскадровке, и посмотрите на консоль отладки:

-[ViewController viewWillLayoutSubviews]
-[RootView layoutSubviews] before super: self.frame={{0, 0}, {375, 667}} _containerView.frame={{20, 40}, {280, 420}} _leafView.frame={{20, 20}, {240, 380}}
-[RootView layoutSubviews] after super: self.frame={{0, 0}, {375, 667}} _containerView.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {240, 380}}
-[ViewController viewDidLayoutSubviews]
-[ContainerView layoutSubviews] before super: self.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {240, 380}}
-[ContainerView layoutSubviews] after super: self.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {295, 567}}

Обратите внимание, что корневой вид [super layoutSubviews] изменяет рамку представления контейнера, но не изменяет рамку вида листа. Затем представление контейнера [super layoutSubviews] изменяет размер листа.

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

Заметьте также, что viewDidLayoutSubviews запускается после того, как представление контроллера просмотра выполнило layoutSubviews, но до, чем более глубоко вложенные потомки выполнили layoutSubviews. Итак, в viewDidLayoutSubviews вы должны снова предположить, что фреймы более глубоко вложенных представлений обновлены не.

Есть способ принудительно обновить фрейм более глубоко вложенного подвью: отправить layoutIfNeeded в его супервизор. Например, я могу изменить -[RootView layoutSubviews], чтобы отправить layoutIfNeeded в _leafView.superview:

- (void)layoutSubviews {
    NSLog(@"%s before super: self.frame=%@ _containerView.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_containerView.frame), NSStringFromCGRect(_leafView.frame));
    [super layoutSubviews];
    NSLog(@"%s after super: self.frame=%@ _containerView.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_containerView.frame), NSStringFromCGRect(_leafView.frame));
    [_leafView.superview layoutIfNeeded];
    NSLog(@"%s after _leafView.superview: self.frame=%@ _containerView.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_containerView.frame), NSStringFromCGRect(_leafView.frame));
}

(Обратите внимание, что в этом примере проект _leafView.superview равен _containerView, но в целом это может и не быть.) Результат:

-[ViewController viewWillLayoutSubviews]
-[RootView layoutSubviews] before super: self.frame={{0, 0}, {375, 667}} _containerView.frame={{20, 40}, {280, 420}} _leafView.frame={{20, 20}, {240, 380}}
-[RootView layoutSubviews] after super: self.frame={{0, 0}, {375, 667}} _containerView.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {240, 380}}
-[ContainerView layoutSubviews] before super: self.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {240, 380}}
-[ContainerView layoutSubviews] after super: self.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {295, 567}}
-[RootView layoutSubviews] after _leafView.superview: self.frame={{0, 0}, {375, 667}} _containerView.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {295, 567}}
-[ViewController viewDidLayoutSubviews]

Итак, теперь я заставил UIKit обновить фрейм _leafView до возвращения -[RootView layoutSubviews].

Что касается "Я предполагаю, что многие операции, такие как установка простого текста на ярлыке, вызовут setNeedsLayout": вы можете использовать тот же метод, который я только что продемонстрировал, чтобы выяснить ответ на этот вопрос.

Ответ 3

Я создал демонстрационное приложение, чтобы узнать, что является минимальным требованием для этой проблемы, и оно довольно низкое:

  • Создание нового приложения с одним представлением
  • Добавить представление таблицы на главном контроллере представления
  • Добавить ячейку в представление таблицы и переопределить ее класс в качестве нового подкласса ячейки таблицы.
  • Добавить представление изображения в ячейку с фиксированными ограничениями ширины и высоты (добавив также верхние и верхние ограничения)
  • В ячейке layoutSubviews установите радиус угла обзора изображения до половины ширины просмотра изображения (результат всегда будет 500)

Изображение не отображается из-за того, что радиус угла равен 500.

Итак, я искал минимальное исправление и из предыдущего тестирования кажется, что в макете отсутствует первый вызов, поэтому я добавил его, переопределив бодрствование от nib:

- (void)awakeFromNib
{
    [super awakeFromNib];
    [self setNeedsLayout];
    [self layoutIfNeeded];
}

Итак, в проекте, над которым я работаю, в настоящее время я создал новый подкласс ячейки представления таблицы, и я переопределил пробуждение от nib. Затем я сделал все остальные ячейки в качестве подкласса этого. Это не решение, но это обходное решение, и поскольку у меня есть 57 подклассов ячейки таблицы, эта процедура довольно быстро реализуется.

Ответ 4

Попробуйте переопределить метод drawRect: в подклассе UIImageView, а затем установить радиус угла.

- (void)drawRect:(CGRect)rect {
self.layer.cornerRadius = self.frame.size.width/2.0f;
[self setClipsToBounds:YES];
}

Ответ 5

Просто добавив ответ @pushkraj для круглого просмотра, ширина и высота UIView должны быть одинаковыми.

Затем вы можете сделать.

- (void)layoutSubviews
{
   [super layoutSubviews];

   view.layer.cornerRadius = view.frame.size.height/2
   view.clipsToBounds = true
}

Ответ 6

Вы можете сделать это следующим образом:

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.myImageView.layer.cornerRadius = self.myImageView.frame.size.width/2.0f;
    self. myImageView.layer.masksToBounds = YES;
}

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

Ответ 7

Чтобы сделать любой круговой обзор, вам нужно обновить радиус угла, а угловой радиус - свойство layer любого вида (viewInfo).

Вот фрагмент кода, совместимый с swift 4

@IBOutlet weak var viewInfo: UIView!

func setCornerRadius(){
    self.viewInfo.layer.cornerRadius = self.viewInfo.frame.size.height/2
}