지난 강의까지 우리는 API 통신을 위한 구조를 설계하고 구현했다. 이제 데이터를 받아 화면에 출력하기 위한 기법을 배운다.
목표 UI
그리드 모양으로 이뤄진 리스트를 만들 것이고 이를 MVVM 아키텍처를 이용하여 구성한다.
ViewModel
이전에 작업했던 CharacterVC에서RMService.shared.execute 메서드를 이용하여 작업했었다. 이 코드를 그대로 가져오고 약간 수정하여 사용하게 된다.
VM이 없으면 struct로 만들어서 사용하자 (빈 VM을 만들어 놨었는지 기억이 안 난다)
struct CharacterListViewViewModel {
func fetchCharacters() {
RMService.shared.execute(.listCharactersRequests, expecting: RMGetAllCharacterResponse.self) { result in
switch result {
case .success(let model):
print(model)
print("Total: "+String(model.info.pages))
print("Page result count: "+String(model.results.count))
case .failure(let error):
print(String(describing: error))
}
}
}
}
View
CollectionView와 Spinner를 제작할 것이다.
Spinner
먼저 스피너부터 설명하면 보통 로딩창에서 잠깐 뜨는 그 회전하는 대기열 같은 녀석이다. 정식으로는 UIActivityIndicator로 간단하게 스피너라고 하는 거 같다. style로 크기를 조절할 수 있고 멈추면 사라지게 하는지에 대한 여부를 넣어서 만든다. 스피너는 그 자체로는 작동하지 않으므로 startAnimating 메서드를 사용해서 동작을 정의해줘야 한다. 우리는 스피너가 가동중일 때는 컬렉션 뷰를 숨길 것이고 통신이 완료되면 다시 스피너를 지울 것이다.
지금 예제에서는 테스트를 위해 외부 요인으로 인한 변화보다 2초 후에 자동으로 컬렉션뷰를 로딩하도록 만들어 뒀다.
final class CharacterListView: UIView {
private let viewModel = CharacterListViewViewModel()
private let spinner: UIActivityIndicatorView = {
let spinner = UIActivityIndicatorView(style: .large)
spinner.hidesWhenStopped = true
spinner.translatesAutoresizingMaskIntoConstraints = false
return spinner
}()
// MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
translatesAutoresizingMaskIntoConstraints = false
addSubviews(spinner)
addConstraints()
spinner.startAnimating()
}
required init?(coder: NSCoder) {
fatalError("Unsupported")
}
private func addConstraints() {
NSLayoutConstraint.activate([
spinner.widthAnchor.constraint(equalToConstant: 100),
spinner.heightAnchor.constraint(equalToConstant: 100),
spinner.centerXAnchor.constraint(equalTo: centerXAnchor),
spinner.centerYAnchor.constraint(equalTo: centerYAnchor)
])
}
private func setUpCollectionView() {
DispatchQueue.main.asyncAfter(deadline: .now()+2, execute: {
self.spinner.stopAnimating()
})
}
}
CollectionView
여러 가지 생성법이 있겠지만 여기서는 FlowLayout으로 생성한다. 우리는 아직 안에 채워 넣을 cell을 생성하지 않았다. 임시로 생성해서 넣어주자. 이 코드를 작성하면서 인상 깊었던 것은 View의 영역에 Constrainst를 맞추는 것이었다.
final class CharacterListView: UIView {
private let viewModel = CharacterListViewViewModel()
...
private let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.isHidden = true
collectionView.alpha = 0
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
return collectionView
}()
// MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
translatesAutoresizingMaskIntoConstraints = false
addSubviews(spinner, collectionView)
addConstraints()
spinner.startAnimating()
viewModel.fetchCharacters()
setUpCollectionView()
}
required init?(coder: NSCoder) {
fatalError("Unsupported")
}
private func addConstraints() {
NSLayoutConstraint.activate([
spinner.widthAnchor.constraint(equalToConstant: 100),
spinner.heightAnchor.constraint(equalToConstant: 100),
spinner.centerXAnchor.constraint(equalTo: centerXAnchor),
spinner.centerYAnchor.constraint(equalTo: centerYAnchor),
collectionView.topAnchor.constraint(equalTo: topAnchor),
collectionView.leftAnchor.constraint(equalTo: leftAnchor),
collectionView.rightAnchor.constraint(equalTo: rightAnchor),
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
private func setUpCollectionView() {
// Self를 하거나 ViewModel의 프로토콜을 따르게 할 수도 있다.
collectionView.delegate = viewModel
}
}
View에 ViewModel 결합
우리는 데이터 소스에 VM을 지정하고 스피너가 사라지는 효과를 줄 것이다.
위에서 생성한 setUpCollectionView에 DispatchQueue를 이용하여 화면 변화를 만들어보자. 이로서 2초 후, 0.4초에 걸쳐 collectionView의 alpha 값이 1이 되면서 투명이 풀리게 된다.
private func setUpCollectionView() {
// Self를 하거나 ViewModel의 프로토콜을 따르게 할 수도 있다.
collectionView.delegate = viewModel
collectionView.dataSource = viewModel
DispatchQueue.main.asyncAfter(deadline: .now()+2, execute: {
self.spinner.stopAnimating()
self.collectionView.isHidden = false
UIView.animate(withDuration: 0.4) {
self.collectionView.alpha = 1
}
})
}
'Swift > Rick & Morty' 카테고리의 다른 글
[iOS] Rick&Morty - #8 Showing Characters (0) | 2023.03.05 |
---|---|
[iOS] Rick&Morty - #7 CollectionViewCell (0) | 2023.03.04 |
[iOS] Rick&Morty - #5 API Call (0) | 2023.03.02 |
[iOS] Rick&Morty - #4 API Request (0) | 2023.03.01 |
[iOS] Rick&Morty - #3 Data Models (0) | 2023.02.28 |