Archive/패캠(올인원)

ch17 나의 ScrollView Guide! 상하좌우!!

lgvv 2021. 6. 29. 23:59

✅  이번시간에는 스크롤 뷰에 대해서 알아보자.

사실 예전에 스크롤 뷰를 급하게 사용할 일이 있어서 구글링해서 사용했던 적이 있는데, 그때 정확하게 이해한 것이 아니라서 다른곳에 적용할때 안되었던 기억이... 그럼 아무튼 다시 공부해보도록 할까?

 

(들어가기 앞서...)

강의에서는 Nested ScrollView(네스티드 스크롤 뷰) 라는 용어를 많이 사용하는데, 네스티드 스크롤 뷰란, 이중 스크롤 뷰라는 의미로, 스크롤 뷰에서 스크롤 뷰를 하나 더 추가한 것을 의미한다. 스크롤 뷰가 즉, 2개 있다고 생각하면 편할듯 하다.

 

 

 

✅  목차

1. 스크롤 뷰 기본 가이드 - 상하로 움직이기 (Vertical Scroll View)

2. 스크롤 뷰 Advanced - 좌우로도 움직이기 (Horizontal Scroll View)

 

 

✅ 그럼 이제 진짜 시작!! 

1️⃣  Vertical Scroll View

일단 스토리보드에서 뷰 컨트롤러에 스크롤 뷰를 추가해! 

그 이후에 레이아웃을 잡아줄건데, 그냥 상하좌우 0,0,0,0 으로 세팅하면 아래의 그림과 같이 빨간불이 들어오는 것을 볼 수 있어.

이전에는 안들어오는데 조금 이상하지? 

스크롤 뷰에서는 자기 뷰 말고 컨텐츠 뷰에 대한 제약조건도 설정해줘야 빨간색이 없어지니까 일단 넘어가면서 보자

스크롤 뷰를 스토리보드에 넣은 후 추가한 모습
스크롤 뷰의 상태

우리가 레이아웃까지 잡았다면 현재 이런 상태일거야.

일단은 우측 스크롤 뷰 쪽에서 Content Layout Guide를 꺼주도록 하자.

Content Layout Guide를 끄고 View를 추가하고 스크롤 뷰에 레이아웃을 연결한 상태

일단 Guide를 끄면 위의 사진과 다르게 스크롤 뷰에 Guide 두개가 사라진 것을 눈으로 확인할 수 있어.

여기부터 조금 헷갈릴 수가 있어서 스크롤 뷰 안에 있는 뷰를 일반 뷰, 스크롤 뷰를 포함하는 뷰를 슈퍼 뷰라고 부를게.

 

1. 그 다음에는 위의 사진처럼 View(일반 뷰)를 추가하고 레이아웃은 스크롤 뷰와 일반 뷰를 연결해서, leading과 top을 줘.

이제부터 유의해서 봐야해..!

2. ❗️(주의) 그리고 일반 뷰를 슈퍼뷰에 레이아웃을 연결해서 Equal Width를 설정해주고,

3. 그 다음에는 일반 뷰를 자기 자신으로 연결해서 Height를 잡아줘.

(⭐️ 이거 아주 중요한 역할인데, 이 크기를 조정하면 스크롤 뷰의 전체적인 길이를 설정해 줄 수 있어..!)

4. 그 다음에는 일반 뷰를 다시 스크롤 뷰로 레이아웃을 연결해서 트레일링과 바텀을 주기.

 

그럼 가장 기초적인 세팅이 끝나 -> 레이아웃 경고인 빨간색이 사라져!!

5. 일반 뷰를 슈퍼뷰에 Equal Height로 연결해주기

그런데 여기서 일반 뷰의 경우에는 컨텐츠의 성격을 가져서 높이가 바뀔 수도 있잖아?

6. 그래서 일반 뷰와 슈퍼 뷰를 연결한 제약조건을 클릭해서 priority값을 낮게 수정해 줘야해

우선순위 수정

이렇게 수정해 주면 돼. 임의의 값을 줘도, 아니면 여기 정해진 값을 줘도 돼.

 

✅  우선 6번까지 올바르게 수행했다면 스크롤 뷰는 정상적으로 작동할 예정!! 아래에는 눈으로 확인하기 위해서 작성한 부분이야

 

7. 그 다음에는 다음과 같이 imageView와 StackView를 추가해줘.

8. 눈으로 확인하기 쉬워야하니까 이미지 뷰에는 초록색 배경을 스택뷰에는 뷰를 추가한 후 그 뷰의 배경색을 보라색으로 설정해 주기.

9. 아 근데, 스토리보드에서 우리가 시뮬레이터에서 스크롤 하듯이 보고 싶을 때가 있지?

뷰 컨트롤러의 docker를 클릭한 후 시뮬레이티드 사이즈를 프리폼으로 변경후 설정해주기

 

스토리 보드에서 스크롤 뷰를 확인해보자!!

 

✅  그럼 좌우로도 이동하게끔 만들어 볼까?

2️⃣. Horizontal Scroll View

 

1. 우선 스택 뷰안에 3개의 뷰를 넣을거야.

스택뷰의 높이가 현재 600으로 설정되어 있어 -> 이건 헤제해도 돼. 그 이유는 안의 뷰들의 높이 따라서 자동으로 설정이 돼

 

2. 넣은 3개의 뷰에 Heigth를 자기 자신으로 레이아웃을 잡아서 각각 200으로 설정해주기

각 뷰의 높이는 200으로 설정해주기

3. 이제 뷰 3개를 동시에 지우고 스택 뷰 안에 컨테이너 뷰를 넣는다.

