はじめに
前回からの続きでSwiftUIのチュートリアル入門になります。
もと記事サイト

Step0
前回作成したナビゲーションの詳細画面が固定になっているので、今回はListでタップされた対象のLandmarkの名前、マップ位置、画像名を詳細画面に渡して個別に表示できるようにします。
Step1
CircleImage.swiftにimageプロパティを追加します。LandmarkDetail.swiftで表示するための画像をListから受け取って保存します。
SwiftUIではよく使うパターンになるようなので覚えておきましょう!
また、body内でImage("turtlerock")を固定で生成しているのでここも書き換えます。
import SwiftUI
struct CircleImage: View {
var image: Image
var body: some View {
image
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 7)
}
}
struct CircleImage_Previews: PreviewProvider {
static var previews: some View {
CircleImage()
}
}Step2
CircleImageにimageプロパティを設定したので、イニシャライザでimageを設定する必要があります。そのためCircleImage_Previewsでエラーが表示されるので、CircleImageのイニシャライザを設定します。ただし、LandmarkDetail.swiftの方でもイニシャライザが必要になり、エラーが表示されますが、ここでは一旦無視します。
import SwiftUI
struct CircleImage: View {
var image: Image
var body: some View {
image
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 7)
}
}
struct CircleImage_Previews: PreviewProvider {
static var previews: some View {
CircleImage(image: Image("turtlerock"))
}
}Step3
MapView.swiftもStep2同様に動的に表示できるように変更していきます。
import SwiftUI
import MapKit
struct MapView: View {
var coordinate: CLLocationCoordinate2D
@State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 34.011_286, longitude: -116.166_868),
span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
)
var body: some View {
Map(coordinateRegion: $region)
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView(coordinate: CLLocationCoordinate2D(latitude: 34.011_286, longitude: -116.166_868))
}
}Step4
setRegionメソッドを追加します。Mapが表示されるタイミングでこのメソッドが呼ばれ、イニシャライザで渡された座標位置(coordinate)を使ってMKCoordinateRegionを生成し、プロパティに代入しています。
import SwiftUI
import MapKit
struct MapView: View {
var coordinate: CLLocationCoordinate2D
@State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 34.011_286, longitude: -116.166_868),
span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
)
var body: some View {
Map(coordinateRegion: $region)
}
private func setRegion(_ coordinate: CLLocationCoordinate2D) {
region = MKCoordinateRegion(
center: coordinate, span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
)
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView(coordinate: CLLocationCoordinate2D(latitude: 34.011_286, longitude: -116.166_868))
}
}Step5
MapにonAppearを追加し、Mapが表示されるタイミングでsetRegionメソッドが呼ばれるようにしています。onAppearはUIKitのviewWillAppearにあたるようです。
struct MapView: View {
var coordinate: CLLocationCoordinate2D
@State private var region = MKCoordinateRegion()
var body: some View {
Map(coordinateRegion: $region)
.onAppear {
setRegion(coordinate)
}
}
private func setRegion(_ coordinate: CLLocationCoordinate2D) {
region = MKCoordinateRegion(
center: coordinate, span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
)
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView(coordinate: CLLocationCoordinate2D(latitude: 34.011_286, longitude: -116.166_868))
}
}Step6
LandmarkDetail.swiftにlandmarkプロパティを追加し、コンテンツを動的に表示できるようにします。 MapViewとCircleImageのイニシャライザにパラメータがないのでエラーが表示されます。
取り合えず気にしないでおきましょう。
import SwiftUI
struct LandmarkDetail: View {
var landmark: Landmark
var body: some View {
VStack {
MapView()
.ignoresSafeArea(edges: .top)
.frame(height: 300)
CircleImage()
.offset(y: -60)
.padding(.bottom, -60)
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
HStack {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
.font(.subheadline)
.foregroundColor(.secondary)
Divider()
Text("About Turtle Rock")
.font(.title2)
Text("Descriptive text goes here.")
}
.padding()
Spacer()
}
}
}
struct LandmarkDetail_Previews: PreviewProvider {
static var previews: some View {
LandmarkDetail(landmark: landmarks[0])
}
}Step7
LandmarkList.swiftのLandmarkDetail生成時に対象のlandmarkを渡すように変更します。
import SwiftUI
struct LandmarkList: View {
var body: some View {
NavigationView {
List(landmarks) { landmark in
NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
.navigationTitle("Landmarks")
}
}
}
struct LandmarkList_Previews: PreviewProvider {
static var previews: some View {
LandmarkList()
}
}Step8
先程エラーが表示されていたLandmarkDetail.swiftに変更を加えていきます。
固定表示になっていた、Map、CircleImage、Textを変更しています。
import SwiftUI
struct LandmarkDetail: View {
var landmark: Landmark
var body: some View {
VStack {
MapView(coordinate: landmark.locationCoordinate)
.ignoresSafeArea(edges: .top)
.frame(height: 300)
CircleImage(image: landmark.image)
.offset(y: -60)
.padding(.bottom, -60)
VStack(alignment: .leading) {
Text(landmark.name)
.font(.title)
HStack {
Text(landmark.park)
.font(.subheadline)
Spacer()
Text(landmark.state)
.font(.subheadline)
}
.font(.subheadline)
.foregroundColor(.secondary)
Divider()
Text("About \(landmark.name)")
.font(.title2)
Text(landmark.description)
}
.padding()
Spacer()
}
}
}
struct LandmarkDetail_Previews: PreviewProvider {
static var previews: some View {
LandmarkDetail(landmark: landmarks[0])
}
}Step9
body直下のVStackをScrollViewに変更します。これで画面よりもコンテンツが大きければスクロールできます。またSpacerは不要なので削除します。
import 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: -60)
.padding(.bottom, -60)
VStack(alignment: .leading) {
Text(landmark.name)
.font(.title)
HStack {
Text(landmark.park)
.font(.subheadline)
Spacer()
Text(landmark.state)
.font(.subheadline)
}
.font(.subheadline)
.foregroundColor(.secondary)
Divider()
Text("About \(landmark.name)")
.font(.title2)
Text(landmark.description)
}
.padding()
}
}
}
struct LandmarkDetail_Previews: PreviewProvider {
static var previews: some View {
LandmarkDetail(landmark: landmarks[0])
}
}Step10
最後に詳細画面にナビゲーションタイトルが表示されるようにします。
import 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: -60)
.padding(.bottom, -60)
VStack(alignment: .leading) {
Text(landmark.name)
.font(.title)
HStack {
Text(landmark.park)
.font(.subheadline)
Spacer()
Text(landmark.state)
.font(.subheadline)
}
.font(.subheadline)
.foregroundColor(.secondary)
Divider()
Text("About \(landmark.name)")
.font(.title2)
Text(landmark.description)
}
.padding()
}
.navigationTitle(landmark.name)
.navigationBarTitleDisplayMode(.inline)
}
}
struct LandmarkDetail_Previews: PreviewProvider {
static var previews: some View {
LandmarkDetail(landmark: landmarks[0])
}
}Step11
LandmarkList.swiftを選択してLive previewを実行します。


まとめ
これでリストで動的にデータを表示する方法がわかりましたね。
今回勉強になった箇所は、動的に表示する際には***_Previewsのイニシャライザに何かしら固定データを設定しておく必要があるところですかね。



コメント