티스토리 뷰

일하는 중에 스크롤뷰에 이미지뷰를 담아서 스와이프로 넘기는 작업을 했어야 했는데요. (인스타그램처럼)

스크롤뷰 간단하게 생각했는데? 너?

그래서 정리해보는 세로 스크롤뷰(vertical scrollview), 가로 스크롤뷰(horizontal scrollview) 게시글이 되겠습니다요.


 

일단 간단하게 ViewController에 NavigationController를 embed하고,

ViewController에 버튼 두개를 만드는 것으로 시작할게요.

(귀찮아서 그냥 스토리보드 남기고 작업했어요 헤헿)

(레이아웃은 스냅킷을 사용했어영)

 

더보기
import UIKit
import SnapKit

class ViewController : UIViewController {
    let verticalButton = UIButton()
    let horizontalButton = UIButton()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setUI()
        setDetail()
    }
    
    @objc func verticalBtnTapped(_ sender: UIButton) {
        let nextVC = VerticalViewController()
        self.navigationController?.pushViewController(nextVC, animated: true)
    }
    
    @objc func horizontalBtnTapped(_ sender: UIButton) {
        let nextVC = HorizontalViewController()
        self.navigationController?.pushViewController(nextVC, animated: true)
    }
}

extension ViewController {
    func setUI() {
        [verticalButton, horizontalButton].forEach {
            view.addSubview($0)
        }
        
        verticalButton.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.top.equalToSuperview().offset(400)
            make.leading.trailing.equalToSuperview().inset(16)
        }
        
        horizontalButton.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.top.equalTo(verticalButton.snp.bottom).offset(50)
            make.leading.trailing.equalToSuperview().inset(16)
        }
    }
    
    func setDetail() {
        
        [verticalButton, horizontalButton].forEach {
            $0.setTitleColor(.white, for: .normal)
            verticalButton.backgroundColor = .black
            verticalButton.addTarget(self, action: #selector(verticalBtnTapped(_:)), for: .touchUpInside)
            verticalButton.setTitle("Vertical", for: .normal)
            horizontalButton.backgroundColor = .blue
            horizontalButton.addTarget(self, action: #selector(horizontalBtnTapped(_:)), for: .touchUpInside)
            horizontalButton.setTitle("horizontal", for: .normal)
        }
    }
}

 

스크롤뷰 레이아웃 맞추는 걸 모르겠어서 많은 글을 찾아봤는데요.

WWDC 2017에서 frameLayoutGuide, ContentLayoutGuide라는 게 나와서 scrollview의 레이아웃에 사용하라고 하더라고요?

그걸 써볼게요!

 

일단 frameLayoutGuide, ContentLayoutGuide란 뭘까요?

공식 문서의 정의는 아래와 같은데요.

https://developer.apple.com/documentation/uikit/uiscrollview

 

Apple Developer Documentation

 

developer.apple.com

 

 

frameLayoutGuide

스크롤 뷰의 변환되지 않은 프레임 사각형을 기반으로 하는 레이아웃 가이드

 

 

ContentLayoutGuide

스크롤뷰의 변환되지 않은 콘텐츠 사각형을 기반으로 하는 레이아웃 가이드

 

 

처음에는 저도 이 말이 이해가 가지 않았는데, 아래 사진을 보고 이해했답니다.

 

 

아래 분홍색의 큰 틀이 바로 contentLayoutGuide를 선택했을 때 나오는 영역인데요. 컨텐츠(사진)의 영역이기 때문에 view를 벗어나서 자리잡고 있어요. 그 안에 있는 파란색 테두리가 frameLayoutGuide입니다. view크기만큼 보이는 영역이져. 그래서 컨텐츠에 따라 contentLayoutGuide는  frameLayoutGuide와 같을 수도 있고, 더 커질 수도 있겠져?

반대로 frameLayoutGuide의 최대 크기는 스크린 크기가 되겠져?

출처 : https://www.raywenderlich.com/5758454-uiscrollview-tutorial-getting-started

 

이제 이 두개의 개념을 가지고 세로 스크롤뷰를 만들어볼게요.


보통 스크롤뷰에 UIView를 올리거나, StackView를 올리는 식으로 만드는데요. 오늘은 UIView를 올려서 만들어볼게요.

그리고 세로 스크롤뷰에 보여줄 레이블 3개와, 이미지뷰3개를 준비했어요.

더보기
//
//  VerticalViewController.swift
//  ScrollerTEst
//
//  Created by 순진이 on 2022/06/28.
//

import UIKit
import SnapKit


class VerticalViewController : UIViewController {
 
    
    let scrollView = UIScrollView()
    let contentView = UIView()
    let firstLabel = UILabel()
    let secondLabel = UILabel()
    let thirdLabel = UILabel()
    let firstImageView = UIImageView()
    let secondImageView = UIImageView()
    let thirdImageView = UIImageView()
    let lastButton = UIButton()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
    }
}

