[iOS] UIStackView

!·2023년 4월 12일
0

iOS

목록 보기
20/22

Overview

위의 3개의 UILabel 객체를 그림과 같이 레이아웃을 잡아준다고 가정해보자.
스택뷰를 이용하지 않는다면, 그럼 각각의 레이블에 레이아웃을 걸어줘야한다. 게다가 동적으로 레이아웃을 변화해야한다면 이는 만만치 않은 작업이 된다.

만약, 스택뷰를 이용한다면 스택 뷰의 프로퍼티로 3개의 레이블에 레이아웃을 빠르고 쉽게 걸어줄 수 있다.
즉 스택뷰는 여러 개의 뷰를 정렬하는데에 효과적인 객체이다.
스택 뷰 내부의 뷰들은 arrangedSubviews 프로퍼티에 저장되며, 스택뷰들은 이 뷰들의 부모 뷰가 된다.

스택 뷰의 내부 뷰들은 스택 뷰 자체의 프로퍼티로 레이아웃을 설정할 수 있지만, 스택 뷰 자체의 위치나 크기등은 직접 설정해야한다.


Axis

UIStackView의 axis는 스택뷰 내부의 뷰들을 정렬할 축의 방향이다.

  • NSLayoutConstraint.Axis.Vertical : 수직 방향으로 서브 뷰들을 정렬한다.
  • NSLayoutConstraint.Axis.Horizontal : 수평 방향으로 서브 뷰들을 정렬한다.

Distribution

스택 뷰의 axis 방향으로 스택 뷰 내부의 서브 뷰들의 위치나 크기를 결정한다.

  • fill: 서브 뷰들은 내가 지정한 크기로 사이즈가 결정된다.

    다만, 스택 뷰 자체의 width가 정해져있다면 priority에 따라 어떤 뷰의 크기가 줄어들거나 늘어날지 결정된다.
  • fillEqually: 서브 뷰들은 모두 같은 크기로 resize된다. 이 때, 가장 큰 뷰를 기준으로 resize된다.

  • equalSpacing: 서브 뷰들은 동일한 spacing을 같도록 크기가 결정된다.

  • euqalCentering: 서브 뷰들의 center를 기준으로 spacing이 맞춰진다.

    마찬가지로, 이 때 줄어들거나 늘어나는 서브 뷰들의 크기는 priority에 의해 결정된다.


Alignment

스택 뷰의 axis의 수직 방향으로 서브 뷰들의 위치나 크기를 결정한다.


Spacing

spacing은 말그대로 스택 뷰 내부의 서브뷰들 사이의 간격을 의미한다.


동적인 높이를 갖는 테이블 뷰 셀

사용자가 테이블 뷰 셀을 터치 시 동적으로 높이가 변하는 테이블 뷰 셀의 구현원리는 다음과 같다.

  • 테이블 뷰 셀 내부에는 UIStackView 객체가 있다.
  • 스택 뷰 객체 내부에 여러 서브 뷰들이 존재한다.
  • 테이블 뷰 셀을 터치 시 해당 서브 뷰의 isHidden 속성을 토글한다.

UITableViewCell

//
//  Cell.swift
//  StackView_Practice
//
//  Created by LEE on 2023/04/12.
//

import UIKit
class Cell: UITableViewCell{
    
    static let identifier = "Cell"
    
    let stackView: UIStackView = {
        let view = UIStackView()
        view.axis = .vertical
        view.spacing = 0
        view.alignment = .leading
        view.distribution = .fill
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    let titleLabel: UILabel = {
        let label = UILabel()
        label.textColor = .black
        label.textAlignment = .left
        label.font = .systemFont(ofSize: 16)
        label.numberOfLines = 1
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    let desLabel: UILabel = {
        let label = UILabel()
        label.textColor = .black
        label.textAlignment = .left
        label.font = .systemFont(ofSize: 16)
        label.numberOfLines = 1
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        self.selectionStyle = .none
        contentView.addSubview(stackView)
        stackView.addArrangedSubview(titleLabel)
        stackView.addArrangedSubview(desLabel)
        
        NSLayoutConstraint.activate([
            stackView.leftAnchor.constraint(equalTo: contentView.leftAnchor),
            stackView.rightAnchor.constraint(equalTo: contentView.rightAnchor),
            stackView.topAnchor.constraint(equalTo: contentView.topAnchor),
            stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)])
        
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
    
    
}

UITableViewController

//
//  ViewController.swift
//  StackView_Practice
//
//  Created by LEE on 2023/04/12.
//

import UIKit

class ViewController: UIViewController {

    typealias Item = (title: String, des: String, isDesHidden: Bool)
    
    var items: [Item] = (0...100)
        .map{
            Item(title: String($0), des: "description by \($0)", isDesHidden: true)
        }
        
    
    let tableView: UITableView = {
        let view = UITableView()
        view.backgroundColor = .white
        view.separatorStyle = .none
        view.translatesAutoresizingMaskIntoConstraints = false
        view.rowHeight = 50
        view.bounces = true
        return view
    }()
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(tableView)
        tableView.register(Cell.self, forCellReuseIdentifier: Cell.identifier)
        tableView.delegate = self
        tableView.dataSource = self

        NSLayoutConstraint.activate([
            tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
            tableView.rightAnchor.constraint(equalTo: view.rightAnchor),
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
        ])
        
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
    }
    
}

extension ViewController: UITableViewDelegate, UITableViewDataSource{
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier) as! Cell
        
        cell.titleLabel.text = items[indexPath.row].title
        cell.desLabel.text = items[indexPath.row].des
        cell.desLabel.isHidden = items[indexPath.row].isDesHidden
        
        return cell
        
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        items[indexPath.row].isDesHidden.toggle()
//        tableView.reconfigureRows(at: [IndexPath(row: indexPath.row, section: 0)])
        tableView.reloadRows(at: [IndexPath(row: indexPath.row, section: 0)], with: .automatic)
    }
    
    
}

참고 자료

https://ios-development.tistory.com/1222
https://babbab2.tistory.com/154
https://developer.apple.com/documentation/uikit/uistackview

profile
개발자 지망생

0개의 댓글