[UIKit] InstagramClone: ProfileView 3

Junyoung Park·2022년 11월 7일
0

UIKit

목록 보기
87/142
post-thumbnail
post-custom-banner

Build Instagram App: Part 10 (Swift 5) - 2020 - Xcode 11 - iOS Development

InstagramClone:

구현 목표

  • 프로필 뷰의 탭 헤더 뷰 구현
  • 탭 헤더 뷰 버튼 이벤트 구현

구현 태스크

  • 탭 헤더 뷰 - 프로필 뷰 컨트롤러 컴바인 연결
  • 리스트 뷰 - 프로필 뷰의 팔로잉/팔로워 등 데이터를 통한 바인딩

핵심 코드

 private func bind(in output: AnyPublisher<ProfileTabsCollectionReusableView.Output, Never>) {
        tabOutputSubscription = output
            .receive(on: DispatchQueue.main)
            .sink(receiveValue: { [weak self] result in
                switch result {
                case .taggedButtonDidTap: break
                case .gridButtonDidTap: break
                }
            })
    }
  • 프로필 뷰 컨트롤러 단에서 탭 헤더 뷰의 특정 이벤트를 감지하는 방법
private func bind() {
        gridButton
            .tapPublisher
            .sink { [weak self] _ in
                self?.gridButton.tintColor = .systemBlue
                self?.taggedButton.tintColor = .lightGray
                self?.output.send(.gridButtonDidTap)
            }
            .store(in: &cancellables)
        taggedButton
            .tapPublisher
            .sink { [weak self] _ in
                self?.gridButton.tintColor = .lightGray
                self?.taggedButton.tintColor = .systemBlue
                self?.output.send(.taggedButtonDidTap)
            }
            .store(in: &cancellables)
    }
  • 탭 헤더 뷰의 컴포넌트 이벤트를 감지하기 위해 해당 퍼블리셔에 값을 던져줌
func transform() -> AnyPublisher<Output, Never> {
        return output.eraseToAnyPublisher()
    }
  • 뷰 컨트롤러가 해당 아웃풋을 구독, 위의 인터렉션 감지 가능
enum Output {
        case gridButtonDidTap
        case taggedButtonDidTap
    }
  • 아웃풋의 종류 이넘을 통해 관리
guard let tabControllHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: ProfileTabsCollectionReusableView.identifier, for: indexPath) as? ProfileTabsCollectionReusableView else { return UICollectionReusableView() }
            if tabOutputSubscription == nil {
                let output = tabControllHeader.transform()
                bind(in: output)
            }
            return tabControllHeader
  • 컬렉션 뷰 UI를 그릴 때 최초의 한 번만 구독을 할 수 있도록 옵셔널 타이블 선언, 널과 비교해 관리

소스 코드

import UIKit
import Combine

class ProfileTabsCollectionReusableView: UICollectionReusableView {
    struct Constants {
        static let padding: CGFloat = 8
    }
    
    enum Output {
        case gridButtonDidTap
        case taggedButtonDidTap
    }
    static let identifier = "ProfileTabsCollectionReusableView"
    private let gridButton: UIButton = {
        let button = UIButton()
        button.clipsToBounds = true
        button.tintColor = .systemBlue
        button.setBackgroundImage(UIImage(systemName: "square.grid.2x2"), for: .normal)
        return button
    }()
    private let taggedButton: UIButton = {
        let button = UIButton()
        button.clipsToBounds = true
        button.tintColor = .lightGray
        button.setBackgroundImage(UIImage(systemName: "tag"), for: .normal)
        return button
    }()
    private var cancellables = Set<AnyCancellable>()
    private let output: PassthroughSubject<Output, Never> = .init()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setUI()
        bind()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        let size = height - (Constants.padding * 2)
        let gridButtonX = ((width / 2) - size) / 2
        
        taggedButton.frame = CGRect(x: gridButtonX, y: Constants.padding, width: size, height: size)
        gridButton.frame = CGRect(x: gridButtonX + (width / 2), y: Constants.padding, width: size, height: size)
    }
    
    private func setUI() {
        backgroundColor = .systemBackground
        addSubview(gridButton)
        addSubview(taggedButton)
    }
    
    func transform() -> AnyPublisher<Output, Never> {
        return output.eraseToAnyPublisher()
    }
    
    private func bind() {
        gridButton
            .tapPublisher
            .sink { [weak self] _ in
                self?.gridButton.tintColor = .systemBlue
                self?.taggedButton.tintColor = .lightGray
                self?.output.send(.gridButtonDidTap)
            }
            .store(in: &cancellables)
        taggedButton
            .tapPublisher
            .sink { [weak self] _ in
                self?.gridButton.tintColor = .lightGray
                self?.taggedButton.tintColor = .systemBlue
                self?.output.send(.taggedButtonDidTap)
            }
            .store(in: &cancellables)
    }
}
  • 그리드 / 태그 버튼 토글링
  • 해당 버튼 클릭 이벤트를 뷰 컨트롤러 단에서 감지 가능
import UIKit
import Combine

class UserFollowTableViewCell: UITableViewCell {
    enum Output {
        case followButtonDidTap(model: String)
    }
    
    static let identifier = "UserFollowTableViewCell"
    private var cancellables = Set<AnyCancellable>()
    private let profileImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        return imageView
    }()
    private let nameLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 1
        label.font = .systemFont(ofSize: 17, weight: .semibold)
        return label
    }()
    private let userNameLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 1
        label.font = .systemFont(ofSize: 16, weight: .regular)
        return label
    }()
    private let followButton: UIButton = {
        let button = UIButton()
        return button
    }()
    private let output: PassthroughSubject<Output, Never> = .init()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setUI()
        bind()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        nameLabel.text = nil
        profileImageView.image = nil
        userNameLabel.text = nil
        followButton.setTitle(nil, for: .normal)
        followButton.backgroundColor = nil
        followButton.layer.borderWidth = 0
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
    }
    
    private func setUI() {
        contentView.addSubview(nameLabel)
        contentView.addSubview(profileImageView)
        contentView.addSubview(followButton)
        contentView.addSubview(userNameLabel)
        contentView.clipsToBounds = true
    }
    
    func configure(with model: String) {
    }
    
    private func bind() {
        
    }
    
    func transform() -> AnyPublisher<Output, Never> {
        return output.eraseToAnyPublisher()
    }
}
  • 각 리스트 내 유저 정보를 담을 셀

구현 화면

profile
JUST DO IT
post-custom-banner

0개의 댓글