SwiftUI ⌘+クリックで出るメニューでViewを分割したりする

# 環境
Xcode Version 14.0 (14A309)
SwiftUI Tutorialの[Building Lists and Navigation](https://developer.apple.com/tutorials/swiftui/building-lists-and-navigation)を元にしています

# 概要
SwiftUIでコードを扱っているとき、Viewのコードを⌘キー+クリックすることでSwiftUIに対し色々便利なことができるメニューが表示されます。

![スクリーンショット 2022-09-20 13.52.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/687960/aaa2ea7f-639c-34ce-5dbb-b44305601b2c.png)

主にSwiftUIでのみ出るメニューについて説明します。
以下のSwiftUIを操作します。

```swift
struct LandmarkDetail: View {
    var landmark: Landmark

    var body: some View {
        ScrollView {
            MapView(coordinate: landmark.locationCoordinate)
                .ignoresSafeArea(edges: .top)
                .frame(height: 300)

            CircleImage(image: landmark.image)
                .offset(y: -130)
                .padding(.bottom, -130)

            VStack(alignment: .leading) {
                Text(landmark.name)
                    .font(.title)

                HStack {
                    Text(landmark.park)
                    Spacer()
                    Text(landmark.state)
                }
                .font(.subheadline)
                .foregroundColor(.secondary)

                Divider()

                Text("About \(landmark.name)")
                    .font(.title2)
                Text(landmark.description)
            }
            .padding()
        }
        .navigationTitle(landmark.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}
```

## 分割
`Extract`のメニューを使うとビューの分割ができます。
`Extract to Variable`と`Extract to Method`はVStackなどの、ビューを内部に格納するものには使えないようです。
Extract to VariableとExtract to Methodに関しては、SwiftUI以外の用途でも使えますので、SwiftUI専用の機能というわけではありません。

### Extract SubView
ビューを`struct ExtractedView`という名前で切り出します。
この時ビューについているModifierごと切り出されます。
ただし、landmarkなどの値は切り出さないので、ExtractedViewにプロパティを与えたりなど追加対応する必要があります。


```swift
struct LandmarkDetail: View {
    var landmark: Landmark

    var body: some View {
        ScrollView {
            MapView(coordinate: landmark.locationCoordinate)
                .ignoresSafeArea(edges: .top)
                .frame(height: 300)

            ExtractedView()

            VStack(alignment: .leading) {
                Text(landmark.name)
                    .font(.title)

                HStack {
                    Text(landmark.park)
                    Spacer()
                    Text(landmark.state)
                }
                .font(.subheadline)
                .foregroundColor(.secondary)

                Divider()

                Text("About \(landmark.name)")
                    .font(.title2)
                Text(landmark.description)
            }
            .padding()
        }
        .navigationTitle(landmark.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}

struct ExtractedView: View {
    var body: some View {
        CircleImage(image: landmark.image)
            .offset(y: -130)
            .padding(.bottom, -130)
    }
}
```

### Extract to Variable

ビューを変数にします。デフォルトでは `extractedExpr` という名前になります。
Modifierは含みません。

```swift
struct LandmarkDetail: View {
    var landmark: Landmark

    var body: some View {
        ScrollView {
            MapView(coordinate: landmark.locationCoordinate)
                .ignoresSafeArea(edges: .top)
                .frame(height: 300)

            let extractedExpr: CircleImage = CircleImage(image: landmark.image)
            extractedExpr
                .offset(y: -130)
                .padding(.bottom, -130)

            VStack(alignment: .leading) {
                Text(landmark.name)
                    .font(.title)

                HStack {
                    Text(landmark.park)
                    Spacer()
                    Text(landmark.state)
                }
                .font(.subheadline)
                .foregroundColor(.secondary)

                Divider()

                Text("About \(landmark.name)")
                    .font(.title2)
                Text(landmark.description)
            }
            .padding()
        }
        .navigationTitle(landmark.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}
```

### Extract to Method

ビューをメソッドに分割します。デフォルトでは `extractedFunc` という名前になります。
こちらもModifierは含みません。

```swift
struct LandmarkDetail: View {
    var landmark: Landmark

    fileprivate func extractedFunc() -> CircleImage {
        return CircleImage(image: landmark.image)
    }
    
    var body: some View {
        ScrollView {
            MapView(coordinate: landmark.locationCoordinate)
                .ignoresSafeArea(edges: .top)
                .frame(height: 300)

            extractedFunc()
                .offset(y: -130)
                .padding(.bottom, -130)

            VStack(alignment: .leading) {
                Text(landmark.name)
                    .font(.title)

                HStack {
                    Text(landmark.park)
                    Spacer()
                    Text(landmark.state)
                }
                .font(.subheadline)
                .foregroundColor(.secondary)

                Divider()

                Text("About \(landmark.name)")
                    .font(.title2)
                Text(landmark.description)
            }
            .padding()
        }
        .navigationTitle(landmark.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}
```

## Embed

### Embed in VStack, HStack, ZStack

ビューを各種Stackで包みます。

```swift
struct LandmarkDetail: View {
    var landmark: Landmark

    var body: some View {
        ScrollView {
            MapView(coordinate: landmark.locationCoordinate)
                .ignoresSafeArea(edges: .top)
                .frame(height: 300)

            HStack {
                CircleImage(image: landmark.image)
                    .offset(y: -130)
                .padding(.bottom, -130)
            }

            VStack(alignment: .leading) {
                Text(landmark.name)
                    .font(.title)

                HStack {
                    Text(landmark.park)
                    Spacer()
                    Text(landmark.state)
                }
                .font(.subheadline)
                .foregroundColor(.secondary)

                Divider()

                Text("About \(landmark.name)")
                    .font(.title2)
                Text(landmark.description)
            }
            .padding()
        }
        .navigationTitle(landmark.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}
```

### Embed in List
ビューをリスト化します。

```swift
struct LandmarkDetail: View {
    var landmark: Landmark

    var body: some View {
        ScrollView {
            MapView(coordinate: landmark.locationCoordinate)
                .ignoresSafeArea(edges: .top)
                .frame(height: 300)

            List(0 ..< 5) { item in
                CircleImage(image: landmark.image)
                    .offset(y: -130)
                .padding(.bottom, -130)
            }

            VStack(alignment: .leading) {
                Text(landmark.name)
                    .font(.title)

                HStack {
                    Text(landmark.park)
                    Spacer()
                    Text(landmark.state)
                }
                .font(.subheadline)
                .foregroundColor(.secondary)

                Divider()

                Text("About \(landmark.name)")
                    .font(.title2)
                Text(landmark.description)
            }
            .padding()
        }
        .navigationTitle(landmark.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}
```

### Embed…
自由に入力できるEmbedです。
FormやNavigationViewとかに使えると思います。


```swift
struct LandmarkDetail: View {
    var landmark: Landmark

    var body: some View {
        ScrollView {
            MapView(coordinate: landmark.locationCoordinate)
                .ignoresSafeArea(edges: .top)
                .frame(height: 300)

            Form {
                CircleImage(image: landmark.image)
                    .offset(y: -130)
                .padding(.bottom, -130)
            }

            VStack(alignment: .leading) {
                Text(landmark.name)
                    .font(.title)

                HStack {
                    Text(landmark.park)
                    Spacer()
                    Text(landmark.state)
                }
                .font(.subheadline)
                .foregroundColor(.secondary)

                Divider()

                Text("About \(landmark.name)")
                    .font(.title2)
                Text(landmark.description)
            }
            .padding()
        }
        .navigationTitle(landmark.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}
```

### Group
ビューをGroup化します。

```swift
struct LandmarkDetail: View {
    var landmark: Landmark

    var body: some View {
        ScrollView {
            MapView(coordinate: landmark.locationCoordinate)
                .ignoresSafeArea(edges: .top)
                .frame(height: 300)

            Group {
                CircleImage(image: landmark.image)
                    .offset(y: -130)
                .padding(.bottom, -130)
            }

            VStack(alignment: .leading) {
                Text(landmark.name)
                    .font(.title)

                HStack {
                    Text(landmark.park)
                    Spacer()
                    Text(landmark.state)
                }
                .font(.subheadline)
                .foregroundColor(.secondary)

                Divider()

                Text("About \(landmark.name)")
                    .font(.title2)
                Text(landmark.description)
            }
            .padding()
        }
        .navigationTitle(landmark.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}
```


## Repeat
`ForEach`を適応します。

```swift
struct LandmarkDetail: View {
    var landmark: Landmark

    var body: some View {
        ScrollView {
            MapView(coordinate: landmark.locationCoordinate)
                .ignoresSafeArea(edges: .top)
                .frame(height: 300)

            ForEach(0 ..< 5) { item in
                CircleImage(image: landmark.image)
                    .offset(y: -130)
                .padding(.bottom, -130)
            }

            VStack(alignment: .leading) {
                Text(landmark.name)
                    .font(.title)

                HStack {
                    Text(landmark.park)
                    Spacer()
                    Text(landmark.state)
                }
                .font(.subheadline)
                .foregroundColor(.secondary)

                Divider()

                Text("About \(landmark.name)")
                    .font(.title2)
                Text(landmark.description)
            }
            .padding()
        }
        .navigationTitle(landmark.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}
```

### Make Conditional
分岐を設けます。
デフォルトではEmptyViewを用意してくれます。

```swift
struct LandmarkDetail: View {
    var landmark: Landmark

    var body: some View {
        ScrollView {
            MapView(coordinate: landmark.locationCoordinate)
                .ignoresSafeArea(edges: .top)
                .frame(height: 300)

            if true {
                CircleImage(image: landmark.image)
                    .offset(y: -130)
                .padding(.bottom, -130)
            } else {
                EmptyView()
            }

            VStack(alignment: .leading) {
                Text(landmark.name)
                    .font(.title)

                HStack {
                    Text(landmark.park)
                    Spacer()
                    Text(landmark.state)
                }
                .font(.subheadline)
                .foregroundColor(.secondary)

                Divider()

                Text("About \(landmark.name)")
                    .font(.title2)
                Text(landmark.description)
            }
            .padding()
        }
        .navigationTitle(landmark.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}
```