-
Notifications
You must be signed in to change notification settings - Fork 5
Day 15 개발일지 iOS
지난 주에 급하게 적었던 코드들을 아키텍처를 적용하여 정리해 보았다. 뷰 모델의 State와 Input의 데이터흐름을 추상화해주는 구조였다. 또한 Service 계층을 만들어주어 메인 데이터를 관리한다. 토큰이라는 메인 데이터가 있는 우리 프로젝트에 딱 적합한 구조라고 생각이 들어 바로 적용해보았다.
지금은 라이브러리를 사용하여 앱이 정상적으로 동작하도록 하였다. 하지만, 라이브러리에 대한 의존성을 없애고 TOTP 알고리즘을 직접 구현해 볼 것이다.
ViewModel init에서 guard 구문이 필요해서 바로 return 했다가 애를 먹었다... state가 init되지 않아서 에러가 떴던 것이다. 주의하자.
- 작은 셀을 터치하면 큰 셀로 이동되는 애니메이션을 어떻게 구현할까 고민이 되었다. 그런데 SwiftUI에는 이러한 작업을 쉽게 할 수 있도록 기능을 제공해 주고 있었다. 아래 자료의 글쓴이는 이 기능과 사랑에 빠졌다고 하는데 그럴만 한 것 같다. 깊게 파고 들어서 공부하면 더 많고 유용한 애니메이션을 쓸 수 있을 것 같다.
SwiftUI's matchGeometryEffect in iOS 14
작은 셀을 터치했을 때 메인 셀로 작은셀의 정보가 넘어가는데, 이때 옮겨진 작은 셀을 삭제해야하는것인지? 아니면, 그냥 터치하고 띄우는 형식인건지? 고민되었다. 하지만 우리는 이동하기로했다. 즉, 작은 셀에서 삭제하고 메인 셀로 옮기는 것이다. 이는 비교적 쉽게 해결했다. 모든 토큰을 하나의 배열로 관리하고, 메인 셀에 들어가는 토큰만 메인 인덱스라는 값으로 관리를 해주기로 했다. 이렇게 하면 셀을 터치했을 때 메인인덱스만 변경해주면되고, 뷰를 그릴 때에는 메인인덱스를 제외한 토큰만 그리드 상태값에 넣고 메인인덱스로 표시된 토큰은 메인셀 상태값에 넣어주면 된다.
결론: viewModel의 moveToken 로직이 잘못됨.
service에 있는 mainTokenIndex 값을 변경하지 않고, 이전 mainTokenIndex를 가져와서 filteredTokens에 추가해줌.
TokenCellView를 생성할 때
init(service: TokenServiceable, tokenId: UUID) {
let token = service.token(id: tokenId) ?? Token()
let tokenKey = token.key ?? ""
state = TokenCellState(service: service,
token: token,
isShownEditView: false,
password: TOTPGenerator.generate(from: tokenKey) ?? "000000")
init(service: TokenServiceable, token: Token) {
state = TokenCellState(service: service,
token: token,
isShownEditView: false,
password: TOTPGenerator.generate(from: token.key ?? "") ?? "000000")
화면전환 상태값을 뷰모델에 넣어서 넘겨주려고 했다. 그런데 뷰모델 프로토콜에 State 타입이 get-only로 되어 있어서 이 상태값을 넘겨줄 수 없게 되었다. ViewModel에 있는 State의 Setter도 설정해주거나 다른 방법을 찾아봐야 겠다.
TokenCellView나 MainCellView나 들어가는 데이터는 비슷하고, 타이머도 다 같이 돌아야 한다. 고로 같은 뷰라고 봐도 무방하다. 그래서 합치기로 결정! MainCellView를 없애고 MainCellView에만 있던 복사하기 버튼이나 프로그래스 서클(?)을 옮겨주었다.
viewModel.totalTime이 TOTPTimer.shared.totalTime으로 만들어진 값이었는데, 뷰에서 이 값을 읽을 수 없었다. 그래서 TOTPTimer.shared.totalTime를 그대로 했더니 됐다. 이유 모름..
Failed to produce diagnostic for expression; please file a bug report
case .endSearch:
state.searchText = ""
state.isSearching = false
원래 이랬던 엔드 서치 로직
case .endSearch:
state.searchText = ""
state.isSearching = false
state.mainToken = state.service.mainToken()
state.filteredTokens = excludeMainCell()
이렇게 변함
서치했을 때 filtered에는 MainView까지 포함된다. 그래서 돌아올 때 filtered를 다시 원래대로 돌려놓지 않으면, 메인 셀도 포함되어 표시하게 된다. 그런데 메인셀을 클릭했을 때 원래대로 돌아오는 건 이유가 뭘까? 아무튼 갔다가 돌아올 때 filtered를 정리해주니 해결되었다.
- 서치바로 가게 되면 filteredTokens에 MainToken도 포함된다.
- 이 상태에서 다시 Main화면으로 돌아오면, MainToken가 filteredTokens에포함된 채로 화면이 업데이트 된다.
- 그런데 같은 네임 스페이스 안에 똑같은 아이디를 가진 MainTokenCell이 존재 하기 때문에 그리드 셀에 추가된 MainToken은 화면에 표시되지 않지만, 자리는 차지하는 상태로 표시된다.
- 이 셀이 메인 셀에 위치하게 되고, 그래서 메인 셀을 터치하면 작은 셀에 연결된 moveToken이 실행된다. 이때 다시 filteredTokens가 메인 토큰을 걸러진 채로 업데이트 되면서 원래대로 돌아오는 것이다.
- 그래서 서치가 끝날 때 메인 토큰을 제외해주는 로직을 추가해주었더니 해결되었다.
그냥 viewModel.trigger(.startSearch(text)), viewModel.trigger(.endSearch)를 withAnimation으로 감쌌더니 됨
서치바에 있는 상태에서 터치! → endSearch 실행
- 원래
case .endSearch:
state.searchText = ""
state.isSearching = false
state.mainToken = state.service.mainToken()
state.filteredTokens = excludeMainCell()
case .moveToken(let id): // 이름selectToken 같은 게 좀 더 나을지도?
if state.isSearching {
self.trigger(.endSearch)
}
state.service.updateMainTokenIndex(id: id)
state.mainToken = state.service.token(id: id) ?? Token()
state.filteredTokens = excludeMainCell()
}
메인화면에서 그리드에 메인셀을 걸러서 표시하는 로직이 endSearch일 때 한번 moveToken할 때 한번 더 수행된다.
- 고침
리턴을 사용해서 한 번만 실행되도록 변경. 대신에 moveToken할 때에는 선택한 셀이 mainToken이 되도록 한 후 실행.
이렇게 고치고 보니 아래 id로 메인 토큰을 선택하는 부분이 필요없다는 걸 알게 됨. 위에서 mainToken을 재설정 해주었으니 mainToken을 받아오는 함수를 사용하면 됨.
이렇게 하니 위에 endSearch랑 로직 같음. 그래서 함수로 분리
case .endSearch:
state.searchText = ""
state.isSearching = false
// 묶자
state.mainToken = state.service.mainToken()
state.filteredTokens = excludeMainCell()
case .moveToken(let id): // 이름 - selectToken 같은 게 좀 더 나을지도?
state.service.updateMainTokenIndex(id: id)
if state.isSearching {
trigger(.endSearch)
return
}
// 묶자
state.mainToken = state.service.mainToken()
state.filteredTokens = excludeMainCell()
}
-
서치바 갈 떄 왜 메인 셀 애니메이션이 버벅이면서 없어지는지 몰겠다. 시뮬 문제? 한 번 다녀오면 그 다음부터는 버벅임 없이 잘 동작함.
-
searchBar에 searchText가 한번 더 state로 선언되어 사용되고 있다. 이걸 그냥 viewModel에 있는 걸로 바인딩 할 수는 없을까?
(솔직히 쓰기 - 현재 파트너 또는 누군가가 본다고 생각하지 말고 미래의 내가 본다고 생각하며 쓰면 어떨까요??😏)
- 주말에 아키텍쳐 부분을 적용해보려 했다. 완벽히 이해하지는 않았지만 예제 코드보며 어느정도 우리 프로젝트에 적용해보았다! 이해도 얼른 따라야 할텐데,,, 후후 암튼 지금은 SearchBar와 Main만 연결된 상태인데 얼른 내일이 되어 재명님과 함께 다른 부분들도 적용해보고싶다.
- SwiftOTP라이브러리를 한 번 사용해봤다. 열심히 짜놓은 TOTP알고리즘이 있었지만 ㅠㅠ 안타깝게도 올바른 비밀번호를 생성해내지 못 했다. 그런데 이 라이브러리는 몇줄의 코드로.... 가능해졌다. 얼른 이 라이브러리를 뜯어서 우리 앱에도 적용해보고프다.. 근데 엄청나게 어려울 것 같다 ㅠㅠ
- 주말에 열심히 TOTP 알고리즘을 공부해 보았지만, 우선 동작을 위해 swiftOTP 라이브러리를 추가했다. 하지만 공부해서 직접 작성한 코드로 대체하거나 저 라이브러리에서 사용한 코드를 완벽히 이해하도록 할 것이다.
- 원래는 Compoasble 아키텍처를 적용해보려고 했다. 하지만 이 아키텍처 라이브러리를 제공하는 사이트에서 본격적인 동영상 강의는 유료로 제공하여 더 공부를 하기가 어려워졌다. 그 와중에 어진님이 뷰모델 위에 서비스 계층을 두어 데이터 관리를 해주는 자료를 공부하시고 이미 적용하셨다. 굳👍
- 벌써 3주차가 시작되었는데, 이번 주에는 꼭 버전 1.0을 배포할 거다!
- 노트북 거치대를 높여서 눈높이에 맞춰 주었더니 뒷목도 덜아프고 그래서 집중력도 더 좋아졌다. 오늘 집중력 역대급!
© Boostcamp