2020/11/01現在時点で、新しくiOSアプリつくろうとすると、UI実装の選択肢は下記4つがあります。
- xib
- Storyboard
- コードベース(UIKit)
- SwiftUI
僕がiOS開発に入門したときはSwiftUIなんてものは存在しませんでしたが、それでもstoryboardとコード、どちらでやるか悩みました。
(xibは何なのかよくわからなかった)
この記事は一年前の自分が、こんな記事あったら助かっただろうな、と思って書きます。
この記事は、それぞれのUI実装方法をざっくり説明、それぞれのメリットデメリットをあげて、比較します。
環境
Swift: 5.3
Xcode: 12.1
結論
先に結論書いちゃいますが、何の制約もない新規プロジェクトだったらSwiftUIを選択すべきだと思います。
xibとは
2011/10にXcode4.2とiOS5がリリースされて、Storyboardが誕生するまで、UI実装方針はxibだけでした。
NeXT(Appleを追放されたジョブズが一時期経営してたIT企業)の時代のソフトウェア資産のDNAが流れています。
NeXTでGUIを使ってソフトウェアのUIをつくれる画期的なソフトウェア、Interface Builder(以下IB)が開発されて、その編集データは.nibファイル(NeXT Interface Builder) という独自形式で保存されました。
(Interface Builderは更に進化して、今ではXcodeにビルドインされています)
.nibファイルは独自形式で扱いづらいため、XMLに拡張されました。
これがxibです。
XML Interface Builder。
xibの使い方
Xcodeの[File] > [New] > [File]でViewを選びましょう。
すると新規xibファイルが作成できます。
UIのレイアウトはGUIベースでできます。
xibを呼び出すには、Outlet接続するか、コードで呼び出すかの二択あります。
Outlet接続はIBと接続したいコードをAssistantで開いて、UI部品をControlボタン押しながらコードに向けてドラッグアンドドロップしてください。
詳しくはこちらの記事などがよろしいかと。
コードで呼び出す例としては、下記みたいな感じです。
クラスメソッドの記事からの引用なので、詳細が知りたければそちらを参照ください。
import UIKit
class XibView: UIView {
override init(frame: CGRect){
super.init(frame: frame)
loadNib()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
loadNib()
}
func loadNib(){
let view = Bundle.main.loadNibNamed("XibView", owner: self, options: nil)?.first as! UIView
view.frame = self.bounds
self.addSubview(view)
}
}
xibのメリット・デメリット
正直Storyboardの登場で、xibは存在価値をなくしているので、xibメインでUI実装する、という選択肢はないでしょう。
ただxibはxibで、まだユースケースは残っていて、「何度も使い回すUI部品」に使うという用途が残っています。
例えばUITableViewのregister(_:forCellReuseIdentifier:)
は、xibファイルをセルのテンプレートとして登録することができます。
ただその用途にしても、絶対にxib使わないとできないことでもないので、あくまで選択肢の一つとしてあるよ、というだけですね。
Storyboardとは
xibは静的なViewを定義できるだけでしたが、Storyboardはこれに加えて画面遷移も定義できます。
SwiftUIが登場するまで、UI実装方法のメインでした。
ファイルの形式はXMLファイルです。
Storyboardの使い方
まずプロジェクトをStoryboardベースにしましょう。
これでプロジェクト作成すると、下記二つのStoryboardがデフォルトで作成されると思います。
- LaunchScreen.storyboard
- Main.storyboard
LaunchScreenはいわゆるスプラッシュスクリーンで、アプリ起動したとき最初に一瞬表示されるアレです。
ここは個人開発なら自分のFacebookの顔写真を貼ったりしておきましょう。
大事なのはMain.storyboardです。
「Storyboard Entry Point」という矢印が、このstoryboardを起動したときに最初に表示されるViewを指しています。
そして各ViewのCustom Classで埋め込んでるクラスが実行されます。
デフォルトだとViewController.swift
が指定されているので、よしなに名前は変更していきましょう。
main.storyboardは誰が呼ぶんですか? と思うかもしれませんね。
それは実は↓で指定します。
逆に、コードからStoryboardを呼び出すこともできます。
呼び出したいViewにIDを設定してあげる(ここではidentifierとする)と、
let storyboard = UIStoryboard(name: "main", bundle: Bundle(for: ViewController.self))
let viewController = storyboard.instantiateViewController(withIdentifier: "identifier") as! ViewController
こんな感じで扱えます。
そして画面遷移はSegue(セグエ)を使ってできます。
詳しくは昔頑張って書いた記事があったので、そちらを参照ください。
実際の開発では、画面の数も多くなって、Segueだとやりきれなくなって、結局画面遷移はコードで行うこともあるかと思います。
Storyboardのメリット
- GUIベースで直感的にUIが組める
- iOS開発者の経験値が高い
- ネット上に情報が多い
- AutoLayoutとの組み合わせで様々な端末サイズに簡単に対応できる
Storyboardのデメリット
- 言うほど直感的じゃない
- 実体がXMLファイルなので、レビューのときに正しいかが判断しづらい
- コンフリクトしやすい(Storyboard Reference使って分割すると改善する)
- 単純にファイルが重い(Storyboard Reference使って分割すると改善する)
- AutoLayoutがしんどい
- たまに触っただけで変更加わるときあるよね?
↑の解説
StoryboardはGUIで直感的にUI組めるよ!と言うんですけど、実際に使ってみると癖があって、慣れるまで時間がかかりました。
(というか今でも慣れてるとは言いがたいかも……)
たとえばOutlet接続したときに、コードだけ削除してしまって、コンパイルエラーになったり、Custom Classがよく理解できてなくて、型が合ってないクラス突っこもうとしてできなかったり、
結局ちゃんとiOS開発の仕組みわかってる人じゃないと使いこなせない仕組みになっている気がします。
AutoLayoutに関してはStoryboardというかAutoLayoutの問題のような気がします。
コードベース(UIKit)
↑で書いた通り、Storyboardはそれなりに癖がありまして、Storyboard一切使わずにコードベースでやる選択肢もあります。
SwiftUIが登場した今、コードベースという言い方も変なのですが、正確に言うと「UIKitでコードベースに書くやり方」です。
以下、コードベースと書いていきます。
Storyboardでできることは、基本的に全てコードベースに記述可能です。
コードベースのやり方
※こちらの記事がいいかなと思いました
まずStoryboardベースでプロジェクトを作成します。
そしてプロジェクトファイルを選択して、Main Interfaceを空欄にします。
で、AppDelegateを修正します。
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
return true
}
これであとはひたすらコードでViewController
を改造して行けば良いです。
コードベースのメリット
- UI部品の再利用がコードのコピペでできる
- レビューしやすい
コードベースのデメリット
- UI部品のframe指定、AutoLayout指定がしんどい
↑の解説
僕の場合、今働いてる会社がコードベースでやっていて、個人開発でStoryboard使っているんですが、
Storyboardで感じるストレスがない反面、Viewを全部コードで指定するのはかなりしんどいです。
「レビューしやすい」と書いたものの、View周りのロジックは実際ビルドして動かしてみないとわからないケースの方が多い気がします。
Storyboardとコードベースのメリデメについては、宗教論争的なところもあるので、これ以上深入りはしません。
SwiftUIとは
2019年まで、上記3つの選択肢しかありませんでした。
しかしWWDC2019でSwiftUIが登場して、パラダイムシフトが起こりました。
SwiftUIは、Web技術のReactから大きな影響を受けています。
ポイントは「宣言的」(declarative)という特徴です。
宣言的なフレームワークは、それまでのフレームワークのことを命令的(imperative)と呼称します。
例えば決定ボタンを実装したければ、どの言語を使うにせよ、下記のような実装になると思います。
- ボタンを生成する
- ボタンの文字、文字の大きさ、フォント、文字色、背景色を決める
- ボタンの表示位置を指定する
- ボタンを押した際の動作を記述する
宣言的なUI実装はこうではなく、
- ○○の真下に決定ボタン置いて
と記述します。
現実的には色々指定することになるので、
- ボタンを○○の真下に置く、文字はxx、大きさはxx、……
みたいな感じになりますが、本来の宣言的UIの理想の世界は「俺(開発者)がしたいのはこういうこと」と宣言したら、あとはフレームワーク側で意図をくみとって、いい感じのUIをつくってくれることです。
SwiftUIの使い方
最新版のXcodeには、既にSwiftUIでプロジェクトをはじめることができます。
試しに「QiitaSample2」というSwiftUIベースのプロジェクトを生成してみました。
全然違う世界ですね。
この記事でSwiftUIを詳細に解説しようとするのはさすがに尺が厳しすぎるので、サンプルプロジェクトの解説程度にとどめます。
まずContentView.swift
がUI部品です。
Storyboard開発の経験がある方なら、main.storyboard
+ ViewController.swift
に相当する、と説明した方がわかりやすいかもしれません。
ちなみにAppDelegateはQiitaSample2App.swift
です。
SwiftUIで重要なのはView
というプロトコルです。
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, world!")
.padding()
}
}
Viewに準拠したView(ややこしい)はbodyというたった一つのプロパティを持っていて、開発者はここに自分の表示したいViewを宣言的に書いていきます。
ポイントはたった一つのViewしかとれないところです。
複数のUI部品を使用する場合でも、最終的には一つのViewとして組み合わせてから渡す必要があります。
このように、仕組みを極限までシンプルにすることで、カオスになりがちなUIの複雑性に対抗しています。
そしてXcode上で見ると、Viewの変化はすぐにエディタ上にリアルタイムで反映されます。
このリアルタイムプレビューが使えるのは、下記の記述があるからです。
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
SwiftUIでViewをいじるのは、コードからもGUIからも可能です。
たとえばコードからHello Worldの文言を変えたければ、Text("Hello, world!")
で指定されている文言を変えてください。
同じことをGUIでやるなら、
ここからいじれます。
さて、UIをあれこれカスタマイズしていきたいときはどうしたらいいでしょう?
SwiftUIでは、モディファイアをひたすら重ねていきます。
こんな感じです。
struct ContentView: View {
var body: some View {
Text("Hello, world!")
.foregroundColor(.primary)
.frame(width: 200.0, height: 300.0)
.shadow(color: .black, radius: 10, x: 0.0, y: 0.0)
.background(Color.blue)
.clipShape(Circle())
}
}
これでこんなViewになります。
モディファイアの意味はとりあえず置いとくとして、とりあえずUIKitの開発と全然違う、というのがわかっていただければ結構です。
SwiftUIのパワーを感じて欲しいので、下記を試してみましょう。
UIKitのときはライトモード/ダークモードでのUIを試すために、結構大変でしたね。
SwiftUIではこうです。
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environment(\.colorScheme, .dark)
}
}
これでリアルタイムプレビューがダークモード表示になりました。
今は色指定でライトモードとダークモードで変わるものが文字色だけで、その文字色が黒から白になりました。
このまま書きたいトピックは色々あるんですが、この辺で打ち止めにします。
SwiftUIにキャッチアップしたくて、SwiftUI 徹底入門という本を最近読んだのですが、この本はすごくよかったです。
もしSwiftUIに入門したい方はこちらから入るのがいいかなと思いました。
SwiftUIのメリット
- Storyboard + UIKitの開発のデメリットを解消している
- 宣言的な記述によって、コードが簡潔になった
- リアルタイムプレビューによってテストがしやすい
- iOS/iPad OS/WatchOS/MacOSすべてで動くアプリがつくれる
- ウィジェットがつくれる
SwiftUIのデメリット
- バグや機能不足が(だいぶ潰したとはいえ)多い
- 情報が(増えてきたとはいえ)少ない
- iOS開発者の間で経験が十分でない
↑の解説
2019〜2020年上旬の時点だと、「SwiftUI? まあまだ様子見だよねー」という雰囲気が強くて、アーリーアダプターたちが熱心にキャッチアップしている状況でしたが、
WWDC 2020でiOS 14に搭載されるウィジェットで、SwiftUIの使用が事実上強制されたことで、一気に流れが変わったように感じました。
ウィジェットだけでなく、SwiftUI全体も進化していて、「あれこれだいたいのことできるな」という気持ちにさせられました。
もちろんまだ深い機能を使おうとしたら、UIKitのお世話にならないといけない場面はあるかもしれませんが、思った以上に代替できている印象です。
これは異論ある方もいると思うのですが、新規開発であれば、SwiftUIベースではじめない理由は既になく、SwiftUIを選択すべきだと僕は考えます。
UIKitには長い歴史の積み重ねがあるので、開発者の経験も発信された情報量も段違いです。
けれどそれは過去のObjective-CからSwiftへの移行のときも同じ状況だったと思います。
SwiftUIでも同じことが繰り返されるのではないかと思います。
ウィジェットがUIKitで作成できなかったことからもわかる通り、SwiftUIを使わないということは、今後のiOS 14以降の進化から取り残されることでもあると思います。
モバイル開発でトレンドから取り残されるのは割と致命的かなと思います。
ちょっと強い書き方をしてSwiftUIを推したものの、とはいえSwiftUI全然今までとパラダイムが違うので、学習コストはまあまあ発生すると思います。
というか僕自身今がっつりStoryboard + UIKitで開発した個人開発のアプリをSwiftUIに移行させようと計画中ですが、結構大変だなーと思っています。
商用アプリをすべてSwiftUI化すべきだ、とまでは思いません。
けどSwiftUIを勉強していると、UIKitを全面的にSwiftUIに移行しないと負債化する未来が数年後来ていることも想定できるなーと感じます。
単純にイチ開発者として、開発体験がいいです。SwiftUI。
ごちゃごちゃ書いてきましたが、UI実装方法として、SwiftUIの方がStoryboard(UIKit)より優れていると思います。
それでもStoryboard(UIKit)を選ぶ理由があるとすれば、やはりSwiftUIが新しすぎるというのだけが理由だと思います。
ただWWDC 2019時点であった、「SwiftUI発表したものの、全然流行らなくて、数年後なかったことになるのでは?」という懸念は払拭されています。
まとめ
- iOSアプリのUI実装方法はxib, Storyboard, コードベース, SwiftUIの4通りがある
- 新規アプリ開発であれば、SwiftUIを選択すべき
- ただ既存のアプリの新規開発を全部SwiftUIにしろ、とまでは言えない
- ただSwiftUIを使えない事情があるときにStoryboard(UIKit)にすべき
- 個人的にはSwiftUI > Storyboard > コードベースの順で開発しやすさを感じます。Storyboardとコードベースの順位には個人差があるかもしれません