// MARK: - SetUI
extension VerticalViewController {
    final private func setUI() {
        setDetails()
        setLayout()
    }

    final private func setDetails() {
        firstLabel.text = "측면 뭉치"
        secondLabel.text = "정면 뭉치"
        thirdLabel.text = "서 있는 뭉치"
        
        [firstImageView, secondImageView, thirdImageView].forEach {
            $0.contentMode = .scaleAspectFit
            firstImageView.image = UIImage(named: "뭉치1")
            secondImageView.image = UIImage(named: "뭉치2")
            thirdImageView.image = UIImage(named: "뭉치3")
        }
        
        lastButton.setTitle("버튼입니다.", for: .normal)
        lastButton.setTitleColor(.white, for: .normal)
        lastButton.backgroundColor = .black
    }
    
    final private func setLayout() {
        
    }
}

 

 

그 후에 scrollView를 view에 더해주고, 뷰의 크기와 동일하게 만들어줍니다. 

final private func setLayout() {
    [scrollView].forEach {
        view.addSubview($0)
    }

    scrollView.snp.makeConstraints {
        $0.leading.top.trailing.bottom.equalToSuperview()
    }
}

 

 

그리고 contentView의 레이아웃을 잡아줄 때 조심해주면 되는데요.

contentView는 scrollView에 올라가서 컨텐츠를 담을 그릇이라고 생각하면 좋을 거 같아요. 아까 위에서 본 사진에서 contentLayoutGuide는 어땠나요? view보다 훨씬 컸져?

 

 

그 contentLayoutGuide에 contentView를 맞춰주면 됩니당.

 

 

우선 contentView를 view가 아닌 scrollView에 올리고(addSubview), 위에서 공부했던 contentLayoutGuide와 똑같이 맞춰줍니다. 

scrollView.addSubview(contentView)

[firstLabel, secondLabel, thirdLabel, firstImageView, secondImageView, thirdImageView, lastButton].forEach {
    contentView.addSubview($0)
}        

contentView.snp.makeConstraints {
     $0.edges.equalTo(scrollView.contentLayoutGuide)
     $0.width.equalTo(scrollView.frameLayoutGuide)
     $0.height.equalTo(1200)
}

 

 

그리고 다음으로는 contentview의 width 또는 height를 frameLayoutGuide의 width(또는 height)와 똑같이 맞춰줘야 하는데요.

 

어떤 방향(세로? 가로?)으로 스크롤을 원하는지 모르잖아요? 이때 frameLayoutGuide의 width와 맞춰주면 세로 방향, height와 맞춰주면 가로 방향 스크롤이라고 생각하면 돼요!

 

 

그리고 또 중요한 게 있는데요. (이걸 설정 안하면 제대로 스크롤이 안되더라고요.)

스크롤 방향은 정해주었지만, 도대체 어디까지(=height) 스크롤 하면 될지 모르잖아요?

그래서 높이를 정해주어야 합니당!

(만약 가로 방향 스크롤이라면 width를 정해주어야 겠져?)

 

 

스토리보드로 만들 때는 Document Outline 쪽에 제약이 완성되지 않았다는 빨간색 오류가 뜨지만, 코드로 설정할 때는 그런 알림이 없기 때문에 ㅎㅎ scrollView에도 설정하고, contentView에도 설정해보고 해서 됐어여 ㅎㅎㅎ

 

 

그리고 이제 본격적으로 컨텐트뷰에 들어갈 컴포넌트들을 추가해줍니당.

