Interface Builder в Xcode с некоторого времени экономит мне много времени в работе по стандартному лайауту элементов интерфейса и иногда помогает в задаче прототипирования. С версии 6 в Xcode добавили возможность рендера кастомных вьюшек, помеченных атрибутом IBDesignable, а также отображение в билдере полей класса, помеченных атрибутом IBInspectable.
С версии Xcode 7 этой фичей стало более-менее возможно пользоваться, поэтому мне захотелось проверить её возможности.
Почитать про IBDesignable/IBInspectable можно тут и тут.
Стандартный кейс
Давайте создадим кастомную кнопку с возможностью настраивать цвет, толщину и радиус скругления border, причем чтобы все эти параметры можно было контролировать через Interface Builder.
@IBDesignable class BorderedButton : UIButton { /// Толщина границы @IBInspectable var borderWidth: CGFloat { set { layer.borderWidth = newValue } get { return layer.borderWidth } } /// Цвет границы @IBInspectable var borderColor: UIColor? { set { layer.borderColor = newValue?.CGColor } get { return layer.borderColor?.UIColor } } /// Радиус границы @IBInspectable var cornerRadius: CGFloat { set { layer.cornerRadius = newValue } get { return layer.cornerRadius } } } extension CGColor { private var UIColor: UIKit.UIColor { return UIKit.UIColor(CGColor: self) } }
Все работает, билдер обновляет рендер при изменении параметров.
Но ведь такие параметры наверное могут быть не только у нашего класса кнопки, а у любых других кнопок. Почему бы не сделать расширение базового класса UIButton.
extension UIButton { /// Радиус гараницы @IBInspectable var cornerRadius: CGFloat { set { layer.cornerRadius = newValue } get { return layer.cornerRadius } } /// Толщина границы @IBInspectable var borderWidth: CGFloat { set { layer.borderWidth = newValue } get { return layer.borderWidth } } /// Цвет границы @IBInspectable var borderColor: UIColor? { set { layer.borderColor = newValue?.CGColor } get { return layer.borderColor?.UIColor } } }
Сотрём IBInspectable поля класса кастомной кнопки, так как они уже прописаны в расширении. В результате класс останется пустым.
@IBDesignable class BorderedButton : UIButton {}
Добавим еще одну кнопку рядом с нашей кастомной кнопкой, но не будем назначать ей класса (будет стандартный UIButton).
Как видно из результата, Interface Builder сохранил возможность ввода IBInspectable полей даже у базового класса UIButton, однако не рендерит его, так как он не помечен атрибутом IBDesignable.
Расширяем дальше
Похожим образом можно расширить базовый класс UIView.
extension UIView { /// Радиус гараницы @IBInspectable var cornerRadius: CGFloat { set { layer.cornerRadius = newValue } get { return layer.cornerRadius } } /// Толщина границы @IBInspectable var borderWidth: CGFloat { set { layer.borderWidth = newValue } get { return layer.borderWidth } } /// Цвет границы @IBInspectable var borderColor: UIColor? { set { layer.borderColor = newValue?.CGColor } get { return layer.borderColor?.UIColor } } /// Смещение тени @IBInspectable var shadowOffset: CGSize { set { layer.shadowOffset = newValue } get { return layer.shadowOffset } } /// Прозрачность тени @IBInspectable var shadowOpacity: Float { set { layer.shadowOpacity = newValue } get { return layer.shadowOpacity } } /// Радиус блура тени @IBInspectable var shadowRadius: CGFloat { set { layer.shadowRadius = newValue } get { return layer.shadowRadius } } /// Цвет тени @IBInspectable var shadowColor: UIColor? { set { layer.shadowColor = newValue?.CGColor } get { return layer.shadowColor?.UIColor } } /// Отсекание по границе @IBInspectable var _clipsToBounds: Bool { set { clipsToBounds = newValue } get { return clipsToBounds } } }
Теперь параметрами слоя любой вьюшки можно управлять через билдер. Для возможности live-рендера только одно условие — у вьюшки в билдере должен быть указан кастомные класс с атрибутом IBDesignable.
Нестандартный кейс
Допустим, у нас в приложении есть светлая и темная темы. Попробуем стилизовать кнопки с помощью перечисления.
/// Стиль кнопки enum ButtonStyle: String { /// Светлый стиль case Light = "light" /// Темный стиль case Dark = "dark" /// Оттенок var tintColor: UIColor { switch self { case .Light: return UIColor.blackColor() case .Dark: return UIColor.lightGrayColor() } } /// Цвет границы var borderColor: UIColor { return tintColor } /// Цвет фона var backgroundColor: UIColor { return UIColor.clearColor() } /// Толщина границы var borderWidth: CGFloat { return 1 } /// Радиус границы var cornerRadius: CGFloat { return 4 } }
Напишем соответствующее расширение для класса UIButton, которое позволяет выбирать и применять стили к кнопкам:
extension UIButton { /// Стиль кнопки @IBInspectable var style: String? { set { setupWithStyleNamed(newValue) } get { return nil } } /// Применение стиля по его строковому названию private func setupWithStyleNamed(named: String?){ if let styleName = named, style = ButtonStyle(rawValue: styleName) { setupWithStyle(style) } } /// Применение стиля по его идентификатору func setupWithStyle(style: ButtonStyle){ backgroundColor = style.backgroundColor tintColor = style.tintColor borderColor = style.borderColor borderWidth = style.borderWidth cornerRadius = style.cornerRadius } }
Теперь добавляем а билдере еще две кнопки, и в новом поле Style прописываем стили «dark» и «light» соответственно.
Теперь мы можем применять стили к кнопкам одним полем в билдере и наблюдать их реальное отображение. Если ограничится только первым, то нам даже не придется создавать свой IBDesignable класс (который по сути пустой). Ничто не мешает добавить еще несколько стилей, а также расширить тип стиля и сделать динамический выбор применяемых значений в зависимости от класса вьюшки.
Резюме
В статье я не пытался преподнести новый способ стилизации элементов интерфейса. Однако, возможно, данный подход подтолкнет кого-нибудь на другое оригинальное применение данной фичи.
Исходники можно найти на гите.
ссылка на оригинал статьи http://habrahabr.ru/post/274687/
Добавить комментарий