이제 서비스 단 구현을 시작하는 듯 하다.
회의를 추가하고, 수정할 수 있는 기능을 구현하는데
이 기능에서 주요로 중요한 것을
@State, bindings, and the source of truth 이 세가지이다.
알고 있는 방법이긴 하지만,
이를 어떻게 더 잘 쓸 수 있을지에 대해 생각을 해봐야겠다.
Use the edit view to create a new scrum
@State private var isPresentingNewScrumView = false
// 생성뷰를 보여줄 지에 대한 여부
// ~중간 생략~
.toolbar {
//Pass an empty action to the button for now.
Button(action: {
isPresentingNewScrumView = true
}) {
Image(systemName: "plus")
}
.accessibilityLabel("New Scrum")
}
.sheet(isPresented: $isPresentingNewScrumView) {
}
// 이 saveEdits clousure 는 상단의 뷰에서 가져와야한다. 그렇기 때문에 preview 부분도 수정이 필요하다.
let saveEdits: (DailyScrum) -> Void // user가 Done 버튼을 눌렀을 때 저장/수정이 일어나도록 하는 closure
// 생략
#Preview {
@Previewable @State var scrum = DailyScrum.sampleData[0]
DetailEditView(scrum: $scrum, saveEdits: { _ in })
}
DetailEditView 를 사용해서 아래와 같이 NewScrumSheet 를 작성한다.
보면 이 sheet 를 처리했을 때 변경된 값을 DailyScrum 리스트에 바로 append 시켜주고 있으며,
추가 시에는 빈 값 = newScrum 을 넘겨주고 있다.
이렇게 관리하면 따로 따로 관리가 된다.
Sheet 는 Sheet 로써의 역할을 하고
안에 내부의 화면은 DetailEditView 를 이용한다.
import SwiftUI
struct NewScrumSheet: View {
@State private var newScrum = DailyScrum.emptyScrum
@Binding var scrums: [DailyScrum]
var body: some View {
NavigationStack {
DetailEditView(scrum: $newScrum) { dailyScrum in
scrums.append(newScrum)
// 새로운 scrum 을 추가한다
}
}
}
}
#Preview {
NewScrumSheet(scrums: .constant(DailyScrum.sampleData))
}
Challenge
Try refactoring DetailViewto follow a similar pattern, and use a separate structure when presenting the edit sheet.
그렇담 DetailView 에서 수정하는 화면도 리팩토링 해보자.
NavigationStack{
DetailEditView(scrum: $editingScrum, saveEdits: { dailyScrum in
scrum = editingScrum
})
.navigationTitle(scrum.title)
// 3depth
// sheet 에 보여질 내용들을 아래에 구성한다
.navigationTitle(scrum.title)
.toolbar{
ToolbarItem(placement: .cancellationAction) {
Button("취소") {
isPresentingEditView = false
}
}
ToolbarItem(placement: .confirmationAction) {
Button("저장") {
isPresentingEditView = false
}
}
}
}
// 이 코드를 DetailEditView 를 Sheet와 DetailEditView 로 맞춰보겠다
// 이렇게 바꿈
.sheet(isPresented: $isPresentingEditView) {
NavigationStack{
DetailEditView(scrum: $editingScrum, saveEdits: { dailyScrum in
scrum = editingScrum
})
.navigationTitle(scrum.title)
// DetailEditView(scrum: $editingScrum)// 3depth
// // sheet 에 보여질 내용들을 아래에 구성한다
// .navigationTitle(scrum.title)
// .toolbar{
// ToolbarItem(placement: .cancellationAction) {
// Button("취소") {
// isPresentingEditView = false
// }
// }
// ToolbarItem(placement: .confirmationAction) {
// Button("저장") {
// isPresentingEditView = false
// }
// }
// }
}
Add scrum history
회의가 끝난 뒤에 기록할 history 를 저장할 수 있도록 처리해본다.
History Model 을 일단 만들어야한다.
그리고 initializer 까지 추가해준다.
import Foundation
struct History: Identifiable {
let id: UUID
let date: Date
var attendees: [DailyScrum.Attendee]
init(id: UUID = UUID(), date: Date = Date(), attendees: [DailyScrum.Attendee]) {
self.id = id
self.date = date
self.attendees = attendees
}
}
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
// 여기도!!!!! history 추가
var history: [History] = []
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)
}
}
해당 History 가 insert 되는 시점은, MeetingView 가 disappear 되는 시점이다.
.onDisappear {
scrumTimer.stopScrum()
// view 가 사라지면 멈춘다.
// history 를 작성하여 저장한다.
let newHistory = History(attendees: scrum.attendees)
scrum.history.insert(newHistory, at: 0)
}
그리고 startScrum 할 때 타이머를 리셋 후, 스크럼을 시작할 수 있는 로직을 추가해준다.
startScrum() <<<
// scrumTimer 를 reset 시킨 뒤, 시작한다
// attendees 들의 name 을 맵핑하여 리스트로 넘긴다.
scrumTimer.reset(lengthInMinutes: scrum.lengthInMinutes, attendeeNames: scrum.attendees.map { $0.name })
scrumTimer.speakerChangedAction = {
player.seek(to: .zero)
player.play()
}
scrumTimer.startScrum()
// scrumTimer 공부 좀 더 해보자
// 위의 코드를 정리한다.
endScrum() 도 묶어준다.
.onDisappear{
endScrum()
}
private func startScrum() {
// scrumTimer 를 reset 시킨 뒤, 시작한다
// attendees 들의 name 을 맵핑하여 리스트로 넘긴다.
scrumTimer.reset(lengthInMinutes: scrum.lengthInMinutes, attendeeNames: scrum.attendees.map { $0.name })
scrumTimer.speakerChangedAction = {
player.seek(to: .zero)
player.play()
}
// scrumTimer 공부 좀 더 해보자
scrumTimer.startScrum()
}
private func endScrum(){
scrumTimer.stopScrum()
// view 가 사라지면 멈춘다.
// history 를 작성하여 저장한다.
let newHistory = History(attendees: scrum.attendees)
scrum.history.insert(newHistory, at: 0)
}
그리고 나서, DetailView 에 회의 내역 관련 Section 을 하나 추가해주면 끝
// history
Section(header: Text("회의내역")) {
if scrum.history.isEmpty {
Label("회의 내역이 없습니다.", systemImage: "calendar.badge.exclamationmark")
.foregroundColor(.gray)
}
ForEach(scrum.history) { history in
HStack {
Image(systemName: "calendart")
Text(history.date, style: .date) // 로컬라이즈된 날짜 또는 시간을 표시
}
}
}
//source of truth stored !! 알아둬야함
struct MyView:View {
@Binding var cities: [String]
var body:some View {
// view implementation
}
}
'iOS > App' 카테고리의 다른 글
Scrumdinger 개발 08 : Handling errors (0) | 2025.04.08 |
---|---|
Scrumdinger 개발 07 : Persisting data (0) | 2025.04.07 |
Scrumdinger 개발 05 - Managing state and life cycle (0) | 2025.04.05 |
Scrumdinger 개발 04 - Passing data with bindings (0) | 2025.04.03 |
Scrumdinger 개발 03 - Managing data flow between views ~ Creating the edit view (0) | 2025.04.02 |