이번 동물의숲 앱을 만들어보면서, 참 많은 내용을 배워가는 것 같습니다.
기존에 SwiftUI로 만들었지만 UIKit이 오랜만에 땡기더라구요. 이제부터 동물의숲은 UIKit으로 만들기로 했습니다 짜잔~~
이번 포스팅에서는 뷰를 어느정도 xib로 만들어보고, (SnapKit 한스푼으로) 뷰를 그려보겠습니다.
테이블 뷰를 그리기 위해서 어떤 것들이 필요할까요?
1. 첫번째로 테이블 뷰 자체가 있을 것 입니다.
2. 두번째는 Cell이 필요하겠구요.
3. 추가로 저는 헤더까지 해보겠습니다.
각각 새 파일을 생성하고 타입을 알맞게 지정해줍시다.
class FishView: UIView {}
class FishViewCell: UITableCell {}
class FishViewHeader: UITableViewHeaderFooterView {}
편의상 똑같은 이름의 파일을 생성해주겠습니다. View 파일을 추가해줍시다.
방금 만든 클래스와 연결을 시켜주시면 기초단계는 끝입니다.
이후에는 평소 스토리보드를 쓰는 것 처럼 레이아웃잡고 테이블뷰 올리고 IBOulet연결해주시면 끝입니다. Cell과 Header또한 같은 방법으로 만들어주시면 되겠습니다.
뷰 컨트롤러를 만들어서 방금만든 파일을 연결을 해봅시다.
typealias Header = FishViewHeader
typealias Item = FishViewCell
lazy var fishView: FishView = {
let v: FishView = .fromNib()
v.tableView.register(Item.nib, forCellReuseIdentifier: Item.reuseIdentifier)
v.tableView.register(Header.nib, forHeaderFooterViewReuseIdentifier: Header.reuseIdentifier)
return v
}()
cell과 header를 사용하기 위해 등록하는 과정에서 nib과 resueIdentifier가 필요합니다.
확장성을 위해 그리고 편의를 위해 저는 다음과 같은 Extension을 만들었습니다.
extension UIView {
static var nibName: String { String(describing: Self.self) }
static var nib: UINib { UINib(nibName: nibName, bundle: nil) }
static func fromNib<T: UIView>() -> T {
return Bundle(for: T.self).loadNibNamed(String(describing: T.self),
owner: nil,
options: nil)![0] as! T
}
}
extension UITableViewCell {
static var reuseIdentifier: String {String(describing: Self.self)}
}
extension UITableViewHeaderFooterView {
static var reuseIdentifier: String { String(describing: Self.self) }
}
테이블뷰 셀을 만들어 사용하다보면 셀의 Identifier를 하드코딩해서 사용하는 경우를 보셨나요? 이러한 실수를 줄이기 위한 방법입니다. 출력해보면 다음과 같은 결과를 얻을 수 있습니다.
이후 코드로 레이아웃을 잡아주면 뷰 구성은 끝입니다. 뷰컨에 올릴 뷰만 레이아웃을 잡아주면되겠죠?
view.addSubview(fishView)
...
fishView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide)
make.left.bottom.right.equalToSuperview()
}
기존의 Rx를 이용하지 않고 테이블뷰를 구성해야 한다면 생각보다 귀찮은 일이 됩니다.
먼저 데이터를 받을 Subject를 하나 만들어줍니다.
서버로부터 데이터를 해당변수로 넘겨주는 것까지가 API의 목표입니다.
let myData = BehaviorRelay<[Fish]>(value: [])
func getFish() async throws {
print(#function)
let url = URL(string: AddressConstants.url + parameter)
let version: String = AddressConstants.version
let myKey = Bundle.main.apiKey
var request = URLRequest(url: url!)
request.setValue(myKey, forHTTPHeaderField: "X-API-KEY")
request.setValue(version, forHTTPHeaderField: "Accept- version")
let (data, _) = try await URLSession.shared.data(for: request)
let result = try JSONDecoder().decode([Fish].self, from: data)
myData.accept(result)
}
BehaviorRelay는 초깃값을 가질 수 있습니다. 서버로부터 데이터를 받기 전 아무것도 없으니 일단 빈 배열을 가지고 있다가 값이 없을 때 progress와 같은 화면구성을 추가 할 수 있겠습니다. 또한 values값을 변경할 수 있고 최신값을 받을 수 있습니다. 무엇보다 중요한 것은 completed, .error를 발생시키지 않고 Dispose되기 전까지 작동하기 때문에 UI Event에 사용하기에 적절한 Subject Wrapper입니다. 그래서 이름이 accept입니다..! 값을 받아드리기만 하기 때문이죠. (아무튼 좋다는뜻 🤩)
자.. 데이터를 받는 함수도 만들어주었습니다.
글이 길어져 다음 포스팅에는 viewDidLoad에 돌아와 데이터를 받는 함수를 실행하고 테이블뷰와 Rx를 연결해보겠습니다.