728x90
Managing data flow between views
Source of truth
Swift property wrappers @State, @Binding
The system establishes dependencies between the data in @State and the child view that contains the @Binding.
-> the system automatically updates the relevant views
Properties that you label @State are mutable, and the system observes these properties for changes in the view where you define them.
@State 로 지정 되면 속성이 변경되고, 이를 자동으로 감지한다.
Creating the edit view
Add an edit view for scrum details
import Foundation
import ThemeKit
struct DailyScrum: Identifiable {
let id: UUID
var title: String
var attendees: [Attendee]
var lengthInMinutes: Int
// slider 조절을 위해 계산할 수 있는 property 를 설정한다
var lengthInMinutesAsDouble: Double {
// lengthInMinutes
get{
Double(lengthInMinutes)
}
set {
lengthInMinutes = Int(newValue)
}
}
var theme: Theme
init(id: UUID = UUID(), title: String, attendees: [String], lengthInMinutes: Int, theme: Theme) {
self.id = id
self.title = title
self.attendees = attendees.map { Attendee(name: $0) }
self.lengthInMinutes = lengthInMinutes
self.theme = theme
}
}
extension DailyScrum {
struct Attendee: Identifiable {
let id: UUID
var name: String
//Add the required id property and a variable for name.
init(id: UUID = UUID(), name: String) {
self.id = id
self.name = name
}
}
// 이런 식으로 빈 값을 설정해둘 수 있구나. 새롭게 배움**
static var emptyScrum: DailyScrum {
DailyScrum(title: "", attendees: [], lengthInMinutes: 5, theme: .poppy)
}
}
extension 을 잘 활용할 줄 몰랐는데
여러 방법으로 활용할 수 있을 것 같아서 많이 배운 것 같다.
import SwiftUI
struct DetailEditView: View {
@State private var scrum = DailyScrum.emptyScrum
var body: some View {
Form {
Section(header: Text("Meeting Info")) {
TextField("Title", text: $scrum.title)
/**
TextField takes a binding to a String. You can use the $ syntax to create a binding to scrum.title. The current view manages the state of the data property.
*/
HStack {
Slider(value: $scrum.lengthInMinutesAsDouble, in: 5...30, step: 1) {
Text("Length")
//the slider and the label stay in sync.
}
Spacer()
Text("\(scrum.lengthInMinutes) minutes")
}
}
}
}
}
#Preview {
DetailEditView()
}
Display attendees in the edit view
HStack {
TextField("새로운 참석자", text: $attendeeName)
Button(action: {
withAnimation {
//setting the value to the empty string also clears the contents of the text field.
let attendee = DailyScrum.Attendee(name: attendeeName)
scrum.attendees.append(attendee)
// binding to attendeeName, setting the value to the empty string also clears the contents of the text field
// animation block 안에서 처리!
attendeeName = ""
}
}) {
Image(systemName: "plus.circle.fill")
}
.disabled(attendeeName.isEmpty)
}
Add accessibility modifiers & Present the edit view
// Form 코드 수정
Section(header: Text("Meeting Info")) {
TextField("회의명", text: $scrum.title)
/**
TextField takes a binding to a String. You can use the $ syntax to create a binding to scrum.title. The current view manages the state of the data property.
*/
HStack {
Slider(value: $scrum.lengthInMinutesAsDouble, in: 5...30, step: 1) {
Text("Length")
//the slider and the label stay in sync.
}
.accessibilityValue("\(scrum.lengthInMinutes) minutes") // 접근성
Spacer()
Text("\(scrum.lengthInMinutes) minutes")
.accessibilityValue("\(scrum.lengthInMinutes) minutes")
.accessibilityHidden(true) //중복이므로 hidden
}
}
import SwiftUI
struct DetailView: View {
let scrum: DailyScrum
@State private var isPresentingEditView = false //add a Boolean @State property named isPresentingEditView.
var body: some View {
List {
Section(header: Text("Meeting Info")) {
// Label("Start Meeting", systemImage: "timer")
// .font(.headline)
// .foregroundColor(.accentColor)
// 3depth
NavigationLink(destination: MeetingView()) {
Label("Start Meeting", systemImage: "timer")
.font(.headline)
.foregroundColor(.accentColor)
}
HStack {
Label("Length", systemImage: "clock")
Spacer()
Text("\(scrum.lengthInMinutes) minutes")
}
.accessibilityElement(children: .combine)
/**
VoiceOver 사용 시 설정
.accessibilityElement
.accessibilityLabel("내 맘대로 정의") // 사용자가 정의한 라벨 적용
SwiftUI에서 접근성(VoiceOver 등)이 특정 UI 요소를 어떻게 인식할지 정의
.ignore : 접근성 요소로 인식하지 않음
.combine : 하나의 요소로 읽음
.contain : 개별로 읽음
*/
HStack {
Label("Theme", systemImage: "paintpalette")
Spacer()
Text(scrum.theme.name)
.padding(4)
.foregroundColor(scrum.theme.accentColor)
.background(scrum.theme.mainColor)
.cornerRadius(4)
}
.accessibilityElement(children: .combine)
}
// 참석자
Section(header: Text("Attendees")) {
ForEach(scrum.attendees) { attendee in
Label(attendee.name, systemImage: "person")
}
}
}
.navigationTitle(scrum.title)
.toolbar {
Button("수정") {
isPresentingEditView = true
}
}
.sheet(isPresented: $isPresentingEditView) {
NavigationStack{
DetailEditView() // 3depth
// sheet 에 보여질 내용들을 아래에 구성한다
.navigationTitle(scrum.title)
.toolbar{
ToolbarItem(placement: .cancellationAction) {
Button("취소") {
isPresentingEditView = false
}
}
ToolbarItem(placement: .confirmationAction) {
Button("저장") {
isPresentingEditView = false
}
}
}
}
}
}
}
#Preview {
//Wrap DetailView in a NavigationStack to preview navigation elements on the canvas.
NavigationStack {
DetailView(scrum: DailyScrum.sampleData[0])
}
}
Button(action: { isPresented = true }) { Text("Edit")
}.sheet(isPresented: $isPresented) {
EditView()
}
//The sheet modifier takes a binding to a Bool and a view builder.
ForEach(names, id: \.self) { name in
Text(name)
}
.onDelete { indices in
names.remove(atOffsets: indices)
}
The method remove(atOffsets:) uses the IndexSet to remove elements from the array.
indices
onDelete 클로저의 매개변수로 전달되는 값.
삭제할 항목의 인덱스들을 담고 있는 IndexSet 타입의 값이다.
사용자가 삭제한 리스트 항목들의 위치 정보(인덱스 번호)를 포함한다.
728x90
'iOS > App' 카테고리의 다른 글
Scrumdinger 개발 05 - Managing state and life cycle (5) | 2025.04.05 |
---|---|
Scrumdinger 개발 04 - Passing data with bindings (0) | 2025.04.03 |
Scrumdinger 개발 02 - Creating a navigation hierarchy (0) | 2025.04.01 |
Scrumdinger 개발 01 - Displaying data in a list (0) | 2025.03.31 |
[Challenge 1] 나의 새로운 CBL 챌린지 (0) | 2025.03.24 |