[새싹 iOS] 12주차_FSPagerView + Custom Cell

임승섭·2023년 10월 8일
0

새싹 iOS

목록 보기
26/44

이용한 라이브러리 : FSPagerView
https://github.com/WenchaoD/FSPagerView

완성본

  • 셀 하나에 이미지뷰, 레이블, 버튼 등 여러 객체가 들어간다. 즉, 커스텀 셀을 사용한다
    • 앨범 커버와 records 가 버튼 역할을 수행한다
  • 화면 전환 효과는 .linear 를 이용한다
  • 앱스토어 링크

설치

  • 기존에 라이브러리를 설치할 때는 항상 SPM만 이용했는데, FSPagerView는 SPM을 지원하지 않는다
  • 그래서 처음으로 cocoapods를 이용해서 설치했다

CocoaPods 설치

  • 먼저 맥북에 CocoaPods를 설치해야 한다
  • cocoapods 설치 : sudo gem install cocoapods
  • 버전 확인 : pod --version

라이브러리 설치

  • 프로젝트 폴더로 들어와서 원하는 라이브러리를 설치해준다
  • Podfile 생성 : pod init
  • Podfile 내부로 진입 : sudo vi Podfile
  • 알맞은 위치에 코드 작성 : pod 'FSPagerView'
  • :wq 로 내용 저장 후, pod install

  • 설치를 완료하면, 프로젝트 내에 처음 보는 파일들이 생성되어 있다
  • 기존에 프로젝트를 열 때는 .xcodeproj 확장자 파일로 열었지만, cocoapods 라이브러리를 설치한 이후에는 .xcworkspace 확장자 파일로 열어야 한다

참고


코드

Setting PagerView

let pagerView = FSPagerView()

func settingPagerView() {

    // 커스텀 셀 등록 (MainPagerViewCell 클래스는 밑에서 구현)
    pagerView.register(MainPagerViewCell.self, forCellWithReuseIdentifier: MainPagerViewCell.description())
    
	
    // 프로토콜 연결
    pagerView.dataSource = self
    pagerView.delegate = self
    
    
    // 각종 설정
    pagerView.isInfinite = true
    pagerView.transformer = FSPagerViewTransformer(type: .linear)
    
    
    // pagerView의 itemSize 설정
    let bounds = UIScreen.main.bounds
    pagerView.itemSize = CGSize(
        width: bounds.height * 0.35,
        height: bounds.height * 0.65
    )
    
    pagerView.interitemSpacing = 20
}

Custom Cell

  • 기본적으로 FSPagerViewCelltextLabelimageView 객체가 있다. 앨범 커버를 띄우기 위해 imageView를 이용했고, textLabel은 따로 사용하지 않았다
  • 이 외에 필요한 객체들은 새로 생성해서 레이아웃을 잡아주었다.
    • 객체를 생성하는 클로저 구문과 객체의 레이아웃을 잡는 코드는 생략하였다
class MainPagerViewCell: FSPagerViewCell {
    
    // 셀 내에 있는 버튼 클릭 액션을 프로토콜을 이용해서 전달한다 (아래에서 프로토콜 선언 예정)
    var parentVC: PlayButtonActionProtocol?
    
    /* ===== 객체 인스턴스 생성 ===== */
    // 1. 앨범 커버 이미지 -> 기본 imageView 사용
    // 2. 제목 레이블
    let titleLabel = {...}
    // 3. 아티스트 레이블
    let artistLabel = {...}
    // 4. 앨범 커버 위의 버튼 (투명)
   	// - 기본 imageView에 같은 크기의 투명한 버튼을 올렸다
    lazy var playButton = {...}
    // 5. 재생 / 정지 이미지
    let playImageView = {...}
    // 6. records 레이블
    let recordLabel = {...}
    // 7. records 레이블 위의 버튼 (투명)
    // - 앨범 커버와 마찬가지로 같은 크기의 투명한 버튼을 올렸다
    lazy var recordButton = {...}
    // 8. 장르 레이블 in 스택뷰
    let stackView: UIStackView = {...}
    // 8. 장르 레이블
    let genre1Label = {...}
    let genre2Label = {...}
    let genre3Label = {...}
    // 9. 음악 재생 설명 레이블
    let explainPlayLabel = {...}
    
    
 
