iOS 프로젝트/작심삼일

[iOS] 작심삼일 개발 일지 6 - 대규모 2.0 패치

유정주 2023. 7. 1. 19:09
반응형

서론

지난 포스팅에서 작심삼일을 사용하면서 느낀점과 개선 계획에 대해 다루었습니다.
(작심삼일 개발 일지 5 - 실사용 후기 & 개선 계획)
이중에서 여러 개 목표를 등록할 수 있는 기능을 포함해 대규모 2.0 패치를 진행했습니다.
 
이번 포스팅에서는 변경점을 간단히 안내하겠습니다.
사용하시면서 의견이 있으시다면 댓글로 리뷰 부탁드립니다.
 
 

작심삼일 다운로드 링크

‎작심삼일: 원클릭 목표 달성

‎항상 큰 목표를 잡고 포기하거나 실패하는 일이 많습니다. 작심삼일을 이용해 작심삼일에 도전하세요!

apps.apple.com

 
 

앱 사용 일수 표시

앱을 사용한 일수를 표시하였습니다.
 

상단 왼쪽에 "작심 1일" 문구가 보이시나요?
오늘이 1일차 사용이라 작심 1일로 표시된 것입니다.
이렇게 앱을 사용한 일수를 표시해서 성취감을 느낄 수 있는 내용이 많도록 기능을 추가했습니다.
 
 

목적 리스트 화면

목적 리스트 화면을 추가했습니다.
 

 
목표가 없을 때, 목표가 있을 때, 즐겨찾기한 목표가 있을 때로 나뉩니다.
 
목표가 없을 때는 도전 중인 목표가 없다는 문구가 표시됩니다.
네비게이션바의 + 버튼을 눌러서 새로운 목표를 추가할 수 있습니다.
 
목표를 추가하면 목표 리스트가 표시됩니다.
왼쪽에서는 작심삼일 아이콘을, 가운데에는 목표와 마지막에 달성한 날짜를, 오른쪽에는 달성한 횟수를 보여줍니다.
달성한 적이 없다면 위 스크린샷처럼 "달성 기록 없음"으로 표시됩니다.
 
2.0.0에서는 즐겨찾기 기능이 추가되었습니다.
즐겨찾기한 목록은 첫 번째 섹션에 표시됩니다.
 
 

달성한 목표가 있을 때

달성한 목표가 있을 때는 아래처럼 표시됩니다.
 

오늘 달성한 목표는 흐리게 표시되서 미달성한 목표가 잘 보이게 구현했습니다.
달성한 아이템을 보면 왼쪽 아이콘과 날짜, 오른쪽 횟수가 변경된 것을 볼 수 있습니다.
 
여기서 날짜는 3일까지는 오늘, 1일 전, 2일 전으로 표시되는데 3일 이후부터는 yyyy-MM-dd로 표시됩니다.
"작심삼일"이라는 아이덴티티에 맞춰서 3일을 강조하고 싶었습니다 ㅎㅎ
 
 

아이패드 화면

아이패드에서는 아이템을 2행으로 표시합니다.

1행으로 길게 표시하니까 공간을 낭비하더라고요.
그래서 아이패드는 분기 처리해서 2행으로 표시하도록 구현했습니다.
 
 

새로운 목표 화면

새로운 목표 등록 화면도 약간 변했습니다.
기존에서 크게 변하지는 않았는데요. (과하게 UI를 바뀌는건 의도와 맞지 않다고 생각함)
편의성과 짧은 목표로 강제한 근거를 강조했습니다.
 
 

첫 번째로, 현재 입력한 글자수를 표시해주었습니다.
작심삼일 앱은 짧은 목표 달성을 강제하고 있어서 최대 목표 글자수를 15자로 설정했습니다.
그래서 지금까지는 입력이 안 되면 "왜 더 입력이 안 되지?"라고 의문이 생길 수 있었는데요.
이를 방지하기 위해 글자수를 표시해주는 label을 표시했습니다.
 
두 번째로 짧은 목표의 장점을 알려주는 텍스트를 추가했습니다.
짧은 명분에 대한 명분을 추가하고, 사용자의 목표 설정에 도움을 줍니다.
 
이렇게 새로운 목표 화면은 짧은 목표에 대한 설득력을 높이는 방향으로 개선했습니다.
 
 

즐겨찾기 기능 추가

목표 달성 화면에 즐겨 찾기 기능을 추가했습니다.
 

