개발일지

[PhotiCase] 2주

사과먹는사람 2022. 9. 11. 17:17
728x90
728x90

 

9월 5일

  • 각 파일에서 필요없는 코드를 걷어냈다.
  • 4일에 저번 1주차에 뭘 했는지 쓰면서 어디서 데이터를 가져올지 API를 비교하는 내용을 썼는데, 그렇게 정리해서 적어보니 네이버 API는 단점이 너무 많았다.
    • 포스터 사이즈가 다른 데다 저화질이다. 고화질로 받아올 수 있는 옵션도 없어서 검색 목록에서 포스터가 보일 때는 포스터가 지나치게 늘어나있거나 해서 사용자들이 볼 때 그리 좋아보이지는 않았다.
    • 줄거리가 없다.
    • 영화 코드가 없다. 그래서 데이터베이스에 삽입할 때는 코드를 임의로 만들거나 해야 한다.
  • 사실 KOBIS에도 문제는 있었다. KOBIS는 아예 포스터를 제공하지 않는다. KOBIS의 경우, 영화 목록을 검색할 수 있는 API가 있고 영화 코드를 쿼리로 줬을 때 그 영화에 대한 상세 정보를 제공하는 API가 있다. 그런데 여전히 포스터는 없음(...)
  • 네이버를 계속 쓰다 보면 나중에 이 앱을 좀 규모 있게 키운다고 했을 때 문제가 생길 것 같아서 그냥 API를 다른 걸로 써야겠다는 생각을 했다. 무려 저번 주 글을 쓰면서... 그래서 대안이 될 만한 다른 API를 찾아봤다. 한국영상자료원에서 제공하는 KMDB API가 꽤 좋아보였다. 그런데 이건 바로 API 키를 받을 수 있는 게 아니라 관리자에 의해 승인을 바아야 했다. 당장은 어디에 쓸 건지 용도를 정확히 설명할 수가 없었다.
  • 말이 길었는데 결론은 TMDB로 갈아탔다. 원래 TMDB 안 쓰려고 했는데(한국어가 잘 안 되어 있을 것 같은 편견이 있었다) 그냥 language=ko_KR 다니까 한국어로 잘 나온다. 한국판 포스터 있으면 포스터도 한국판으로 나오고. 물론 줄거리를 표시하는 데에서 띄어쓰기가 제대로 되어 있지 않다거나 하는 사소한 문제가 있긴 한데 그걸 커버할 정도로 괜찮았다. 게다가 TV 시리즈 검색 API도 제공하고 있다는 게 또 하나의 큰 장점이었다. (물론 영화 검색 API랑 응답이 달라서 손질은 좀 해야하지만) 그냥 알아볼수록 안 쓸 이유가 딱히 없는 API여서 썼다.  
    • 사실 TMDB에도 이상하게 제공 안 하는 게 있는데 대표적인 게 감독, 러닝타임, 장르명이다. 뭔가 당연하게 있어야 할 것 같은데 없어서 조금 의아한 부분이긴 한데... 감독 정보가 주어지지 않는 건 약간 크리티컬하지만 그래도 TMDB가 제일 낫다.
    • 그리고 대체 무슨 기준으로 페이지를 나눠서 보여주는 건지는 모르겠다. popular나 voteCount가 중요한 건지 했는데 딱히 그건 아닌 것 같다. 그래서 결과를 받아올 때 뭐가 먼저 나올지 예측이 안 된다는 불편함은 좀 있다. 
  • API를 변경하기로 결심했다면 다른 것보다도 이걸 먼저 바꿔야겠다는 생각이 들어 여기부터 코드를 건드렸다. 일단 네이버와 KOBIS API를 요청하는 키를 다 없애버리고 fetch하는 함수 부분은 대부분 재활용했다. 모델은 아예 새로 만들었다. 
  • 원래 감독, 러닝타임, 장르 정보 등이 있던 자리를 밀어버리고 연도와 평점만 남겼다. 그리고 줄거리를 추가했다.
  • 네이버에서 영화 목록 정보를 가져오는 방식과 TMDB에서 목록을 가져오는 방식이 좀 달랐다. 네이버에서는 start에서 display만큼 정보를 가져올 수 있다면, TMDB는 한 page에 20개씩 영화가 담겨 있었고 그 page를 요청해야 했다. 임의로 start를 정할 수는 없었다. prefetch하는 부분 코드를 변경했다.
  • 그리고 이번에도 핑크 무비 거르는 작업을 했다. 하다가 코드가 좀 꼬이는 것 같아서 그냥 하지 말까... 싶었는데 아니야 해야 했다... 도망치면 안 돼... 여기서는 핑크 무비에 공통적으로 나오는 장르 번호를 알아내서 오직 그 번호만 있으면 제외하는 걸로 했다. 최소한 나 이거 봤다~ 라고 기록할 만한 건 드라마 같은 장르로라도 구분되기 때문에 뭔가 잘못 거르거나 하는 건 딱히 없는 것 같았다.
  • 아무튼 이 날은 계속 네이버, KOBIS API를 TMDB로 바꾸는 데에만 썼다.

 

 

