iOS의 scrollView와 tableView

호씨·2024년 12월 6일
0

iOS ScrollView 사용법

1. ScrollView 개요

ScrollView는 iOS에서 콘텐츠가 화면보다 클 때 스크롤을 통해 모든 콘텐츠를 볼 수 있게 해주는 뷰이다. 주로 다음과 같은 상황에서 사용된다:

  • 긴 폼이나 설문지
  • 이미지나 텍스트가 화면을 벗어나는 경우
  • 가로 스크롤이 필요한 갤러리나 캐러셀
  • 줌인/줌아웃이 필요한 이미지 뷰어

2. ScrollView 구현하기

2.1 기본 설정

class ScrollViewController: UIViewController {
    // ScrollView 선언
    private let scrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        // 스크롤 인디케이터 설정
        scrollView.showsVerticalScrollIndicator = true
        scrollView.showsHorizontalScrollIndicator = false
        return scrollView
    }()
    
    // ContentView 선언 - 실제 내용을 담을 컨테이너 뷰
    private let contentView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupScrollView()
    }
    
    private func setupScrollView() {
        // 뷰 계층 구조 설정
        view.addSubview(scrollView)
        scrollView.addSubview(contentView)
        
        // 제약조건 설정
        NSLayoutConstraint.activate([
            // ScrollView 제약조건
            scrollView.topAnchor.constraint(equalTo: view.topAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            
            // ContentView 제약조건
            contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            // 가로 스크롤을 막기 위한 width 설정
            contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
        ])
    }
}

2.2 콘텐츠 추가하기

private func addContent() {
    // StackView를 사용하여 콘텐츠 정렬
    let stackView = UIStackView()
    stackView.axis = .vertical
    stackView.spacing = 20
    stackView.translatesAutoresizingMaskIntoConstraints = false
    
    contentView.addSubview(stackView)
    
    // StackView 제약조건
    NSLayoutConstraint.activate([
        stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
        stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
        stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
        stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20)
    ])
    
    // 콘텐츠 추가 예시
    for i in 0..<10 {
        let label = UILabel()
        label.text = "Content \(i)"
        label.textAlignment = .center
        label.backgroundColor = .systemGray6
        label.heightAnchor.constraint(equalToConstant: 100).isActive = true
        stackView.addArrangedSubview(label)
    }
}

3. ScrollView 고급 기능

3.1 스크롤 델리게이트 구현

class ScrollViewController: UIViewController, UIScrollViewDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        scrollView.delegate = self
    }
    
    // 스크롤 시작
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        print("스크롤 시작")
    }
    
    // 스크롤 중
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let offset = scrollView.contentOffset.y
        print("현재 스크롤 위치: \(offset)")
    }
    
    // 스크롤 종료
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        print("스크롤 종료")
    }
}

3.2 페이징 기능 구현

class PagingScrollViewController: UIViewController {
    private let scrollView: UIScrollView = {
        let scroll = UIScrollView()
        scroll.isPagingEnabled = true  // 페이징 활성화
        scroll.translatesAutoresizingMaskIntoConstraints = false
        return scroll
    }()
    
    private func setupPaging() {
        // 페이지 크기 설정
        let pageWidth = view.frame.width
        let pageHeight = view.frame.height
        
        // 여러 페이지 추가
        for i in 0..<3 {
            let pageView = UIView()
            pageView.translatesAutoresizingMaskIntoConstraints = false
            pageView.backgroundColor = [.red, .green, .blue][i]
            
            scrollView.addSubview(pageView)
            
            NSLayoutConstraint.activate([
                pageView.widthAnchor.constraint(equalToConstant: pageWidth),
                pageView.heightAnchor.constraint(equalToConstant: pageHeight),
                pageView.topAnchor.constraint(equalTo: scrollView.topAnchor),
                pageView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, 
                                                constant: pageWidth * CGFloat(i))
            ])
        }
        
        // 전체 콘텐츠 크기 설정
        scrollView.contentSize = CGSize(width: pageWidth * 3, height: pageHeight)
    }
}

3.3 줌 기능 구현

class ZoomableScrollViewController: UIViewController, UIScrollViewDelegate {
    private let scrollView: UIScrollView = {
        let scroll = UIScrollView()
        scroll.minimumZoomScale = 1.0
        scroll.maximumZoomScale = 3.0
        return scroll
    }()
    
