Круглые пиксели в Айфоне

Когда у всех будет что-нибудь вроде Айфона шесть плюс, тогда мы забудем про пиксели и попадание в них, но, пока на рынке еще много устройств с «нормальной» пиксельной сеткой, часто попадаются плохорешаемые задачи. Сейчас мне нужно было нарисовать круглую кнопку (модную аватарку) и кнопку-сосиску. Причем не просто решить задачу в лоб. А понять, как это сделать максимально идеально.

Варианты

Вариантов решения у обеих задач много. Первый, самый старый вариант, «как все делали раньше» — при помощи картинок. Если нужно было скруглить уголки, брали четыре картинки цвета фона и ставили в углы обычной, прямоугольной. Получалась видимость скругления. Также можно и любую другую форму сделать, например, сделать квадратную «маску» цвета фона с дыркой внутри, и положить ее сверху на фотку — получается круглая аватарка.

У такого решения одно достоинство, его можно реализовать на любой версии iOS, любыми средствами. При должной аккуратности, оно очень быстро работает. Из недостатков — уголки и маски закрывают не только часть нужной картинки, но и весь остальной фон. Если на фоне картинка, или градиент, или просто что-то меняющееся (выделение ячейки), то начинаются трудности.

Второе решение — использование свойства слоя cornerRadius. Оно предназначено для создания скругленных углов, но можно довести его до экстремума и поставить радиус скругления в половину размера кнопки (или другой вьюшки). Получается аккуратный такой кружок (если был квадрат). Из минусов можно вспомнить тормоза, если на экране много таких, скругленных, слоёв (это обычно происходит в таблицах при скроллинге). В современных устройствах и версиях iOS, правда, этого можно избежать.

Третье решение также использует параметр слоя, но уже другой. mask позволяет отмаскировать слой любой фигурой. Удобно? Очень. Нужно лишь подобрать правильную маску.

Есть и еще варианты. Например, использовать CoreGraphics напрямую и нарисовать что-нибудь прямо в контексте (CGContext), их приходится использовать реже. Если их использовать, могут появиться сложности с анимацией, но, зато, настолько низкоуровневое решение позволяет максимально оптимизировать код по скорости и контролировать использование памяти.

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

Круглая кнопка

Round_CornerRadius.png

Если посмотреть на то, что получается в результате скругления кнопки, выяснится, что layer.cornerRadius дает более «равномерное» скругление, чем маскирование эллипсом или скругленным прямоугольником (при помощи маски). Скругленный прямоугольник вообще ведет себя странно, в какой-то момент отказываясь скруглять квадрат, чуть позже превращаясь в круг. Поведение можно посмотреть, если скачать/собрать/запустить приложение-пример по ссылке в конце записи.

На анимации слева показаны оба варианта. Обратите внимание на края. Тот вариант, у которого более острые верх и низ — это маска. Более гладкий — cornerRadius.

Сосисочная кнопка

Sausage_All.png

Изменения в сосисочной кнопке более выражены, поэтому показываю их без анимации. Справа — вариант layer.cornerRadius. Видны «углы» на стыках полукруга и сторон.

Посередине — вариант, который предлагает ОС в UIBezierPath bezierPathWithRoundedRect:cornerRadius: Сгладились эти переходы, кнопка стала более плавной сверху и снизу, но появилась плоская часть слева (радиус скругления по-прежнему половина кнопки, он не одинаковый для всех тестов)

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

Сложности

На картинках приведены сплошные кнопки. А что, если кнопка должна быть контурная? Для этого, в случае использования layer.cornerRadius, нужно проставить layer.borderColor и layer.borderWidth. Эти параметры не кастомизируются состояниями кнопки, поэтому придется хитрить, переопределяя UIButton. В случае использования маски, я обычно рендерю картинку для разных состояний кнопки. При редеринге получаю контур, потом заливаю его одним цветом, рисую линию по контуру другим и сохраняю полученное изображение в UIImage. Это не только позволяет легко создавать разные кнопки, но и кешировать их, чтобы приложение работало быстрее.

Есть ещё одна проблема, которая почти не видна в приведенных примерах. Если нарисовать, например, белую сосисочную кнопку, скруглив ее при помощи layer.cornerRadius (правый вариант), то по краям сверху и снизу по всей ширине иногда появляются темные полоски. Что это? Наверняка погрешности алгоритма сглаживания. Точно сказать трудно, исходников алгоритма я не видел.

Выводы и программа, чтобы побаловаться

Во-первых, печально, что нет единого решения. Я в будущем планирую использовать для «круглых кнопок» layer.cornerRadius, а для вытянутых сосисок — UIBezierPath. Собственное скругление будет работать для углов с небольшим радиусом.

Если вам интересно самим поисследовать разные варианты, можно взять исходники на ГитХабе: https://github.com/bealex/Test_RoundButton

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