9월 6일

  • 코드를 고치는 과정에서 뭔가 잘못됐는지 특정 검색어를 입력하면 검색어보다 결과가 적게 나오거나 아예 결과가 없다고 나오는 문제가 있었다. 결과가 적게 나오는 문제는 주로 성인 영화를 걸러내서 누적 결과와 다음에 요청할 페이지 수가 맞지 않아 prefetch를 못 해서 생기는 문제였다. 이 부분을 수정했다.
  • TMDB는 releaseDate 등을 돌려줄 때 연도만 돌려주는 게 아니라 yyyy-MM-dd 식으로 전체 날짜 전부를 줬다. 근데 그렇게까지 정밀할 필요는 없고 연도만 필요해서 연도만 표시하는 것으로 바꿨다.
  • 원래 영화 검색 탭에서 검색 결과에서 상세 페이지로 들어갔을 때 별점 슬라이더로 별점을 줄 수 있도록 하는 기능 코드가 삽입되어 있었는데 이것을 "직접 추가" 뷰로 옮겼다. 검색 탭의 상세 페이지에는 아직 안 봤다면 "기록이 없어요" 하고 레이블을 보여주고, 봤다면 sum(점수) / 지금까지 본 횟수를 나눠서 보여주기로 했다. 즉, 상세 페이지에서 내 별점을 수정할 수 없도록 했다.
  • 포토카드를 만드는 데에서 이미지 뷰의 크기가 묘하게 포스터 비율과 안 맞아서 위아래로 잘리는 부분들이 조금 생겼다. 찾아보니 포스터 비율은 보통 1.1:41 이거나 1:1.3이라고 해서 넓이를 screen width 80%로 맞추고, 높이는 넓이에 대해 1.43배 곱하는 것으로 constraints를 변경했다.
  • 이 때까지도 이미지를 선택했을 때, 이미지 크기가 포토카드 이미지뷰보다 크면 이미지의 일정 부분만 이미지뷰에 보이는 문제가 있었다. 사실 큰 문제는 아니지만, 기왕이면 이미지뷰의 크기에 맞춰서 이미지가 적당히 늘어나고 줄어들어서 전체를 볼 수 있으면 더 좋을 거 아닌가? 어떻게 해야 하나 고민하다가 UIScrollView의 setZoomScale 메소드를 사용해 동적으로 크기를 줄이는 방법을 썼다.
  • 원래 SQLite3 쓰려다가 Realm 쓰기로 했다. 마지막까지도 무슨 데이터베이스를 써서 로컬에 저장할까 고민했는데 알면 알수록 Realm이 꽤 괜찮아보였다. 물론 SQLite3랑 완전 똑같지는 않지만 모델을 설계하고 쿼리 짜는 것도 크게 보면 엄청 다르진 않고 간단해서 좋았다.
  • 모델을 짰다. @Persisted 어트리뷰트를 붙여서 선언하고, convenience init으로 생성한다.
  • 데이터베이스에 접근해야 할 때마다 매번 try! Realm() 하는 게 좋지 않을 것 같았다. 예전에 SQLite 사용할 때도 DBManager 같은 걸 하나 두거나 Pool을 둬서 데이터베이스 객체는 private으로 생성하고 Manager를 static 싱글턴으로 생성해 Manager를 갖다쓰는 방식으로 DB에 접근했는데, 이번에도 RealmManager라는 걸 만들어서 사용했다. 
import Foundation
import RealmSwift

class RealmManager {
    private let db: Realm
    static let sharedInstance = RealmManager()

    // RealmManager 프라이빗 생성자. DB를 열 수 없을 경우 크래쉬
    private init() {
        do {
            db = try Realm()
        } catch {
            fatalError(error.localizedDescription)
        }
    }
    
    // R: 데이터베이스에서 데이터 가져오기    
    public func fetchAll<T: Object>(object: T.Type) -> Results<T> {
        return db.objects(T.self)
    }
    
