이번에는 TodoList를 만들어보기로 했씀다!
사실 수업시간에 강사님이 데모로 보여주시긴 했었거든여
근데 들을때는 뭔가.. 느낌이 왔었는데 막상 혼자 하려고 하니까 진짜 와장창 막막하더라구요
분명… 좀 어려울때만 참고할라했는데… 어느순간 복붙하고 있는 저를 만났습니다….
구래서 조금 바꾸거 이것저것 넣고 해봤는데… 암튼 그렇습니다!
새롭게 학습한 내용
- UserDefaults
- Codable
구현 기능
- sheet 를 이용한 데이터 추가
- sheet 를 이용해서 데이터 수정 기능
- swipe 를 이용해서 데이터 삭제
- 끝…?
학습한 내용
Error Handling
앱을 아무리 잘 설계했다 해도 개발자가 통제할 수 없는 상황은 언제나 발생할 수 있습니다.
그럴때를 대비해서 필요한게 이 에러핸들링인데 구체적으로 발생한 에러를 확인할 수 있도록 사용할 수 있습니다~
발생한 에러를 알려주면 그에 맞춰서 적절하게 대응할 수 있겠죠?
에러 처리
- 원하는 결과가 나오지 않을 경우 → 에러를 발생(throwing)
- throwing한 에러를 잡아서 처리
에러를 던질 때는 에러 타입을 이용하며 Error 프로토콜을 따르는 모든 값이 가능함
let connectionOK = true
let connectionSpeed = 30.0
let fileFound = false
enum FileTransfer: Error {
case noConnection
case lowBandwidth
case fileNotFound
}
func transferFile() throws {
guard connectionOK else {
throw FileTransfer.noConnection
}
guard connectionSpeed > 30 else {
throw FileTransfer.noConnection
}
guard fileFound else {
throw FileTransfer.fileNotFound
}
}
위 예에서는 enum 을 통해 발생할 수 있는 에러 타입을 선언했습니다
이후 transferFile() 함수에서 에러를 던질 수 있다고 선언하기 위해 throws 키워드를 사용하고,
발생할 수 있는 에러와 그때 던질 에러들을 throw 키워드로 던지고 있음!@!@!!!
근데 또 요것을 그냥 쓸 수 있는건 아니고 try 를 이용해서 사용한다!
try → 다음에 오는 구문에서 에러가 발생할 수 있다!!!!
func sendFile() -> String {
do {
defer {
print("휴 끝났나?")
print("끝났당~")
try transferFile()
} catch FileTransfer.noConnection {
return "NO Network"
} catch FileTransfer.lowBandwidth {
return "Speed too Low"
} catch FileTransfer.fileNotFound {
return "File not Found"
} catch {
return "정의되지 않은 에러"
}
return "굳 에러엄슴"
}
sendFile() 함수에서 try 를 이용해 transferFile() 를 호출함
에러가 발생한다면 catch하고 아니라면 정상적으로 return이 될 것임!
또한 모든 에러를 정의할 수 있는 것이 아니라면 마지막에 catch를 사용해 정의되지 않은 에러에 대한 처리도 맡김
defer 은 무엇을 리턴하든 함수가 반환하기 직전에 수행되는 것을 정하는 영역임!!!!!!!!!!
User Defaults
UserDefaults 특징
- 일종의 데이터베이스로 가장 기본이 되며, 큰 데이터보다는 간단 데이터 저장에 적합함
- 앱을 삭제하면 데이터도 삭제됨 → 영구 저장에 부적합
- 데이터가 따로 암호화되지 않기 때문에 보안이 중요한 데이터는 사용 금지!
- key-value 쌍으로 데이터를 저장
- 여기서 key는 String이지만 value는 모든 객체를 담을 수 있음
Codable
이것도 사실 데이터를 저장하려고 오늘 배웠는데
입력 데이터 → json형태로 인코딩 → userdefaults에 저장, 뭐 수정, 삭제 등
이 순서로 사용했던거 같음 (확실하진 않음 )
그래서 복습 겸 사용했던 Codable에 대해서 알아봤습니당
🍏 typealias Codable = Decodable & Encodable
- Codable은 Encodable과 Decodable이 합쳐진 것!!!
- Encodable - data를 Encoder에서 변환해주려는 프로토콜로 바꿔주는 것
- Decodable - data를 원하는 모델로 Decode 해주는 것
예시로 json을 들어보겠습니당
Encodable은 모델을 json으로 인코드
Decodable은 json을 내가 원하는 모델로 디코드
- 이러한 Codable은 프로토콜이라 struct, enum, class 전부 채택이 가능
코드설명
Model
모델에는 id, title, date, content 등의 변수를 선언했습니다
Identifiable 을 따르기 위해 UUID()를 이용해서 id를 만들어 주고,
todolist의 아이템을 포스트잇(?)마냥 색상을 넣어주려고 color를 이용하려 했었습니당
근데 데이터를 인코딩, 디코딩 하려면 Codable 을 따라야 하는데, Color 타입은 이걸 만족하지 않더라구여
그래서 이거 컬러를 연산 프로퍼티로 만들고 인덱스를 이용해서 컬러값을 바꿀 수 있도록 하게 했습니다!
dateString을 이용해서 날짜를 계산하게 했습니다!
import Foundation
import SwiftUI
struct Model: Identifiable, Codable {
var id: UUID = UUID()
var title: String
var date: Date = Date()
var content: String
//인코딩, 디코딩을 위해서 Codable을 따라야 하는데 Color타입은 안됨
var colorIndex: Int = 3
var color: Color {
get {
switch colorIndex {
case 0:
return .cyan
case 1:
return .purple
case 2:
return .blue
case 3:
return .yellow
case 4:
return .brown
default:
return .white
}
}
set {
switch newValue {
case .cyan:
colorIndex = 0
case .purple:
colorIndex = 1
case .blue:
colorIndex = 2
case .yellow:
colorIndex = 3
case .brown:
colorIndex = 4
default:
colorIndex = 5
}
}
}
var dateString: String {
let dateFormatter: DateFormatter = DateFormatter()
let calendar = Calendar.current // 현재 달력을 가져옴
let components = calendar.dateComponents([.day], from: date, to: Date()) //일수 계산
dateFormatter.dateFormat = "MM-dd EEEE HH:mm"
if let days = components.day {
if days == 0 {
return "오늘"
} else if days == 1 {
return "어제"
} else {
return dateFormatter.string(from: date)
}
}
return ""
}
}
ModelStore
여기서 이제 model을 정의하고 인코딩, 디코딩 및 UserDefaults에 데이터를 저장합니다.
사실 UserDefaults는 앱의 설정 등과 같은 간단한 내용을 저장하는데 쓴다고 하더라구여
여기서는 간단한 예시이기 때문에 학습의 개념으로 사용했고, 추후에 Firebase등으로 바꿔보겠습니다.
많은 뷰에서 데이터의 변화를 감지하고, 뷰를 다시 그려야 하기 때문에 ObservableObject를 채택합니다
models를 만들어서 @Published 프로퍼티 래퍼로 받아 해당 프로퍼티의 변화를 감지합ㄴ디ㅏㅇ
saveTodoList()
models의 데이터를 JSON형식으로 만들어 UserDefaults에 저장하기 위해 함수를 만듦.
인코딩 시 에러가 발생할 수 있기 때문에 do catch try 를 이용해서 에러 핸들링합니다!
여기서는 models를 JSON형태로 인코딩하여 유저디폴트에 저장하기로 했음!
따라서 JSONEncoder타입의 encoder를 만든 후 Data타입에 저장했음!
models라는 키로 인코딩한 데이터를 유저디폴트에 저장한것임!
func saveTodoList() {
do {
let encoder: JSONEncoder = JSONEncoder() // JSONEncoder 생성
// encoding 시도
let data: Data = try encoder.encode(models)
// UserDefaults에 저장!
UserDefaults.standard.set(data, forKey: "models")
} catch {
print("제이쓴형식으로 만들긴 했는데 UserDefaults 저장은 실패!")
}
}
fetchTodoList()
UserDefaults에 저장된 값을 불러오기 위해 만든 함수입니다.
값을 불러온 후 JSONDecoder를 이용해 디코딩을 하여 models에 넣습니다!
위 함수와 마찬가지로 에러 핸들링을 진행하며, models라는 키로 유저 디폴트에 저장된 데이터를 불러온 후
디코딩,
func fetchTodoList() {
do {
if let data = UserDefaults.standard.object(forKey: "models") as? Data {
let decoder: JSONDecoder = JSONDecoder()
models = try decoder.decode([Model].self, from: data)
}
} catch {
print("UserDefaults로부터 가져오기 실패!")
}
}
+) 우리 스터디 대장님이 추가로 알려준게 있는데
디코딩*(여기서는 JSON→ models)* 할 때 들어가는 타입은 Decodable을 채택한 타입 자체가 들어가야 한다는 거임!!
내 경우에는 Model이 그 타입이고 위에서 배열로 생성했기 때문에 [Model].self 가 되는 것!!!
TodoListView
- @ObservedObject 로 modelStore를 선언하여 데이터 변화에 따라 뷰를 다시 그릴 수 있도록 합니다.
- List 내부 버튼에 .swipeActions 를 이용하여 버튼 슬라이드를 통해 항목을 삭제할 수 있도록 했습니당
- 또한 뷰 제일 하단에 .onAppear 를 사용해서 유저디폴트에 저장된 데이터를 뷰가 시작될 때 가져올 수 있도록 해보았습니다!~
Button {
self.model = model
isShowingDetail = true
} label: {
ItemView( model: model)
}
.swipeActions {
// 스와이프 동작 구성
Button(role: .destructive) {
// 스와이프 동작을 통해 실행될 액션
modelStore.removeTodoList(model)
} label: {
Label("삭제", systemImage: "trash")
}
}
- .toolbar 를 이용해 뷰의 우측 상단에 항목을 추가할 수 있는 버튼을 만들었고,
- 버튼으로 isShowingSheet의 Bool 값을 변경하여 AddTodoLIstView .sheet가 올라오도록 합니다
AddTodoListView
- TodoListView에서 sheet를 표시하기위해 사용한 변수를 @Binding 해 옵니다.
- 왜냐면 여기서 그 값을 다시 false로 만들어서 시트를 내려줘야 하걸랑요
DetailView
- 여기서는 제목, 내용, 날짜 모두 표시가 되도록 구현했고, 이 값들을 textField 에 넣어서 수정할 수 있또록 구현해 보았습니다
- 이 전에 LoginView를 만들 때 사용했던 @FocusState를 사용해서 제목 수정이 끝난 후 엔터를 입력하면 내용을 작성하는 필드로 포커스가 옮겨지도록 구현했습니다.
'Project > TeamStudy' 카테고리의 다른 글
SwiftUI를 이용해서 LoginView 만들기 (0) | 2023.06.26 |
---|