🍎 IOS/SwiftUI

[SwiftUI] 운동 기록 어플 만들기 : 캘린더 만들기 ①

콩리또 2022. 5. 10. 23:58
들어가기 앞써

안녕하세요 ios 개자이너를 꿈꾸는 콩리또 입니다.

개발은 처음이라 아무것도 모르지만.. Swift 앱 개발에 던져져서 고분분투 하고 있는 중인데요

아직 허접한 실력이지만 나중을 위해 기록을 남기려고합니다...🥹

보시고 더 나은 방법이나 틀린 내용이 있으면 언제든지 댓글 남겨주세요!! 지적 댓글은 늘 환영합니다!

그럼 시작하도록 하겠습니다😀

 

 

 

SwiftUI로 운동 기록 캘린더 만들기


 

0. 오늘 리뷰할 화면

일단 저는 개발의 "개"자도 모르는 "개린이"이기떄문에 유튜브에 검색해서 나온 동영상을 보고 따라했습니다.

Swift를 잘 아시는 분은 이 유튜브 동영상을 보고 기초틀은 따라하면 될 것 같아요!

근데 이 동영상이 설명이 없는 영상이라... 저는 전혀 이해를 못한 상태에서 따라 쳤는데요...🥹

이제라도 이해해보고자 리뷰를 진행합니다.. 나름 열심히 해석해 봤지만 틀린내용이... 많을 수도 있는점 감안해주세요...

포스팅은 저 같은 Swift 초보자분들이 이해할 수 있도록 저의 눈높이에서 작성될 예정이라 기본적인 내용 많은 점 참고해주세요!

오늘 설명드릴 화면은 아래처럼 기록이 있는 날 캘린더에 이미지가 표시되는 화면입니다!

 

기록이 있는 날은 아이콘으로 표시

 


 

1. View 만들기

일단 Main.ViewCustomDataPicker 2개의 swift 파일을 만들어주세요

> 파일을 나누는 이유는 재사용할 수 있는 코드가 좋은 코드라 기능별로 파일을 쪼개서 제작하는게 좋다고 하더라고요! 

  • Main.View : 캘린더(CustomDataPicker)와 AddBtn이 들어갈 예정
  • CustomDataPicker : 캘린더

메인화면은 MainView와 CustomDataPicker로 나뉜다.

 


 

2. 현재 날짜 가져오기

Main.View현재 날짜를 가져오는 변수를 선언하고 CustomDataPicker를 불러와주세요.

  1. Date() : 현재 날짜를 불러올 수 있음. 이때 날짜를 "2022-05-07 00:00:00 +0000" 이런 형식으로 가져오게 되는데, 추후에 DateFormatter()를 사용하면 원하는 형식에 맞게 날짜를 변경할 수 있음
  2. body에 Vstack를 만들어 CustomDataPicker를 불러옴 
struct MainView: View {
	@State var currentData: Date = Date()
	//Date() = 현재 날짜를 불러옴, 이떄 "2022-05-07 00:00:00 +0000" 이런 형식으로 날짜를 가져옴

 var body: some View {
     VStack {
        CustomDataPicker(currentDate: $currentData)
        //CustomDataPicker에 currentDate를 바인딩 할 것이기 때문에 $로 가져옴
		}
	}
}

 

CustomDataPicker : MainView에서 선언한 currentDate를 바인딩해서 가져옴

 @Binding var currentDate: Date
 // MainView에서 선언한 변수를 바인딩해옴
 // 바인딩이란? : 변수를 저장해서 가져 오는 것, View와 View끼리의 데이터 연동이 가능!
 //            A뷰에서 선언한 변수를 바인딩한 B뷰에서 바꿀 경우, A뷰에서도 해당 변수가 변경됨!

 


 

3. 현재 월 날짜 가져오기

extension을 사용해 Date 타입에 각 월에 속한 날짜를 가져오는 함수를 추가할거에요

  1. CustomDataPicker 의 import 아래에(struct 밖) extension을 선언해주세요

extension : 클래스, 구조체, 열거형 혹은 프로토콜 타입에 기능 추가 가능 ( 관련문서(한국어) 보기 )

=> 쉽게 말해, extension을 통해 내가 추가하고 싶은 기능을 넣어 확장(커스텀) 할 때 사용하는 코드라고 생각해주세요!

참고로 extension에는 연산 프로퍼티만 사용 가능! (저장 프로퍼티는 사용불가)

import SwiftUI

extension Date {
//extension + 확장할 타입
}

struct CustomDataPicker: View { ... }

   

2. 선언한 extension에 getAllDates() 라는 각 월의 날짜를 가져오는 함수를 작성했습니다. 

import SwiftUI

extension Date {
    
