안녕하세요 Skillist에요.
PhotoDetailViewController에 구현한 제스처를 알아보죠
231라인 : 제스처를 추가합니다.
233라인 : Fullscreen에 대한 제스처에요.
237라인 : PullDownGesture에요. VC를 아래로 끌어내려 사라지게끔하는 제스처에요.
242라인 : 뷰를 터치하면 isFullScreen값만 토글합니다.
그럼 isFullScreen값을 토글하면 어떤 일이 일어나는지 볼까요?
네, 프로퍼티 옵저버로 willSet, didSet을 사용했습니다.
82라인 : didSet에선 StatusBar와 HomeBar를 세팅하구요
66라인 : 변경될 값에 따라서, topInfoView의 isHidden을 설정하고 애니메이션을 수행합니다. 간단하네요??
아 물론, StatusBar와 HomeBar의 hide를 설정하기 위해서 위와 같은 코드를 추가해야 해요.
다음은 PullDown Gesture를 알아볼게요.
pullDownGesture에 사용되는 프로퍼티 입니다.
89라인 : pullDown 제스처로 VC가 사라지고 있는 중이라면, topInfoView를 사라지게 합니다.
그게 아니라면, isFullScreen 프로퍼티 값을 가져와, ishidden 값을 설정합니다.
근데, 지금 보니까 isHidden 코드가 중복이네요;;;;;;;;;
103라인 : 현재 pullDown gesture에 대한 이동한 y값을 저장합니다.
다음은 pullDownGresture에 대한 메인 로직이에요.
251라인 : 제스처가 계속 진행중인 경우 수행합니다.
253라인 : 저는 VC를 아래로 내릴경우에만 dismiss할겁니다. 그럴거에요. 그래서 vc를 위로 올릴 경우(y < 0)는 거릅니다.
258라인 : pullDown에 대한 y값을 확인하여 VC의 alpha값을 설정합니다. VC를 아래로 내리면 내릴수록 투명하게 할거에요.
271라인 : 제스처가 끝난 경우 수행해요
273라인 : VC를 아래로 충분히 내렸으면, dismiss합니다.
279라인 : VC를 아래로 대충 내렸을 경우, dismiss하지 않고 VC를 다시 원상복구합니다.
이렇게 제스처를 알아봤어요. 설명해보니까 엄청 간단간단간단간단한 로직들로 보이는데, 구현할땐 엄청 머리 굴렸거든요? 엄청 간단해보이네요.
오늘도 수고하셨어요~
잘못되거나 부족한 내용 등, 피드백 감사합니다!
Skillist의 Unsplash 프로젝트
https://github.com/DeveloperSkillist/UnsplashCloneCode
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 전체 코드 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
import UIKit
class PhotoDetailViewController: UIViewController {
//PhotoListViewController의 목록 위치 변경
weak var cellChangeDelegate: CellChangeDelegate?
private lazy var topInfoView: UIView = {
let topInfoView = UIView()
topInfoView.backgroundColor = .darkGray
return topInfoView
}()
private lazy var titleLabel: UILabel = {
let uiLabel = UILabel()
uiLabel.font = .systemFont(ofSize: 25, weight: .bold)
uiLabel.textColor = .white
uiLabel.textAlignment = .center
return uiLabel
}()
private lazy var cancelButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "xmark"), for: .normal)
button.addTarget(self, action: #selector(dismissDetailView), for: .touchUpInside)
button.tintColor = .white
return button
}()
private lazy var shareButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "square.and.arrow.up"), for: .normal)
button.addTarget(self, action: #selector(sharePhoto), for: .touchUpInside)
button.tintColor = .white
return button
}()
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.minimumLineSpacing = 0
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.isPagingEnabled = true
collectionView.showsHorizontalScrollIndicator = false
collectionView.backgroundColor = .black
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(PhotoDetailCollectionViewCell.self, forCellWithReuseIdentifier: "PhotoDetailCollectionViewCell")
collectionView.layoutIfNeeded()
return collectionView
}()
var photos: [Photo] = []
var startRow = 0
//fullScreen여부에 따라 일부 view hide or show
private var isFullscreen = false {
willSet {
if !newValue {
topInfoView.isHidden = newValue
}
UIView.animate(withDuration: 0.5, animations: {
if newValue {
self.topInfoView.alpha = 0.0
} else {
self.topInfoView.alpha = 1.0
}
}) { [weak self] success in
self?.topInfoView.isHidden = newValue
}
}
didSet { //상단바와 하단 홈바 hide or show
self.setNeedsStatusBarAppearanceUpdate()
self.setNeedsUpdateOfHomeIndicatorAutoHidden()
}
}
//VC를 pullDown하여 VC 종료 중 일부 view hide or show
private var isVCDismissing: Bool = false {
willSet {
if newValue {
topInfoView.isHidden = true
topInfoView.isHidden = true
view.backgroundColor = .clear
} else {
topInfoView.isHidden = isFullscreen
topInfoView.isHidden = isFullscreen
view.backgroundColor = .black
}
}
}
//pullDown에 대한 Y위치를 저장
private var viewPullDownY: CGFloat = 0
@objc func dismissDetailView() {
self.dismiss(animated: false, completion: nil)
}
@objc func sharePhoto() {
var shareObjects: [Any] = []
shareObjects.append("unsplash_share_title".localized)
shareObjects.append(photos[currentItemRow].links.html)
let activityViewController = UIActivityViewController(activityItems: shareObjects, applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
setupLayout()
setupGesture()
}
private func setupLayout() {
view.backgroundColor = .black
[
collectionView,
topInfoView
].forEach {
view.addSubview($0)
}
topInfoView.snp.makeConstraints {
$0.top.leading.trailing.equalToSuperview()
$0.bottom.equalTo(view.safeAreaLayoutGuide.snp.top).offset(50)
}
collectionView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
[
cancelButton,
shareButton,
titleLabel
].forEach {
topInfoView.addSubview($0)
}
cancelButton.snp.makeConstraints {
$0.leading.equalTo(topInfoView).inset(20)
$0.centerY.equalTo(titleLabel)
$0.width.height.equalTo(20)
}
shareButton.snp.makeConstraints {
$0.trailing.bottom.equalTo(topInfoView).inset(20)
$0.centerY.equalTo(titleLabel)
$0.width.height.equalTo(20)
}
titleLabel.snp.makeConstraints {
$0.top.equalTo(view.safeAreaLayoutGuide.snp.top)
$0.leading.equalTo(cancelButton.snp.trailing).offset(10)
$0.trailing.equalTo(shareButton.snp.leading).offset(-10)
$0.bottom.equalTo(topInfoView.snp.bottom).inset(10)
}
titleLabel.text = photos[startRow].user.name
collectionView.reloadData()
collectionView.layoutIfNeeded()
//PhotoListViewController에서 선택한 아이템을 보여주기
collectionView.scrollToItem(at: IndexPath(row: startRow, section: 0), at: .centeredHorizontally, animated: false)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override var prefersStatusBarHidden: Bool {
return isFullscreen
}
override var prefersHomeIndicatorAutoHidden: Bool {
return isFullscreen
}
}
extension PhotoDetailViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return photos.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoDetailCollectionViewCell", for: indexPath) as? PhotoDetailCollectionViewCell else {
return UICollectionViewCell()
}
cell.setup(photo: photos[indexPath.row])
return cell
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
//사진을 변경(스크롤이 끝나면)한 경우 현재 row를 가져와 상단의 사진 정보 변경
let row = currentItemRow
titleLabel.text = photos[row].user.name
cellChangeDelegate?.changedCell(row: row)
}
var currentItemRow: Int {
//현재 row 계산하기
return Int(collectionView.contentOffset.x / collectionView.frame.size.width)
}
}
extension PhotoDetailViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
//MARK: Gesture
extension PhotoDetailViewController {
func setupGesture() {
//fullScreen toggle 제스처 등록
let fullscreenGesture = UITapGestureRecognizer(target: self, action: #selector(toggleFullScreen))
self.view.addGestureRecognizer(fullscreenGesture)
//VC PullDown 제스처 등록
let pullDownGesture = UIPanGestureRecognizer(target: self, action: #selector(pullDownDismissGesture(sender:)))
self.view.addGestureRecognizer(pullDownGesture)
}
//fullScreen toggle 제스처
@objc func toggleFullScreen() {
isFullscreen.toggle()
}
//VC PullDown 제스처
@objc func pullDownDismissGesture(sender: UIPanGestureRecognizer) {
switch sender.state {
//제스처가 변경중인 경우
case .changed:
viewPullDownY = sender.translation(in: view).y
if viewPullDownY < 0 { //VC를 위로 올릴 경우, dismiss 수행을 하지 않기 위해서 break
break
}
isVCDismissing = true //PullDown 진행중인 경우 일부 view hide
UIView.animate(
withDuration: 0.5,
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 1,
options: .curveEaseOut,
animations: {
self.view.transform = CGAffineTransform(translationX: 0, y: self.viewPullDownY)
self.view.alpha = 1 - (self.viewPullDownY / (self.view.bounds.height * 0.7)) //alpha 변경
}
)
//제스처가 끝난 경우
case .ended:
//pullDown의 위치를 확인하여, 일정 이동한 경우 dismiss 수행
if viewPullDownY >= 200 {
dismiss(animated: true)
break
}
//dismiss를 수행하지 않을 경우, alpha 변경 및 일부 view show
isVCDismissing = false
UIView.animate(
withDuration: 0.5,
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 1,
options: .curveEaseOut,
animations: {
self.view.transform = .identity
self.view.alpha = 1
}
)
default:
break
}
}
}
'iOS 개발 > Unsplash 클론 코딩' 카테고리의 다른 글
Unsplash - 11. Account 화면 (0) | 2021.12.04 |
---|---|
Unsplash - 10. underLine을 가지는 TextField (0) | 2021.12.01 |
Unsplash - 8. 사진 상세 화면(PhotoDetailViewController, Cell)의 메인 로직 (0) | 2021.12.01 |
Unsplash - 7. 사진 목록 구현하기 (0) | 2021.11.30 |
Unsplash - 6. 사진 목록 받아오기 (0) | 2021.11.30 |