드디어 최종 프로젝트가 시작되었다. 다들 의견을 다양하고 적극적으로 내주셔서 많은 내용을 정하고 진도를 빠르게 나갈 수 있었다.
이번에는 여행을 다녀오고 기록을 할 수 있는 앱을 만들어 보려고 한다. 이번 프로젝트에서도 역시 여러가지 다양한 시도를 할 예정이고 아무래도 마지막 팀 프로젝트이다 보니 후회 없이 마무리 하고 싶다.
상세 페이지 구현
내가 맡은 내용은 여행의 기록을 넣을 수 있는 상세 페이지 구현이다. 먼저 우리가 만들려고 하는 내용을 살펴 보면 여행지에 대한 정보 및 쓰고 싶은 내용과 사진, 그리고 지도가 들어가며 같이 다녀온 인물을 추가할 수 있도록 구현할 예정이다.
먼저 기본적인 UI를 구성해보자. 스크롤 뷰를 주로 사용하여 스크롤 시 입력할 수 있는 부분의 면적을 넓히고 밑에 다양한 내용을 넣을 수 있도록 구현할 예정이다.
ScrollView
let backgroundImageView = UIImageView().then {
$0.contentMode = .scaleAspectFill
$0.clipsToBounds = true
$0.backgroundColor = .black
}
let topContentView = UIView().then {
$0.backgroundColor = .clear
}
let weatherButton = UIButton(type: .system).then {
let imageConfig = UIImage.SymbolConfiguration(pointSize: 37, weight: .light)
let image = UIImage(systemName: "sun.min", withConfiguration: imageConfig)
$0.setImage(image, for: .normal)
$0.tintColor = .white
$0.contentHorizontalAlignment = .center
$0.contentVerticalAlignment = .center
}
let locationLabel = UILabel().then {
$0.text = "----"
$0.font = UIFont.systemFont(ofSize: 50)
$0.textColor = .white
}
let selectDateButton = UIButton(type: .system).then {
$0.setTitle("Select Dates", for: .normal)
$0.tintColor = .white
}
이런식으로 이전과 동일하게 컴포넌트를 코드로 생성해 구현하였다. 달라진 점이 있다면 이번에는 새로운 라이브러리인 Then을 사용한다는 것이다.
Then을 사용하므로써 중복적으로 사용해야하는 코드 내용을 줄일 수 있고 쉽게 사용할 수 있는 것 같아 좋은 라이브러리인 것 같다.
많은 내용을 코드로 구현하였으나 이전 내용과 중복되는 내용이 많으므로 생략하고 오토레이아웃 설정을 살펴보자
backgroundImageView.snp.makeConstraints {
$0.top.equalToSuperview()
$0.leading.trailing.equalTo(view.safeAreaLayoutGuide)
$0.height.equalTo(350)
}
topContentView.snp.makeConstraints {
$0.top.leading.trailing.bottom.equalToSuperview()
}
weatherStackView.snp.makeConstraints {
$0.top.equalTo(150)
$0.leading.trailing.equalTo(topContentView).inset(37)
}
아직 이미지가 들어오기 전이기 때문에 이미지가 들어올 부분에 검은 바탕의 이미지 뷰를 넣어두고 그 위에 view를 생성한 뒤 날짜 및 지역명이 나올 수 있는 스택 뷰를 올려주었다.
scrollView.snp.makeConstraints {
$0.top.equalTo(backgroundImageView.snp.bottom).offset(-40)
$0.leading.trailing.bottom.equalTo(view.safeAreaLayoutGuide)
}
contentView.snp.makeConstraints {
$0.edges.equalTo(scrollView.contentLayoutGuide)
$0.width.equalTo(scrollView.frameLayoutGuide)
$0.height.equalTo(1500)
}
그런 다음 그 밑에 부터 스크롤 뷰가 들어와 그 안에있는 콘텐츠뷰안에 다양한 콘텐츠들이 들어올 수 있도록 오토레이아웃을 짜주었다. 사진에서 보는 것과 같이 스크롤 뷰에 모서리가 둥글게 하기 위해 살짝 탑을 올린 뒤 구현하여 자연스럽게 연결될 수 있도록 하였다.
그런 다음 콘텐츠 뷰 안에 구성되어야 할 컴포넌트들을 넣어주면 어느정도 가닥이 잡히게 된다.
ScrollView Delegate 활용
스크롤뷰 델리게이트를 활용해 스크롤이 시작했을때 살짝 스크롤 뷰 부분이 위로 올라와 편집 할 수 있는 영역이 높아질 수 있도록 구현하였다.
scrollView.delegate = self
먼저 당연하게 델리게이트를 셀프로 지정해주고
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset.y
if offset > 0 {
UIView.animate(withDuration: 0.3) {
self.weatherStackView.axis = .horizontal
self.weatherStackView.spacing = 10
self.weatherStackView.alignment = .center
self.weatherStackView.snp.remakeConstraints {
$0.top.equalTo(self.view.safeAreaLayoutGuide).offset(20)
$0.centerX.equalToSuperview()
}
self.scrollView.layer.cornerRadius = 40
self.view.clipsToBounds = true
self.scrollView.snp.remakeConstraints {
$0.top.equalTo(self.weatherStackView.snp.bottom).offset(10)
$0.leading.trailing.bottom.equalTo(self.view.safeAreaLayoutGuide)
}
self.contentView.snp.remakeConstraints {
$0.edges.equalTo(self.scrollView.contentLayoutGuide)
$0.width.equalTo(self.scrollView.frameLayoutGuide)
$0.height.equalTo(1500)
}
self.backgroundImageView.snp.updateConstraints {
$0.height.equalTo(200) // 줄어든 높이
}
self.view.layoutIfNeeded()
}
} else {
UIView.animate(withDuration: 0.3) {
self.weatherStackView.axis = .vertical
self.weatherStackView.spacing = 10
self.weatherStackView.alignment = .leading
self.weatherStackView.snp.remakeConstraints {
$0.top.equalTo(150)
$0.leading.trailing.equalTo(self.topContentView).inset(37)
}
self.scrollView.snp.remakeConstraints {
$0.top.equalTo(self.backgroundImageView.snp.bottom).offset(-40)
$0.leading.trailing.bottom.equalTo(self.view.safeAreaLayoutGuide)
}
self.contentView.snp.remakeConstraints {
$0.edges.equalTo(self.scrollView.contentLayoutGuide)
$0.width.equalTo(self.scrollView.frameLayoutGuide)
$0.height.equalTo(1500)
}
self.backgroundImageView.snp.updateConstraints {
$0.height.equalTo(350) // 원래 높이로 복원
}
self.view.layoutIfNeeded()
}
}
}
스크롤이 시작된다면 상단의 배경 이미지의 높이가 줄어들고 스택뷰가 위로 올라가게 된다.
그 이후 다시 올라왔을때 원래대로 돌아와 원래 높이를 가질 수 있도록 구현한다.
애니메이션 효과를 넣어 자연스럽게 위치가 옮겨지도록 설정하면 아래와 같이 구현이 완료 된다 🙂
사진 넣기 CollectionView
이전에 버튼을 넣어 PHPicker를 활용해 사진첩을 가져오도록 구현하였으나, 사진이 들어온 이후에는 컬렉션 뷰로 변경되어 사진이 들어와야하기 때문에 이 내용을 적용하기 적합한 점은 컬렉션 뷰로 구현하는게 맞는 것 같아 수정하게 되었다.
class GalleryCollectionViewCell: UICollectionViewCell {
static let identifier = "GalleryCollectionViewCell"
let imageView = UIImageView()
let addButton = UIButton(type: .system)
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupUI() {
contentView.addSubview(imageView)
contentView.addSubview(addButton)
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
addButton.setTitle("+", for: .normal)
addButton.setTitleColor(.white, for: .normal)
addButton.titleLabel?.font = UIFont.systemFont(ofSize: 32)
addButton.backgroundColor = #colorLiteral(red: 0.7674039006, green: 0.7674039006, blue: 0.7674039006, alpha: 1)
addButton.layer.cornerRadius = 16
addButton.clipsToBounds = true
addButton.isHidden = true
addButton.isUserInteractionEnabled = false
addButton.snp.makeConstraints {
$0.edges.equalToSuperview()
}
}
func configure(with image: UIImage?) {
if let image = image {
imageView.image = image
imageView.isHidden = false
addButton.isHidden = true
} else {
imageView.isHidden = true
addButton.isHidden = false
}
}
}
사진이 들어오기 전에는 아래 사진과 같이 플러스 버튼만 보여지도록 구현한 뒤 PHPicker를 통해 사진이 들어오게 되면
이렇게 각각 다른 크기를 가진 셀을 가져오도록 구현하였다.
CollectionView Cell Custom
각각 셀별로 다른 크기를 가지기 위해 UICollectionViewDelegateFlowLayout 를 사용하여 각각 셀의 레이아웃을 설정한다.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if collectionView == galleryCollectionView {
if selectedImages.isEmpty {
return CGSize(width: 173, height: 115)
} else {
switch indexPath.row {
case 0, 3:
return CGSize(width: 173, height: 115)
case 1, 2:
return CGSize(width: 173, height: 223)
default:
return CGSize(width: 173, height: 115)
}
}
} else {
return CGSize(width: 73, height: 73)
}
}
각 셀의 인덱스 값에 따라 이렇게 구현하면 각각 셀의 크기가 달라지는 컬렉션 뷰 셀이 구현된다 🙂
ㅎㅎ 이걸 바라고 TIL을 써온 건 아니지만 막상 받아보니 뿌듯하고 요즘 살짝 지치는 감이 없지 않았는데 다시 힘내서 갈 수 있는 동력을 얻은 것 같다!
이제 진짜 얼마 안남았으니 조금만 더 힘내보자
'◽️ Programming > T I L' 카테고리의 다른 글
[Project 일지] 여행 기록 앱 만들기 (3) - isExpanded , isUserInteractionEnabled (0) | 2024.06.03 |
---|---|
[Project 일지] 여행 기록 앱 만들기 (2) - MapKit , CustomFlowLayout (0) | 2024.05.31 |
[Project 일지] 단어장 앱 만들기 (6) (0) | 2024.05.20 |
[Project 일지] 단어장 앱 만들기 (5) (0) | 2024.05.19 |
[Project 일지] 단어장 앱 만들기 (4) (1) | 2024.05.16 |