Swift for Beginners: Create Collection View in Xcode (iOS - 2022)
>
private let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 120, height: 120)
layout.minimumLineSpacing = 10
layout.scrollDirection = .vertical
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CustomCollectionViewCell.identifier)
return collectionView
}()
extension CollectionViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return viewModel.imageModels.value?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCollectionViewCell.identifier, for: indexPath) as? CustomCollectionViewCell else { return CustomCollectionViewCell() }
if let models = viewModel.imageModels.value {
let model = models[indexPath.row]
cell.configure(with: model)
}
return cell
}
}
import Foundation
import UIKit
import Combine
class CollectionViewModel {
private let urlString: String
private var cancellables = Set<AnyCancellable>()
private let picsumModels: PassthroughSubject<[PicsumModel], Never> = .init()
let imageModels: CurrentValueSubject<[ImageModel]?, Never> = .init(nil)
init(urlString: String = "https://picsum.photos/v2/list") {
self.urlString = urlString
addSubcription()
}
private func addSubcription() {
guard let url = URL(string: urlString) else { return }
URLSession
.shared
.dataTaskPublisher(for: url)
.receive(on: DispatchQueue.global(qos: .background))
.tryMap(handleOutput)
.decode(type: [PicsumModel].self, decoder: JSONDecoder())
.sink { completion in
switch completion {
case .finished: break
case .failure(let error):
print(error.localizedDescription)
}
} receiveValue: { [weak self] models in
self?.picsumModels.send(models)
}
.store(in: &cancellables)
picsumModels
.receive(on: DispatchQueue.global(qos: .background))
.sink { [weak self] models in
models.forEach { model in
self?.handlePicsumModel(with: model)
}
}
.store(in: &cancellables)
}
private func handlePicsumModel(with model: PicsumModel) {
guard let url = URL(string: model.download_url) else { return }
URLSession
.shared
.dataTaskPublisher(for: url)
.receive(on: DispatchQueue.global(qos: .background))
.tryMap(handleOutput)
.compactMap({UIImage(data: $0)})
.sink { completion in
switch completion {
case .failure(let error):
print(error.localizedDescription)
case .finished: break
}
} receiveValue: { [weak self] image in
var currentModels = self?.imageModels.value ?? []
let currentModel = ImageModel(imageId: model.id, image: image)
currentModels.append(currentModel)
self?.imageModels.send(currentModels)
}
.store(in: &cancellables)
}
private func handleOutput(output: URLSession.DataTaskPublisher.Output) throws -> Data {
guard
let response = output.response as? HTTPURLResponse,
response.statusCode >= 200 && response.statusCode < 300 else {
throw URLError(.badServerResponse)
}
return output.data
}
}
import UIKit
import Combine
class CollectionViewController: UIViewController {
private let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 120, height: 120)
layout.minimumLineSpacing = 10
layout.scrollDirection = .vertical
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CustomCollectionViewCell.identifier)
return collectionView
}()
private let viewModel: CollectionViewModel
private var cancellables = Set<AnyCancellable>()
init(viewModel: CollectionViewModel = CollectionViewModel()) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setUI()
}
private func setUI() {
view.backgroundColor = .systemRed
collectionView.delegate = self
collectionView.dataSource = self
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
viewModel
.imageModels
.receive(on: DispatchQueue.main)
.sink { [weak self] models in
self?.collectionView.reloadData()
}
.store(in: &cancellables)
}
}
extension CollectionViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
}
}
extension CollectionViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return viewModel.imageModels.value?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCollectionViewCell.identifier, for: indexPath) as? CustomCollectionViewCell else { return CustomCollectionViewCell() }
if let models = viewModel.imageModels.value {
let model = models[indexPath.row]
cell.configure(with: model)
}
return cell
}
}
import UIKit
class CustomCollectionViewCell: UICollectionViewCell {
static let identifier = "customCollectionViewCell"
private let idLabel: UILabel = {
let label = UILabel()
label.textColor = .black
label.textAlignment = .center
label.font = .preferredFont(forTextStyle: .headline)
return label
}()
private let imageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .center
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .secondarySystemGroupedBackground
contentView.addSubview(idLabel)
contentView.addSubview(imageView)
contentView.clipsToBounds = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
idLabel.frame = CGRect(x: 10, y: 0, width: contentView.frame.size.width - 15, height: contentView.frame.size.height / 2)
imageView.frame = CGRect(x: 10, y: contentView.frame.size.height / 2, width: contentView.frame.size.width - 15, height: contentView.frame.size.height / 2)
}
private func setLayout() {
idLabel.translatesAutoresizingMaskIntoConstraints = false
imageView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(idLabel)
contentView.addSubview(imageView)
idLabel.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
idLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
idLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
imageView.leadingAnchor.constraint(equalTo: idLabel.trailingAnchor).isActive = true
}
func configure(with model: ImageModel) {
idLabel.text = model.imageId
imageView.image = model.image
}
}
struct ImageModel {
let imageId: String
let image: UIImage
}
struct PicsumModel: Codable {
let id: String
let author: String
let width: Int
let height: Int
let url: String
let download_url: String
}
매우 간단한 복습 과정.