    // W: 데이터베이스에 데이터 쓰기
    public func save<T: Object>(object: T, _ errorHandler: @escaping ((_ error: Swift.Error) -> Void) = { _ in return }) {
        do {
            try db.write {
                db.add(object)
            }
        } catch {
            errorHandler(error)
        }
    }
    
    // U: 데이터베이스 데이터 수정. 없다면 새로 생성
    // 모든 내용들을 싹 채우지 않으면 이미 있던 것은 빈 값으로 채워짐 (일부 수정이 아니라 아예 갈아끼우는 느낌)
    public func update<T: Object>(object: T, _ errorHandler: @escaping ((_ error: Swift.Error) -> Void) = { _ in return }) {
        do {
            try db.write {
                db.add(object, update: .modified)
            }
        } catch {
            errorHandler(error)
        }
    }
    
    // D: 데이터베이스 데이터 삭제
    public func delete<T: Object>(object: T, _ errorHandler: @escaping ((_ error: Swift.Error) -> Void) = { _ in return }) {
        do {
            try db.write {
                db.delete(object)
            }
        } catch {
            errorHandler(error)
        }
    }
    
    // Delete All: 데이터베이스의 모든 데이터 삭제
    public func deleteAll(errorHandler: @escaping ((_ error : Swift.Error) -> Void) = { _ in return }) {
        do {
            try db.write {
                db.deleteAll()
            }
        }
        catch {
            errorHandler(error)
        }
    }
}

 

 

9월 7일

  • "직접 추가"에서 직접 편집한 사진을 사진 앨범에 저장할 수 있도록 Info.plist에서 권한 받는 부분을 추가했다. 실제로 권한을 확인하는 부분은 PHPhotoLibrary.requestAuthorization으로 할 수 있었다. 
  • 이 날 제일 어려웠던 부분은 스크롤뷰에 나타난 부분만 스크린샷을 찍어 이미지 파일로 저장하는 부분이었다. 검색을 많이 했고 결국엔 뭐가 필요한지 알아내서 구현에도 성공했다. ㅎㅎㅎ 사실 이 스크린샷을 document에 저장하는 코드가 더 중요한 것 같았다. 스크린샷을 찍는 건 별로 쓰지 않는(?) 기능일 것 같지만 FileManager로 로컬에 쓰는 코드는 꽤 많이 쓸 것 같은 느낌이 들었다. 이 기회에 FileManager를 이용해 데이터를 불러오고 저장하는 방법을 리마인드할 수 있었다.
  • 전날 씨름했던 부분.. 이미지를 이미지뷰 크기에 맞게 조정하는 부분은 생각보다 쉽게 해결했다. 이거.. 이미지 뷰를 width.height.equalToSuperview()로 constraints 잡아주면 되는 거였다. -_- 이거 해결 보려고 진짜 오랜 시간 동안 고민했는데 이걸 못 해서...??? 등잔 밑이 어둡다더니..
  • 검색 > 상세 화면에서 볼 수 있는 포스터를 탭하면 포스터를 크게 확인할 수 있는 모달이 나타나도록 구현했다. 이미지뷰에 TapGestureRecognizer 붙이면 됐다.
  • 직접 추가가 아니라 검색한 상세페이지에서도 영화 기록을 추가할 수 있도록 했다. 추가 뷰를 따로 만들지는 않았다. 어차피 똑같이 생겼으니까 뷰컨만 새로 생성해서 쓰면 된다. 
  • 사진 편집 후 Realm에 데이터를 진짜 삽입할 수 있도록 변경했다. 
  • 사진 편집 후에는 단순히 "완료되었습니다" 하는 뷰를 보여주는 대신 방금 기록한 정보에 대한 뷰를 보여줄 수 있도록 구현하기로 했다. 처음엔 그냥 "성공했습니다" 이런 거 보여주려고 했지만, 생각해 보니 그건 사용자가 불필요한 탭 한 번 더 하게 유도할 뿐이고, 굳이 성공했다는 걸 알려주고 싶다면 토스트 같은 걸 띄워야 할 것 같아서 그냥 결과를 보여주도록 변경했다. 이 때 보여주는 뷰는 왼쪽으로 스와이프해서 뒤로 갈 수 없고, 백 버튼을 탭했을 때 루트 뷰로 돌아가도록 디자인했다.
  • 원래 Realm용 모델에는 date 정보가 하나만 있었는데, "기록한 날짜"와 "본 날짜"를 따로 관리하는 게 좋은 것 같아서 모델을 변경했다. 그런데 이렇게 바꾼 다음 그냥 deleteAll하고 새로 만들려니까 migration하라고 크래쉬가 났다. 이 때 아예 폰에서 앱을 삭제한 다음 다시 설치하는 게 제일 쉽긴 하다. 개발 단계니까 가능한 해결 방법이다.

 

 

