[Swift-30-Projects] 05. IOS 클론코딩 Artistry

테이블뷰와 셀프 사이징 테이블뷰을 활용한 간단한 앱입니다. 엄청 간단한 앱이라 금방 만들겠네 했었는데, 생각보다 모르는 부분이 많아서 이번 기회에 배운 내용들을 하나씩 정리 하려고 합니다.
구현을 하면서 막혔던 이슈들에 대한 정리로 서술됩니다.

전체 코드는 이곳에 있습니다.

먼저 앱이 동작하는 것을 보시고, 아 이런 건 이렇게 만들어야지가 바로 떠오르신다면 이 글을 볼 필요가 없습니다!

앱의 동작은 이곳에 있습니다.

앱의 구조


앱은 크게 2개의 화면으로 되어있습니다. 하나는 아티스트들을 나열하는 테이블 뷰와 클릭 했을 때 상세 내용을 보여주는, 상세 테이블 입니다. 상세 페이지로 이동하기 위해서 네비게이션 바에 넣어주었습니다.

커스텀 네비게이션 바

앱의 가장 첫 화면에서 보이는 네이게이션 바를 그리면서 문제가 생겼습니다. 스토리보드에서 navigationBar의 스타일 옵션중에 prefersLargeTitles을 true로 설정하고 배경색을 주고 폰트까지 설정한다음 화면을 보니. 원하는 모습이 나오지 않았습니다. 원래 원했던 것은 위까지 다 차있는 모습이었는데, navigationBar 부분만 색상이 찼습니다.

당연히 navigationBar의 부분의 배경색만 바뀌는게 맞습니다. 내가 원하는대로 바꾸려면 커스터마이징을 해주어야 합니다. 그리고 그 커스터 마이징을 하기 위해서는 UINavigationBarAppearance를 공부해야했습니다. 자세한 정리는 아래에 참고에 써 놓도록 하겠습니다. 블로그에 정리하는 것도 좋지만 잘 정리된 내용을 정독하는 방법도 개인적으로는 효율적이라고 생각합니다.

let myColor = UIColor(red: 118.0/255.0, green: 158.0/255.0, blue: 71.0/255.0, alpha: 1.0)
let appearance = UINavigationBarAppearance()
appearance.backgroundColor = myColor
appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white,
                                       .font: UIFont(name: "Optima-Bold", size: 46)!]
appearance.titleTextAttributes = [.foregroundColor: UIColor.white,
                                  .font: UIFont(name: "Optima-Bold", size: 24)!]

navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationBar.standardAppearance = appearance
navigationController?.navigationBar.compactAppearance = appearance
navigationController?.navigationBar.scrollEdgeAppearance = appearance
navigationController?.navigationBar.tintColor = .white

위와 같은 코드로, 네비게이션 바를 커스터마이징 해주었습니다. 기능만 생각했을 때는 단순하게 네비게이션 바 윗부분만 칠해주면 되었는데, 스토리보드에 없어서 폰트며 색상까지 다 설정해주면서 많은 작업을 코드로 하게되었습니다.

메인 테이블 뷰

매인 테이블 뷰를 그리는데 있어서 큰 어려움은 없었습니다. stroyBoard에서 테이블 뷰 IBOutlet을 하나 만들어주고, delegate와 dataSource를 설정해주고, 저는 커스텀 셀을 하나 만들어서 register 해 주었습니다. 스토리 보드에 그려도 문제 없었지만, 혹시 나중에 다른 종류의 셀이 메인테이블 뷰에 들어갈 수 있기 때문에, 커스텀 셀 만드는 연습을 하기 위해 만들어 주었습니다.

한 가지 문제점은 테이블 뷰의 높이 였습니다. 이 앱에서는 다양한 크기의 이미지와, 다양항 종류의 텍스트를 셀에 포함하게 됩니다. 설명이 truncate되지 않아야 하기 때문에 textView를 사용했습니다. 그래도 케이스를 좀 나누어 문제가 될 만한 경우를 생각해 보겠습니다.

  • 이미지가 크고 글이 작다.
    • 최대 width가 있기 때문에, 비율만 맞춰서 높이를 지정하면 그 크기만큼이 cell의 높이가 됩니다.
  • 이미지가 width가 좁고 높이가 길다.
    • 비율은 유지해되, 최대 width로 늘려줍니다.
  • 이미지가 작고 글이 크다.
    • 글의 높이만큼이 cell의 높이가 됩니다.