    func getAllDates() -> [Date] {
	//getAllDates = 날짜 타입의 배열로 가져올것
        
        let calendar = Calendar.current
        
        let startDate = calendar.date(from: Calendar.current.dateComponents([.year, .month], from: self))!
        //startDate = 캘린더의 년,월 정보를 가져오고 각 월에 해당하는 날짜를 가져오기
        
        let range = calendar.range(of: .day, in: .month, for: startDate)!
        //range 사용해 startDate의 월에서 해당 일자를 구하고 벗어나는 날은 nil처리를 해라..?
        
        return range.compactMap { day -> Date in
            return calendar.date(byAdding: .day, value: day - 1, to: startDate)!
        }
        //해석 : startDate의 모든 일자에 -1을 한 값을 리턴해서 nil값이 없는 배열로 만들어라
        //compactMap 리턴의 Value: day에 -1해주는거 startDate랑 연관 있는것같은데.. 잘모르겠네욤..ㅎㅎ
    }
}
//getAllDates() = 각 월마다의 해당 일자를 가져오고 각 월의 Date 배열을 만들어라

 

음.... 저는 이렇게 해석을 햇었는데요...

열심히 해석해봤지만 틀린 부분이 있을 수 있습니다... 혹시 있다면 댓글 부탁드려요!!🥹

아무튼 위 코드에서 작성한 메소드들을 설명해드리겠습니다!

 

 

★Calendar : 날짜 계산 비교 기능을 제공하는  struct

더보기

Calendar.current : 현재 사용하고 있는 달력을 가져옴 (그레고리안 달력)

let calendar = Calendar.current
//Calendar.current는 그레고리안 달력을 의미

calendar.Identifier를 사용하면 다른 형식을 달력도 불러올 수 있어요! ( 관련문서 )

let calendar = Calendar(identifier: .buddhist) 
// 불교달력

 

★.date( from: ) : Calendar 타입에서 사용

더보기

date(from: )은 파라미터로 DateComponents 타입을 전달하면 지정한 Components를 토대로 Date?를 리턴해줍니다. 

? = 옵셔널, 값이 있을수도 nil일 수도 있다는 뜻이에요!

※ DateComponents 관련내용은 옆의 링크에서 확인하실 수 있어요! (관련문서)

func date(from components: DateComponents) -> Date?

 

date( from: ) 사용 예제

 저 말만 보면 이해가 되시나요..? 저는 저렇게 글만 보면 이해가 안되기 때문에... 

간단한 예제로 예시를 보여드리겠습니다! 설명은 주석을 참고해주세요!

struct  calendarTest: View{
    
    let myDateComponents = DateComponents(year: 2022, month: 12, day: 12)
    // DateComponents를 이용해 원하는 날짜를 만들 수 있습니다.
    
    let calendar = Calendar.current
    // 기준이 되는 달력이 있어야하기 떄문에 claendar도 선언해주세요!
    
    let myDate: Date
   // myDateComponents 날짜가 들어갈 변수를 선언
    
    init(){
        myDate = calendar.date(from: myDateComponents) ?? Date()
        //date(from:)에는 DateComponents 형식을 파라미터로 받기때문에
        //위에서 선언해준 myDateComponents을 전달해줄 수 있었습니다!
        //근데 swift가 안전성을 추구하기 때문에, 값이 없을떄는 현재 날짜를 전달해주는 코드를 뒤에 써줘야해요!
        // ?? : 값이 없을떄(nil 일때) 오른쪽을 실행한다.
    }
    
    var body: some View{
        Text("\(myDate)")
        //myDateComponents에 넣은 날짜(2022.12.12)를 Date 타입으로 불러와줌
    }
}
결과 화면

 

아하!  date(from: )는 DateComponents를 넣으면

해당 날짜를 Date 타입으로 만들어주는 메소드인 것을 확인할 수 있습니다!😎

 

 

★.range( of:  in:  for:  ) : Calendar 타입에서 사용

더보기

range(of: in: for: )는 캘린더에 값을 입력하면 Range<int>? 로 범위를 구해주는 함수입니다! 

만약 값이 논리적으로 크지않거나, 구성요소의 조합이 의미가 없는 경우 nil을 반환합니다! (그래서 반환값 뒤에 ?가 붙음) 

func range(of: Calendar.Component, in : Calendar.Component, for: Date) -> Range<Int>?
//of : 캘린더의 작은 범위 (예 : day) 
//in : of보다 큰 캘린더 범위 (예 : month)
//for : 범위를 구하고 싶은 시간 (Date타입이 들어가야함)

 

 

자 일단 여기에서 SwiftUI로 캘린더 만들기 ①편을 마무리 하도록 하겠습니다..

게으른 주제에 완벽을 추구하다보니 글쓰는 시간이 길어져 힘드네요..

보시고 문제 있으신 점은 댓글로 피드백 부탁드립니다!

SwiftUI로 캘린더 만들기 ②편도 기대해주세요~ 만관부><😎