    private let imageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFit
        return imageView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        scrollView.delegate = self
    }
    
    // UIScrollViewDelegate 메서드
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return imageView
    }
    
    func scrollViewDidZoom(_ scrollView: UIScrollView) {
        // 줌 시 이미지 중앙 정렬
        let offsetX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
        let offsetY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
        scrollView.contentInset = UIEdgeInsets(top: offsetY, left: offsetX, 
                                             bottom: 0, right: 0)
    }
}

4. 일반적인 문제 해결

4.1 키보드 처리

class ScrollViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        setupKeyboardHandling()
    }
    
    private func setupKeyboardHandling() {
        // 키보드 노티피케이션 등록
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(keyboardWillShow),
            name: UIResponder.keyboardWillShowNotification,
            object: nil
        )
        
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(keyboardWillHide),
            name: UIResponder.keyboardWillHideNotification,
            object: nil
        )
    }
    
    @objc private func keyboardWillShow(notification: Notification) {
        guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else {
            return
        }
        
        let contentInsets = UIEdgeInsets(
            top: 0,
            left: 0,
            bottom: keyboardFrame.height,
            right: 0
        )
        
        scrollView.contentInset = contentInsets
        scrollView.scrollIndicatorInsets = contentInsets
    }
    
    @objc private func keyboardWillHide(notification: Notification) {
        scrollView.contentInset = .zero
        scrollView.scrollIndicatorInsets = .zero
    }
}

4.2 스크롤 위치 제어

extension ScrollViewController {
    // 특정 위치로 스크롤
    func scrollToPosition(y: CGFloat) {
        let point = CGPoint(x: 0, y: y)
        scrollView.setContentOffset(point, animated: true)
    }
    
    // 특정 뷰로 스크롤
    func scrollToView(_ view: UIView) {
        let rect = scrollView.convert(view.bounds, from: view)
        scrollView.scrollRectToVisible(rect, animated: true)
    }
}

5. 성능 최적화 팁

5.1 스크롤 성능 개선

// 스크롤 중 이미지 로딩 최적화
extension ScrollViewController {
    private func optimizeScrollPerformance() {
        // 스크롤 중 이미지 로딩 일시 중지
        scrollView.decelerationRate = .fast
        
        // 메모리 사용량 최적화
        scrollView.contentInsetAdjustmentBehavior = .never
        
        // 스크롤 중 그림자 렌더링 비활성화
        view.layer.shouldRasterize = true
        view.layer.rasterizationScale = UIScreen.main.scale
    }
}

iOS TableView 사용법

1. TableView 개요

TableView는 iOS에서 가장 많이 사용되는 UI 컴포넌트 중 하나로, 데이터를 리스트 형태로 표시하는 데 사용된다. 다음과 같은 특징을 가진다:

  • 수직 스크롤이 가능한 행들의 단일 열
  • 섹션을 통한 데이터 그룹화
  • 셀 재사용을 통한 메모리 효율성
  • 다양한 인터랙션 지원 (선택, 삭제, 재정렬 등)

2. TableView 기본 구현

2.1 기본 설정

class TableViewController: UIViewController {
    // TableView 선언
    private let tableView: UITableView = {
        let table = UITableView()
        table.translatesAutoresizingMaskIntoConstraints = false
        // 기본 셀 등록
        table.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        return table
    }()
    
    // 데이터 소스
    private var items: [String] = ["Item 1", "Item 2", "Item 3"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupTableView()
    }
    
    private func setupTableView() {
        // 뷰에 TableView 추가
        view.addSubview(tableView)
        
        // 델리게이트 설정
        tableView.delegate = self
        tableView.dataSource = self
        
        // 제약조건 설정
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
}

// TableView 프로토콜 구현
extension TableViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = items[indexPath.row]
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("Selected: \(items[indexPath.row])")
        tableView.deselectRow(at: indexPath, animated: true)
    }
}

3. 커스텀 TableViewCell

3.1 커스텀 셀 구현

class CustomTableViewCell: UITableViewCell {
    static let identifier = "CustomTableViewCell"
    
    // UI 컴포넌트 정의
    private let titleLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = .systemFont(ofSize: 16, weight: .bold)
        return label
    }()
    
    private let descriptionLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = .systemFont(ofSize: 14)
        label.textColor = .gray
        return label
    }()
    
    // 초기화
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupCell()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // 셀 설정
    private func setupCell() {
        contentView.addSubview(titleLabel)
        contentView.addSubview(descriptionLabel)
        
        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12),
            titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
            
            descriptionLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),
            descriptionLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            descriptionLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
            descriptionLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12)
        ])
    }
    
    // 데이터 설정 메서드
    func configure(with item: Item) {
        titleLabel.text = item.title
        descriptionLabel.text = item.description
    }
}

