0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SwiftUI初学者がチーム向けに勉強会資料を作った Part1

Last updated at Posted at 2024-12-04

はじめに

 私が所属するチーム向けにSwiftUI勉強会を開催することとなったため、資料を記事として投稿します。SwiftUI初学者なので間違いなどあれば、教えていただきたいです!

目次

事前準備

 まずは、Xcodeで新しいプロジェクトを作成します。Xcodeを立ち上げ、Create New Projectを選択してください。

 すると、次のような画面になるので、iOSAppを選択して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はプロジェクト作成時にデフォルトで生成されているファイルで、中身は次のようになっています。

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を用いています。マクロは新しいコードを追加しますが、既存のコードを変更したり修正することはないため、安全に使用できます。

ContentView.swift
#Preview {
    ContentView()
}

 Swift Macrosが登場する前はPreviewProviderを継承したstructを定義して以下のように記載していたため、Swift Macrosを用いたプレビューの定義の方が簡素になりました。

ContentView.swift
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

ContentView.swiftの説明は以上になります。

では、実際に画面レイアウトを設計してみましょう。
はじめに、試しにHello, world!こんにちは、世界!に変更してみましょう。

ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("こんにちは、世界!")
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

すると、プレビュー画面のテキストがこんにちは、世界!に変更されたと思います。

また、Image()systemNameplay.rectangle.fillに変更してみましょう。

ContentView.swift
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の基本であるVStackHStackZStackをそれぞれ説明します。

VStack・HStack・ZStack

VStack

 1つ前の章でも軽く触れましたが、VStack{}内に記載した部品を縦方向に並べてくれます。Stack積み重ねるの意味、VVerticalの略で垂直の意味なので、VStack{}垂直(縦)方向に部品を積み重ねるための入れ物と考えれば、イメージしやすいかもしれません。
 では、VStackを使って部品を並べてみましょう。テキストを縦方向に3つ並べて自己紹介文を作ってみます。

ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("私は〜〜です")
            Text("趣味は〇〇です")
            Text("よろしくお願いします!")
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

 すると、3つの文が縦方向に並んでいることが確認できると思います。

 また、SwiftUIでは基本図形として、四角形や円を使用できます。せっかくなので四角形と円を縦に並べてみましょう。四角形はRectangle()、円はCircle()です。

ContentView.swift
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()の引数にwidthheightを指定することで、幅や高さを調整することができます。Rectangle()については、指定した幅・高さ通りの図形になりますが、Circle()widthheightの値が異なる.frame()モディファイアを指定した場合、値の小さい方に合わせた図形サイズになります(例:width = 200、height = 100とした場合、円の直径は100になる)。
 .foregroundStyle()は色を指定することができ、青色にしたい場合は.blueのように引数に記載してあげればOKです。

プレビュー画面を確認すると次のように青色の長方形と赤色の円が縦方向に並んでいることが確認できると思います。

HSTack

 HStack{}内に記載した部品を横方向に並べてくれます。HHorizontalの略で水平の意味なので、前述のVStack{}と同様の捉え方をするならば、HStack{}水平(横)方向に部品を積み重ねるための入れ物です。

 では、HStackを使って部品を並べてみましょう。前述の四角形と円を縦方向に並べるコードをHStackに変更してみましょう。

ContentView.swift
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も同様の方法で実現できます。

ContentView.swift
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は部品を重ねて配置し、画面に奥行きを表現することができます。VStackV(Vertical:垂直)HStackH(Horizontal:水平)のように、ZStackZの意味を探しましたがわからなかったので、私は勝手に三次元のZ軸方向という解釈をしています。
では、ZStackを使って部品を並べてみましょう。色と大きさの異なる円を3つ重ねてみます。

ContentView.swift
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については、VStackHStackのように引数spacingは指定できません。

少し複雑なレイアウト

 これまでは、HStackVStackZStackだけ用いたレイアウトを紹介しましたが、組み合わせることで少し複雑なレイアウトにも対応することができます。以下のようなコードを作成した場合、どのようなレイアウトになるか想像できますか?

ContentView.swift
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で横に並べ、赤色の長方形とHStackVStackで縦に並べています。そして、黒色の正方形とVStackZStack積み重ねます。

 このように、HStackVStackZStackを用いることで、複雑なレイアウトも実現することができます。次節から課題を掲載しているので取り組んでみてください。

課題

 課題のサンプルコードを記載していますが、サンプルコードと完全一致していなくても他の方法でレイアウトを作成できていればOKです。

Level1

 次のような自己紹介カードを作成してください。また、モディファイアを用いて以下のように各要素を設定してください。

  • 各種設定
  • 「自己紹介カード」
    • サイズ:300x50
    • 下線あり
    • フォントはタイトルサイズ
    • 文字は太字
    • 余白をつける(余白の数値設定は必要なし)

  • 「名前」や「年齢」といった記入欄
    • サイズ:300x30
    • 下線なし
    • フォントは本文サイズ
    • 文字は太字
    • 余白をつける(余白の数値設定は必要なし)

  • 自己紹介カードの枠線
    • 青色で太さは2.0

サンプルコード
ContentView.swift
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

 次のようなイラストを作成してください。部品はVStackZStackは用いずにZStackのみでレイアウト実装してください。また、モディファイアを用いて以下のように各要素を設定してください。

  • 各種設定
    • 赤色の半円と白色の半円(元の円のサイズ:200x200
      • 半円はモディファイアで実現可能
    • 黒色の細長い長方形(サイズ:200x10
    • 中央に黒色と白色の円(黒色円のサイズ:70x70、白色円のサイズ:50x50
    • 黒色の枠線(幅:2.0
サンプルコード
ContentView.swift
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階の窓とバルコニー、2階の窓とバルコニーの間にはそれぞれ縦方向に20のスペースを設けてください
    • 屋根(三角形)はRectangle()Circle()のようにTriangle()はないので、Pathを用いてカスタムShapeを自作してください(サイズ:300x150
Pathを用いたカスタムShapeの作成手順
  1. Shapeプロトコルを準拠した構造体を用意する
  2. Pathを用いて図形を描画する
基本的な使い方
struct CustomShape: Shape {
    func path(in rect: CGRect) -> Path {
        Path { path in
            // pathに描画メソッドを追加して図形を描く
        }
    }
}
ContentView.swift
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() 
        }
    }
}
サンプルコード
ContentView.swift
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)をお楽しみに!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?