티스토리 뷰
collectView 코드로 짜기
import UIKit
class ViewController: UIViewController {
var colors: [UIColor] = [
.systemRed, .systemPurple, .systemBlue, .systemCyan, .systemGray, .systemMint, .systemPink, .systemBrown, .systemIndigo, .systemOrange, .systemYellow, .systemRed, .systemPurple, .systemBlue, .systemCyan, .systemGray, .systemMint, .systemPink, .systemBrown, .systemIndigo, .systemOrange, .systemYellow, .systemGreen, .systemRed
]
let flowLayout = UICollectionViewFlowLayout()
lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
// 생성에 순서를 보장받을 수 없기 때문에 collectionView를 늦게 만들어 주면 됨(lazy var). 자동 완성이 안되는 걸 보면 알 수 있음.
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
}
}
// MARK: - UICollectionViewDataSource
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return colors.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCollectionViewCell.identifier, for: indexPath) as? CustomCollectionViewCell else { fatalError() }
cell.backgroundColor = colors[indexPath.item]
cell.label.text = "\(indexPath.item)"
return cell
}
}
// MARK: - UI
extension ViewController {
final private func configureUI() {
setFlowLayout()
setConstraints()
addGesture()
}
final private func setFlowLayout() {
flowLayout.itemSize = CGSize(width: view.frame.width / 3.3, height: view.frame.height / 4)
flowLayout.minimumLineSpacing = 20 // 아이템 간의 세로 거리
flowLayout.minimumInteritemSpacing = 10 // 세로 스크롤에서 아이템 간에 가로 거리
flowLayout.sectionInset = UIEdgeInsets(top: 4, left: 4, bottom: 4, right: 4)
// flowLayout.scrollDirection = .horizontal
}
final private func setConstraints() {
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CustomCollectionViewCell.identifier)
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
}
collectionView를 생성할 때는 layout이 필요한데, collectionView의 생성 시점에 layout이 생성된다는 것을 보장할 수 없음(자동 완성이 되지 않음) -> collectionView를 lazy var로 선언

CollectionViewCell 옮기기
1. collectionView에 UILongPressGesture를 add해주기
final private func addGesture() {
let gesture = UILongPressGestureRecognizer(target: self, action: #selector(didLongPressed(_:)))
collectionView.addGestureRecognizer(gesture)
}
2. sender를 통해 다양한 값을 받아올 수 있음 (location, state 등)
@objc func didLongPressed(_ sender: UILongPressGestureRecognizer) {
let location = sender.location(in: collectionView)
print("location:",location)
switch sender.state {
case .began:
print("began")
guard let indexPath = collectionView.indexPathForItem(at: location) else { return } //indexPath가 존재하지 않는 경우도 있음
collectionView.beginInteractiveMovementForItem(at: indexPath)
print("indexPath:", indexPath)
case .changed:
print("changed")
collectionView.updateInteractiveMovementTargetPosition(location)
case .ended:
print("ended")
collectionView.endInteractiveMovement()
case .cancelled:
// 전화가 오거나, 알림문자가 오거나 했을 때
collectionView.cancelInteractiveMovement()
print("cancelled")
default:
break
}
}
.began에서 option binding을 하는 이유는 cell 이외에 곳에서 gesture가 발생할 시에는 nil이 발생하기 때문에
3. UICollectionViewDelegate 설정
별다른 코드를 치지 않더라도 해당 메서드를 선언하는 것만으로도 LongPressGesture를 통해 cell 이동이 가능
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
}
}
4. 셀 이동 시 발생하는 cell 변경에 대한 대응
상단의 셀을 옮기고 나서 스크롤을 아래로 내렸다 다시 올라가면 옮겼던 셀이 다시 제자리에 있음. 셀의 위치를 바꿨다 하더라도 셀의 재사용성 때문에 상단으로 다시 올라갔을 때 셀이 다시 그려지고, 그 때 원래 배열대로 그려지기 때문에 변하지 않는 것
-> 배열 자체도 셀의 이동과 함께 변경을 해주면 해당 문제 해결됨.
-> 옮겨진 요소(여기서는 컬러)를 해당 배열에서 제거한 후, 옮겨진 자리(indexPath)로 다시 컬러를 insert하면 됨
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
// 셀의 재사용성 때문에 셀의 위치를 바꿨다고 하더라도, 다시 위로 올렸을 때 원래 배열에 따라 다시 셀이 조정되기 때문에 변화가 반영되지 않는다.
// 옮긴 indexPath를 반영하여 배열을 변경
let color = colors.remove(at: sourceIndexPath.item) // 배열에서 아이템을 빼서
colors.insert(color, at: destinationIndexPath.item)
}
}

