Магия IBDesignable или расширяем функциональность Interface Builder в Xcode

от автора

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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *