iOS/App

Scrumdinger 개발 06 : Updating app data

태애니 2025. 4. 6. 18:11
728x90

 

 

이제 서비스 단 구현을 시작하는 듯 하다.

회의를 추가하고, 수정할 수 있는 기능을 구현하는데

 

이 기능에서 주요로 중요한 것을

@State, bindings, and the source of truth 이 세가지이다.

알고 있는 방법이긴 하지만,

이를 어떻게 있을지에 대해 생각을 해봐야겠다.

 

Use the edit view to create a new scrum 

 
ScrumsView.swift 코드에 @State 를 적용한다.
 
DetailEditView를 활용하여 새로 등록할 때도 해당 화면을 활용할 수 있도록 한다.
@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
    }
}
728x90