はじめに
私が所属するチーム向けにSwiftUI勉強会を開催することとなったため、資料を記事として投稿します。SwiftUI初学者なので間違いなどあれば、教えていただきたいです!
目次
事前準備
まずは、Xcodeで新しいプロジェクトを作成します。Xcodeを立ち上げ、Create New Project
を選択してください。
すると、次のような画面になるので、iOS
のApp
を選択してNext
を押してください。
この画面では、次のように設定してNext
を押します。
項目 | 説明 | 今回の設定 |
---|---|---|
Product Name | プロジェクト名、基本的には作成するアプリに関する名前を入力 | 任意 |
Team | Xcodeに登録済みのApple IDを選択 | Apple IDを選択 |
Organization Identifier | アプリを公開するときに必要となるID | 任意 |
Bundle Identifier | アプリの一意のID | Organization Identifierに合わせて自動入力 |
Interface | StoryboardかSwiftUIを選択 | SwiftUI |
Language | 開発言語を選択 | Swift |
Storage | アプリ内でデータを保存する手法を選択 | None |
Include Test | テストコードを含むか | チェックなし |
Next
を押し、任意のディレクトリにプロジェクトを保存すると、次のようにプロジェクトが立ち上がります。これで準備は完了です。
Part1のお品書き
基本操作とレイアウト
作成したプロジェクトの画面では画面左側にContentView、右側にはiPhoneの画面が表示されていると思います。そのため、画面のレイアウトを作成したい場合、ContentViewのコードを変更すると、右側のiPhoneのプレビュー画面に即時反映されます。
ContentViewについて
コードを触る前にContentView.swift
を簡単に説明します。ContentView.swift
はプロジェクト作成時にデフォルトで生成されているファイルで、中身は次のようになっています。
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
#Preview {
ContentView()
}
ContentView.swift
を確認すると、body{}
内にはVStack{}
があり、その中にはさらに、Image()
やText()
が確認できると思います。画面のレイアウトを作成する場合、基本的にはbody{}
の中にコードを追加していくことになります。
body{}
内のVStack{}
は{}
内に記載された部品を縦方向に並べることができます。プレビュー画面と照らし合わせて見てみると、地球っぽいアイコン、Hello, Worldのテキストが縦方向に並んでいることが確認できます。
VStack{}
の後に.padding()
がありますが、これは余白設定で、VStack{}
の領域の外側に余白を追加しています。.padding()
がある場合とない場合の領域(青枠)を比較すると、次のようになります。青枠を表示したい場合は、プレビュー画面の左下に3つ並んでいるアイコンの真ん中(Selectableボタン)を押し、コード内のVStack
をクリックすると表示されます。
コード後半部には以下のような#Preview
のブロックが記載されていることが確認できると思いますが、これはSwiftUI
で作成したViewをプレビューしてくれます。これはソースコードのコンパイル時にコードを変換して部分的に生成し、Swiftで繰り返しコードを書く手間を省くことができるSwift Macros
を用いています。マクロは新しいコードを追加しますが、既存のコードを変更したり修正することはないため、安全に使用できます。
#Preview {
ContentView()
}
Swift Macros
が登場する前はPreviewProvider
を継承したstruct
を定義して以下のように記載していたため、Swift Macros
を用いたプレビューの定義の方が簡素になりました。
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
ContentView.swift
の説明は以上になります。
では、実際に画面レイアウトを設計してみましょう。
はじめに、試しにHello, world!
をこんにちは、世界!
に変更してみましょう。
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("こんにちは、世界!")
}
.padding()
}
}
#Preview {
ContentView()
}
すると、プレビュー画面のテキストがこんにちは、世界!
に変更されたと思います。
また、Image()
のsystemName
をplay.rectangle.fill
に変更してみましょう。
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "play.rectangle.fill")
.imageScale(.large)
.foregroundStyle(.tint)
Text("こんにちは、世界!")
}
.padding()
}
}
#Preview {
ContentView()
}
すると、色は違いますがYoutubeのようなアイコンが表示されたかと思います。
次の章からSwiftUIの基本であるVStack
、HStack
、ZStack
をそれぞれ説明します。
VStack・HStack・ZStack
VStack
1つ前の章でも軽く触れましたが、VStack
は{}
内に記載した部品を縦方向に並べてくれます。Stack
は積み重ねる
の意味、V
はVertical
の略で垂直
の意味なので、VStack{}
は垂直(縦)方向に部品を積み重ねるための入れ物と考えれば、イメージしやすいかもしれません。
では、VStack
を使って部品を並べてみましょう。テキストを縦方向に3つ並べて自己紹介文を作ってみます。
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("私は〜〜です")
Text("趣味は〇〇です")
Text("よろしくお願いします!")
}
.padding()
}
}
#Preview {
ContentView()
}
すると、3つの文が縦方向に並んでいることが確認できると思います。
また、SwiftUIでは基本図形として、四角形や円を使用できます。せっかくなので四角形と円を縦に並べてみましょう。四角形はRectangle()
、円はCircle()
です。
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Rectangle()
.frame(width: 200, height: 100)
.foregroundStyle(.blue)
Circle()
.frame(width: 100, height: 100)
.foregroundStyle(.red)
}
.padding()
}
}
#Preview {
ContentView()
}
Rectangle()
やCircle()
それぞれの次の行には.frame()
や.forgrondStyle()
が続いていますが、これらはモディファイアといい、Viewに表示される要素について設定を加えるための修飾子です。前述の.padding()
などもモディファイアです。
.frame()
の引数にwidth
とheight
を指定することで、幅や高さを調整することができます。Rectangle()
については、指定した幅・高さ通りの図形になりますが、Circle()
にwidth
とheight
の値が異なる.frame()
モディファイアを指定した場合、値の小さい方に合わせた図形サイズになります(例:width = 200、height = 100とした場合、円の直径は100になる)。
.foregroundStyle()
は色を指定することができ、青色にしたい場合は.blue
のように引数に記載してあげればOKです。
プレビュー画面を確認すると次のように青色の長方形と赤色の円が縦方向に並んでいることが確認できると思います。
HSTack
HStack
は{}
内に記載した部品を横方向に並べてくれます。H
はHorizontal
の略で水平
の意味なので、前述のVStack{}
と同様の捉え方をするならば、HStack{}
は水平(横)方向に部品を積み重ねるための入れ物です。
では、HStack
を使って部品を並べてみましょう。前述の四角形と円を縦方向に並べるコードをHStack
に変更してみましょう。
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
Rectangle()
.frame(width: 200, height: 100)
.foregroundStyle(.blue)
Circle()
.frame(width: 100, height: 100)
.foregroundStyle(.red)
}
.padding()
}
}
#Preview {
ContentView()
}
先程、縦方向に並んでいた図形が横方向に変わったことが確認できると思います。
前述のサンプルコードの場合、図形と図形の間に少し間隔が空いています。図形同士のスペースの大きさを数値で設定したい場合は、HStack
の引数を次のように指定してあげるとスペースを設定することができ、VStack
も同様の方法で実現できます。
import SwiftUI
struct ContentView: View {
var body: some View {
HStack(spacing: 0, content: {
Rectangle()
.frame(width: 200, height: 100)
.foregroundStyle(.blue)
Circle()
.frame(width: 100, height: 100)
.foregroundStyle(.red)
})
}
}
#Preview {
ContentView()
}
HStack
の引数にspacing
を指定することで、部品同士のスペースを指定することができ、引数content
に並べたい部品を指定してあげることで、指定したスペースを保った状態で部品を並べることができます。
今回のサンプルコードのようにspacing
の値を0にすることで、下記のスクリーンショットのように図形同士を横にピッタリ並べることができます。
ZSTack
ZStack
は部品を重ねて配置し、画面に奥行きを表現することができます。VStack
のV(Vertical:垂直)
やHStack
のH(Horizontal:水平)
のように、ZStack
のZ
の意味を探しましたがわからなかったので、私は勝手に三次元のZ軸方向という解釈をしています。
では、ZStack
を使って部品を並べてみましょう。色と大きさの異なる円を3つ重ねてみます。
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Circle()
.frame(width: 300, height: 300)
.foregroundStyle(.red)
Circle()
.frame(width: 200, height: 200)
.foregroundStyle(.blue)
Circle()
.frame(width: 100, height: 100)
.foregroundStyle(.yellow)
}
}
}
#Preview {
ContentView()
}
すると、直径の大きい円から順番に3つ重なっていることが確認できると思います。ZStack
については、VStack
やHStack
のように引数spacing
は指定できません。
少し複雑なレイアウト
これまでは、HStack
、VStack
、ZStack
だけ用いたレイアウトを紹介しましたが、組み合わせることで少し複雑なレイアウトにも対応することができます。以下のようなコードを作成した場合、どのようなレイアウトになるか想像できますか?
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Rectangle()
.frame(width: 300, height: 300)
.foregroundStyle(.black)
VStack {
Rectangle()
.frame(width: 200, height: 100)
.foregroundStyle(.red)
HStack(spacing: 0, content: {
Rectangle()
.frame(width: 100, height: 100)
.foregroundStyle(.blue)
Circle()
.frame(width: 100, height: 100)
.foregroundStyle(.yellow)
})
}
}
}
}
#Preview {
ContentView()
}
上記のサンプルコードを実装してプレビュー画面で確認すると、次のようになります。
少しだけ解説をすると、青色の正方形と黄色の円はHStack
で横に並べ、赤色の長方形とHStack
をVStack
で縦に並べています。そして、黒色の正方形とVStack
をZStack
積み重ねます。
このように、HStack
、VStack
、ZStack
を用いることで、複雑なレイアウトも実現することができます。次節から課題を掲載しているので取り組んでみてください。
課題
課題のサンプルコードを記載していますが、サンプルコードと完全一致していなくても他の方法でレイアウトを作成できていればOKです。
Level1
次のような自己紹介カードを作成してください。また、モディファイアを用いて以下のように各要素を設定してください。
- 各種設定
-
「自己紹介カード」
- サイズ:
300x50
- 下線あり
- フォントはタイトルサイズ
- 文字は太字
- 余白をつける(余白の数値設定は必要なし)
- サイズ:
-
「名前」や「年齢」といった記入欄
- サイズ:
300x30
- 下線なし
- フォントは本文サイズ
- 文字は太字
- 余白をつける(余白の数値設定は必要なし)
- サイズ:
-
自己紹介カードの枠線
- 青色で太さは2.0
サンプルコード
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
SampleTitleText(text: "自己紹介カード")
.underline()
.padding()
SampleBodyText(text: "名前:")
.padding()
SampleBodyText(text: "年齢:")
.padding()
SampleBodyText(text: "出身地:")
.padding()
SampleBodyText(text: "一言:")
.padding()
}
.border(.blue, width: 2)
}
}
struct SampleTitleText: View {
var text: String?
var body: some View {
Text(text ?? "None Text")
.font(.title)
.bold()
.frame(width: 300, height: 50, alignment: .center)
}
}
struct SampleBodyText: View {
var text: String?
var body: some View {
Text(text ?? "None Text")
.font(.body)
.bold()
.frame(width: 300, height: 30, alignment: .leading)
}
}
#Preview {
ContentView()
}
Level2
次のようなイラストを作成してください。部品はVStack
、ZStack
は用いずにZStack
のみでレイアウト実装してください。また、モディファイアを用いて以下のように各要素を設定してください。
-
各種設定
- 赤色の半円と白色の半円(元の円のサイズ:
200x200
)- 半円はモディファイアで実現可能
- 黒色の細長い長方形(サイズ:
200x10
) - 中央に黒色と白色の円(黒色円のサイズ:
70x70
、白色円のサイズ:50x50
) - 黒色の枠線(幅:
2.0
)
- 赤色の半円と白色の半円(元の円のサイズ:
サンプルコード
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Circle()
.trim(from: 0.5, to: 1.0)
.frame(width: 200, height: 200)
.foregroundStyle(.red)
Circle()
.trim(from: 0.0, to: 0.5)
.frame(width: 200, height: 200)
.foregroundStyle(.white)
Rectangle()
.frame(width: 200, height: 10)
.foregroundStyle(.black)
Circle()
.frame(width: 70, height: 70)
.foregroundStyle(.black)
Circle()
.frame(width: 50, height: 50)
.foregroundStyle(.white)
Circle()
.stroke(lineWidth: 2.0)
.frame(width: 200, height: 200)
}
}
}
#Preview {
ContentView()
}
Level3
次のようなイラストを作成してください。また、モディファイア等を用いて以下のように各要素を設定してください。
-
各種設定
- 茶色の長方形(サイズ:
250x280
) - 赤色の長方形(サイズ:
260x40
) - 窓(色:シアン)
- 1階の窓(サイズ:
40x70
) - 2階の窓(サイズ:
50x70
) - 幅
2.0
の枠をつける
- 1階の窓(サイズ:
- 1階の窓とバルコニー、2階の窓とバルコニーの間にはそれぞれ縦方向に
20
のスペースを設けてください - 屋根(三角形)は
Rectangle()
やCircle()
のようにTriangle()
はないので、Path
を用いてカスタムShapeを自作してください(サイズ:300x150
)
- 茶色の長方形(サイズ:
Pathを用いたカスタムShapeの作成手順
-
Shape
プロトコルを準拠した構造体を用意する -
Path
を用いて図形を描画する
struct CustomShape: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
// pathに描画メソッドを追加して図形を描く
}
}
}
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
CustomRectangle()
.frame(width: 100, height: 100)
.foregroundStyle(.blue)
CustomRectangle()
.stroke(lineWidth: 5.0)
.frame(width: 200, height: 100)
.foregroundStyle(.green)
}
}
}
#Preview {
ContentView()
}
struct CustomRectangle: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: rect.minX, y: rect.minY)) // 開始位置
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY)) // 開始位置から線を追加する
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY)) // 上の行で追加した線の終点から、新たに線を追加する
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY)) // 上の行で追加した線の終点から、新たに線を追加する
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY)) // 上の行で追加した線の終点から、新たに線を追加する
path.closeSubpath()
}
}
}
サンプルコード
import SwiftUI
struct ContentView2: View {
var body: some View {
VStack(spacing: 0, content: {
Triangle()
.frame(width: 300, height: 150)
.foregroundStyle(.red)
ZStack {
Rectangle()
.frame(width: 250, height: 280)
.foregroundStyle(.brown)
VStack {
HStack(spacing: 0, content: {
ZStack {
Window(width: 50, height: 70)
}
ZStack {
Window(width: 50, height: 70)
}
})
Spacer().frame(height: 20)
Rectangle()
.frame(width: 260, height: 40)
.foregroundStyle(.red)
Spacer().frame(height: 20)
HStack(spacing: 0, content: {
ZStack {
Window(width: 40, height: 70)
}
ZStack {
Window(width: 40, height: 70)
}
ZStack {
Window(width: 40, height: 70)
}
})
}
}
})
}
}
struct Triangle: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: rect.midX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
path.closeSubpath()
}
}
}
struct Window: View {
var width: CGFloat?
var height: CGFloat?
var body: some View {
Rectangle()
.frame(width: width, height: height)
.foregroundStyle(.cyan)
Rectangle()
.stroke(lineWidth: 2.0)
.frame(width: width, height: height)
}
}
#Preview {
ContentView2()
}
まとめ
Part1の勉強会資料は以上となります。SwiftUIの勉強を始めてあまり月日が経っていないので、まだ初学者の段階ですが少し理解は深まったと思います。資料作成と並行して勉強をしないといけないので少し大変ですが、次回作(Part2)をお楽しみに!