    @objc
    func playButtonClicked() {
    	// 1. 갑자기 이미지 등장 (정지 or 재생)
        // 2. 점점 흐려지면서 없어짐
        
        if parentVC == nil { return }
        
        // (프로토콜을 이용한 함수 실행)
        parentVC?.play()

        self.playImageView.image = UIImage(systemName: (parentVC?.isPlaying ?? false) ? "play.circle" : "pause.circle")
        
        // 1.
        self.playImageView.alpha = 1
        // 2. 
        UIView.animate(withDuration: 1.5) {
            self.playImageView.alpha = 0
        } completion: { _ in
            print("finish")
        }
		
        // (프로토콜을 이용한 값전달)
        parentVC?.isPlaying.toggle()
    }
    
    @objc
    func recordButtonClicked() {
    	// (프로토콜을 이용한 함수 실행)
        parentVC?.showBottomSheet()
    }
    
    
    override func prepareForReuse() {
        // 기본 imageView를 언제든지 커스텀 상태로 유지시킨다
        self.imageView?.contentMode = .scaleAspectFit
        self.imageView?.snp.makeConstraints { make in
            make.top.horizontalEdges.equalTo(self).inset(12)
            make.height.equalTo(imageView!.snp.width)
        }
    }

    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        addSubview(titleLabel)
        addSubview(artistLabel)
        addSubview(playImageView)
        addSubview(playButton)
        addSubview(recordLabel)
        addSubview(recordButton)

        addSubview(stackView)        
        stackView.addArrangedSubview(genre1Label)
        stackView.addArrangedSubview(genre2Label)
        stackView.addArrangedSubview(genre3Label)
        
        addSubview(explainPlayLabel)
  
        if self.imageView == nil { return }
        
        /* 레이아웃 코드는 생략 */
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func designCell(_ sender: MusicItemTable) {
    	// 인스턴스 객체들에 원하는 값을 넣어준다
        if let str = sender.bigImageURL, let url = URL(string: str) {
            self.imageView?.kf.setImage(with: url)
        }
        
        titleLabel.text = sender.name
        artistLabel.text = sender.artist
        
        recordLabel.text = (sender.count > 1) ? "\(sender.count) records" : "\(sender.count) record"
        
        // ( 두 번째 인덱스 제외 ("음악") )
        for (index, item) in [genre1Label, genre2Label, genre3Label].enumerated() {
            item.isHidden = false
            
            var genreIdx = index
            if index >= 1 {
                genreIdx = index + 1
            }
            
            if genreIdx <= sender.genres.count - 1 {
                item.text = sender.genres[genreIdx]
            } else {
                item.isHidden = true
            }
        }
    }
}


Delegate, DataSource

extension PagerViewController: FSPagerViewDelegate, FSPagerViewDataSource {

	// 아이템의 개수
    func numberOfItems(in pagerView: FSPagerView) -> Int {
        return viewModel.numberOfItems()
    }
    
    // 셀 설정
    func pagerView(_ pagerView: FSPagerView, cellForItemAt index: Int) -> FSPagerViewCell {
        
        guard let cell = pagerView.dequeueReusableCell(withReuseIdentifier: MainPagerViewCell.description(), at: index) as? MainPagerViewCell else { return FSPagerViewCell() }
        
        // index로 데이터 값을 받아오고, 그대로 셀 디자인에 사용한다
        viewModel.cellForItemAt(index) { item in
            cell.designCell(item)
        }
        cell.parentVC = self
        
        return cell
    }
    
    // 선택 막기
    func pagerView(_ pagerView: FSPagerView, shouldHighlightItemAt index: Int) -> Bool {
        return false
    }
    
    // 다음 아이템으로 넘어갔을 때 
    func pagerViewDidEndDecelerating(_ pagerView: FSPagerView) {
    	// (음악을 멈추고, player를 교체하는 코드)
        player.pause()
        isPlaying = false
        replacePlayer() 
    }
}

Protocol

protocol PlayButtonActionProtocol: AnyObject {
    // 셀 클래스 내의 버튼이 눌렸을 때 뷰컨트롤러 내에서 함수가 실행되게 하기 위함
    var isPlaying: Bool { get set }
    func play()
    func showBottomSheet()
}

마무리

  • cocoapods 자체를 처음 써보기 때문에 설치하는 과정에서 약간 헤맸다
  • FSPagerView 관련 자료가 적고, 너무 오래된 자료들이라서 직접 코드를 작성해가면서 테스트했다
  • 특히 커스텀 셀을 만드는 자료는 찾지 못해서, 다른 custom cell 만드는 방법을 생각하면서 직접 구현해보았다

0개의 댓글