Develop Apps for iOSをやってみる 〜その5〜
はじめに
DX推進室の野上です。
前回 の続きです
どこから?
今回はここから続けます。
最初に
今回も用意されたプロジェクトファイルがあります。
が、いつものように無視します(ホントはダメ)
編集用のViewを作成する
データモデルを更新する
編集用のViewを作成する前に、データモデルを更新しましょう。
DailyScrumに編集可能なプロパティを追加します。
extensionを使ってDailyScrumにDataを追加します。
Dataの中の初期値も設定しましょう。
import SwiftUI
struct DailyScrum: Identifiable {
let id: UUID
var title: String
var attendees: [String]
var lengthInMinutes: Int
var color: Color
init(id: UUID = UUID(), title: String, attendees: [String], lengthInMinutes: Int, color: Color) {
self.id = id
self.title = title
self.attendees = attendees
self.lengthInMinutes = lengthInMinutes
self.color = color
}
}
extension DailyScrum {
static var data: [DailyScrum] {
[
DailyScrum(title: "Design", attendees: ["Cathy", "Daisy", "Simon", "Jonathan"], lengthInMinutes: 10, color: Color("Design")),
DailyScrum(title: "App Dev", attendees: ["Katie", "Gray", "Euna", "Luis", "Darla"], lengthInMinutes: 5, color: Color("App Dev")),
DailyScrum(title: "Web Dev", attendees: ["Chella", "Chris", "Christina", "Eden", "Karla", "Lindsey", "Aga", "Chad", "Jenn", "Sarah"], lengthInMinutes: 1, color: Color("Web Dev"))
]
}
}
extension DailyScrum {
struct Data {
var title: String = ""
var attendees: [String] = []
var lengthInMinutes: Double = 5.0
var color: Color = .red
}
var data: Data {
return Data(title: title, attendees: attendees, lengthInMinutes: Double(lengthInMinutes), color: color)
}
}
※colorの初期値はサイトでは .randomになっていますが、エラーになるので.redを入れています。
Scrumの詳細を編集するViewを追加する
EditViewではタイトル、長さ、色を変更できるようにします。
@State を使うことで、変数の内容が変わったら自動的にViewを再描画されるようになります。
まずは、EditView.swiftファイルを作りましょう。
import SwiftUI
struct EditView: View {
var body: some View {
Text("Hello World")
}
}
struct EditView_Previews: PreviewProvider {
static var previews: some View {
EditView()
}
@State をつけてDailyScrum.Data型の変数を定義します。
この変数が変更されると自動的にViewが再描画されます。
続いてEditViewを作成していきます。
import SwiftUI
struct EditView: View {
@State private var scrumData: DailyScrum.Data = DailyScrum.Data()
var body: some View {
List {
Section(header: Text("Meeting Info")) {
TextField("Title", text: $scrumData.title)
HStack {
Slider(value: $scrumData.lengthInMinutes, in: 5...30, step: 1.0) {
Text("Length")
}
Spacer()
Text("\(Int(scrumData.lengthInMinutes)) minutes")
}
ColorPicker("Color", selection: $scrumData.color)
}
}
}
}
struct EditView_Previews: PreviewProvider {
static var previews: some View {
EditView()
}
}
ColorPickerに対して$記号をつけて変数を渡しています。
これは@Stateがついた変数(状態変数)を参照渡しする際に使用します。
つまり、ColorPicker側で色を選択すると、scrumData.colorに値が設定されます。
ここまでで画面は以下のようになります。
EditViewにAttendeesを表示する
先ほど作成したEditViewにAttendeesのセクションを追加します。
配列の要素を削除するには.removeを追加するには.appendを使います。
また、.onDeleteを使用すると要素をフリックした際に削除できるようになります。
import SwiftUI
struct EditView: View {
@State private var scrumData: DailyScrum.Data = DailyScrum.Data()
@State private var newAttendee = ""
var body: some View {
List {
Section(header: Text("Meeting Info")) {
TextField("Title", text: $scrumData.title)
HStack {
Slider(value: $scrumData.lengthInMinutes, in: 5...30, step: 1.0) {
Text("Length")
}
Spacer()
Text("\(Int(scrumData.lengthInMinutes)) minutes")
}
ColorPicker("Color", selection: $scrumData.color)
}
Section(header: Text("Attendees")) {
ForEach(scrumData.attendees, id: \.self) { attendee in
Text(attendee)
}
.onDelete { indices in
scrumData.attendees.remove(atOffsets: indices)
}
HStack {
TextField("New Attendee", text: $newAttendee)
Button(action: {
withAnimation {
scrumData.attendees.append(newAttendee)
newAttendee = ""
}
}) {
Image(systemName: "plus.circle.fill")
}
.disabled(newAttendee.isEmpty)
}
}
}
.listStyle(InsetGroupedListStyle())
}
}
struct EditView_Previews: PreviewProvider {
static var previews: some View {
EditView()
}
}
最後にaccessibilityの設定をします。
import SwiftUI
struct EditView: View {
@State private var scrumData: DailyScrum.Data = DailyScrum.Data()
@State private var newAttendee = ""
var body: some View {
List {
Section(header: Text("Meeting Info")) {
TextField("Title", text: $scrumData.title)
HStack {
Slider(value: $scrumData.lengthInMinutes, in: 5...30, step: 1.0) {
Text("Length")
}
.accessibilityValue(Text("\(Int(scrumData.lengthInMinutes)) minutes"))
Spacer()
Text("\(Int(scrumData.lengthInMinutes)) minutes")
.accessibilityHidden(true)
}
ColorPicker("Color", selection: $scrumData.color)
.accessibilityLabel(Text("Color picker"))
}
Section(header: Text("Attendees")) {
ForEach(scrumData.attendees, id: \.self) { attendee in
Text(attendee)
}
.onDelete { indices in
scrumData.attendees.remove(atOffsets: indices)
}
HStack {
TextField("New Attendee", text: $newAttendee)
Button(action: {
withAnimation {
scrumData.attendees.append(newAttendee)
newAttendee = ""
}
}) {
Image(systemName: "plus.circle.fill")
.accessibilityLabel(Text("Add attendee"))
}
.disabled(newAttendee.isEmpty)
}
}
}
.listStyle(InsetGroupedListStyle())
}
}
struct EditView_Previews: PreviewProvider {
static var previews: some View {
EditView()
}
}
ここまでで画面は以下のようになります。
EditViewを表示する
DetailViewからEditViewを呼び出せるようにします。
DetailViewの右上にEditというボタンを用意して、押下されたらEditViewをモーダルで表示するようにします。
戻ってこれるようにNavigationViewで階層構造であることを設定します。
import SwiftUI
struct DetailView: View {
let scrum: DailyScrum
@State private var isPresented = false
var body: some View {
List {
Section(header: Text("Meeting Info")) {
NavigationLink(
destination: MeetingView()) {
Label("Start Meeting", systemImage: "timer")
.font(.headline)
.foregroundColor(.accentColor)
.accessibilityLabel(Text("start meeting"))
}
HStack {
Label("Length", systemImage: "clock")
.accessibilityLabel(Text("meeting length"))
Spacer()
Text("\(scrum.lengthInMinutes) minutes")
}
HStack {
Label("Color", systemImage: "paintpalette")
Spacer()
Image(systemName: "checkmark.circle.fill")
.foregroundColor(scrum.color)
}
.accessibilityElement(children: .ignore)
}
Section(header: Text("Attendees")) {
ForEach(scrum.attendees, id: \.self) { attendee in
Label(attendee, systemImage: "person")
.accessibilityLabel(Text("person"))
.accessibilityValue(Text(attendee))
}
}
}
.listStyle(InsetGroupedListStyle())
.navigationBarItems(trailing: Button("Edit") {
isPresented = true
})
.navigationTitle(scrum.title)
.fullScreenCover(isPresented: $isPresented) {
NavigationView {
EditView()
.navigationTitle(scrum.title)
.navigationBarItems(leading: Button("Cancel") {
isPresented = false
}, trailing: Button("Done") {
isPresented = false
})
}
}
}
}
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
DetailView(scrum: DailyScrum.data[0])
}
}
}
この時点では、選んだScrumのデータはEditViewに渡されません。
アプリをbuildして画面が遷移できることを確認しましょう。
ここまでのまとめ
- 状態変数(@State)は値が更新されると自動的にViewが再描画される
- 状態変数を参照渡しする場合は$記号をつける
- fullScreenCoverでモーダル表示できる
次回はデータバインディングです。