在模組化的過程中,可能會遇到需要共用的 UI 元件,
一般的 CustomView: UIView 沒辦法顯示在 Storyboard 上,會呈現空空的 View,
如果想要在 Xcode 上直接顯示的話,就是需要使用 IBDesignable 和 IBInspectable。

功能說明

  • IBDesignable:在客製化 View 前加上此關鍵字,Xcode 可直接顯示此 View 在 Storyboard。
  • IBInspectable:在客製化屬性上加上此關鍵字來告訴,Xcode 可直接顯示屬性在右方的 Interface Builder。

[範例一]:可視的客製化View

  • 步驟一:建立客製化 View,並標上 @IBDesignable,讓他變成可以在 Storyboard 被看到
  • 步驟二:為了使我們的客製化類別能夠被即時的預覽,需要兩個initializers,required initoverride init
  • 步驟三:加入標上 @IBInspectable 的屬性,並且此屬性一定要定義類型(例如: UIColor、Int、CGFloat)
  • 步驟四:在 Storyboard 上加上新 View,並將 CustomClass 設定為 RainbowView,此時會跑出 Designables 為 Up to date 就是正確。

有時會失敗看到 Designables 為 Build Fail,需要清 DerivedData 或重啟 Xcode 或是 Editor > Debug Selected Views / Refresh All Views

  • 下面是範例程式碼

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    @IBDesignable class RainbowView: UIView {

    @IBInspectable var firstColor: UIColor = UIColor.red
    @IBInspectable var secondColor: UIColor = UIColor.yellow
    @IBInspectable var thirdColor: UIColor = UIColor.blue

    @IBInspectable var innerCircleRadius: CGFloat = 80
    @IBInspectable var middleCircleRadius: CGFloat = 150
    @IBInspectable var outerCircleRadius: CGFloat = 215

    required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    }

    override init(frame: CGRect) {
    super.init(frame: frame)
    }

    override func draw(_ rect: CGRect) {
    addCirle(arcRadius: innerCircleRadius, capRadius: 20, color: self.firstColor)
    addCirle(arcRadius: middleCircleRadius, capRadius: 20, color: self.secondColor)
    addCirle(arcRadius: outerCircleRadius, capRadius: 20, color: self.thirdColor)
    }

    func addCirle(arcRadius: CGFloat, capRadius: CGFloat, color: UIColor) {
    // 中心位置
    let X = self.bounds.midX
    let Y = self.bounds.midY

    // 上半圓
    let pathBottom = UIBezierPath(ovalIn: CGRect(x:(X - (arcRadius/2)), y:(Y - (arcRadius/2)), width:arcRadius, height:arcRadius)).cgPath
    self.addOval(lineWidth: 20.0, path: pathBottom, strokeStart: 0, strokeEnd: 0.5, strokeColor: color, fillColor: UIColor.clear, shadowRadius: 0, shadowOpacity: 0, shadowOffsset: CGSize.zero)

    // 左邊的小圓點
    let pathMiddle = UIBezierPath(ovalIn: CGRect(x:(X - (capRadius/2)) - (arcRadius/2), y:(Y - (capRadius/2)), width:capRadius, height:capRadius)).cgPath
    self.addOval(lineWidth: 0.0, path: pathMiddle, strokeStart: 0, strokeEnd: 1.0, strokeColor: color, fillColor: color, shadowRadius: 5.0, shadowOpacity: 0.5, shadowOffsset: CGSize.zero)

    // 下半圓
    let pathTop = UIBezierPath(ovalIn: CGRect(x:(X - (arcRadius/2)), y:(Y - (arcRadius/2)), width:arcRadius, height:arcRadius)).cgPath
    self.addOval(lineWidth: 20.0, path: pathTop, strokeStart: 0.5, strokeEnd: 1.0, strokeColor: color, fillColor: UIColor.clear, shadowRadius: 0, shadowOpacity: 0, shadowOffsset: CGSize.zero)
    }

    ///利用路徑畫圖
    func addOval(lineWidth: CGFloat, path: CGPath, strokeStart: CGFloat, strokeEnd: CGFloat, strokeColor: UIColor, fillColor: UIColor, shadowRadius: CGFloat, shadowOpacity: Float, shadowOffsset: CGSize) {
    let arc = CAShapeLayer()
    arc.lineWidth = lineWidth
    arc.path = path
    arc.strokeStart = strokeStart
    arc.strokeEnd = strokeEnd
    arc.strokeColor = strokeColor.cgColor
    arc.fillColor = fillColor.cgColor
    arc.shadowColor = UIColor.black.cgColor
    arc.shadowRadius = shadowRadius
    arc.shadowOpacity = shadowOpacity
    arc.shadowOffset = shadowOffsset
    layer.addSublayer(arc)
    }
    }
  • 除此之外,在 Xcdoe 直接調整屬性可以讓他直接改變預覽呦,十分方便!


[範例二]:可視的客製化View並綁定使用Xib

  • 步驟一:建立客製化View,並標上@IBDesignable,且加上一個contentView

  • 步驟二:為了使我們的客製化類別能夠被即時的預覽,需要這兩個initializers,required initoverride init

  • 步驟三:加上利用Nib初始化View

  • 步驟四:加入標上 @IBInspectable 的屬性,並且此屬性一定要定義類型(例如: UIColor、Int、CGFloat)

    • 下面是範例程式碼
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      @IBDesignable class InputBarView: UIView {
      ///內容View
      @IBOutlet weak var contentView: UIView!

      ///標題Label
      @IBOutlet weak var titleLabel: UILabel!

      ///標題文字
      @IBInspectable var titleText: String?{
      get {
      return titleLabel.text
      }
      set {
      titleLabel.text = newValue
      }
      }

      required init?(coder aDecoder: NSCoder) {
      super.init(coder: aDecoder)
      initViewFromNib()
      }

      override init(frame: CGRect) {
      super.init(frame: frame)
      initViewFromNib()
      }

      /// 利用Nib初始化View
      private func initViewFromNib(){
      let bundle = Bundle(for: type(of: self))
      let nib = UINib(nibName:"\(type(of: self))", bundle: bundle)
      if let view = nib.instantiate(withOwner: self, options: nil)[0] as? UIView {
      self.contentView = view
      self.contentView.frame = bounds
      self.addSubview(contentView)
      }
      }

      /// 完成設定Nib的事件
      override public func awakeFromNib() {
      super.awakeFromNib()
      //可以在這做一些子代View設定....
      }
      }
  • 步驟五:開啟 Xib 必做兩件事

    • 第一件事:設定 File’s owner 的 CustomClass
    • 第二件事:連結 File’s owner 到 View
    • 客製化內部View,此處新增一個 Label (依照狀況選用)
  • 步驟六:在 Storyboard 上加上新 View,並將 CustomClass 設定為 InputBarView,並且去設定 Title text

有時會失敗看到 Designables 為 Build Fail,需要清 DerivedData 或重啟 Xcode 或是 Editor > Debug Selected Views / Refresh All Views

其實用 Xib 在模組化的過程中,需要額外處理 import bundle 的問題,所以沒有很建議這樣做
所以漸漸地我也已改習慣用純程式碼,未來的天下 SwiftUI,也類似純程式碼的感覺,本身就自帶預覽功能,方便許多


Reference

  1. 利用IBDesignable和IBInspectable建立客製化元件
  2. iOS- 利用 IBDesignable 和 IBInspectable 來延伸 Storyboard 的屬性
  3. How to visualize reusable xibs in storyboards using IBDesignable
  4. Create an IBDesignable UIView subclass with code from an XIB file
  5. 在storyboard裡使用xib的view