🧐 문제 상황
코드의 불필요한 중복, 로직의 분리 필요
기존의 RecordView만 보더라도 총 240줄의 긴 파일이었고, 당장 뷰와 로직의 분리가 되지 않아 유지보수, 가독성 부분에서 안좋은 부분이 많았습니다. 또한 필터링 부분이나 불필요한 print, 중복되는 언래핑등으로 코드의 개선이 필요했습니다. 또한 이러한 중복적인 계산로직으로 카드가 나타나는 UI를 표시하는 부분이 안보이기도 하는 오류도 발생했다.
RecordView만 봐도 알 수있듯이 시간에 쫓겨서 개발한 코드이기도 하고 이전에 작성한 코드이기도 하다보니 개선사항이 많이 보였다. 😎
// 불필요한 중복 계산, 로직과 뷰의 분리 문제
struct RecordView: View {
// 너무 많은 속성 변수들
@AppStorage("authState") var authState: AuthState = .logout
@FetchRequest (entity: CoreRunningRecord.entity(), sortDescriptors: []) var runningRecord: FetchedResults<CoreRunningRecord>
@State private var selectedIndex: Int = 0
@State private var scrollOffset: CGFloat = 0
@State private var filteredRecords: [CoreRunningRecord] = []
@State private var gpsArtRecords: [CoreRunningRecord] = []
@State private var freeRecords: [CoreRunningRecord] = []
@State private var selectedSortOption: SortOption = .latest
@State private var isDeleteData = false
@State private var navigationTitle = "모든 아트"
var body: some View {
ZStack(alignment: .top) {
// ... 중략
if authState == .login {
ScrollView {
RecordHeader(scrollOffset: scrollOffset)
if filteredRecords.isEmpty {
// ... 중략
} else {
LazyVStack(alignment: .leading) {
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 16) {
ForEach(filteredRecords.prefix(5), id: \.id) { record in
if let courseName = record.courseData?.courseName,
let coursePaths = record.courseData?.coursePaths,
let startDate = record.healthData?.startDate,
let score = record.courseData?.score {
let data = pathToCoordinate(coursePaths)
let cardType = getCardType(forScore: score)
NavigationLink {
RecordDetailView(isDeleteData: $isDeleteData, record: record, cardType: cardType)
} label: {
RecordCardView(size: .carousel, type: cardType, name: courseName, date: formatDate(startDate), coordinates: data!)
}
.padding(.bottom, 8)
}
}
}
VStack(alignment: .leading) {
HStack {
// ... 중략
NavigationLink {
RecordGridView(title: "GPS 아트", records: gpsArtRecords)
} label: { ... }
}
LazyHStack(spacing: 16) {
ForEach(gpsArtRecords.prefix(3), id: \.id) { record in
if let courseName = record.courseData?.courseName,
let coursePaths = record.courseData?.coursePaths,
let startDate = record.healthData?.startDate,
let score = record.courseData?.score {
let data = pathToCoordinate(coursePaths)
let cardType = getCardType(forScore: score)
NavigationLink {
RecordDetailView(isDeleteData: $isDeleteData, record: record, cardType: cardType)
} label: {
RecordCardView(size: .list, type: cardType, name: courseName, date: formatDate(startDate), coordinates: data!)
}
}
}
}
}
VStack(alignment: .leading) {
HStack {
// ... 중략
NavigationLink {
RecordGridView(title: "자유러닝", records: freeRecords)
} label: { ... }
}
LazyHStack(spacing: 16) {
ForEach(freeRecords.prefix(3), id: \.id) { record in
if let courseName = record.courseData?.courseName,
let coursePaths = record.courseData?.coursePaths,
let startDate = record.healthData?.startDate,
let score = record.courseData?.score {
let data = pathToCoordinate(coursePaths)
let cardType = getCardType(forScore: score)
NavigationLink {
RecordDetailView(isDeleteData: $isDeleteData, record: record, cardType: cardType)
} label: {
RecordCardView(size: .list, type: cardType, name: courseName, date: formatDate(startDate), coordinates: data!) }
}
}
ForEach(0 ..< max(0, 3 - freeRecords.count), id: \.self) { _ in
RecordEmptyCardView(size: .list)
}
}
}
}
}
}
}
// ... 중략
}
// 뷰와 로직의 분리 필요
.onAppear {
filteredRecords = Array(runningRecord)
.sorted { ... }
gpsArtRecords = filteredRecords
.filter { ... }
.sorted { ... }
freeRecords = filteredRecords
.filter { ... }
.sorted { ... }
print("Filtered Records Count: \(filteredRecords.count)")
print("GPS Art Records Count: \(gpsArtRecords.count)")
print("Free Records Count: \(freeRecords.count)")
}
.overlay {
if isDeleteData { ... }
}
}
func getCardType(forScore score: Int32) -> CardType { ... }
func formatDate(_ date: Date) -> String { ... }
func pathToCoordinate(_ paths: NSOrderedSet) -> [CLLocationCoordinate2D]? { ... }
}
💡 해결 과정
RecordViewModel을 만들어서 모든 변수를 옮기고, CoreRunningRecord를 RunningRecord로 바꿔주어 @Published var를 활용하여 뷰에 뿌려주었다.
class RecordViewModel: ObservableObject {
@Published var recentRunningRecords: [RunningRecord] = []
@Published var gpsArtRunningRecords: [RunningRecord] = []
@Published var freeRunningRecords: [RunningRecord] = []
private let userDataModel = UserDataModel()
private let persistenceController = PersistenceController.shared
func loadRunningRecords() {
let fetchRequest: NSFetchRequest<CoreRunningRecord> = CoreRunningRecord.fetchRequest()
do {
let coreRunningRecords = try persistenceController.container.viewContext.fetch(fetchRequest)
let runningRecords = coreRunningRecords.compactMap {
userDataModel.convertToRunningRecord(coreRecord: $0)
}
recentRunningRecords = runningRecords
.sorted(by: { $0.healthData.startDate > $1.healthData.startDate })
gpsArtRunningRecords = runningRecords
.filter { $0.runningType == .gpsArt }
.sorted(by: { $0.courseData.score ?? -1 > $1.courseData.score ?? -1 })
freeRunningRecords = runningRecords
.filter { $0.runningType == .free }
.sorted(by: { $0.healthData.startDate > $1.healthData.startDate })
} catch {
print("코어데이터에서 러닝기록을 가져오는데 실패했습니다: \(error)")
}
}
func deleteCoreRunningRecord(_ id: String) { ... }
func updateCoreRunningRecord(_ id: String, courseName: String) { ... }
func saveShareData(_ runningRecord: RunningRecord) { ... }
func getCardType(for score: Int?) -> CardType { ... }
func sortingOptions(for title: String) -> [SortOption] { ... }
func sortRecords(_ record1: RunningRecord, _ record2: RunningRecord) -> Bool { ... }
}
결과적으로 확실히 뷰가 간결해 졌다. 240줄의 파일이 77줄로 줄었으며, 뷰의 역할도 분리해줬다.
struct RecordView: View {
@AppStorage("authState") var authState: AuthState = .logout
@StateObject private var viewModel = RecordViewModel()
var body: some View {
ZStack(alignment: .top) {
if authState == .login {
ScrollView {
Color.clear.frame(height: 0)
.onScrollViewOffsetChanged { offset in
viewModel.scrollOffset = offset
}
RecordHeaderView(scrollOffset: viewModel.scrollOffset)
if viewModel.recentRunningRecords.isEmpty {
RecordEmptyRunningView()
} else {
LazyVStack(alignment: .leading) {
ScrollView(.horizontal, showsIndicators: false) {
RecordListView(viewModel: viewModel, type: .main)
}
VStack(alignment: .leading) {
RecordListHeaderView(viewModel: viewModel, type: .gpsArt)
RecordListView(viewModel: viewModel, type: .gpsArt)
}
VStack(alignment: .leading) {
RecordListHeaderView(viewModel: viewModel, type: .free)
RecordListView(viewModel: viewModel, type: .free)
}
}
}
}
.overlay(alignment: .top) {
RecordInlineHeaderView(scrollOffset: viewModel.scrollOffset)
}
} else {
RecordLookAroundView()
}
}
.onAppear {
viewModel.loadRunningRecords()
}
.overlay {
if viewModel.isDeleteData {
RunningPopup(text: "기록을 삭제했어요")
.frame(maxHeight: .infinity, alignment: .top)
}
}
}
}
'→ Outline' 카테고리의 다른 글
[Project-Outline] watchOS 1차 리팩토링 (0) | 2024.08.10 |
---|---|
[Project-Outline] Smooth Algorithm 개선 (0) | 2024.08.04 |
[Project-Outline] MapSnapshot 적용 (0) | 2024.08.04 |