본문 바로가기

아이폰/안드 개발자 시점

[iOS] CollectionView로 구현하는 페이징 & 리스트

반응형

안드로이드에선 페이징 기능 구현시 뷰페이저를 사용하고 리스트는 리싸이클러뷰를 사용합니다. 

iOS의 경우 페이징 과 리스트 모두 CollectionView로 구현이 가능합니다.  

페이징 기능의 예로 5초 단위로 움직이는 광고 배너 구현 코드를 첨부했으며 예제에서 일부 설정만 변경하면 스크롤이 가능한 리스트가 됩니다.  ( 언어는 스위프트를 사용했습니다 ) 

 

CollectionView 추가

코드가 아닌 Storyboard로 추가 했고,  뷰 생성시 자동으로 추가되는 CollectionView Cell은 하나의 리스트(아이템)를 뜻합니다. 

CollectionView Constraint

오토 레이아웃을 사용하여 가로 넓이는 꽉차도록 했으며 높이는 원하는 값으로 고정 했습니다.

CollectionView Constraint

 

Attributes Inspector 설정 

  • Layout(Flow) : 디폴트 값으로  변경하지 않으시면 됩니다.
  • Scroll Direction : 해당 값에 따라 리스트 스크롤의 방향을 가로, 세로로 설정할 수 있습니다.
    • Horizontal : 가로
    • Vertical : 세로
  • Indicators : 스크롤 바를 노출할지 여부 입니다. 
  • Scrolling : 리스트를 유저 액션으로 스크롤 했을때 어떻게 작동할지 설정합니다.
    1.  Scrolling Enabled : 일반 리스트처럼 스크롤 할 수 있습니다.
    2.  Paging Enabled :  하나의 아이템이 넘어가면 스크롤을 멈추도록 하는 페이징 기능을 활성화 하는데, Scrolling Enabled와 함께 사용할 경우 페이징 기능이 활성화 됩니다.  ( 저는 유저 액션이 아닌 코드로 5초간 넘길거라서 비활성화 합니다. ) 

※ 유저 클릭등의 액션을 비활성화 하고 싶은 경우 'Interaction'에 해당하는 값들을 비활성화 하면 됩니다. 

Attributes Inspector

 

Cell에 해당하는 Class 생성

별도 class를 생성해 UICollectionViewCell을 상속받도록 합니다. 

new File -> Cocoa Touch Class 선택하고 'Also create XIB file' 을 체크하면 xml 파일이 함께 생성 됩니다.

생성된 클래스에 셀의 변수 및 이벤트를 작성합니다. 

class MainAdBannerCell: UICollectionViewCell{
    @IBOutlet weak var adImg: UIButton!
    @IBOutlet weak var labelAdCnt: UILabel!
    @IBOutlet weak var viewAdCnt: UIView!
    
    @IBAction func clickAd(_ sender: UIButton) {
        print("clickAd")
    }
    
    func setCount(strIndex: String){
        viewAdCnt.layer.cornerRadius = 10
        labelAdCnt.text = strIndex
    }
}

생성한 UICollectionview cell 부분에 위에 생성한 Class를 연결하고 코드상에서 호출할때 사용할 indetifier를 기입합니다. 

 

Controller로 돌아와서 Collection View 변수 추가 및 Delegate, DataSource를 설정합니다. ( 레이아웃 크기 조정시 UICollectionViewDelegateFlowLayout도 추가 ) 

delegate와 datasource 프로토콜을 추가하면 필수로 생성해야 하는 메소드들이 있어서 컨트롤러에 에러 표시가 뜹니다. fix를 통해 추가 해줍니다. 

그리고 외부의 생성된 커스텀 셀을 collectionView에 등록합니다. ( .register 사용 ) 

