기획 의도
내 취미는 양서류, 절지류, 파충류 같은 희귀 애완동물을 키우는 것이다. 요즘은 취업 준비 때문에 대부분 분양 보냈지만... 아무튼 이런 취미를 가졌기에 희귀 애완동물 커뮤니티에 자주 들어가는데 보통 한두 마리를 키우는 게 아닌 몇십 ~ 수백 마리 단위로 키우는 사람들도 자주 보이고, 자신의 개체 관리 기록 (먹이 급여나 탈피 같은 기록할 사항)을 메모장에다가 기록해 두는 사람들도 종종 보았다.
그래서 이런 희귀 애완동물을 관리한 기록을 저장할 수 있는 앱이 있으면 좋겠다고 생각했고, 희귀애완동물을 관리하는 것을 목적으로 앱을 기획했다.
앱 디자인
일단 우리나라에서 희귀동물하면 보통 파충류, 양서류, 절지류로 나눠지고 가장 많이 키워진다. 그래서 펫 리스트 화면에서 이 3가지의 종을 필터링해서 볼 수 있도록, 상단에 CollectionView로 선택을 할 수 있도록 디자인해두었다. 그리고 햄스터나 친칠라 같은 포유류도 나름 희귀(?) 애완동물이니 추가했다.
펫 상세화면은 처음에는 가장 오른쪽 이미지로 디자인 했었지만, 뭔가 비어 보이는 느낌이 많이 들어서 당근마켓, 네이버 카페 앱 디자인을 참고해서 디자인했다.
그다음은 관리 목록을 기록하는 화면인데, 처음에는 사용자가 직접 작업을 정의해서 기록할 수 있게 하려고 했지만, 그럴 경우 여러 가지 고려해야 할 사항이 많아서 메모라는 작업을 넣어서 그 외의 작업은 메모를 이용하도록 기획했다.
DB
DB의 경우 Realm DB를 사용했다. DB 테이블은 크게 4종류로 나뉘는데, 종, 개체 정보, 개체 무게, 개체별 작업 목록 이렇게 나누고 DB와 직접 통신하는 코드 역시 이에 따라 나눴다.
처음에는 DB에 직접 접근하는 코드를 각 Stroage에 마다 만들었지만, 프로젝트를 진행하다 보니, 접근할 때마다 guard 문을 쓰는 점과 모두 동일한 DB에 접근하는 건데 생성할 필요가 있을까? 해서 Dipendency InjectionContainer를 만들어져 DI로 각 stroage마다 동일한 realm 객체를 넣어주는 식으로 구현했다. 그런데 이런 식으로 동일한 realm 객체를 넣고, 백그라운드 스레드에서 realm을 사용할 경우 오류가 발생할 수 독 있는데, 내 프로젝트에서는 백그라운드 스레드를 사용할 일이 없어서 그냥 넣었다. 나중에 고쳐야 할 듯..
final class RealmDIContainer: StorageDIContainer {
let realm: Realm = {
guard let realmPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("repcare.realm", conformingTo: .data) else {fatalError()}
do {
let bundleRealm = try Realm(fileURL: realmPath)
return bundleRealm
} catch {
fatalError(error.localizedDescription)
}
}()
lazy var petStorage: PetStorage = {
let petStorage = RealmPetStorage(realm: realm)
return petStorage
}()
lazy var speciesStorage: SpeciesStorage = {
let speciesStorage = RealmSpeciesStorage(realm: realm)
return speciesStorage
}()
lazy var taskStroage: TaskStorage = {
let taskStorage = RealmTaskStorage(realm: realm)
return taskStorage
}()
lazy var weightStorage: WeightStorage = {
let weightStorage = RealmWeightStorage(realm: realm)
return weightStorage
}()
}
애완동물 이미지는 저장된 펫의 id값을 이용하여 document 디렉터리에 id값의 폴더를 생성한 후, 거기다가 각 개체의 이미지를 넣는 방식으로 구현했다.
이렇게 하니까 개체를 삭제 및 수정, 객체의 아이디 값에 해당하는 폴더만 건드리면 되므로 처리하기 편했다.
종 선택 화면 & 필터링 화면
NSCollectionViewDiffableDatasource를 이용하여 구현했다. NSCollectionViewDiffableDatasource을 사용하니까 자동으로 애니메이션이 나와서 자주 사용할 듯하다. 하지만 화면을 구현하는데 생각보다 시간이 굉장히 많이 걸렸었다.
희귀 애완동물의 경우 종이나 모프 같은 게 딱 정해져 있는 게 아닌 경우가 많다 보니, 사용자가 직접 추가 및 수정, 삭제할 수 있도록 구현했다. 일단 분류, 종을 필수로 선택해야지만 적용 버튼이 활성화되도록 해두었다.
당연히 이렇게 작동해야지! 하듯 구현하려고 했는데, 로직적으로 생각할 분분히 굉장히 많았다.
예시를 들면, 저렇게 분류, 종, 상세종이 선택된 상태에서 가장 상위에 해당하는 분류가 다른 것으로 선택된다면 종을 제외한 상세종, 모프/로컬은 텅 빈 상태가 되어야 하고, 기존에 선택된 종 역시 선택이 안된 걸로 변경되어야 하고, 분류만 선택된 상태이므로 적용 버튼이 비활성화돼야 한다.
이렇듯 생각보다 고려해야 할 사항이 많아서 굉장히 수정을 많이 했다.
펫 상세화면
진짜 진짜 * 100 시간이 많이 걸렸던 화면이다. 개발 공수를 산정할 때 만드는데 처음에 2시간 정도 걸리겠지? 하고 시작했는데, 막상 하고 보니 어려워서 30시간 이상 걸렸다.
네이버 카페나, X, 인스타그램의 프로필에 들어가면 보이는 UI인데 볼 때는 쉬워 보이지만 막상 구현해 보려고 하니 여러 가지 지식이 필요했다. 어찌어찌 만들긴 했다만, 아직 버그가 남아 있어서 추후 수정 예정이다.
만든 코드는 여기에 포스팅해두었다.
각종 이슈
개발하면서 발생한 이슈들 중 몇 가지를 블로그에 정리해 두었다.
PHPicker로 이미지를 가져왔을 때, 이미지가 회전한 채로 가지고 오던 이슈
NSCollectionViewDiffableDataSource apply시 셀 내부 요소 변경되지 않던 이슈
후기
기획은 거창했지만, 현실은 그와 달랐다. 별로 안 걸릴 것이라 생각하던 기능이 별의별 이슈로 개발을 더디게 했으며 시간에 쫓기다 보니 기획한 기능을 다 만들지도 못했다. 게다가 구현하기에 급급해 소스코드도 개판이 되기도 하고..
기획단계에서도 대충 이러이러하겠지 하고 넘어갔던 거에서 시간도 많이 잡아먹었다. 만들고 아 빼먹었다 하고 수정하고, 만들고 아 빼먹었다 하고 수정하고.. 이게 반복되니 시간낭비가 많았다.
시간이 벌써 11월이다. 대학 졸업하고 벌써 9개월이라는 시간이 흘렀다. 추웠던 졸업식을 시작으로 정보처리기사를 따고, 개인 프로젝트도 하나 진행하고, 기업에 지원하고, 연합동아리에 지원하다 보니 시간이 어느덧 무더운 여름으로 바뀌었고, 새싹에 들어가서 iOS 개발자를 목표로 하는 사람들과 같이 공부를 하면서 프로젝트를 하다보니 어느덧 졸업식 때만큼 추웠던 날이 되었다.
대학교 입학할 때는 아, 고등학생 때 열심히 할걸 하고 후회했고, 대학교를 졸업할때는 아, 학부생때 열심히 할껄하고 후회했고, 지금도 졸업하자마자 열심히 공부할껄, 좀 더 프로젝트를 할껄 하고 후회했다. 지금 글을 쓰고있는 이 순간도 언젠가 시간이 지나면 이때 좀 더 열심히 할껄 하고 후회하고 있을 수도 있다. 하지만 지나간 시간은 돌아오지 않으니 미래에 이 순간을 후회하지 않기 위해 좀 더 열심히 하기로 해보자 한다.
다운로드 링크
https://apps.apple.com/kr/app/%EB%A0%99%EC%BC%80%EC%96%B4/id6470812043
'iOS > 앱 개발' 카테고리의 다른 글
권한 요청, 설정 이동을 추상화(protocol)를 이용하여 재사용 가능하게 사용하기 (1) | 2023.11.21 |
---|---|
MVVM Inout패턴 + 네트워크 에러 핸들링 (0) | 2023.11.10 |
Swift의 namespace (0) | 2023.07.30 |
원시값을 감싸서 유지보수하기 쉬운 코드를 만들어보자 (0) | 2023.06.26 |
ApplicationDelegate, SceneDelegate (0) | 2023.05.27 |