日本語を勉強しています。上手く使えない場合が多いと思いますが、共有してみたいから、頑張って書きます。
この前、新規アプリを開発し始めた時、UI はコードか、Storyboard か、Xib か、どっちかで進めていくかなを考えました。
もちろん、コードで UI を実装できます、しかも数年前、コードで全ての画面を作る人が少なくない、でも、スクリーンサイズの充実化、開発ツールの進展に伴う、Auto Layout、Size Classes は誕生しました、コードで UI を実装するのは日々複雑になりました。
自分は Storyboard または Xib を使用して画面を作るのが好きだけど、やはりたまに迷う、 Storyboard と Xib どっちを選んだ方がいいですか?
Storyboard or Xib
チームの旧バージョン iOS Guidelines にあるルールは:
StoryBoard はコンフリクトしやすく、チーム開発には向いていないため、なるべく利用しない。各画面や画面部品ごとに .xib ファイルを用いる。
半分賛成半分反対。
一つの Storyboard に複数の ViewController がある場合、コンフリクトはぜったい発生しやすい、もしこの罠にはまると、めんどいことになるかもしれない、コンフリクトを解消するため、.xml を修正した経験がある人もいると思います。けれども、これは Storyboard の問題ではない、これをきっかけに Storyboard を諦めると、損になってしまうと思います。そもそも、Storyboard は Xib を置き換えるためのものではありません。
Storyboard が重視する点は画面の関係性と遷移関係、 Xib を利用して、共通コンポーネントを作って、再利用 (reuse) できるのはすごく良いだと思います。
この新規アプリはシリーズアプリなので、共通のデザインで実装していきました。アプリ内はいくつかの dialog があって、ほぼ header と footer が付いています。こんな利用シーンで、Xib を利用していいんじゃないですかって思いましたので、結局 dialog を ViewController にした、Storyboard で Present Modally を利用して、遷移関係ははっきり見えます。header と footer の部分は Xib を作って、独立な View として、必要に応じて、画面にロードできます。その上、@IBInspectable
と @IBDesignable
を定義すれば、結構使いやすくなります。メリットは画面デザインと遷移関係両方はっきり見えて、再利用も簡単にできます;デーメリットは Auto Layout の設定を何度もやらないといけませんかな。
心得
Storyboard Reference
「コンフリクトは発生しやすい」ーー Storyboard Reference が誕生した後、こういう話は完全に誤解 (false statement) になりました。
Storyboard Reference は Xcode 7 からサポートされ始めました、部品ライブラリから画面へドラッグできます。既存の Storyboard を分解したいならば、Editor -> Refactor to Storyboard で実現できます。例えば、Container View は二個あって、こんな場合画面の中にある controller の数は三つです。分解して、下のようななります、Container View は別 Storyboard に分かれています:
それで、チームワークでも、コンフリクトはほぼなくなるかな。もちろん、設計の良い構造を目指して工夫することはいちばん大切なことです。
Loadable Nib
重複コードを減らせるため、型安全のため、Nib のロードをプロトコルにしよう!
protocol Loadable: class {
static var nibName: String { get }
}
extension Loadable {
static var nibName: String { return String(describing: Self.self) }
}
UIView
の Extension を定義して、ロードされたい View は Loadable
プロトコルを採用することが要求されます。ちなみに、Nib は Xib がコンパイルされた後のものです。
extension UIView {
// Custom Class
func instantiateFromNib<T: UIView>(_:T.Type) -> T where T: Loadable {
if let nib = UINib(nibName: T.nibName, bundle: nil).instantiate(withOwner: nil, options: nil).first as? T {
return nib
} else {
fatalError("Nib \(T.nibName) is not exist ?!")
}
}
// File's Owner
func instantiateFromNibOwner<T: UIView>(_:T.Type) where T: Loadable {
let bundle = Bundle(for: type(of: self))
if let nib = UINib(nibName: T.nibName, bundle: bundle).instantiate(withOwner: self, options: nil).first as? UIView {
nib.frame = self.bounds
nib.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addSubview(nib)
} else {
fatalError("Nib \(T.nibName) is not exist ?!")
}
}
}
それで、簡潔なコードで初期化できます:
let view:ClassName = self.instantiateFromNib(ClassName.self)
self.instantiateFromNibOwner(ClassName.self)
追記:LoadableNib Library と Demo を Github にアップされました。
ちょっと前、Reusable というライブラリを知りました。Nib の再利用について同じことをやっています、しかも、Cell、Storyboard、ViewController の再利用も実現されました、もっと強いと言えます。
Nib の再利用について、上記方法と Reusable の相違点は:
-
Reusable に、Nib を初期化する部分は
UIView
の Extension じゃなくて、Protocol Extension です。 - File's Owner として再利用する時,Reusable は Auto Layout を使っていて、自分は frame から着手しました。。
@IBDesignable
和 @IBInspectable
@IBDesignable
:リアルタイムにビューを描画する。
@IBInspectable
:Runtime Attributes を定義する。
例:DialogHeaderView
を定義、前に @IBDesignable
を付して、このクラスの headerTitle
属性を @IBInspectable
付きで定義します:
@IBDesignable class DialogHeaderView: UIView {
@IBInspectable var headerTitle: String = "" {
didSet {
navigationBar.topItem?.title = self.headerTitle
}
}
...
}
ターゲットビューに UIView
をおいて、Custome Class を DialogHeaderView
に設定,この時 Attribuite Inspector に @IBInspectable
付きの属性に値を設置できます:
Runtime Attributes 欄からも設置できますが:
一瞬、ターゲットビューに DialogHeaderView
がリアルタイムで表示されます〜
ちょっと残念ですが、リアルタイムビルドの途中に失敗することは少なくないです。念のため、デバッグ方法を覚えておいたほうがいい:Editor -> Debug Selected Views。だいたいすぐエラーを解消できると思います。
型安全
Loadable
を除いて、型安全のため、ほかのできることまだあります。
Storyboard は存在するから、Segue
の定義はぜったいあリます。identifier
は文字列なので、ビューの文字列とコード中の文字列が合っていない場合はたまたまあることで、R.swift の導入をお勧めしたいです。
R.swift は広く使われて型安全の対応をしています、画像 (Image)、フォント (Font)、多言語 (Localization) なども、R.swift から得るところは少なくないです。