MINRYUL
류링류링
MINRYUL
전체 방문자
오늘
어제
  • 분류 전체보기
    • Swift
      • 학습
    • iOS
      • Toy-Project
      • 학습
      • Metal
    • CS
    • TIL

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • Protocol
  • tuist
  • Clean Architecture
  • collectionView
  • AttributeText
  • urlsession
  • Existential type
  • dynamic frameworkm
  • WWDC
  • Existential any
  • RxCocoa
  • METAL
  • configuration_bulid_dir
  • WWDC 2024
  • ios
  • CollectionView Cell
  • demangle
  • opaque type
  • static framework
  • RxNimble
  • ViewStore
  • TDD
  • Custom Calendar
  • some
  • Any
  • RxTest
  • RxSwift
  • BDD
  • Swift
  • TableView Cell

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
MINRYUL

류링류링

iOS/학습

Custom Horizontal FlowLayout

2022. 6. 21. 20:11

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!
    @IBOutlet weak var collectionViewHeight: NSLayoutConstraint!
    
    let dataSource: [String] = ["아이템", "아이템111", "아이템”222", "아이템3333", "아이템1111", "아이템3123123", "아이템123123", "아이템", "아이템12313", "아이템222", "아이템31231", "아이템23333", "아이템", "아이템232332", "아이템123123", "아이템66", "아이템2", "아이템", "아이템123123", "아이템3", "아이템123312", "아이템123123213", "아이템", "아이템", "아이템321312", "아이템3", "아이", "아이템2", "아이템", "아이템", "아이템4444", "아이템", "아이템", "아이템123123"]
    
    let sectionCount: CGFloat = 5
    let sectionHight: CGFloat = 37
    var sectionWidth: CGFloat = 0
    
    let itemHeight: CGFloat = 27
    
    let itemPadding: CGFloat = 8
    let padding: CGFloat = 5
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        self._configureView()
    }
    
    private func _configureView() {
        self.sectionWidth = self.view.bounds.width
        
        self.collectionView.dataSource = self
        
//        let layout = UICollectionViewFlowLayout()
//
//        layout.estimatedItemSize = CGSize(width: 100, height: itemHight)
//        layout.minimumInteritemSpacing = itemPadding
//        layout.minimumLineSpacing = linePadding
        
        let layout = HorizontalFlowLayout()
        layout.delegate = self
        self.collectionView.collectionViewLayout = layout
        
        self._makeSectionWidth()
        self.collectionViewHeight.constant = sectionHight * sectionCount - padding * 2
    }
    
    private func _makeSectionWidth() {
        var sumSize: CGFloat = padding
        dataSource.forEach {
            sumSize += (self._makeTitleWidth(title: $0) + padding)
        }
        
        let oneLineWidth = sumSize / sectionCount
        var lastIndex: Int = 0
        var lineWitdh: CGFloat = padding
        var lineWitdhs: [CGFloat] = []
        
        dataSource.enumerated().forEach { index, item in
            let itemWidth: CGFloat = _makeTitleWidth(title: item)
            
            lineWitdh += itemWidth
            if lineWitdh > oneLineWidth {
                let widthDiff = lineWitdh - oneLineWidth
                if widthDiff < itemWidth/3 {
                    lineWitdh -= itemWidth
                    lineWitdhs.append(lineWitdh)
                    lineWitdh = itemWidth + padding
                    lastIndex = index
                } else {
                    lineWitdhs.append(lineWitdh)
                    lineWitdh = 0
                    lastIndex = index + 1
                }
            } else {
                lineWitdh += padding
            }
        }
        
        lineWitdh = padding
        for i in lastIndex..<dataSource.count {
            let itemWidth: CGFloat = _makeTitleWidth(title: dataSource[i])
            lineWitdh += itemWidth + padding
        }
        lineWitdhs.append(lineWitdh)
        
        guard let maxLineWitdh = lineWitdhs.max() else { return }
        self.sectionWidth = maxLineWitdh
    }
    
    private func _makeTitleWidth(title: String) -> CGFloat {
        return title.size(withAttributes:
                            [NSAttributedString.Key.font: UIFont.systemFont(
                                ofSize: 15, weight: .regular
                            )]).width + itemPadding * 2
    }
}

extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return dataSource.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as? CollectionViewCell else {
            return UICollectionViewCell()
        }
        
        cell.display(item: dataSource[indexPath.item])
        return cell
    }
}

extension ViewController: HorizontalFlowLayoutDelegate {
    func collectionView(
      _ collectionView: UICollectionView, sizeForPillAtIndexPath indexPath: IndexPath
    ) -> CGSize {
        let item = dataSource[indexPath.item]
        return CGSize(width: _makeTitleWidth(title: item), height: itemHeight)
    }
    
    func collectionView(
      _ collectionView: UICollectionView, insetsForItemsInSection section: Int
    ) -> UIEdgeInsets {
        return UIEdgeInsets(top: 3, left: itemPadding, bottom: 3, right: itemPadding)
    }
    