9월 9일

  • 홈 탭에 보이는 달력도 Realm 데이터에 따라 그 날 등록한 기록을 보여줄 수 있도록 기능을 추가했다. 이 때 Date랑 한판했는데 결론은 내가 32,400 더해서 9시간 차이를 어떻게 해보는 게 능사가 아니라는 것이다. Date는 그냥 Date 자체로 놔둬야 하며... 뷰에서 시간까지 표현할 필요가 있다면 그 때 DateFormatter를 사용해서 한국 시간으로 보이도록 바꿔줘야 했다.
  • 달력에 보여줄 그림은 resize를 할까 그냥 표현할까 고민했다. 둘 다 시도해봤는데 메모리 차이가 크지 않아서 그냥 resize를 안 했다. 
  • 원래 placeholder 자리는 아주 연한 회색으로 날짜가 표시되었는데 그냥 alpha값 0으로 밀었다. 그랬더니 한 달이 몇 주든 간에 셀 크기는 원래대로 유지되지만 placeholder는 안 나타나서 이번 달 날짜들만 보이는 깔끔한 달력으로 변했다. isSelected가 됐을 때, placeholder라면 actionController도 나타나지 않고 동그라미 표시도 되지 않도록 변경했다. 
    • 하지만 눈에 보이지 않는다고 실제로 placeholder가 없는 건 아니어서 만약 이벤트가 있다면(즉, 해당 날짜에 기록이 있다면) 사진은 나타났다. 이걸 해결하려면 지금 달력에 나타난 달의 시작과 끝을 계산해야 했다. endOfMonth 계산할 때는 주의해야 하는 게, 만약 9월이라고 하면 9월 30일이 마지막 날은 맞지만 9월 30일로 냅다 해버리면 0시까지만 계산되어 30일부터 10월 1일 0시까지 24시간은 누락된다. 그래서 아예 10월 1일을 구하고 1초를 빼버리는 게 더 좋은 방법이다.
  • fsCalendar 또 웃긴 걸 발견했는데, fsCalendar 기능 중에 numberOfEvents라고 이벤트가 있는 날 동그라미로 indicator가 날짜 밑에 나타나는 게 있다. 근데 이 numberOfEvents가 date인데, 이 date는 0시만 해당된다. 예를 들어 9월 9일에 이벤트가 있는지 검사할 때 쓰는 date가 9월 9일 0시라서 어떤 이벤트가 그 날 있는지 확인하려면 시간을 반드시 0시로 새로 맞춰서 검사해야 된다.
    • 이 프로젝트를 마칠 쯤에는 fsCalendar의 대가가 되어 있을 것 같다.
  • 이전, 다음 달 등 현재 달력에 나타나는 달이 바뀌었을 때 사진이 달력에 잘 표현될 수 있도록 구현했다. 이건 calendarCurrentPageDidChange 라는 데이터소스 메소드를 활용하면 된다. 이렇게 하기 위해서는 Realm에서 fetch해오는 건 따로 함수로 빼오는 게 더 활용성이 좋아서 따로 메소드로 뺐다.
  • 앱을 잠깐 재워놓고 폰으로 다른 걸 하고 있는데 xcode에 메모리가 부족해서 앱이 종료되었다는 알림이 떴다. 메모리가 부족하다니? 하는 것도 없는 게 좀 이해하기 힘들어서 테스트를 했다.
    • 달력 화면에서는 기본적으로 30MB 정도 사용한다. fsCalendarCell의 이미지 뷰에 이미지가 나타나니 스크롤할 때 살짝 뚝뚝 끊기는 문제가 생기는데, 이건 작게 리사이징해도 나타나는 문제라서 더 이상 어떻게 개선해야 할지 모르겠어서 나중에 해결하기로 하고 일단 내버려뒀다. 달력에서는 메모리를 심하게 사용하지 않았다.
    • 정말 큰 문제는 검색이었는데, 셀에 가져올 데이터를 fetch를 하는 데에서 메모리 사용량이 엄청났다. 그냥 무지성으로 쭉쭉 아래로 내리는데 메모리가 엄청나게 치솟아서 400MB에 달하기도 했다.
    • 게다가 검색어를 바꿔서 다시 검색하면 전에 fetch한 게 굳이 필요없어진다. 그런데 그 때도 메모리가 자동으로 비워지지 않고 계속 사용량이 늘어났다.
    • 왜 이렇게 메모리 사용량이 큰가? TMDB에서 영화 포스터를 가져오는 걸 original로 하고 있었다. 이러니 메모리가 엄청나게 치솟지... 사이즈를 original 말고 좀 작게 가져올 수 있는 방법 없을까 찾아보니 이렇게 사이즈를 지정해줄 수가 있었다. 원본 사이즈가 아니라 좀 줄여서 가져오니 검색에서 쓰는 메모리는 10~20MB 정도로 정리됐다. 뜻밖의 수확이었다. 한편, 내가 이걸 작게 가져올 생각을 못 했다는 게 뭔가 이상했다. -_-

 

 