컨테이너 뷰 2개를 스택 뷰 안에 넣은 후 각 컨테이너 뷰의 높이를 잡아줄건데 200으로 한다.

컨테이너 뷰를 넣을때, 이미 생성된 컨테이너 뷰를 복사 붙여넣기를 수행하면, 파란색과, 노란색으로 표현된 뷰 컨트롤러가 생성되지 않는데, 그럴때는 뷰 컨트롤러를 하나 만든 후에, 컨테이너 뷰 중 뷰 컨틀롤러를 갖고 있지 않는 것에 연결한다.

이때 연결할때는 Embed 를 세그로 주면 된다. -> 뷰 컨트롤러 모양이 아래와 같이 작게 바뀐다.

컨테이너 뷰
스택 뷰 안에 컨테이너 뷰를 넣은 모습
Embed를 세그로 주는 모습

4. 다음에는 레이블과 컬렉션 뷰를 뷰에 넣고 컬렉션 뷰 안에 이미지 뷰를 추가한다.

레이아웃은 레이블은 탑(6), 트레일링(8)

컬렉션 뷰는 트레일링(0), 리딩(0), 바텀(6), 높이(160)으로 준다. 

❗️ 여기서 노란색 배경을 가진 컨테이너를 만들때 오류가 발생해서, 그냥 파란색 복붙한 후 사용했다.

레이아웃을 준 모습

 

5. 다음으로는 커스텀 클래스를 연결하고, 커스텀 클래스에 IBOulet을 타이틀을 연결한다.

그리고 컬렉션 뷰는 델리게이트랑, 데이터소스 뷰 컨트롤러 위임하는거 잊지말기.

커스텀 클래스 추가.

😀  RecommendListViewController.swift 코드!

//
//  RecommendListViewController.swift
//  MyNetflix
//
//  Created by Hamlit Jason on 2021/06/29.
//

import UIKit

class RecommendListViewController: UIViewController {

    @IBOutlet weak var sectionTitle: UILabel!
    let viewModel = RecommentListViewModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        updateUI()
    }
    
    func updateUI() {
        sectionTitle.text = viewModel.type.title
    }
}

extension RecommendListViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return viewModel.numOfItems
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RecommendCell", for: indexPath) as? RecommendCell else {
            return UICollectionViewCell()
        }
        
        let movie = viewModel.item(at: indexPath.item)
        cell.updateUI(movie: movie)
        return cell
    }
}


extension RecommendListViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 120, height: 160)
    }
}

class RecommentListViewModel {
    enum RecommendingType {
        case award
        case hot
        case my
        
        var title: String {
            switch self {
            case .award: return "아카데미 호평 영황"
            case .hot: return "취한저격 HOT 콘텐츠"
            case .my: return "내가 찜한 콘텐츠"
            
            }
        }
    }
    
    private (set) var type: RecommendingType = .my
    private var items: [DummyItem] = []
    
    var numOfItems: Int {
        return items.count
    }
    
    func item(at index: Int) -> DummyItem {
        return items[index]
    }
    
    func updateType(_ type: RecommendingType) {
        self.type = type
    }
    
    func fetchItems() {
        self.items = MovieFetcher.fetch(type)
    }
}

class RecommendCell: UICollectionViewCell {
    @IBOutlet weak var thumbnailImage: UIImageView!
    
    func updateUI(movie: DummyItem) {
        thumbnailImage.image = movie.thumbnail
    }
}

class MovieFetcher {
    static func fetch(_ type: RecommentListViewModel.RecommendingType) -> [DummyItem] {
        switch type {
        case .award:
            let movies = (1..<10).map { DummyItem(thumbnail: UIImage(named: "img_movie_\($0)")!) }
            return movies
        case .hot:
            let movies = (10..<19).map { DummyItem(thumbnail: UIImage(named: "img_movie_\($0)")!) }
            return movies
        case .my:
            let movies = (1..<10).map { $0 * 2 }.map { DummyItem(thumbnail: UIImage(named: "img_movie_\($0)")!) }
            return movies
        }
    }
}

struct DummyItem {
    let thumbnail: UIImage
}

 

7. 다음은 UpComingViewController.swift 를 만든 후에 아래의 코드와 같이 작성한다.

import UIKit

class UpComingViewController : UIViewController {
    
    var awardRecommendListViewController: RecommendListViewController!
    var hotRecommendListViewController: RecommendListViewController!
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "award" {
            let destinationVC = segue.destination as? RecommendListViewController
            awardRecommendListViewController = destinationVC
            awardRecommendListViewController.viewModel.updateType(.award)
            awardRecommendListViewController.viewModel.fetchItems()
        } else if segue.identifier == "hot" {
            let destinationVC = segue.destination as? RecommendListViewController
            hotRecommendListViewController = destinationVC
            hotRecommendListViewController.viewModel.updateType(.hot)
            hotRecommendListViewController.viewModel.fetchItems()
        }
    }
    
}

 

8. 이후에는 세그를 지정해 줘야겠지?

파란색 세그는 award, 노란색 세그는 hot으로 세그를 준다.

❗️시뮬레이터를 돌렸을 때, 나는 화면이 제대로 표시되지 않았는데, 코드를 다시 작성했더니 되었다.. 혹시라도 안된다면, 내 코드를 복붙해서 스토리보드 쪽을 바꿔볼 것!

파란색 - award, 노란색 - hot

 

 

이러면 일단 끝!!

 

이후에는 파이어베이스를 이용해서 검색어를 받아오는 작업을 해보자!

 

 

(추가)

스크롤 뷰에서 RecommendListViewController 가 어떻게 구성되었는지 궁금하다면...

여기서 RecommendListViewController부분을 참고하길 바래

https://rldd.tistory.com/126