Delegate Pattern
A뷰컨에서 B뷰컨으로 정보를 전달하는 것은 쉽지만, 다시 B에서 A로 정보를 역전달하는 것은 쉽지 않음. 이 때 delegate pattern을 사용하면 됨
1. 정보를 전달하는 뷰컨 (여기서는 secondVC)
- 정보를 전달하는 쪽에서 protocol과 delegate를 선언해주면 됨.
- modal 형식으로 present된 secondVC가 dismiss될 때 정보를 전달하면 되므로, deinit 시점에 정보를 전달함
- 해당 시점(deinit)에 weak var로 선언한 delegate에게 프로토콜의 요구사항(didDismissed 메서드)을 실행시킴 (delegate야 이 메서드를 실행해줘1!!)
import UIKit
protocol SecondViewControllerDelegate: AnyObject {
func didDismissed(text: String)
}
class SecondViewController: UIViewController {
let label = UILabel()
weak var delegate: SecondViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
view.backgroundColor = .yellow
}
deinit {
delegate?.didDismissed(text: "🍎2번 뷰컨에서 건너옴")
}
}
//MARK: -UI
extension SecondViewController {
final private func configureUI() {
setAttributes()
setConstraints()
}
final private func setAttributes() {
label.text = "나는 두 번째 뷰컨"
}
final private func setConstraints() {
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
2. 정보를 받는 뷰컨 (여기서는 firstVC)
- firstVC는 secondVC의 delegate 변수를 self로 선언 (secondeVC.label.text = "바보"와 똑같은 동작임. secondVC의 delegate 변수는 바로 firstVC = self 라고 선언해주는 것)
- delegate를 self로 선언했기 때문에 해당 프로토콜을 선언하고, 해당 메서드를 반드시 구현해야 함 (textField, tableView, collectionView 등의 delegate와 동일하게 작동)
- firstVC가 delegate기 secondVC가 deinit 시점에 didMisssed가 불리면 firstVC에서 해당 메서드가 실행됨.
import UIKit
class FirstViewController: UIViewController {
let label = UILabel()
let button = UIButton(type: .system)
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
}
}
extension FirstViewController: SecondViewControllerDelegate {
func didDismissed(text: String) {
label.text = text
}
}
//MARK: -UI
extension FirstViewController {
final private func configureUI() {
setAttributes()
setConstraints()
}
final private func setAttributes() {
label.text = "나는 첫 번째 뷰컨"
button.setTitle("2번 뷰컨으로", for: .normal)
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
@objc func buttonTapped(_ sender: UIButton) {
let secondVC = SecondViewController()
secondVC.delegate = self
present(secondVC, animated: true)
}
final private func setConstraints() {
view.addSubview(label)
view.addSubview(button)
label.translatesAutoresizingMaskIntoConstraints = false
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor),
button.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 20),
button.widthAnchor.constraint(equalToConstant: 100),
button.heightAnchor.constraint(equalToConstant: 30),
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
}
}
delegate = self 선언을 반드시 연결되는 시점(?, 버튼을 누른다거나, 다음 화면으로 넘어간다거나)에만 해야한다는 착각을 하고 있었음. 그 시점은 언제든지 될 수 있음.
'순진이의 하루 > study정리' 카테고리의 다른 글
8. 고차함수, UIViewPropertyAnimator_2022.08.29 (0) | 2022.08.29 |
---|---|
7. UserDefaults, Animation_2022.08.24 (0) | 2022.08.24 |
4. CustomView_2022.08.10 (0) | 2022.08.09 |
3. 스토리보드 삭제, tableView cell configuration_2022.08.08 (0) | 2022.08.04 |
2. Alert TextField 추가, lazy var_2022.08.03 (0) | 2022.08.03 |
- Total
- Today
- Yesterday