List의 row가 될 뷰를 추출해줌.
그리고 새로운 파일로 옮겨줬다
샘플 데이터를 만들고 ForEach로 표현!
navigationBar아이템들을 추가해줌
leading에는 EditButton, trailing에는 navigationLink를!
이렇게 보니까 navigationLink는 세그웨이랑 비슷하다
그 다음엔 AddView를 구성해줌
첫번째 화면에서 Add를 누르면 나올 뷰
textField 넣고, 버튼 으로 구성
struct ItemModel: Identifiable {
let id: UUID = UUID()
let title: String
let isCompleted: Bool
}
새로운 모델을 만들어줬다
가끔 새로 만들어준 스크립트가 자동완성이 안될 경우에 Product-Clean Build Folder 해주면 됨
ForEach에 [ItemModel] 타입의 데이터가 들어갈 예정이다.
-> ListRow도 수정해줘야겠다
기존엔 스트링이 들어가던 데이터를 ItemModel형태로 받고,
프리뷰에선 가짜 데이터들 임시로 넣어줌 (static으로 선언했음)
그리고 프리뷰가 진짜 사이즈만 나올 수 있게 .previewLayout설정을 해줌
그리고 삼항연산자로 이미지를 교체해준다
뷰모델을 만들어줄 예정
그 전에 간단한 로직들은 구현을 해주자
리스트 뷰에서 .onDelete 메소드를 붙여줌. indexSet을 atOffsets에 전달해서 모델리스트에서도 삭제 가능하게 해줬다
deleteItem으로 메소드를 따로 빼주게 되면 저렇게 축약도 가능해짐
onMove 메소드도 구현해주려고 한다
IndexSet이랑 Int를 받는 메소드를 파면 됨
이제 Edit 버튼을 누르게 되면 수정이 가능해짐!
🤔(근데 지금 롱프레스로도 리스트를 움직이는 게 가능한데 이렇게 움직이면 버그가 발생함 나중에 수정 필요)
지금처럼 delete하고 onMove하는 기능들은 ViewModel에서 관리하면 좋을 거 같다
새로운 ListViewModel.swift 생성!
하나씩 이식해주자
먼저 모델을 옮겨줄 건데 class에선 @State를 쓸 수 없다
@Published를 붙여줘야됨
그리고 getItems라는 append 메소드를 작성해서 기존의 빈배열에서 init될 때 실행되게 해줌
여기에 전의 listView의 삭제, 무브 메소드들 이식하고
이걸 EnvironmentObject로 만들자
TodoListApp파일로 와서 @StateObeject로 listViewModel을 새로 만들어줬다
그럼 이렇게 해주려면 ListViewModel에서 ObservableObject를 채택해야겠죠
그 후에 ListView에 꽂아줘도 되겠지만 다른 하위 뷰에서 언제든 접근 가능하게
.environmentObject로 넘겨줌!!
그리고 ListView에서 @EnvironmentObject로 뷰모델을 선언해주면 끝!
그리고 수정해주면 되는데 여기까지 수정했을 때 프리뷰가 크래쉬남
지금 EnvironmentObject가 MainApp이 실행될 때 추가되고 있어서
프리뷰에도 따로 추가해줘야함
요렇게!
Save 눌렀을 때 텍스트 필드에 있는 내용들 저장할 수 있게 해주자
save 되는 로직은 뷰모델에 넣어주면 되겠죠!
요렇게!
그리고 save를 누르면 자동으로 전의 뷰로 돌아가게해주고 싶다
다시 Environment를 사용해서 dismiss 액션을 만들어줌
그리고 방어로직을 작성해줘야할 거 같다
텍스트필드의 텍스트를 체크하는 메소드를 작성하고 이 값이 참일 때만 로직이 실행되게 해줌
제대로 입력을 안했을 땐 alert을 띄워줘야할 거 같다
두가지 프로퍼티래퍼로 감싸진 프로퍼티 선언하고
.alert(isPresented: ) 메소드를 뷰 어디에서든 넣어주면 됨
근데 이렇게 구현하는 방식이 예전 방식이라
🤔 새로운 방법 찾아보기
ListView로 돌아와서
탭이 될때 원하는 로직인 체크마크가 켜지는 로직을 구현해야함
-> ViewModel에서 만들어주면 되겠죠!
updateItem이라는 메소드를 만들어주는데 현재 파라미터로 ItemModel을 받고 있지만
어떤 item이 선택된 건지 index가 필요함
메소드를 통해 들어온 item과 listViewModel의 items 어레이 중에 id가 같은
이 조건을 첫번째로 만족하는 인덱스를 index라고 표현하거
축약하면 이렇게 표현 가능함
들어온 index를 가지고 새로이 isCompleted를 체크해줌
그런데 이렇게 하면 새로 초기화하면 또 문제가 생김 ItemModel의 id가 매번 새로 생성되게 됨
🤔 id 새로 안생기게 init에서 처리 해줬음 그리고 모델 내부에 updateCompletion이라는 메소드를 하나 생성해줌
이렇게 해준 이유는 안정성을 위해서!!
Struct내의 프로퍼티들을 var로 바꿔서 구현해 줄 수도 있지만 그렇게 사용하면 예기치 못한 에러를 발생 시킬 수도 있음 모델은 Immutable으로 사용하는 게 좋다!
List뷰로 돌아와서 updateItem 해주면!
체크마크 구현 완료
이렇게 CRUD 구현 성공!!
@AppStorage를 지금 사용하지 않는 이유는 ListViewModel이 class여서!
@AppStorage는 struct내에서 사용하기 좋음
UserDefaults를 사용하려면 지금의 ItemModel을 Json으로 변환하고 저장해야함
그러려면 ItemModel은 Codable 해야겠죠
listViewModel에서 ItemModel을 Encode 하고 UserDefaults에 저장한다.
이 때 key는 전역 상수로 선언해줌
그리고 현재의 로직들이 실행될 때 전부 saveItems가 실행되어야함
함수 내부에 바로 작성할 수도 있겠지만 다른 방법을 사용해보자
프로퍼티 옵저버로 items가 변경될 때마다 saveItems가 되게 해줌
기존의 getItems는 수정이 필요하다
샘플데이터 말고 진짜 데이터가 필요함
guard문을 사용해서 decoding하는데 여러개의 guard문은 중첩해서 표현할 수도 있다
이렇게!
그리고 지금의 items array에 추가해주면 load 되겠죠!!
현재 리스트에 아무 item도 없다면 앱이 아무것도 실행되고 있지 않은 것처럼 보임
유저가 알 수 있게 noti도 주고 animation도 추가해보자
list의 item이 비어 있다면 하나의 뷰를 띄우고 그게 아니라면
기존의 list를 띄울 수 있게 해줌
NoItemsView라는 새로운 뷰를 만들어줬다
ScrollView로 만든 이유는
maxHeight이 적용되어 있어서 이 뷰를 가져오게 될 때 항상 상단으로 밀어줄라고 넣어줌
그리고 혹시 모르니 스크롤뷰 최대로 키워줌
Text들 추가해주고,
프리뷰에 NavigationView도 추가해줌
실제로는 NavigationView 안에 존재할거니까 추가해준거
NavigationLink를 추가해서 버튼을 누르면 AddView로 갈 수 있게 해줌
여기서 guard 문은 animate 로직이 두번 실행되지 않게 하기 위해서 추가해줌
onAppear에서 실행되니까 이 뷰가 뜰 때마다 애니메이션이 새로 추가되는 걸 피하기 위해서
Animation이 계속해서 반복되게 해주고
추가로 여러가지 효과들을 적용해주면!! cool 한 이펙트를 만들었다
그리고 AccentColor를 커스텀하는 것도 가능한데
여기에 새로운 ColorSet 추가해주고
변수로 생성해주면!
요렇게도 가능함!
colorLiteral말고 이렇게 사용해줘야할 듯
그리고 트랜지션 추가해줬음
🤔 트랜지션이 지금 양쪽으로 먹히고 있음.
Add되어 질때랑 delete될때 둘 다 트랜지션되는중...
어떻게 하면 한쪽만 줄 수 있을까?
Add 뷰로 가게 되었을 때 키보드가 뜨게 하고 싶음
FocusState 프로퍼티래퍼로 Bool 선언하고
텍스트필드에 바인딩해준 다음에
.onAppear에서 true로 값 바꿔주면 됨!
accentColor에 dark 추가해서 다크모드용 색상을 지정 가능함
maxWidth 적당히 조절해서 가로모드일 때 사이즈 조절 가능
iPad에서 잘 되나 보자
?!!
이상하게 나오고 있음
지금처럼 NavigationView는 iPad에서 다른 스타일로 표시되서 그럼!
mainApp파일에서 navigationViewStyle을 스택으로 바꿔주면 해결 가능
🤔 이렇게 해주면 해결은 가능한데 지금의 표현은 언젠가 사라질 표현임..
대체 표현 찾아보기
런치 스크린 구현 방식이 바뀜
info 에서 기존에 있는 LaunchScreen 한번 지우고
임시로 AppIcon 넣어서 테스트 해봤는데 잘 나옴
근데 SwiftUI AppIcon 사이즈가 1024*1024 라서
launchScreen 용 Image는 따로 넣어줘야 할 거 같다
을 다운 시켜보다가 깨달음을 얻음
꼭 최신문법으로 스크립트 작성하는 게 좋은 건 아니라는 거!!
FocusState나 dismiss 같은 것들은 14.0으로 낮추면 새로 코드를 짜줘야함...
옷 근데 #available 같은 것들을 사용해서 뭔가 해결할 수 있긴 한가본데
🤔 나중에 알아보자