環境
Xcode Version 14.0 (14A309)
SwiftUI TutorialのBuilding Lists and Navigationを元にしています
概要
SwiftUIでコードを扱っているとき、Viewのコードを⌘キー+クリックすることでSwiftUIに対し色々便利なことができるメニューが表示されます。
主にSwiftUIでのみ出るメニューについて説明します。
以下のSwiftUIを操作します。
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にプロパティを与えたりなど追加対応する必要があります。
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は含みません。
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は含みません。
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で包みます。
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
ビューをリスト化します。
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とかに使えると思います。
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化します。
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
を適応します。
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を用意してくれます。
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)
}
}