상단 오른쪽 별 모양 버튼을 눌러서 목표를 즐겨찾기할 수 있습니다.
즐겨찾기한 아이템은 목표 리스트 화면에서 우선적으로 표시됩니다.
이 화면의 UI는 크게 개선점이 없지만, 코드는 뜯어고치는 수준으로 달라졌습니다 ㅎㅎ;
 
 

코드 수정 느낀 점

작심삼일 앱은 제가 처음으로 개발한 앱입니다.
 
그래도 당시에는 최선을 다해 코드를 작성했는데...
지금 보니 정말 형편이 없더라고요 ㅠ
특히, 확장성 부문에서 최악이었습니다.
 
예를 들어, 여러 개 목표 등록을 구현할 때 난감했습니다.
기존에는 하나의 목표만 고려하고 코드를 작성해서 Goal 구조체의 프로퍼티를 모두 UserDefaults로 관리했습니다.
이 구조를 여러 개의 목표를 등록할 수 있게끔 CoreData를 도입하고, Goal 구조체를 간략화했습니다.
 
아래는 기존 코드와 변경한 코드입니다.
한 번 비교해 보세요 ㅎㅎ
 

기존 코드

final class Goal {
    static let shared: Goal = Goal()
    
    var goal: String? {
        didSet {
            UserDefaults.standard.set(self.goal, forKey: "goal")
            UserDefaults.standard.synchronize()
        }
    }
    
    var day: Int = 0 {
        didSet {
            UserDefaults.standard.set(self.day, forKey: "day")
            UserDefaults.standard.synchronize()
        }
    }
    
    var destination: String {
        "작심 \(day)일"
    }
    
    var clickDate: Date? {
        didSet {
            UserDefaults.standard.set(self.clickDate, forKey: "clickDate")
            UserDefaults.standard.synchronize()
            
            print("Set Date: \(UserDefaults.standard.object(forKey: "clickDate") ?? "nil")")
        }
    }
    
    var isAlert: Bool {
        get {
            UserDefaults.standard.bool( forKey: "isAlert")
        }
        set (newValue) {
            UserDefaults.standard.set(newValue, forKey: "isAlert")
            UserDefaults.standard.synchronize()
        }
    }
    
    var isDone: Bool {
        get {
            guard let clickDate = UserDefaults.standard.object(forKey: "clickDate") as? Date else {
                return false
            }
            
            return Calendar.current.isDateInToday(clickDate)
        }
    }
}

읽기도 힘들고 모델의 역할이 너무 많습니다.
 
 

개선 코드

struct Goal: Hashable {
    var id: UUID = UUID()
    
    var goal: String //목표
    var count: Int = 0 //달성 횟수
    var createdAt: Date //생성 날짜
    var lastCompletedDate: Date? //마지막 완료 날짜
    var isBookmarked: Bool = false //즐겨찾기
}

extension Goal {
    //오늘 완료 했는지
    var isCompleted: Bool {
        guard let lastCompletedDate = lastCompletedDate else {
            return false
        }
        
        return Calendar.current.isDateInToday(lastCompletedDate)
    }
    
    var displayCount: String {
        return "\(count)일"
    }
    
    var displayLastCompleteDate: String {
        guard let lastCompletedDate = lastCompletedDate else {
            return "달성 기록 없음"
        }
        
        let dateFormatter: DateFormatter = .init()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        
        guard let distanceDay = Calendar.current.dateComponents([.day], from: lastCompletedDate, to: Date()).day,
                  distanceDay <= 3 else {
            return dateFormatter.string(from: lastCompletedDate)
        }
        
        if distanceDay == 0 {
            return "오늘"
        } else {
            return "\(distanceDay)일 전"
        }
    }
}

모델 본연의 역할에만 집중하도록 개선했고,
가독성을 높이기 위해 확장한 계산 프로퍼티는 extension에 정의했습니다.
 
 

마무리

작심삼일 앱 대규모 2.0 패치를 진행하면서 옛날의 저와 비교했을 때 많이 성장했구나 느꼈습니다.
(현직자인 분들이 봤을 때는 많이 부족하겠지만요 ㅠㅠ)
리팩토링을 진행하면서 확장성의 중요성도 느낄 수 있었네요.
 
하고 싶은게 많은데 일단 취업을 가장 하고 싶네요 ㅋㅋ
저도, 토이 앱도 포기하지 않고 계속 발전하겠습니다.
 
감사합니다.
 


아직은 초보 개발자입니다.
더 효율적인 코드 훈수 환영합니다!
공감 댓글 부탁드립니다.

 
 
 

반응형