iOS Development Course - Use Swift 5 and UIKit to Build a Netflix Clone
Storyboard는 결과물을 예측하기 쉬우며, 소스코드를 일일이 파악하지 않아도 UI를 확인할 수 있다는 장점이 있다. 하지만 협업에서 충돌 문제가 발생할 가능성이 크며, 앱이 커질수록 가독성이 떨어져 코드 리뷰가 어렵다는 단점이 존재한다.
Code Base로 작업할 시, 협업에서의 충돌 문제로부터 Storyboard에 비해 자유로운 편이며, 재사용성 또한 높다. Storyboard에 비해 숙련되기까지 어렵다는 단점이 존재하지만, 작업 전체에 통제감을 줄 수 있기 때문에 현업에서는 Code Base로 작업을 하는 경우가 많다.
개인적으로도 SwiftUI에 익숙하기 때문에 코드를 베이스로 하는 작업이 매력적으로 느껴졌다.
// HomeViewControllr.swift
import UIKit
class HomeViewController: UIViewController {
// 클로저 패턴
private let homeFeedTable: UITableView = {
let table = UITableView(frame: .zero, style: .grouped)
table.register(CollectionViewTableViewCell.self, forCellReuseIdentifier: CollectionViewTableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
// 스토리 보드 없이 레이아웃을 구성할 때, 뷰 위에 다른 뷰를 추가하는 경우 addSubview()를 사용한다.
view.addSubview(homeFeedTable)
homeFeedTable.delegate = self
homeFeedTable.dataSource = self
homeFeedTable.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: 450))
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
homeFeedTable.frame = view.bounds
}
}
extension HomeViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 20
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: CollectionViewTableViewCell.identifier, for: indexPath) as? CollectionViewTableViewCell else {
return UITableViewCell()
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 40
}
}
DataSource: 데이터를 받아 이를 뷰에 그려주는 역할 → 무엇을 어떻게 보여줄 것인가?
Delegate: 동작을 제시 → 사용자가 보이는 것들 중 무언가에 대한 액션을 취한다면 그에 대한 동작 수행
UITableViewDataSource
UITableViewDelegate
간단하게 DataSource는 "보여주는" 것들을 담당하며, Delegate는 어떤 행동에 대한 "동작"을 제시한다. DataSource가 데이터를 받아 뷰를 그려주는 역할이라면(뭘 어떻게 보여줄래?), Delegate는 사용자가 보이는 부분 중 어떤 것을 클릭하거나 어떤 행동을 했을 때, 그 행동에 대한 동작을 수행하게 된다.(이 row를 클릭하면 뭘 할래?)
Fram과 Bounds 모두 UIView의 instance property이다. Frame과 Bounds 모두 타입은 CGRect 이다. 따라서 frame과 bounds는 사각형으로 그려지기 때문에 origin(x 좌표, y 좌표)과 size(width, height)를 가진다.
frame: frame은 SuperView(상위뷰)의 좌표시스템안에서 View의 위치와 크기를 나타낸다.
bounds: View의 위치와 크기를 자신만의 좌표시스템안에서 나타낸다.
그렇다면 언제 무엇을 사용하는가?
frame은 View의 위치나 크기를 설정할 때 사용한다.
bounds는 View 내부에 그림을 그릴 때, trnasformation 후 View의 크기를 알고 싶을 때, 하위 View를 정렬하는 것과 같이 내부적으로 변경하는 경우에 사용한다.
frame은 단순히 UIView와 동일한 위치나 크기가 아니라 view가 회전되었을 때도 감쌀 수 있는 사각형을 의미한다.
bounds를 옮기면 마치 subview가 움직이는 것 처럼 보이는데 이 현상은 superview의 frame은 변하지 않고 그대로 있되, subview들을 그리는 좌표계의 기준이 기준이 달라졌기 때문이다.
frame의 위치를 변경하면 뷰가 이동하지만, bounds의 경우 뷰포트가 이동한다. 50pt 만큼 x 좌표를 증가시키면 실제로는 50pt 만큼 왼쪽으로 이동한 것 처럼 보인다.
대표적으로 스크롤 뷰를 사용할 때 bounds가 사용된다. 스크롤 뷰는 스크롤 될 때마다 스크롤 뷰의 bounds를 업데이트 한다. 만약 왼쪽으로 스와이프해서 스크롤하면 bounds의 x 좌표가 증가한다.
UIViewController는 UIKit으로 구성된 앱의 뷰 계층관계를 관리하고 뷰를 그리는 로직을 담고 있다. (앱 화면의 콘테츠를 표시하는 로직과 담당하는 객체이다.)
앱에는 하나 이상의 ViewController가 존재하며, 각가의 ViewController는 생명주기를 가지고 있다. 생명주기는 각가의 ViewController가 화면에 나타나거나 사라지는 것을 의미한다.
그림의 흐름에서 Appearing은 화면에 뷰가 나타나는 중을 나타내며, Appeared는 뷰가 화면에 나타나는게 완료된 상태, Disappearing은 뷰가 화면에서 사라지는 중, Disappeared는 뷰가 화면에서 사라진 상태를 의미한다.
각각의 상태로 변경될 때 그림에 표시된 method가 iOS 시스템에 의해 자동으로 호출된다.
순환적으로 발생하기 때문에 화면 전환에 따라 발생해야 하는 로직을 적절한 곳에서 실행시켜야 한다.
viewDidLoad()
viewWillAppear(_:)
viewDidAppear(_:)
viewWillDisappear(_:)
viewDidDisappear(_:)
Neflix를 사용해본 사람이라면, 바로 Home 화면의 그것이 떠오를 것이다.
클론 코딩은 장단점이 참 명확하기에 장점만을 잘 취할 수 있도록 더욱 UIkit 및 Code Base의 앱 개발 지식에 대한 정리를 잘 해두어야겠다. 잘 활용한다면 충분히 좋은 학습 방법이라고 생각한다.
다음 포스트에서는 Table의 HeaderView를 디테일하게 작업하는 것에서부터 생기는 궁금증들을 이어서 정리 할 계획이다.