より高度な表現が可能となった──Graphics and Layout
プログラムでの処理の向上のほかに、UIの外観や表示形式についても機能が充実しました。主要な変更点について、Shape Style/Layoutの2つでまとめられています。
| ジャンル | 概要 |
|---|---|
| Shape Style | UIの外観 |
| Layout | 画面レイアウト |
それぞれの項目について簡単なサンプルコードとともに紹介します。
Shape Style
UIの前景スタイル(foregroundStyle)と背景スタイル(backgroundStyle)を指定できるようになりました。前景スタイルには色や影などの装飾、背景スタイルには背景として画像/色/グラデーションなどを指定することができます。
struct ContentView: View {
var body: some View {
Image(systemName: "swift")
.frame(width: 150,height: 150)
.background(in: Circle())
.foregroundStyle(.white.shadow(.drop(color:.black, radius:2, x:4, y:4))) // -------- ①
.backgroundStyle(.orange.gradient) // -------- ②
.font(.system(size:60))
}
}
最初にSwiftのアイコン画像を円の形状で用意します。ここにforegroundStyleメソッドでアイコンの色を白、黒い影をつけます(①)。影をつけるときには、色/影のエフェクト/オフセットを指定することができます。その後にbackgroundStyleメソッドで、オレンジのグラデーションで背景色をつけます。サンプルのようにロゴ画像のような外観を簡単に実現できるようになりました。
Layout
動的にレイアウトを変更するAnyLayoutとグリッド表示を行うGridという新しい仕様が追加されました。具体的なサンプルをあげて説明します。
AnyLayout
AnyLayoutはレイアウトを動的に変更できるプロトコルです。画面上に配置済みのビューを破棄することなどはなく、動的にレイアウトを変更できます。AnyLayoutを利用してレイアウトを動的に変更するサンプルは次のとおりです。
struct ContentView: View {
// レイアウトを切り替えるフラグ
@State var isChangeLayout: Bool = false
// 前節のサンプルで作成したImage
let image: some View = Image(systemName: "swift")
.frame(width: 150,height: 150).font(.system(size:60))
.background(in: Circle()).backgroundStyle(.orange.gradient)
.foregroundStyle(.white.shadow(.drop(color:.black, radius:2, y:4)))
var body: some View {
VStack {
// 動的に変更するレイアウトを定義
let layout: AnyLayout = isChangeLayout ? AnyLayout(HStackLayout()) : AnyLayout(VStackLayout()) // -------- ①
// Imageを2つ並べてタップでレイアウトを変更
layout {
image
image
}
.padding()
.animation(.spring(), value: isChangeLayout)
.onTapGesture { // --------②
isChangeLayout.toggle()
}
}
}
}
バインディング変数isChangeLayoutの値によって水平方向または垂直方向のAnyLayoutを定義します。AnyLayoutの定義には、条件付きの水平方向レイアアウトを定義するHStackLayout構造体、条件付きの垂直方向レイアアウトを定義するVStackLayout構造体を利用します。プレビュー画面とともに利用するHStack/VStackの構造体ではないことに気を付けてください。onTapGestureメソッドで、タップ時にisChangeLayoutの値を切り替わり、レイアウトもアニメーションを伴って切り替わります(②)。
Grid
Gridは、グリッドの形式で表示を行う構造体です。Gridブロック内にGridRow構造体を配置することで、行単位での表示を指定することができます。Gridを使った簡単なサンプルは次のとおりです。
struct SquareView: View { // グリッドのセルに表示するビューを定義
let color: Color
var body: some View {
color.frame(width: 50, height: 50)
}
}
struct ContentView: View {
var body: some View {
Grid { // -------- ①
// 赤5セルを表示
GridRow { // --------②
ForEach(0..<5) { _ in
SquareView(color: .red)
}
}
// 青3セルを空白を挟んで表示
GridRow { // --------②
SquareView(color: .blue)
Color.clear.gridCellUnsizedAxes([.horizontal, .vertical]) // --------③
SquareView(color: .blue)
Color.clear.gridCellUnsizedAxes([.horizontal, .vertical]) // --------③
SquareView(color: .blue)
}
// 緑3セルを中央に詰めて表示
GridRow { // --------②
Color.clear.gridCellUnsizedAxes([.horizontal, .vertical]) // --------③
ForEach(0..<3) { _ in
SquareView(color: .green)
}
Color.clear.gridCellUnsizedAxes([.horizontal, .vertical]) // --------③
}
}
}
}
グリッドは、Gridブロック(①)の中にGridRowブロックを行を設ける(②)ことで表示できます。グリッドのセルを空けたい場合は、色やビューで必要なスペースを埋めるgridCellUnsizedAxesメソッドを利用します(③)。
またGridブロックは、一定の領域を確実に表示するという性質を利用して、表を作成するという使い方もできます。連載第1回で紹介した複数のリストをまとめるTableはiOSには未対応です。Gridをうまく使うと、iOSでも複数のリストをまとめて表示することができます。Gridを利用して、月当たりの売り上げデータを表にするサンプルは次のようになります。
struct ContentView: View {
var body: some View {
Grid(alignment: .leading, horizontalSpacing: 0, verticalSpacing: 10) { // -------- ①
ForEach(MonthAmount.examples) { item in
GridRow(alignment: .firstTextBaseline) { // -------- ②
Text(item.month)
Text("\(item.amount)")
}.padding([.leading, .trailing])
GridRow {
Rectangle().frame(height: 1).gridCellColumns(2) // -------- ③
}
}
}
}
}
Grid構造体自体には、セルを揃える基準/水平方向のスペース/垂直方向のスペースを指定することができます(①)。Gridブロックの中で、月当たりの売り上げデータをForEachでループします。最初に月名と売り上げを表示します(②)。その後に、高さを1として矩形をグリッドのセルを統合するgridCellColumnsメソッドで2セル分配置します(③)。ループが終わると、月当たりの売り上げデータが表で表示されます。
まとめ
今回は「What's new in SwiftUI」の後半部分で紹介されたSwiftUI 4の新しい機能について説明しました。次回からSwiftUI 4の機能を使ったもう少し実用的なサンプルを作成していきます。
