Build Instagram App: Part 8 (Swift 5) - 2020 - Xcode 11 - iOS Development
supplementaryView
구현func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
guard kind == UICollectionView.elementKindSectionHeader else { return UICollectionReusableView() }
if indexPath.section == 0 {
guard let profileHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: ProfileInfoHeaderCollectionReusableView.identifier, for: indexPath) as? ProfileInfoHeaderCollectionReusableView else { return UICollectionReusableView() }
return profileHeader
} else {
guard let tabControllHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: ProfileTabsCollectionReusableView.identifier, for: indexPath) as? ProfileTabsCollectionReusableView else { return UICollectionReusableView() }
return tabControllHeader
}
}
indexPath.section
을 통해 판별 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if section == 0 {
return CGSize(width: collectionView.width, height: collectionView.height / 3)
} else {
return CGSize(width: collectionView.width, height: 65)
}
}
func configure(with model: UserPostModel) {
var imageSubscription: AnyCancellable?
imageSubscription = NetworkingManager
.download(with: model.postURL)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: NetworkingManager.handleCompletion,
receiveValue: { [weak self] data in
self?.photoImageView.image = UIImage(data: data)
imageSubscription?.cancel()
})
}
one-shot
퍼블리셔를 통해 지속적으로 퍼블리셔를 구독하지 않고 이미지 다운로드 이후 캔슬import Foundation
import Combine
class NetworkingManager {
enum NetworkingError: LocalizedError {
case badURLResponse(url: URL)
case unknown
var errorDescription: String? {
switch self {
case .badURLResponse(url: let url): return "[🔥] Bad Response from URL: \(url.absoluteString)"
case .unknown: return "[⚠️] Unknown error occured"
}
}
}
static func handleURLResponse(output: URLSession.DataTaskPublisher.Output, url: URL) throws -> Data {
guard
let response = output.response as? HTTPURLResponse,
response.statusCode >= 200 && response.statusCode < 300 else
{ throw NetworkingError.badURLResponse(url: url) }
return output.data
}
static func download(with url: URL) -> AnyPublisher<Data, Error> {
return URLSession
.shared
.dataTaskPublisher(for: url)
.tryMap({try handleURLResponse(output: $0, url: url)})
.retry(3)
.eraseToAnyPublisher()
}
static func handleCompletion(completion: Subscribers.Completion<Error>) {
switch completion {
case .failure(let error):
print(error.localizedDescription)
case .finished: break
}
}
}
import UIKit
import Combine
class PhotoCollectionViewCell: UICollectionViewCell {
static let identifier = "PhotoCollectionViewCell"
private let photoImageView: UIImageView = {
let imageView = UIImageView()
imageView.clipsToBounds = true
imageView.contentMode = .scaleAspectFill
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
photoImageView.frame = contentView.bounds
}
override func prepareForReuse() {
super.prepareForReuse()
photoImageView.image = nil
}
private func setUI() {
contentView.addSubview(photoImageView)
contentView.clipsToBounds = true
contentView.backgroundColor = .secondarySystemBackground
accessibilityLabel = "User post image"
accessibilityHint = "Double-tap to open post"
}
func configure(with image: UIImage) {
photoImageView.image = image
}
func configure(with model: UserPostModel) {
var imageSubscription: AnyCancellable?
imageSubscription = NetworkingManager
.download(with: model.postURL)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: NetworkingManager.handleCompletion,
receiveValue: { [weak self] data in
self?.photoImageView.image = UIImage(data: data)
imageSubscription?.cancel()
})
}
}
configure
함수 구현한 커스텀 컬렉션 뷰 셀import UIKit
class ProfileViewController: UIViewController {
private var collectionView: UICollectionView?
override func viewDidLoad() {
super.viewDidLoad()
setUI()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView?.frame = view.bounds
}
private func setUI() {
view.backgroundColor = .systemBackground
setNavigationBar()
setCollectionView()
}
private func setCollectionView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.sectionInset = UIEdgeInsets(top: 0, left: 1, bottom: 0, right: 1)
layout.minimumLineSpacing = 1
layout.minimumInteritemSpacing = 1
let size = (view.width - 4) / 3
layout.itemSize = CGSize(width: size, height: size)
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView?.delegate = self
collectionView?.dataSource = self
collectionView?.register(PhotoCollectionViewCell.self, forCellWithReuseIdentifier: PhotoCollectionViewCell.identifier)
collectionView?.register(ProfileInfoHeaderCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: ProfileInfoHeaderCollectionReusableView.identifier)
collectionView?.register(ProfileTabsCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: ProfileTabsCollectionReusableView.identifier)
guard let collectionView = collectionView else { return }
view.addSubview(collectionView)
}
private func setNavigationBar() {
navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "gear")?.withTintColor(UIColor.black, renderingMode: .alwaysOriginal), style: .done, target: self, action: #selector(settingButtonDidTap))
}
@objc private func settingButtonDidTap() {
let vc = SettingsViewController()
vc.title = "Settings"
navigationController?.pushViewController(vc, animated: true)
}
}
extension ProfileViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
}
}
extension ProfileViewController: UICollectionViewDelegateFlowLayout {
}
extension ProfileViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
guard kind == UICollectionView.elementKindSectionHeader else { return UICollectionReusableView() }
if indexPath.section == 0 {
guard let profileHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: ProfileInfoHeaderCollectionReusableView.identifier, for: indexPath) as? ProfileInfoHeaderCollectionReusableView else { return UICollectionReusableView() }
return profileHeader
} else {
guard let tabControllHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: ProfileTabsCollectionReusableView.identifier, for: indexPath) as? ProfileTabsCollectionReusableView else { return UICollectionReusableView() }
return tabControllHeader
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if section == 0 {
return CGSize(width: collectionView.width, height: collectionView.height / 3)
} else {
return CGSize(width: collectionView.width, height: 65)
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0 {
return 0
} else {
return 30
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PhotoCollectionViewCell.identifier, for: indexPath) as? PhotoCollectionViewCell else { return UICollectionViewCell() }
if let image = UIImage(named: "background") {
cell.configure(with: image)
}
return cell
}
}
import Foundation
enum UserPostType {
case photo, video
}
struct UserPostModel {
let identifier: String
let postType: UserPostType
let postURL: URL
let thumnailImage: URL
let caption: String?
let comments: [PostCommentModel]
let likeCount: [PostLikeModel]
let createdDate: Date
let taggedUers: [UserModel]
}