UI
iOS
WWDC
Swift
SwiftUI

[Swift] SwiftUIでの画面遷移とプレゼンテーション


前置き

とりあえず、SwiftUIチュートリアルを触り、後半の方はサク読みで一応見てみたが、、、

いざ、自分が作りたいものを考えたときに、あれ、、、画面遷移とかどうすんの?書いてなくね?

っていうのがチュートリアルの書簡。。。(書いてあったらごめんなさいw)

ってなわけで、実践的に使えそうなものを調べたり、リファレンス読んでごにょってたりして、、

それをまとめた感じです。


コード例

コード例は、最低限のコードにしている部分が多いので、その部分はご容赦ください、、、笑

比較のため再現可能な既存コードも書いてますが、必ずしもそれが対応しているというわけではないです。参考程度に。


画面遷移


① Navigation

move1.gif


既存

navigationController?.pushViewController(viewController, animated: true)


SwiftUI

// 遷移元

struct ContentView: View {
var body: some View {
NavigationView {
NavigationButton(destination: SubContentView()) {
Text("Show Next")
}
}
}
}

// 遷移先
struct SubContentView: View {
var body: some View {
Text("SubContentView")
}
}


備考

NavigationViewを配置することで、旧来のUINavigationController(NavigationBar)と同じ役割を担える模様。


② Present

move2.gif


既存

present(viewController, animated: true)


SwiftUI

// 遷移元

struct ContentView: View {
var body: some View {
PresentationButton(Text("Present"), destination: SubContentView())
}
}

// 遷移先
struct SubContentView: View {
var body: some View {
Text("SubContentView")
}
}


備考

挙動が全く一緒というわけではない。

SwiftUIの方では、次の画面が画面全体に重なるわけではないのに注意。

(※他にpresentに対応するものがちゃんとある、という場合はご教授いただけると、、)


プレゼンテーション

画面作成を行うにあたり、簡単なUIがいくつか用意されているので、ここで紹介していく。


①Modal

present1.gif

struct ContentView: View {

@State var modal: Modal?

var body: some View {
Button(action: {
self.modal = Modal(Text("Modal"), onDismiss: {})
}) {
Text("Show Modal")
}.presentation(modal)
}
}


備考


  • 画面遷移のPresentと同じように見えるが、こちらはあまりカスタマイズできないので、リッチなUIは作れない

  • デフォルト引数としてonDismissのクロージャを持つので、閉じる際に何か処理を追加できるメリットあり


②Popover

present2.gif


既存

// UIPopoverPresentationControllerDelegate を使用して

let vc = UIViewController()
vc.modalPresentationStyle = .popover
vc.preferredContentSize = CGSize(width: 200, height: 300)
vc.popoverPresentationController?.sourceView = view
vc.popoverPresentationController?.delegate = self
present(vc, animated: true)


SwiftUI

struct ContentView: View {

@State var popover: Popover?

var body: some View {
Button(action: {
self.popover = Popover(content: Text("Popover"), dismissHandler: {})
}) {
Text("Show Popover")
}.presentation(popover)
}
}


備考


  • 画面が透過されているが、Modalと挙動は同じ

  • Modalと違いEdgeが設定できるなど、少しだけデザインに幅を持たせられる


③ActionSheet

present3.gif


既存

let alert = UIAlertController(title: "Title", 

message: "Message",
preferredStyle: . actionSheet)

let defaultAction = UIAlertAction(title: "Default", style: .default)
let destructiveAction = UIAlertAction(title: "Destructive", style: .destructive)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)

alert.addAction(defaultAction)
alert.addAction(destructiveAction)
alert.addAction(cancelAction)

present(alert, animated: true)


SwiftUI

struct ContentView: View {

@State var actionSheet: ActionSheet?

var body: some View {
Button(action: {
self.actionSheet = ActionSheet(title: Text("Title"),
message: Text("Message"),
buttons: [.default(Text("Default")),
.destructive(Text("Destructive")),
.cancel()])
}) {
Text("Show ActionSheet")
}.presentation(actionSheet)
}
}


備考



  • UIAlertControllerとして一括りにされていたのが独立した

  • 書き方は前と大体同じ印象


④Alert

present4.gif


既存

let alert = UIAlertController(title: "Title", preferredStyle: .alert)

present(alert, animated: true)


SwiftUI

struct ContentView: View {

@State var isShown = false

var body: some View {
Button(action: {
self.isShown = true
}) {
Text("Show Alert")
}.presentation($isShown, alert: {
Alert(title: Text("Alert"))
})
}
}


備考


  • ①~③と違いBool値をBindする必要がある

  • 基本的にはUIAlertControllerと同じ使い方が可能


ライフサイクル

ライフサイクルというと大袈裟だが、Viewそのものからコントロールできるものが登場。


① onAppear


既存

func viewDidAppear(animated: Bool)


SwiftUI

struct ContentView: View {

var body: some View {
Text("Appear").onAppear(perform: {
// do something
})
}
}


② onDisappear


既存

func viewDidDisappear(animated: Bool)


SwiftUI

struct ContentView: View {

var body: some View {
Text("Disappear").onDisappear(perform: {
// do something
})
}
}


備考

①と②はチェーンで連続してかける


その他


後書き

もっと良いベストプラクティスがあればコメント欲しいとこです。(本気で)

知見がたまったら、改めてこちらに記載する、、かも、、?

まだBeta版なので変わることありそうだなぁ、、