- 최근 조그맣게 시작한 개인 프로젝트가 있다.
- 무엇인가를 키우는 사람을 위한 SNS iOS 앱을 만드는 프로젝트이다.
- 아직 기획도 채 끝나지 않은 초기 단계에 있는 프로젝트이다.
- 프로젝트를 진행하면서 사용하는 기술들을 남겨보고자 한다.
무한 스크롤링
- SNS의 최고 미덕은 '끊임없이 스크롤 되는 피드'라고 생각한다.
- 그래서 기초적인 디자인을 배치한 뒤, 바로 무한 스크롤부터 구현하기 시작했다.
- 이 무한 스크롤은 결국 흔히 '피드'라고 불리는 부분에 사용될 것이다.
- 이것을 구현하기 위해 테이블 뷰를 사용할지, 아니면 컬렉션 뷰를 사용할지 고민을 많이 했다.
- 하지만 개인 프로젝트인 만큼, 잘못되면 나중에 고치자는 생각으로
- 조금 더 간단한 테이블 뷰를 사용해서 구현하기 시작했다.
- 테이블 뷰를 오토 레이아웃으로 정렬한 뒤, 테이블 뷰 셀을 테이블 뷰에 넣었다.
- 사실 이 글을 작성할 때는 뒤의 기능들까지 구현한 상태여서 추가적인 테이블 뷰 셀이 존재한다.
구현
- 그리고 이 테이블 뷰 셀을 갖고 프로그래밍적으로 구현하기 시작한다.
- 그 전에, 해당 뷰 컨트롤러에 커스텀 클래스를 연결해주고,
- 테이블 뷰를 dataSource와 delegate 연결해준다.
- 그러면 기본 준비는 다 되었다.
- 이제 다음과 같이 코드를 구현 해준다.
import UIKit
class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate
{
@IBOutlet weak var tableView: UITableView!
var fetchingMore = false
var items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
override func viewDidLoad()
{
super.viewDidLoad()
let loadingNib = UINib(nibName: "LoadingCell", bundle: nil)
tableView.register(loadingNib, forCellReuseIdentifier: "loadingCell")
}
func numberOfSections(in tableView: UITableView) -> Int
{
return 3
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if section == 0
{
return 1
} else if section == 1
{
return items.count
} else if section == 2 && fetchingMore
{
return 1
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
if indexPath.section == 0
{
let cell = tableView.dequeueReusableCell(withIdentifier: "storyCell", for: indexPath)
return cell
} else if indexPath.section == 1
{
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath) as! FeedTableViewCell
cell.username.setTitle("User \(items[indexPath.row])", for: .normal)
return cell
} else
{
let cell = tableView.dequeueReusableCell(withIdentifier: "loadingCell", for: indexPath) as! LoadingCell
cell.spinner.startAnimating()
return cell
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView)
{
let offsetY = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
if offsetY > contentHeight - scrollView.frame.height
{
if !fetchingMore
{
beginBatchFetch()
}
}
}
func beginBatchFetch()
{
fetchingMore = true
tableView.reloadSections(IndexSet(integer: 2), with: .none)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: {
let newItems = (self.items.count...self.items.count + 10).map { index in index }
self.items.append(contentsOf: newItems)
self.fetchingMore = false
self.tableView.reloadData()
})
}
}
import UIKit
class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate
{
@IBOutlet weak var tableView: UITableView!
var fetchingMore = false
var items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
override func viewDidLoad()
{
super.viewDidLoad()
let loadingNib = UINib(nibName: "LoadingCell", bundle: nil)
tableView.register(loadingNib, forCellReuseIdentifier: "loadingCell")
}
...
}
- 이 부분은 피드 페이지를 위한 뷰 컨트롤러 HomeViewController의 시작 부분이다.
- HomeViewController 클래스를 만들고, 기본적으로 UIViewController를 상속 시켜준다.
- 그리고 테이블 뷰를 위한 클래스들을 상속 시켜준다.
- UITableViewDataSource, UITableViewDelegate를 말이다.
- 그리고 나서는 tableView라는 UITableView형의 IBOoutlet 변수를 만들어주고,
- fetchingMore 변수와 items 배열을 생성했다.
- viewDidLoad는 뷰가 화면에 로드 되면 호출되는 함수이다.
- 뷰 로드를 하면서 loadingCell을 생성하기 위해 저렇게 구현했다.
func numberOfSections(in tableView: UITableView) -> Int
{
return 3
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if section == 0
{
return 1
} else if section == 1
{
return items.count
} else if section == 2 && fetchingMore
{
return 1
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
if indexPath.section == 0
{
let cell = tableView.dequeueReusableCell(withIdentifier: "storyCell", for: indexPath)
return cell
} else if indexPath.section == 1
{
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath) as! FeedTableViewCell
cell.username.setTitle("User \(items[indexPath.row])", for: .normal)
return cell
} else
{
let cell = tableView.dequeueReusableCell(withIdentifier: "loadingCell", for: indexPath) as! LoadingCell
cell.spinner.startAnimating()
return cell
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView)
{
let offsetY = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
if offsetY > contentHeight - scrollView.frame.height
{
if !fetchingMore
{
beginBatchFetch()
}
}
}
func beginBatchFetch()
{
fetchingMore = true
tableView.reloadSections(IndexSet(integer: 2), with: .none)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: {
let newItems = (self.items.count...self.items.count + 10).map { index in index }
self.items.append(contentsOf: newItems)
self.fetchingMore = false
self.tableView.reloadData()
})
}
- 이 부분들은 tableView의 delegate 메소드들이다.