본문 바로가기

프로젝트

(74)
06. 더 나아가기 더 나아가기 | 난이도 선택하기, 개선하기 강의는 끝났지만 난이도를 선택하는 부분은 빠져있다. enum FastingPlan: String { case beginner = "12:12" case intermediate = "16:8" case advanced = "20:4" var fastingPeriod: Double { switch self { case .beginner: return 12 case .intermediate: return 16 case .advanced: return 20 } } } beginner와 intermediate, advance를 화면에서 직접 선택할 수 있도록 추가로 구현한다. 난이도 선택하기 | 인터페이스 구성하기 현재 앱의 화면은 위와 같고, 난이도를 선택하기 위해 다..
05. 기능 구현 #3 기능 구현 #3 | Upcoming time, Percentage, 실제 시간 반영하기 Upcoming time 타이머의 상태가 notStarted이면 elapsedTime과 remainingTime을 표시하는 것은 부자연스럽다. 선택된 난이도에 따라서 현재시간을 기준으로 다음 식사 시간을 나타내도록 수정한다. // MARK: Timer VStack(spacing: 30) { // MARK: Upcoming Time if fastingManager.fastingState == .notStarted { VStack(spacing: 5) { Text("Upcoming fast") .opacity(0.7) Text("\(fastingManager.fastingPlan.fastingPeriod, specifie..
04. 기능 구현 #2 기능 구현 #2 | Timer 구현 Timer 구현 | fastingTime, feedingTime 계산하기 class FastingManager: ObservableObject { @Published private(set) var fastingState: FastingState = .notStarted @Published private(set) var fastingPlan: FastingPlan = .intermediate @Published private(set) var startTime: Date @Published private(set) var endTime: Date var fastingTime: Double { return fastingPlan.fastingPeriod } var feeding..
03. 기능 구현 #1 기능 구현 #1 | FastingManager FastingManager | FastingState FastingManager는 본격적인 식단 관리 기능의 구현에 해당한다. FastingManager라는 이름의 파일을 새로 생성해 구현한다. import Foundation class FastingManager: ObservableObject { } ObservableObject 프로토콜을 채용한 클래스의 형태로, 해당 해당 클래스에 접근하는 View가 Published로 지정된 속성을 사용할 수 있게 된다. import Foundation enum FastingState { case notStarted case fasting case feeding } class FastingManager: Observa..
02. 인터페이스 디자인 #2 인터페이스 디자인 #2 ContentView ContentView | Header body의 가독성을 높이기 위해 구성되는 View들은 extension으로 구현한다. header는 현재 상태를 문장으로 표시하기 위한 TextView와 이후 Fasting Plan을 변경하는 버튼을 포함한다. struct ContentView: View { var body: some View { VStack { header ProgressRing() } } } extension ContentView { // MARK: Title, Fasting Plan var header: some View { VStack { Text("Let's get started!") .font(.headline) .foregroundColor(C..
01. 인터페이스 디자인 #1 인터페이스 디자인 #1 ProgressRing, Timer ProgressRing ProgressRing은 총 진행상황을 표시할 PlaceHolder와 현재 진행 상황을 표시할 진행링 두 개의 View의 조합이다. 같은 좌표에 겹쳐 표시돼야 하므로 ZStack으로 구현한다. struct ProgressRing: View { @State var progress = 0.0 var body: some View { 우선 진행 상태를 나타낼 변수를 하나 정의한다. var body: some View { ZStack { // MARK: Placholder Ring Circle() .stroke(lineWidth: 20) .foregroundColor(Color(UIColor.systemGray)) .opacity(..
00. 시작하며 이제 막 UIKit에 대한 공부를 마치고 작은 프로젝트를 진행하던 작년에 이야기다. 작은 프로젝트를 몇 개 해 봤지만 여전히 개념들은 어려웠고, 특히나 만들려고 했던 인터벌 타이머는 thread 분리와 함께 완전히 박살났다. 1초 간격으로 실행 돼야 할 코드가 어느 때는 0.5초 만에, 어느 때는 1초가 넘어서 동작하니 제대로 된 기능을 한다고 볼 수 없다. 올해는 SwiftUI를 배웠고, 다시 이전의 프로젝트를 재시동 하기 전에 시계나 타이머에 관한 프로젝트를 몇 개 해 보고자 한다. 이번에 만들어 보는 FastingTimer는 식단 관리 앱이다. 원형의 Indicator를 가지고 진행 상황을 알려주고, 매 초 업데이트 되니 내가 구현하려던 매커니즘과 동일하다고 할 수 있다. 앱 Swift Swift ..
26. 기본 UI 구현하기 #9 기본 UI 구현하기 #9 프로필 수정 버튼 표시하기 ProfileView에는 EditProfile 버튼이 존재한다. 지금은 본인의 Profile인지 상대방의 Profile인지 구분 없이 Edit Profile을 표시하지만, 이 둘을 구분해 Follow 버튼으로 치환해 표시할 수 있도록 구현해 본다. class ProfileViewModel: ObservableObject { @Published var tweets = [Tweet]() @Published var likedTweets = [Tweet]() private let service = TweetService() private let userService = UserService() let user: User init(user: User) { sel..
25. DB와 연결하기 #4 DB와 연결하기 #4 좋아요 한 Tweet 표시하기 ProfileView의 Likes 탭에는 해당 계정이 작성했던 Tweet도, 답변을 달았던 Tweet도 아닌 좋아요를 눌렀던 Tweet을 표시해야 한다. 이전에 구현한 좋아요 기능에서 사용하는 user-likes 하위 collection 활용하면 굉장히 간단하게 구현할 수 있다. 좋아요 한 Tweet 표시하기 | TweetService, fetchLikedTweets fetchTweets(foruid:) func fetchTweets(foruid uid: String, completion: @escaping([Tweet]) -> Void) { Firestore.firestore().collection("tweets").whereField("uid", i..
24. 기능 구현 #10 기능 구현 #10 좋아요 취소(unLike) 기능 추가하기 좋아요 취소(unLike) 기능 추가하기 | TweetService, unLikeTweet 이미 좋아요가 된 글을 취소할 수 있는 기능이 있어야 한다. Tweet에 관련된 기능인 TweetService LikeTweet, uploadTweet, fetchTweets(), fetchTweets(foruid:) 모두 TweetService에 정의돼 있으므로 unLikeTweet도 해당 파일에 구현하면 된다. likeTweet func likeTweet(_ tweet: Tweet, completion: @escaping() -> Void) { guard let uid = Auth.auth().currentUser?.uid else { return } ..