관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] UIScrollView와 UIStackView로 UITableView, UICollectionView 처럼 구현 방법 (뷰의 tag, 수평 스크롤 뷰) 본문

UI 컴포넌트 (swift)

[iOS - swift] UIScrollView와 UIStackView로 UITableView, UICollectionView 처럼 구현 방법 (뷰의 tag, 수평 스크롤 뷰)

jake-kim 2023. 2. 2. 22:23

UIScrollView와 UIStackView로 구현한 UI

구현 아이디어

  • Cell의 UI가 복잡하거나 여러가지의 타입이 있는 경우 보통 UITableView나 UICollectionView를 사용하지만, 위처럼 단순한 수평 스크롤 뷰는 UIScrollView와 UIStackView로 쉽게 구현 가능
  • 주의할점은 UITableView, UICollectionView는 Cell을 재사용하여 UI성능에 이점이 있으므로, 데이터가 많을때는 UITableView나 UICollectionView를 사용
  • 구현 핵심 부분은 데이터를 선택했을때 해당 데이터의 index를 알아내는 것인데, 이것또한 UIView의 tag를 활용하면 쉽게 구현이 가능

구현

* UI를 코드로 구현할 때 편의를 위해 아래 라이브러리 사용

pod 'SnapKit'
pod 'Then'
  • 수평 스크롤 뷰인 MyView 구현
import UIKit
import Then
import SnapKit

final class MyView: UIView {
}
  • 필요한 UI 선언
private let scrollView = UIScrollView()
private let stackView = UIStackView().then {
    $0.axis = .horizontal
    $0.spacing = 12
}
  • 필요한 프로퍼티 선언
    • items: 외부에서 데이터를 주입하면 그만큼 UIButton을 만들어서 stackView에 사용될 데이터
    • buttons: 스택뷰에 들어갈 데이터며, 저장하고 있다가 아래 selectedIndex에서 이전 버튼은 isSelected를 false로 하고 현재 선택된 버튼에는 isSelected = true로 하기 위해 선언
var items = [String]() {
    didSet { setUpData() }
}
private var buttons = [UIButton]()
private var selectedIndex: Int? {
    didSet {
        guard let selectedIndex else { return }
        
        if let oldValue {
            buttons[oldValue].isSelected = false
        }
        buttons[selectedIndex].isSelected = true
    }
}
  • 최고화 부분
init() {
    super.init(frame: .zero)
    setUp()
}
required init?(coder: NSCoder) {
    fatalError()
}
  • autolayout
    • 수평 스크롤뷰이기 때문에 stackView의 height도 고정
private func setUp() {
    addSubview(scrollView)
    scrollView.addSubview(stackView)
    
    scrollView.snp.makeConstraints {
        $0.edges.equalToSuperview()
    }
    stackView.snp.makeConstraints {
        $0.edges.height.equalToSuperview()
    }
}
  • 핵심부분인 setUpData() 구현
    • observer property인 items값이 변경되면 호출되는 메소드
var items = [String]() {
    didSet { setUpData() }
}

private func setUpData() {
    // 구현
}
  • items이 중복으로 입력될 수 있으므로, 구현에 앞서서 clearData() 메소드 호출
    • 핵심 코드는 UIButton의 tag값을 입력해주는것
private func setUpData() {
    clearData()
    items
        .enumerated()
        .map { index, title in
            let button = UIButton()
            button.setTitle(title, for: .normal)
            button.setTitleColor(.lightGray, for: .normal)
            button.setTitleColor(.blue, for: .highlighted)
            button.setTitleColor(.systemBlue, for: .selected)
            button.addTarget(self, action: #selector(tapButton), for: .touchUpInside)
            button.tag = index
            buttons.append(button)
            return button
        }
        .forEach(stackView.addArrangedSubview)
}

private func clearData() {
    buttons.forEach { $0.removeFromSuperview() }
}
  • 버튼이 눌렸을 때 tag를 활용하여 selectedIndex에 값 입력
@objc private func tapButton(_ sender: UIButton) {
    print(sender.tag)
    selectedIndex = sender.tag
}
  • 위에서 선언했던 selectedIndex에서 이전 button과 새로 선택된 버튼을 찾아서 isSelected 반영
private var selectedIndex: Int? {
    didSet {
        guard let selectedIndex else { return }
        
        if let oldValue {
            buttons[oldValue].isSelected = false
        }
        buttons[selectedIndex].isSelected = true
    }
}

(완료)

* 전체 코드: https://github.com/JK0369/ExScroll

Comments