Written by
Amy
on
on
Swiping Menu
4개의 메뉴를 좌우로 스와이프 하는 구조로 UI 구성해보기
MainViewController
- class
MainViewController
: UIViewController - Horizontal 하게 움직이는
collectionView
를 property로 가지고 있다. - 스와이프 할 때 따라서 같이 움직여줄
menuView
를 property로 가지고 있다.
import UIKit
class MainViewController: UIViewController {
let cellId = "CellId"
@IBOutlet weak var menuView: MenuView!
@IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
menuView.mainController = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
collectionView.isPagingEnabled = true
}
}
MenuView
MenuView
또한 내부에collectionView
를 가지고 있다.didSelectItemAt
되었을 때, 자신을 컨트롤 하는mainController
의scrollToMenuIndex
을 실행한다.- 스와이프 할 때 메뉴 하단에서 위치를 보여줄 horizontal Bar가 있으며, left Anchor를 조절하기 위한
horizontalBarLeftAnchorConstraint
property가 있다.
class MenuView: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
let cellId = "CellID"
let firstPageIndexPath = IndexPath(item: 0, section: 0)
lazy var collectionView: UICollectionView = {
let cv = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
cv.register(MenuCell.self, forCellWithReuseIdentifier: cellId)
cv.dataSource = self
cv.delegate = self
cv.backgroundColor = .clear
return cv
}()
var horizontalBarLeftAnchorConstraint: NSLayoutConstraint?
var mainController: MainViewController?
func setupHorizontalBar() {
let horizontalBarView = UIView()
horizontalBarView.backgroundColor = UIColor(white: 0.95, alpha: 1)
horizontalBarView.translatesAutoresizingMaskIntoConstraints = false
addSubview(horizontalBarView)
horizontalBarLeftAnchorConstraint = horizontalBarView.leftAnchor.constraint(equalTo: self.leftAnchor)
horizontalBarLeftAnchorConstraint?.isActive = true
horizontalBarView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
horizontalBarView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1/4).isActive = true
horizontalBarView.heightAnchor.constraint(equalToConstant: 4).isActive = true
}
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(collectionView)
addConstraintsWithFormat(format: "H:|[v0]|", views: collectionView)
addConstraintsWithFormat(format: "V:|[v0]|", views: collectionView)
collectionView.selectItem(at: firstPageIndexPath, animated: true, scrollPosition: [])
setupHorizontalBar()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
self.addSubview(collectionView)
addConstraintsWithFormat(format: "H:|[v0]|", views: collectionView)
addConstraintsWithFormat(format: "V:|[v0]|", views: collectionView)
collectionView.selectItem(at: firstPageIndexPath, animated: true, scrollPosition: .top)
setupHorizontalBar()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MenuCell
cell.backgroundColor = .purple
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.width/4 - 10, height: collectionView.frame.size.height)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
mainController?.scrollToMenuIndex(indexPath.item)
}
}
class MenuCell: UICollectionViewCell {
override var isSelected: Bool {
didSet {
if isSelected {
self.backgroundColor = .orange
}else {
self.backgroundColor = .purple
}
}
}
override var isHighlighted: Bool {
didSet {
if isHighlighted {
self.backgroundColor = .orange
}else {
self.backgroundColor = .purple
}
}
}
}
MainViewController 핵심 메소드
scrollViewDidScroll
: 스크롤 될 때, menuView에 있는 horizontalBar의 Left Anchor Constraint 를 조정한다.scrollViewWillEndDragging
: 스크롤 될 때targetOffset
을 통해 page index를 구해서 menuView에서도 알맞은 cell을 선택해준다.
extension MainViewController: UICollectionViewDelegateFlowLayout, UICollectionViewDelegate, UICollectionViewDataSource {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
menuView.horizontalBarLeftAnchorConstraint?.constant = scrollView.contentOffset.x / 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
cell.backgroundColor = colors[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.width, height: collectionView.frame.size.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let index = targetContentOffset.pointee.x / collectionView.frame.width
let page = IndexPath(item: Int(index), section: 0)
menuView.collectionView.selectItem(at: page, animated: true, scrollPosition: .bottom)
}
func scrollToMenuIndex(_ index: Int) {
let indexPath = IndexPath(item: index, section: 0)
collectionView.scrollToItem(at: indexPath, at: [], animated: true)
}
}
Extension
- 코드로 AutoLayout을 적용해야 하는 일이 많아졌기 떄문에 UIView를 Extension 했다.
extension UIView {
func addConstraintsWithFormat(format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
view.translatesAutoresizingMaskIntoConstraints = false
viewsDictionary[key] = view
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
}