    func collectionView(
      _ collectionView: UICollectionView, itemSpacingInSection section: Int
    ) -> CGFloat {
        return padding
    }
    
    func collectionView(
      _ collectionView: UICollectionView, lineWidthSection: Int
    ) -> CGFloat {
        return sectionWidth
    }
}

 

 

import UIKit

protocol HorizontalFlowLayoutDelegate: AnyObject {
  func collectionView(
    _ collectionView: UICollectionView, sizeForPillAtIndexPath indexPath: IndexPath
  ) -> CGSize
  func collectionView(
    _ collectionView: UICollectionView, insetsForItemsInSection section: Int
  ) -> UIEdgeInsets
  func collectionView(
    _ collectionView: UICollectionView, itemSpacingInSection section: Int
  ) -> CGFloat
  func collectionView(
    _ collectionView: UICollectionView, lineWidthSection: Int
  ) -> CGFloat
}

final class HorizontalFlowLayout: UICollectionViewFlowLayout {
  
  weak var delegate: HorizontalFlowLayoutDelegate?
  
  var layoutHeight: CGFloat = 0.0
  var layoutWidth: CGFloat = 0.0
  
  private var itemCache: [UICollectionViewLayoutAttributes] = []
  
  override func prepare() {
    super.prepare()
    
    itemCache.removeAll()
    
    guard let collectionView = collectionView else {
      return
    }
    
    var layoutWidthIterator: CGFloat = 0.0
    
    for section in 0..<collectionView.numberOfSections {
      let insets: UIEdgeInsets = delegate?.collectionView(collectionView, insetsForItemsInSection: section) ?? UIEdgeInsets.zero
      let interItemSpacing: CGFloat = delegate?.collectionView(collectionView, itemSpacingInSection: section) ?? 0.0
      let maxLineWidth: CGFloat = delegate?.collectionView(collectionView, lineWidthSection: section) ?? 0.0
      var itemSize: CGSize = .zero
      layoutWidth = maxLineWidth
      for item in 0..<collectionView.numberOfItems(inSection: section) {
        let indexPath = IndexPath(item: item, section: section)
        
        itemSize = delegate?.collectionView(collectionView, sizeForPillAtIndexPath: indexPath) ?? .zero
        
        if (layoutWidthIterator + itemSize.width + insets.left + insets.right) > maxLineWidth {
          layoutWidthIterator = 0.0
          layoutHeight += itemSize.height + interItemSpacing
        }
        
        let frame = CGRect(
          x: layoutWidthIterator + insets.left,
          y: layoutHeight,
          width: itemSize.width,
          height: itemSize.height
        )
        let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
        attributes.frame = frame
        itemCache.append(attributes)
        layoutWidthIterator = layoutWidthIterator + frame.width + interItemSpacing
      }
      
      layoutWidthIterator = 0.0
    }
  }
  
  override func layoutAttributesForElements(in rect: CGRect)-> [UICollectionViewLayoutAttributes]? {
    super.layoutAttributesForElements(in: rect)
    
    var visibleLayoutAttributes: [UICollectionViewLayoutAttributes] = []
    
    for attributes in itemCache {
      if attributes.frame.intersects(rect) {
        visibleLayoutAttributes.append(attributes)
      }
    }
    
    return visibleLayoutAttributes
  }
  
  override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    super.layoutAttributesForItem(at: indexPath)
    return itemCache[indexPath.row]
  }
  
  override var collectionViewContentSize: CGSize {
    return CGSize(width: layoutWidth, height: contentHight)
  }
  
  private var contentWidth: CGFloat {
    guard let collectionView = collectionView else {
      return 0
    }
    let insets = collectionView.contentInset
    return collectionView.bounds.width - (insets.left + insets.right)
  }
  
  private var contentHight: CGFloat {
    guard let collectionView = collectionView else {
      return 0
    }
    let insets = collectionView.contentInset
    return collectionView.bounds.height - (insets.bottom + insets.top)
  }
  
  override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
    layoutHeight = 0.0
    layoutWidth = 0.0
    return true
  }
}

 

'iOS > 학습' 카테고리의 다른 글

TCA) Store vs ViewStore  (0) 2023.02.13
Storyboard 에서 Generic Type 의존성 주입하기 2  (0) 2022.08.29
RxNimble + Quick Unit Test  (0) 2022.05.26
iOS View 에서의 init 함수들  (0) 2022.05.08
iOS RxDataSource로 Custom Xib CalendarView 만들기  (0) 2022.05.07
    'iOS/학습' 카테고리의 다른 글
    • TCA) Store vs ViewStore
    • Storyboard 에서 Generic Type 의존성 주입하기 2
    • RxNimble + Quick Unit Test
    • iOS View 에서의 init 함수들
    MINRYUL
    MINRYUL
    열심히 살자

    티스토리툴바