다이나믹한 높이의 테이블 뷰를 만들때의 규칙을 깨닫는데 삽질이 있었습니다. 테이블 뷰 셀의 RowHeight을 100으로 지정해도, 테이블 뷰의 RowHeight이 200으로 지정되면 200으로 그려집니다.
반대로 고정값을 가진 테이블 뷰를 그린다면, 테이블뷰 RowHeight을 200으로 지정해도, 테이블 뷰 셀의 RowHeight을 100으로 지정하면, 100으로 그려진다고 이해했는데 잘 못 알고있다면 코멘트 부탁드립니다.

다이나믹한 테이블 뷰를 그리는 방법을 2가지 알게 되었습니다.

  • 하나는 UITableViewDelegate 메소드 중 heightForRowAt 메소드로 indexPath별로 높이를 지정해 주는 방법입니다.
    • 이 방법은 다른 종류의 cell을 사용할 때 cell별로 높이를 지정해주는데 적합하다고 합니다.
  • 나머지 하나는 rowHeight에 UITableView.automaticDimension를 선언 해주는 것 입니다.
    • 같은 종류의 셀의 다양한 크기를 표현하는 방법 이라고 합니다.
    • 셀마다 계산되는 높이대로 그려줍니다. 그러므로 constraints를 잘 설정 해주어야 합니다.
    • 설정 해주고 estimatedRowHeight 값을 설정해주어야 합니다.

2번째 방법대로 설정해주고, 테이블 뷰셀을 그려줍니다. 이미지의 width를 200으로 고정했습니다. 옆으로 넘어가거나 모자라서 쪼그라드는 것을 막기위함입니다. textView를 사용해 글자를 잘리지 않게 해 주었습니다. 이미지와 superview와의 거리를 적절하게 설정해주었습니다.

다이나믹한 테이블 뷰를 처음 그려 보았습니다. 매우 신기!

데이터 로컬에서 불러오기

이미지와, 설명과 같은 값은 사실 보통은 서버에서 내려 받아야 합니다. 하지만 서버도 없기 때문에 local의 값을 불러와서 사용해야 했습니다. Bundle.main.url을 사용해 url을 구하고, Data(contentsOf: url)로 데이터를 불러와 모델에 넣어주었습니다.

서버 없이 목 데이터만을 가지고 프로젝트를 만들 때 매우 유용해보였습니다. 잘 기억 해 두었다가 다른 프로젝트의 초반에 서버가 없을 때 사용하면 좋아보였습니다.

LargeTitles 애니메이션

첫 페이지에서, prefersLargeTitles = true 옵션으로 잘 나오는 것을 확인했습니다. 메인 테이블에서 셀을 하나 누르면, 디테일 테이블 을 보여주는데, 이때 navigationController?.navigationBar.prefersLargeTitles = false를 사용했더니, 화면이 넘어가면서 계속 title이 남아있는 기현상을 볼 수 있었습니다. 그래서 구글링 해보니, navigationItem.largeTitleDisplayMode = .never를 사용하면 부드럽게 넘어갈 수 있다는 것을 알 수 있었습니다. 이런 사소한 팁은 알고 있으면 금방 넘어가는데 모르면 구현하는데 오랜 시간을 잡아먹는 것 같습니다.

더 보기 구현

didSelectRowAt 일 때 라벨이 변경되어야 하기 때문에, tableView.beginUpdates(), tableView.endUpdates() 메소드 들을 사용했지만, 현재는 대신 사용할 수 있는 메소드가 추가되어 코드에는 반영하지 못했지만 추가 해 두었습니다.

tableView.performBatchUpdates {
    tableView.scrollToRow(at: indexPath, at: .none, animated: true)
} completion: { (result) in
    if result {
        tableView.deselectRow(at: indexPath, animated: true)
    }
}

정리

언제나 볼 때는 매우 간단하지만 실제 구현해보면, 디테일한 부분을 구현하기 위해 공부해야 할 것들이 많음을 느낍니다. 다음에 까먹었을 때, 어디를 다시 공부해야 하는지라도 알 수 있도록 남겨두겠습니다.

작은 노하우가 쌓여 개발실력을 높여준다고 믿고 다음 앱을 구현해서 돌아오겠습니다.

참고

댓글

이 블로그의 인기 게시물

[IOS] AppDelegate는 뭐하는 녀석이지?

[git] git의 upstream과 origin 헷갈리는 사람 손!

[git] Github 이슈 라벨(issue labels)