4. TableView 고급 기능

4.1 섹션 구현

class SectionedTableViewController: UIViewController, UITableViewDataSource {
    // 섹션별 데이터 구조
    struct Section {
        let title: String
        var items: [String]
    }
    
    private var sections: [Section] = [
        Section(title: "Section 1", items: ["Item 1", "Item 2"]),
        Section(title: "Section 2", items: ["Item 3", "Item 4"])
    ]
    
    // 섹션 개수
    func numberOfSections(in tableView: UITableView) -> Int {
        return sections.count
    }
    
    // 섹션별 행 개수
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sections[section].items.count
    }
    
    // 섹션 헤더 타이틀
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return sections[section].title
    }
    
    // 셀 구성
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = sections[indexPath.section].items[indexPath.row]
        return cell
    }
}

4.2 스와이프 액션 구현

extension TableViewController {
    // 스와이프 액션 설정
    func tableView(_ tableView: UITableView, 
                  trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        
        // 삭제 액션
        let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { [weak self] (_, _, completion) in
            self?.deleteItem(at: indexPath)
            completion(true)
        }
        deleteAction.backgroundColor = .red
        
        // 편집 액션
        let editAction = UIContextualAction(style: .normal, title: "Edit") { [weak self] (_, _, completion) in
            self?.editItem(at: indexPath)
            completion(true)
        }
        editAction.backgroundColor = .blue
        
        return UISwipeActionsConfiguration(actions: [deleteAction, editAction])
    }
    
    private func deleteItem(at indexPath: IndexPath) {
        items.remove(at: indexPath.row)
        tableView.deleteRows(at: [indexPath], with: .fade)
    }
    
    private func editItem(at indexPath: IndexPath) {
        // 편집 로직 구현
    }
}

4.3 검색 기능 구현

class SearchTableViewController: UIViewController, UISearchResultsUpdating {
    private let searchController = UISearchController(searchResultsController: nil)
    private var filteredItems: [String] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupSearchController()
    }
    
    private func setupSearchController() {
        searchController.searchResultsUpdater = self
        searchController.obscuresBackgroundDuringPresentation = false
        searchController.searchBar.placeholder = "Search Items"
        navigationItem.searchController = searchController
        definesPresentationContext = true
    }
    
    func updateSearchResults(for searchController: UISearchController) {
        guard let searchText = searchController.searchBar.text?.lowercased() else { return }
        
        filteredItems = items.filter { item in
            return item.lowercased().contains(searchText)
        }
        
        tableView.reloadData()
    }
    
    // TableView DataSource 수정
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return searchController.isActive ? filteredItems.count : items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        let item = searchController.isActive ? filteredItems[indexPath.row] : items[indexPath.row]
        cell.textLabel?.text = item
        return cell
    }
}

5. 성능 최적화

5.1 셀 재사용 최적화

class OptimizedTableViewCell: UITableViewCell {
    // 셀 재사용 시 초기화가 필요한 프로퍼티들
    override func prepareForReuse() {
        super.prepareForReuse()
        // 이미지뷰 초기화
        imageView?.image = nil
        // 레이블 초기화
        titleLabel.text = nil
        descriptionLabel.text = nil
        // 진행 중인 작업 취소
        imageLoadingTask?.cancel()
    }
}

5.2 prefetchDataSource 구현

extension TableViewController: UITableViewDataSourcePrefetching {
    func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
        // 미리 데이터 로드
        for indexPath in indexPaths {
            // 이미지나 데이터 미리 로드
            preloadData(for: indexPath)
        }
    }
    
    func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
        // 필요없어진 프리패치 작업 취소
        for indexPath in indexPaths {
            cancelPreload(for: indexPath)
        }
    }
}

5.3 셀 높이 최적화

extension TableViewController {
    private func optimizeTableView() {
        // 고정 높이 설정
        tableView.rowHeight = 60
        
        // 또는 자동 높이 계산
        tableView.rowHeight = UITableView.automaticDimension
        tableView.estimatedRowHeight = 60
    }
}
profile
이것저것 많이 해보고싶은 사람

0개의 댓글

관련 채용 정보