class HomeController: UIViewController, UICollectionViewDelegate, 
	UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    
    @IBOutlet weak var adBannerList: UICollectionView!
    
       override func viewDidLoad() {
        super.viewDidLoad()
        
        // init Ad Banner
        adBannerList.delegate = self
        adBannerList.dataSource = self
        
        adBannerList.register(UINib(nibName: "MainAdBannerCell", bundle: nil)
                                , forCellWithReuseIdentifier: "MainAdBannerCell")
    }
    
}

 

CollectionView Item Count 설정 (  델리게이트 ) 

섹션별로 아이템을 다르게 지정할경우 section number를 체크 해서 그에 맞는 아이템 갯수를 return하면 됩니다. 

그리고 한 Controller에 2개 이상의 collectionview를 사용할 경우 아래 메소드 인자값(collectionview)과 사용하기 위해 선언해 놓은 collectionview를 비교하면 된다. 

※안드로이드와 다르게 컬렉션 뷰 마다 이벤트를 설정하지 않고 프로토콜은 상속받아서 사용하기 때문에 혼란스러울 수 있습니다.

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        
        if(collectionView == self.adBannerList){
            // 광고 배너의 경우
            return adsItems.count
        }else{
            return realTimeCarItemCount
        }
    }

Cell 그리기 ( 델리게이트 ) 

위에서 지정한 cell의 indentifier를 여기서 사용하며, 실질적인 cell에 값들을 이곳에서 설정합니다.

  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  
        let cell = adBannerList.dequeueReusableCell(withReuseIdentifier: "MainAdBannerCell", for: indexPath) as! MainAdBannerCell
        cell.adImg.imageView?.contentMode = .scaleAspectFit
        cell.adImg.setImage(adsItems[indexPath.row], for: .normal)
        cell.setCount(strIndex: "\(indexPath.row+1)/\(adsItems.count)")
            
        return cell    
    }

위 처럼 코딩하면 배너의 기본적인 설정은 완료 됐습니다. 

일정 시간 후 배너 스크롤(페이징) 하기

현재 페이지 인덱스를 저장하는 Int형 변수 nowAdsPage를 선언하고, 타이머를 이용하여 2초 마다 스크롤 하도록 했습니다.

    func startAdsBannerLoop(){
        let _ = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { _ in
            self.moveNextBanner()
        }
    }

func moveNextBanner(){
        nowAdsPage += 1
        
        if(nowAdsPage >= adsItems.count){
            // 마지막이 지난 경우
            nowAdsPage = 0
        }
        adBannerList.scrollToItem(at: NSIndexPath(item: nowAdsPage, section: 0) as IndexPath, at: .right, animated: true)
        
    }

 

추가 설정 

추가적으로 상세 옵션을 설정할 수 있는 함수들 입니다. 

아이템간 간격 조정 ( cell spacing ) 

       func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return -6
       }

 

셀 사이즈 조정 

위 예제와 같은 배너는 보통 화면에 꽉차게 설정하는데,  아래와 같이 작성 했습니다. 

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: adBannerList.frame.size.width, height: adBannerList.frame.height)
    }

 

유저가 스크롤시 페이지 체크

저는 Collection View에 Intraction을 비활성화 해서 유저가 스크롤 할순 없지만, 스크롤을 허용할 경우 아래 메소드를 통해 인덱스 값을 저장해야 자동 스크롤시 오작동을 막을수 있습니다.  

    //컬렉션뷰 감속 끝났을 때 현재 페이지 체크
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        nowAdsPage = Int(scrollView.contentOffset.x) / Int(scrollView.frame.width)
    }

 

iOS에서 리스트는 TableView, CollectionView 2가지로 구현이 가능합니다.

TableView의 경우 일반적이고 평범한 리스트를 구현할때 많이 쓰고 CollectionView는 grid와 같이 독특한 ui일 경우 사용하는것 같습니다. 구체적인 예는 접하는데로 포스팅해서 링크 남기도록 하겠습니다.

 

추가적으로 궁금하신 사항이나 이해가 잘 안되시는 부분은 댓글로 남겨주시면 감사하겠습니다. 

반응형