관리 메뉴

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

[iOS - swift] custom UIStackView 활용 방법 (UIStackView 상속, flexible한 custom view, StackView margin, padding값) 본문

iOS 응용 (swift)

[iOS - swift] custom UIStackView 활용 방법 (UIStackView 상속, flexible한 custom view, StackView margin, padding값)

jake-kim 2021. 8. 20. 01:59

CustomView를 만들 때 UIStackView를 사용하면 좋은점

  • StackView가 기본적으로 가지고 있는 align 속성 사용 가능 (가운데 정렬도 alignment = .center로 쉽게 레이아웃 설정)
  • 스택뷰에 `addArrangedSubview()`를 통해 view들을 넣어놓고 view들을 hidden시켜도, 자동으로 정렬되기 때문에 stack안에 들어가있는 view들의 레이아웃을 신경쓰지 않아도 되는 장점이 존재

UIStackView를 상속받아서 구현할때 알아야 하는 점

  • StackView의 속성들의 값을 모두 기억
    • stackView.spacing = 0
    • stackView.axis = .horizontal
    • stackView.alignment = .fill (.leading, .trailing, fill ,center, top, bottom, .firstBaseline, .lastBasline)
    • stackView.distribution = .fill (.fill, fillEqually, .fillProportionally, .equalSpacing, .equalCentering)
  • StackView안에 속해있는 특정 뷰를 기준으로 spacing을 주는 방법: setCustomSpacing(:after:) 사용

pointyView 아래에 spacing 

stackView.setCustomSpacing(8, after: pointyView)
  • margin을 주는 방법: 아래 stackView에 label이 들어가 있을때, label이 보이게끔 stackView margin값을 주는 방법

// margin값 부여
stackView.isLayoutMarginsRelativeArrangement = true
stackView.layoutMargins = UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0)

  • 전체 코드
class ViewController: UIViewController {

    lazy var label: UILabel = {
        let label = UILabel()
        label.text = "jake iOS 블로그"
        label.textColor = .white

        return label
    }()

    lazy var stackView: UIStackView = {
        let view = UIStackView()
        view.backgroundColor = .lightGray
        view.isLayoutMarginsRelativeArrangement = true
        view.layoutMargins = UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0)

        view.layer.cornerRadius = 14.0
        view.layer.shadowColor = UIColor.black.cgColor
        view.layer.shadowOffset = CGSize(width: 0.0, height: 3.0)
        view.layer.shadowOpacity = 0.1
        view.layer.shadowRadius = 3.0

        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .white
        view.addSubview(stackView)
        stackView.addArrangedSubview(label)

        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    }
}

UIStackView를 상속받아서 구현 방법

  • 동적인 custom view를 만들기 위해서는 auto layout 사용을 지양
    • 단, 만약 Label을 감싸는 View가 동적으로 변하게 하려면, label.intrinsicContentSize를 사용하여 view를 updateConstraints를 이용하면 가능
    • stackView안의 뷰들을 hidden시키고 보이고, 동적으로 변하는 경우가 있다면 UIStackView를 사용하여 addArrangedSubview에 담고 hidden시켜도 Layout을 따로 신경쓰지 않도록 설계
  • UIStackView의 frame은 따로 설정하지 않으면 외부에서 사용 시 크기가(0,0)으로 되므로 주의 (내부에서 frame 값을 계산하여 대입)
  • alignment = .fill (디폴트)로 설정되어 있는 경우 addArrangedSubview()로 마지막에 추가된 view의 크기가 UIStackView의 frame값에  따라 작아지거나 커지기 때문에 주의

UIStackView를 상속하여 구현

  • BaseStackView 정의
import UIKit

class BaseStackView: UIStackView {
    override init(frame: CGRect) {
        super.init(frame: frame)

        configure()
    }

    @available(*, unavailable)
    required init(coder: NSCoder) {
        fatalError("init(coder: NSCoder) has not been implemented")
    }

    func configure() {}
    func bind() {}
}
  • MyStackView 구현: `타이틀`부분을 쉽게 hidden시키면 서브 타이틀만 표출되는 코드

UIStackView를 상속받아 구현한 뷰

  • 구성: StackView안에 bubleStackView, titleLabel, pointyView, subTitleLabel 존재
  • addArrangedSubviews
    private func addSubviews() {
        addArrangedSubview(bubbleStackView)
        bubbleStackView.addArrangedSubview(titleLabel)
        addArrangedSubview(pointyView)
        addArrangedSubview(subTitleLabel)
    }
  • UIStackView를 상속받아서 커스텀 뷰를 만들때의 핵심
    • label, ImageView와 같은 내부 contents를 대입 후 sizeToFit() 호출 > intrinsicContentSize 값 획득
    • intrinsicContentSize값을 통해서 UIStackView를 담고 있는 가장 밖의 frame.size값 세팅 > sizeToFit() 호출
    // 데이터 입력되면 bind() 호출 > bind()에서 UI 세팅
    var title: String? { didSet { bind() } }
    var subTitle: String? { didSet { bind() } }
    override func bind() {
        super.bind()

        var frameWidth: CGFloat = 0.0
        var frameHeight: CGFloat = 0.0

        if let title = title {
            titleLabel.isHidden = false
            pointyView.isHidden = false
            titleLabel.text = title
            titleLabel.sizeToFit()

            frameWidth = titleLabel.intrinsicContentSize.width
            frameHeight = titleLabel.intrinsicContentSize.height

            setPointyShapeLayerAtBubbleStackView()
            setCustomSpacing(8, after: pointyView)
        } else {
            titleLabel.isHidden = true
            pointyView.isHidden = true
        }

        if let subTitle = subTitle {
            subTitleLabel.text = subTitle
            subTitleLabel.sizeToFit()

            frameWidth = max(frameWidth, subTitleLabel.intrinsicContentSize.width)
            frameHeight = max(frameHeight, subTitleLabel.intrinsicContentSize.height)
        }

		// frame값을 설정해주지 않으면 (0,0)으로 되는것 주의
        frame.size = CGSize(width: frameWidth, height: frameHeight)

        sizeToFit()
    }
  • 사용하는 쪽: width, height없이 위치만 지정해주면 크기가 동적으로 변하여 그대로 사용 가능
class ViewController: UIViewController {

    lazy var stackView: MyStackView = {
        let view = MyStackView(title: "타이틀", subTitle: "서브 타이틀")

        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    }
}

* 전체 소스 코드: https://github.com/JK0369/StackView_flexible

Comments