Skip to content

Instantly share code, notes, and snippets.

@mendesbarreto
Created March 29, 2018 22:52
Show Gist options
  • Save mendesbarreto/eb8a542da8e765f7f8d2e2542a9b08bf to your computer and use it in GitHub Desktop.
Save mendesbarreto/eb8a542da8e765f7f8d2e2542a9b08bf to your computer and use it in GitHub Desktop.
Crete a Colletion view with item selector
//
// ViewController.swift
// ItemSelector
//
// Created by douglas.barreto on 3/26/18.
// Copyright © 2018 Douglas Mendes Barreto. All rights reserved.
//
import Foundation
import UIKit
struct ItemSelectorViewModel {
var numberList: [Int] = [Int](0...20)
}
struct ItemSelectorCellViewModel {
let title: String
}
class ItemSelectorPresenter {
}
extension UICollectionViewCell {
class var reusableIdentifier: String {
return String(describing: self)
}
}
class SelectItemItemSelectorUseCase {
func select() {
}
}
final class ItemSelectorCell: UICollectionViewCell {
let animationScaleIn = CGPoint(x: 2, y: 2)
let animationScaleOut = CGPoint(x: 1, y: 1)
private let titleLabel: UILabel = UILabel()
override var isSelected: Bool {
didSet {
if oldValue != isSelected {
animate(by: isSelected)
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupLayout()
}
private func setupLayout() {
addSubview(titleLabel)
titleLabel.anchorToFit(in: self)
titleLabel.textAlignment = .center
titleLabel.font = UIFont(name: "System", size: 15)
titleLabel.textColor = .white
backgroundColor = .black
}
required init?(coder aDecoder: NSCoder) {
fatalError("This method could not be initialized", file: #file, line: #line)
}
func bind(to item: ItemSelectorCellViewModel) {
titleLabel.text = item.title
}
private func animate(by isSelected: Bool) {
prepareToAnimate()
if isSelected {
animateSelected(withScale: animationScaleIn, andColor: .blue)
} else {
animateSelected(withScale: animationScaleOut, andColor: .white)
}
}
private func prepareToAnimate() {
transform = .identity
}
private func animateSelected(withScale scale: CGPoint, andColor color: UIColor) {
UIView.animate(withDuration: 0.35) { [weak self] in
self?.transform = CGAffineTransform(scaleX: scale.x, y: scale.y)
}
UIView.transition(with: titleLabel, duration: 0.35, options: .transitionCrossDissolve, animations: {
self.titleLabel.textColor = color
}, completion: nil)
}
}
final class ItemSelector: UIView {
var itemSelected: Int = 0
private var viewModel = ItemSelectorViewModel()
private var range: CountableClosedRange<Int>
private let collectionView: UICollectionView
private let flowLayout = UICollectionViewFlowLayout()
init(range: CountableClosedRange<Int>) {
collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
self.range = range
let screen = UIScreen.main.bounds
super.init(frame: screen)
autoresizesSubviews = true
backgroundColor = .purple
addSubview(collectionView)
setupConstraints()
setupFlowLayout()
setupCollection()
}
private func setupCollection() {
collectionView.register(ItemSelectorCell.self, forCellWithReuseIdentifier: ItemSelectorCell.reusableIdentifier)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.isScrollEnabled = true
collectionView.reloadData()
collectionView.allowsMultipleSelection = false
collectionView.allowsSelection = true
}
private func setupFlowLayout() {
let cellSize = CGSize(width: 50, height: 50)
let screenBounds = UIScreen.main.bounds
let screenCenter = screenBounds.width * 0.5
let cellCenter: CGFloat = cellSize.width * 0.5
let leftInsets = screenCenter - cellCenter
let rightInsets = screenCenter - cellCenter
flowLayout.scrollDirection = .horizontal
flowLayout.itemSize = cellSize
flowLayout.sectionInset = UIEdgeInsets(top: 0, left: leftInsets, bottom: 0, right: rightInsets)
}
convenience init() {
self.init(range: 0...0)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init?(coder aDecoder: NSCoder) not implemented")
}
func setupConstraints() {
collectionView.frame = frame
collectionView.backgroundColor = .black
collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
}
func update(range: CountableClosedRange<Int> ) {
self.range = range
}
}
extension ItemSelector: UICollectionViewDelegateFlowLayout {
//swiftlint:disable force_cast
func nextSelectedIndex () -> Int {
let collectionViewLayout = self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout
let cellWidth = collectionViewLayout.itemSize.width
let collectionViewCenterPointX: CGFloat = collectionView.bounds.size.width * 0.5
let offsetX = collectionViewLayout.sectionInset.left + 15
let collectionItemWidth: CGFloat = ( cellWidth + ( collectionViewLayout.minimumInteritemSpacing ))
var visibleCenterPositionOfScrollView: CGFloat = 0
visibleCenterPositionOfScrollView = (collectionView.contentOffset.x - offsetX) + collectionViewCenterPointX
visibleCenterPositionOfScrollView.round(.toNearestOrAwayFromZero)
var indexSelected: CGFloat = visibleCenterPositionOfScrollView / collectionItemWidth
indexSelected.round(.toNearestOrAwayFromZero)
return max(0, Int(indexSelected))
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
goToNextSelectedIndex()
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
goToNextSelectedIndex()
}
func goToNextSelectedIndex() {
collectionView.selectItem(at: IndexPath(row: nextSelectedIndex(), section: 0),
animated: true,
scrollPosition: .centeredHorizontally)
}
func collectionView (_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.selectItem(at: indexPath,
animated: true,
scrollPosition: .centeredHorizontally)
}
}
extension ItemSelector: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return viewModel.numberList.count
}
//swiftlint:disable force_cast
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cellReusableIdentifier = ItemSelectorCell.reusableIdentifier
let cell = collectionView.dequeueReusableCell(withReuseIdentifier:cellReusableIdentifier,
for: indexPath) as! ItemSelectorCell
cell.bind(to: ItemSelectorCellViewModel(title: "\(viewModel.numberList[indexPath.row])"))
return cell
}
}
final class ViewController: UIViewController {
let itemSelector = ItemSelector()
override func viewDidLoad() {
super.viewDidLoad()
itemSelector.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(itemSelector)
itemSelector.leadingAnchor(equalTo: view)
.trailingAnchor(equalTo: view)
.topAnchor(equalTo: view)
.heightAnchor.constraint(equalToConstant: 100).isActive = true
setupConstraints()
}
override func viewDidAppear(_ animated: Bool) {
itemSelector.goToNextSelectedIndex()
}
private func setupConstraints() {
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
import Foundation
import class UIKit.UIView
import struct CoreGraphics.CGFloat
extension UIView {
func startAnchor() -> Self {
translatesAutoresizingMaskIntoConstraints = false
return self
}
@discardableResult
func anchorToFit(in view: UIView) -> Self {
return startAnchor().trailingAnchor(equalTo: view).leadingAnchor(equalTo: view)
.bottomAnchor(equalTo: view)
.topAnchor(equalTo: view)
}
@discardableResult
func leadingAnchor(equalTo view: UIView, constant: CGFloat = 0) -> Self {
leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: constant).isActive = true
return self
}
@discardableResult
func trailingAnchor(equalTo view: UIView, constant: CGFloat = 0) -> Self {
trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: constant).isActive = true
return self
}
@discardableResult
func topAnchor(equalTo view: UIView, constant: CGFloat = 0) -> Self {
topAnchor.constraint(equalTo: view.topAnchor, constant: constant).isActive = true
return self
}
@discardableResult
func bottomAnchor(equalTo view: UIView, constant: CGFloat = 0) -> Self {
bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: constant).isActive = true
return self
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment