[UIKit] BlogPost: PostView

Junyoung Park·2022년 11월 24일
0

UIKit

목록 보기
102/142
post-thumbnail

Building Subscription Blogging App: Part 8 – View Posts/Feed (2021, Xcode 12, Swift 5) – iOS

BlogPost: PostView

구현 목표

  • 포스팅 뷰 구현

구현 태스크

  • 데이터베이스 내 블로그 포스팅 데이터 패치
  • 오토 레이아웃을 통한 다이나믹 테이블 뷰 셀 크기 조정
  • 섹션 별 셀 관리를 위한 셀 종류 이넘 관리 및 UITableViewDiffableDataSource 적용

핵심 코드

private func applyConstraints() {
        postImageView.translatesAutoresizingMaskIntoConstraints = false
        postTitleLabel.translatesAutoresizingMaskIntoConstraints = false
        let postImageViewConstraints = [
            postImageView.leadingAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.leadingAnchor),
            postImageView.widthAnchor.constraint(equalToConstant: contentView.width / 4.0),
            postImageView.heightAnchor.constraint(equalToConstant: contentView.width / 4.0),
            postImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
        ]
        NSLayoutConstraint.activate(postImageViewConstraints)
        
        let postTitleLabelConstraints = [
            postTitleLabel.topAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.topAnchor),
            postTitleLabel.leadingAnchor.constraint(equalTo: postImageView.trailingAnchor, constant: 5),
            postTitleLabel.trailingAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.trailingAnchor),
            postTitleLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: contentView.width / 4.0),
            postTitleLabel.bottomAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.bottomAnchor)
        ]
        NSLayoutConstraint.activate(postTitleLabelConstraints)
    }
  • 포스팅 프리뷰 셀을 담당하는 뷰
  • 이미지 및 라벨 텍스트 길이에 맞춰 셀 크기를 리사이즈하기 위한 contentView 크기와 매칭된 오토 레이아웃
  • 이미지 사이즈는 고정, 포스팅 라벨 타이틀 크기에 맞춰 셀 사이즈를 리사이즈 → 포스팅 라벨 사이즈가 이미지보다 작을 수 있으므로 높이 최소 값을 줌으로써 픽스
enum PostDetailSection: Int, CaseIterable, Hashable {
    case header
    case text
}

enum PostDetailItem: Hashable {
    case headerItem(PostModel)
    case textItem(PostModel)
}
  • UITableViewDiffableDataSource를 관리하기 위한 섹션 및 아이템 이넘화
  • 해시 가능하기 때문에 해당 프로토콜을 따를 수 있음
func bind(with tableView: UITableView) {
        dataSource = .init(tableView: tableView, cellProvider: { tableView, indexPath, item in
            switch item {
            case .headerItem(let post):
                guard let cell = tableView.dequeueReusableCell(withIdentifier: PostHeaderTableViewCell.identifier, for: indexPath) as? PostHeaderTableViewCell else { return nil }
                cell.configure(with: post)
                return cell
            case .textItem(let post):
                guard let cell = tableView.dequeueReusableCell(withIdentifier: PostTextTableViewCell.identifier, for: indexPath) as? PostTextTableViewCell else { return nil }
                cell.configure(with: post)
                return cell
            }
        })
        applySnapshot()
    }
    
    private func applySnapshot() {
        var snapshot = NSDiffableDataSourceSnapshot<PostDetailSection, PostDetailItem>()
        snapshot.appendSections(PostDetailSection.allCases)
        snapshot.appendItems([PostDetailItem.headerItem(post)], toSection: .header)
        snapshot.appendItems([PostDetailItem.textItem(post)], toSection: .text)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
  • 뷰 모델은 뷰 컨트롤러가 viewDidLoad 단에서 메모리에 올린 테이블 뷰를 파라미터로 건네받아 데이터 소스 이니셜라이즈
  • 스냅샷에 추가한 아이템의 종류에 따라 셀 바인딩 가능
func configure(with model: PostModel) {
        postTitleLabel.text = model.title
        if let urlString = model.headerImageURL {
            if let image = NSCacheManager.shared.getImage(with: urlString) {
                postImageView.image = image
            } else {
                guard
                    let urlString = model.headerImageURL,
                    let url = URL(string: urlString) else { return }
                URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
                    guard
                        let data = data,
                        let response = response as? HTTPURLResponse,
                        response.statusCode >= 200 && response.statusCode < 400,
                        let image = UIImage(data: data) else { return }
                    NSCacheManager.shared.setImage(with: urlString, image: image)
                    DispatchQueue.main.async { [weak self] in
                        self?.postImageView.image = image
                    }
                }
                .resume()

            }
        }
    }
  • 모델을 통해 특정 셀 UI를 패치하는 함수
  • 불필요한 이미지 다운로드를 방지하기 위한 싱글턴으로 관리하는 캐시를 사용
  • 캐시에 이미지가 있다면 캐시 사용, 그렇지 않다면 최초 다운로드 이후 해당 URL 문자열을 키값으로 캐시 내 저장
import Foundation
import UIKit

class NSCacheManager {
    static let shared = NSCacheManager()
    private var imageCache: NSCache<NSString, UIImage> = {
        let cache = NSCache<NSString, UIImage>()
        cache.countLimit = 100
        cache.totalCostLimit = 1024 * 1024 * 1024
        return cache
    }()
    private init() {}
    
    func getImage(with name: String) -> UIImage? {
        guard let image = imageCache.object(forKey: name as NSString) else { return nil }
        return image
    }
    
    func setImage(with name: String, image: UIImage) {
        imageCache.setObject(image, forKey: name as NSString)
    }
}
  • 전역으로 접근 가능하기 위한 싱글턴 구현

구현 화면

profile
JUST DO IT

0개의 댓글