scrollView가 아닌 contentView에 addSubview해주면 되구요, contentView 안에서 레이아웃을 맞춰주면 됩니당.

        [firstLabel, secondLabel, thirdLabel, firstImageView, secondImageView, thirdImageView, lastButton].forEach {
            contentView.addSubview($0)
        }
        
        firstLabel.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.top.equalTo(contentView).offset(50)
        }
        
        firstImageView.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.top.equalTo(firstLabel.snp.bottom).offset(15)
            make.height.equalTo(300)
            make.leading.trailing.equalTo(contentView)
        }
        
        secondLabel.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.top.equalTo(firstImageView.snp.bottom).offset(50)
        }
        
        secondImageView.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.top.equalTo(secondLabel.snp.bottom).offset(15)
            make.height.equalTo(300)
            make.leading.trailing.equalToSuperview()
        }
        
        thirdLabel.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.top.equalTo(secondImageView.snp.bottom).offset(50)
        }
        
        thirdImageView.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.top.equalTo(thirdLabel.snp.bottom).offset(15)
            make.height.equalTo(300)
            make.leading.trailing.equalToSuperview()
        }
        
        lastButton.snp.makeConstraints { make in
            make.bottom.equalTo(contentView)
            make.leading.trailing.equalToSuperview().inset(20)
            
        }

 

하위 컴포넌트들은 그냥 일반적으로 레이아웃 잡아주는 것과 동일하게 잡아주면 됩니당.

 

 

전체코드는 아래에 둘게요!

더보기
import UIKit
import SnapKit

class VerticalViewController : UIViewController {
 
    
    let scrollView = UIScrollView()
    let contentView = UIView()
    let firstLabel = UILabel()
    let secondLabel = UILabel()
    let thirdLabel = UILabel()
    let firstImageView = UIImageView()
    let secondImageView = UIImageView()
    let thirdImageView = UIImageView()
    let lastButton = UIButton()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        setUI()

    }

}

// MARK: - SetUI
extension VerticalViewController {
    final private func setUI() {
        setDetails()
        setLayout()
    }

    final private func setDetails() {
        firstLabel.text = "측면 뭉치"
        secondLabel.text = "정면 뭉치"
        thirdLabel.text = "서 있는 뭉치"
        
        [firstImageView, secondImageView, thirdImageView].forEach {
            $0.contentMode = .scaleAspectFit
            firstImageView.image = UIImage(named: "뭉치1")
            secondImageView.image = UIImage(named: "뭉치2")
            thirdImageView.image = UIImage(named: "뭉치3")
        }
        
        lastButton.setTitle("버튼입니다.", for: .normal)
        lastButton.setTitleColor(.white, for: .normal)
        lastButton.backgroundColor = .black
    }
    
    final private func setLayout() {
        [scrollView].forEach {
            view.addSubview($0)
        }


        scrollView.snp.makeConstraints {
            $0.leading.top.trailing.bottom.equalToSuperview()
        }
        
        scrollView.addSubview(contentView)

        [firstLabel, secondLabel, thirdLabel, firstImageView, secondImageView, thirdImageView, lastButton].forEach {
            contentView.addSubview($0)
        }

        
        contentView.snp.makeConstraints {
            $0.edges.equalTo(scrollView.contentLayoutGuide)
            $0.width.equalTo(scrollView.frameLayoutGuide)
            $0.height.equalTo(1200)
        }
        
        firstLabel.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.top.equalTo(contentView).offset(50)
        }
        firstImageView.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.top.equalTo(firstLabel.snp.bottom).offset(15)
            make.height.equalTo(300)
            make.leading.trailing.equalTo(contentView)
        }
        
        secondLabel.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.top.equalTo(firstImageView.snp.bottom).offset(50)
        }
        
        secondImageView.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.top.equalTo(secondLabel.snp.bottom).offset(15)
            make.height.equalTo(300)
            make.leading.trailing.equalToSuperview()
        }
        
        thirdLabel.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.top.equalTo(secondImageView.snp.bottom).offset(50)
        }
        
        thirdImageView.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.top.equalTo(thirdLabel.snp.bottom).offset(15)
            make.height.equalTo(300)
            make.leading.trailing.equalToSuperview()
        }
        lastButton.snp.makeConstraints { make in
            make.bottom.equalTo(contentView)
            make.leading.trailing.equalToSuperview().inset(20)
            
        }
    }
}

일단 간단한 프로젝트는 끝!인데요. 몇 가지 해결하지 못한 궁금증도 있네여.

다음을 위해 여기에 기록해 둘게여.

 

1. contentView의 높이를 일단은 1200으로 설정하긴 했지만, 아이폰 기종마다 다른 UI가 나오지는 않을지? (그래서 lastButton의 bottom에 맞추려고 했으나 크래시 발생

 

 

2. 만약 컴포넌트의 크기가 바뀔 수 있는 경우 (때마다 통신하여 다른 길이의 텍스트를 받아온다던가)에는 높이 설정을 어떻게 할지? 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크