사비성(sabisung)의 컴퓨터

40개의 항목
 

무한 스크롤링

무한 스크롤이란?

우측 스크롤인 경우 마지막 항목에 도달하면 다시 처음 항목으로 돌아가서 스크롤이 되고,
좌측 스크롤인 경우 처음에 도달하면 다시 마지막 항목부터 스크롤이 되는 것을 말한다.

소개

스크롤 뷰를 이용한 무한 스크롤을 구현한 샘플 코드이다.
이미지가 1개일 경우에는 스크롤이 되지 않지만 2개 이상일 경우에는 무한으로 스크롤을 한다.
스크롤이 마지막에 도달하면 다시 첫번째 이미지부터 스크롤이 된다.
사용자가 직접 스크롤링을 하는 경우도 마찬가지다.
물론, 좌/우로 스크롤이 가능하다.

트릭

무한 스크롤이라고 해서 스크롤 항목을 무한대로 가지고 있지는 않다.
무한대면 어쩔….ㅠㅠ
단지, 트릭을 사용할 뿐이다.
즉, 스크롤을 하여 마지막 항목에 도달하면 첫번째 항목을 삭제 후 이를 다시 마지막에 추가를 해서 마치 첫번째로 돌아가는 것처럼 하는 것이다.
첫번째 항목인 경우 좌측으로 스크롤을 하는 경우에는 마지막 항목을 삭제 후 이를 다시 첫번째에 추가해서 마지막으로 돌아가는 것처럼 보이도록 하는 것이다.

구현

//
//  ViewController.swift
//  ImageRotate
//
//  Created by sabisung on 2021/06/18.
//

import UIKit

class ViewController: UIViewController {
    
    /// 스크롤되는 이미지 리스트
    let images: [UIImage] = [
        UIImage(named: "Image-1")!,
        UIImage(named: "Image-2")!,
        UIImage(named: "Image-3")!,
        UIImage(named: "Image-4")!,
        UIImage(named: "Image-5")!
    ]
    /// ImageView 리스트
    var imageViews: [UIImageView] = []
    /// 자동 스크롤 시간 간격
    let TIMER_INTERVAL: TimeInterval = 3
    /// Timer
    var timer: Timer? = nil
    /// 드래깅 여부
    var isDragging: Bool = false
    
    /// 스크롤 뷰
    @IBOutlet weak var scrollView: UIScrollView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        setup()
    }
    
    /// UI 설정
    func setup() {
        scrollView.isPagingEnabled = true
        scrollView.showsHorizontalScrollIndicator = false
        scrollView.showsVerticalScrollIndicator = false
        scrollView.delegate = self
        
        imageViews = createImageViews()
        if images.count < 3 {
            imageViews.append(contentsOf: createImageViews())
        }
        
        scrollView.isScrollEnabled = images.count > 1
        scrollView.contentSize = CGSize(width: scrollView.frame.width * CGFloat(imageViews.count), height: scrollView.frame.height)
        scrollView.contentOffset = CGPoint(x: 0, y: 0)
        
        layoutImageViews()
        
        startTimer()
    }
    
    /// 스크롤되는 이미지 뷰 생성
    /// - Returns: 이미지 뷰 리스트
    func createImageViews() -> [UIImageView] {
        return images.map { image -> UIImageView in
            let imageView = UIImageView()
            imageView.contentMode = .scaleAspectFill
            imageView.backgroundColor = .clear
            imageView.image = image
            
            scrollView.addSubview(imageView)
            
            return imageView
        }
    }
    
    /// 이미지 뷰 레이아웃 설정
    func layoutImageViews() {
        imageViews.enumerated().forEach { (index, imageView) in
            imageView.frame = CGRect(x: scrollView.frame.width * CGFloat(index),
                                     y: 0,
                                     width: scrollView.frame.width,
                                     height: scrollView.frame.height)
        }
    }
    
    /// 타이머 시작
    func startTimer() {
        guard images.count > 1 else { return }
        stopTimer()
        timer = Timer.scheduledTimer(timeInterval: TIMER_INTERVAL,
                                     target: self,
                                     selector: #selector(timerCallback),
                                     userInfo: nil,
                                     repeats: false
        )
    }
    
    /// 타이머 중지
    func stopTimer() {
        if let t = timer {
            t.invalidate()
        }
        timer = nil
    }
    
    /// 타이머 콜백
    /// - Parameter sender: 타이머
    @objc func timerCallback(_ sender: Timer) {
        var offset = scrollView.contentOffset
        offset.x += scrollView.frame.width
        scrollView.setContentOffset(offset, animated: true)
    }
}

extension ViewController: UIScrollViewDelegate {
    func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
        isDragging = true
        stopTimer()
    }
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        isDragging = false
        startTimer()
    }
    
    func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
        addImageViewAtLast()
        startTimer()
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if !isDragging {
            return
        }

        let offsetX = scrollView.contentOffset.x
        
        if offsetX > scrollView.frame.size.width * 1.5 {
            addImageViewAtLast()
        }

        if offsetX < scrollView.frame.size.width * 0.5 {
            addImageViewAtFirst()
        }
    }
    
    /// 첫번째 이미지 뷰를 삭제후 마지막에 삽입
    func addImageViewAtLast() {
        if let imageView = self.imageViews.first {
            self.imageViews.remove(at: 0)
            self.imageViews.append(imageView)
            self.layoutImageViews()
            self.scrollView.contentOffset.x -= self.scrollView.frame.width
        }
    }
    
    /// 마지막 이미지 뷰를 삭제후 처음에 삽입
    func addImageViewAtFirst() {
        if let imageView = self.imageViews.last {
            self.imageViews.removeLast()
            self.imageViews.insert(imageView, at: 0)
            self.layoutImageViews()
            self.scrollView.contentOffset.x += self.scrollView.frame.width
        }
    }
}

동작

이미지 #1

샘플 소스 다운로드

Sample Source Download (ImageRotate.zip)