9월 10일

  • 원래 홈 탭에서 달력의 어떤 날짜 셀을 탭했을 때 "작품 추가하기"라는 액션 컨트롤러를 띄울 수 있도록 구현했는데, 그 부분을 제거했다. 기록이 있는 날과 없는 날 모두 동일하게 내비게이션 컨트롤러를 통해 새로운 뷰를 보여줄 수 있도록 변경하기로 했다.
  • 기록 추가 뷰에서 날짜만 선택하는 게 아니라 시간도 선택할 수 있도록 했다. 종일반하는 사람들이 아니더라도 몇 시에 봤는지를 기록할 수 있으면 더 편리할 게 분명했다.
  • 기록 추가 뷰의 segmented control은 일단 삭제했다. 추후 공연, TV 프로그램 추가도 가능하게 할 예정이니 그 때 가서 다시 해도 늦지 않을 것 같다는 생각에서였다.
  • 영화 상세 뷰를 보여줄 때, 이 영화가 이미 평가한 영화일 때와 아닐 때를 구분해서 보여주는 영역이 있다. 만약 평가한 영화일 경우, sum(영화 관람 시 준 별점) / 영화 관람 횟수로 나눠 별점을 소수점 1자리까지 표시하도록 했다.
    • 또한 관람 기록이 있다면 관람 기록에 가로 방향으로 스크롤할 수 있는 카드 스크롤뷰를 만들었다. 스크롤뷰 안에는 기록의 사진과 관람 날짜, 시간이 표시된다.
    • 이 때 실수한 게 있는데 CollectionView로 안 만들고 ScrollView 안에 StackView 넣어서 만든 거다. 이렇게 하면 겉보기에는 CollectionView랑 비슷할 수 있지만 탭했을 때 뭔가 액션을 취해야 하는 그런 작업을 넣기가 굉장히 힘들어진다.
    • 지금까지 CollectionView와 StackView로 구현하는 것 사이에 도대체 무슨 차이가 있을까... 생각했는데 직접 이 상황에 부딪혀보니 확실히 용도가 다르다. isUserInteractionEnabled 한다고 끝이 아니다. 이 경험으로 언제 스택뷰를 쓰고 언제 컬렉션뷰를 써야 할지 깨달았다.

 

 

9월 11일

  • 위에 적었던 대로 StackView의 아이템들을 탭할 수 있게 변경했지만 결국엔 CollecitonView로 갈아끼웠다. 굳이 CompositionalLayout을 쓸 이유는 없을 것 같아서(크기나 레이아웃이 크게 변하는 건 없고 그냥 한 줄로 쭉 배치되는 레이아웃이라) FlowLayout으로 만들었다. 
    • cellForItemAt과 didSelectItemAt은 위대하다.
  • 영화 기록을 볼 수 있는 뷰에서, 만약 뒤로 갔을 때 직접 추가(사진을 추가하는) 뷰로 이동하게 된다면 그냥 루트 뷰로 이동할 수 있도록 변경했다. 직접 추가 뷰로 다시 가서 이미지를 재편집하고 추가하게 된다면 id가 중복되는 불상사가 벌어지기 때문에 그런 일을 막아야 했다.
  • 앱 이름에 한국어 적용을 추가했다. 다국어 지원을 하게 될지는 모르겠지만, 일단은 앱 이름이 영어로 나타나는 문제 때문에 추가했다. fscalendar에서 토요일, 일요일 표시 색깔을 나눈 것도 다시 조정했다. 
  • 날짜가 다음 날로 변경되면 홈 탭에서도 자동으로 오늘을 표시하는 동그라미가 오늘 날짜로 옮겨가도록 했다.
  • 처음으로 앱 아이콘을 추가했다. 친구들에게 앱 아이콘 시안을 보여주면서 어떤 게 나아보이냐고 많이 물어봤는데, 처음 디자인한 건 지금 봐도 정말 별로다. ㅋㅋㅋ 지금이 훨씬 나은 것 같다.

 

 

 

728x90
반응형