UIKit

UIKit | Content Hugging Priority 이해하기(가로 세로 레이아웃 조정)

ziziDev 2024. 8. 10. 11:18
반응형

 오늘은 iOS 개발에서 자주 마주치는 Content Hugging Priority에 대해 이야기해보려고 합니다.

 

이 개념은 뷰의 크기 조정에 있어 꽤 중요한 역할을 하지만, 처음 접할 때는 조금 헷갈릴 수 있어요. 그래서 Content Hugging Priority가 뭔지, 그리고 언제 어떻게 사용해야 하는지 쉽게 설명해드리겠습니다.

Content Hugging Priority란?

Content Hugging Priority라는 건 말 그대로 뷰가 자신의 콘텐츠 크기를 "껴안고(hugging)" 유지하려는 우선순위(priority)를 의미해요. 뷰의 콘텐츠가 많거나 적더라도, 이 우선순위에 따라 그 뷰가 얼마나 자신의 크기를 고수할지를 결정하게 됩니다.

예를 들어, UILabel을 생각해볼게요. 이 라벨에 긴 텍스트가 들어가면 라벨의 너비가 자연스럽게 늘어나겠죠? 그런데 이 라벨이 다른 뷰와 함께 Auto Layout을 통해 배치되어 있다면, 그 라벨이 얼마나 크기를 유지하려고 할지가 중요해집니다. 이때 Content Hugging Priority를 설정하게 되는 거죠.

 

축(axis)란?

iOS에서 Content Hugging Priority를 설정할 때는 축(axis)이라는 개념을 이해해야 해요. 축은 두 가지가 있어요:

  • 가로 방향 (horizontal) 축: 뷰가 가로로 자신의 크기를 유지하려고 하는 우선순위입니다.
  • 세로 방향 (vertical) 축: 뷰가 세로로 자신의 크기를 유지하려고 하는 우선순위입니다.

언제 사용해야 할까?

이 우선순위는 기본적으로 모든 뷰에 설정되어 있지만, 가끔은 이를 커스터마이즈해야 할 때가 있어요. 예를 들어, 두 개의 라벨이 나란히 있을 때, 한쪽 라벨의 텍스트 길이가 변할 때 다른 쪽 라벨이 얼마나 영향을 받을지 결정하고 싶을 때입니다.

 

let label = UILabel()
label.setContentHuggingPriority(.defaultHigh, for: .horizontal)

 

이 코드에서 라벨의 Content Hugging Priority를 가로 방향으로 높게 설정했어요. 이 말은, 이 라벨이 가로로는 크기를 줄이거나 늘리기 싫어한다는 뜻이죠. 즉, 다른 뷰가 공간을 더 차지하려고 할 때, 이 라벨은 자신의 공간을 지키려고 노력할 거예요.

 

여기서 제가 이 부분에 대해서 어디에 적용하면 좋을지에 대해서 예시를 하나 들고 왔어요

 

 

이렇게 축에 맞지않게 정렬되어있는걸

이모지와 아래 텍스트를 맞추어 주는 역할이라고

생각하시면 됩니다

 

import UIKit
import SnapKit

class StackViewController: UIViewController {
    
    private let buttonStackView = UIStackView()
    private let scheduleButton = UIButton()
    private let projectButton = UIButton()
    private let iepButton = UIButton()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupButtonStackView()
        setupConstraints()
        // Do any additional setup after loading the view.
    }
    
    private func setupButtonStackView() {
        view.addSubview(buttonStackView)  // 먼저 view에 추가합니다.

        buttonStackView.axis = .horizontal
        buttonStackView.distribution = .fillEqually
        buttonStackView.spacing = 10

        let buttonData = [
            (":날짜:", "일정"),
            (":연필2:", "프로젝트"),
            (":직소:", "개별화\n교육계획")
        ]
        
        [scheduleButton, projectButton, iepButton].enumerated().forEach { index, button in
            let (emoji, title) = buttonData[index]
//            button.backgroundColor = ColorScheme.cardBackground
            button.layer.cornerRadius = 12
            button.layer.shadowColor = UIColor.black.cgColor
            button.layer.shadowOpacity = 0.1
            button.layer.shadowOffset = CGSize(width: 0, height: 2)
            button.layer.shadowRadius = 4
//            button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
            let stackView = UIStackView()
            stackView.axis = .vertical
            stackView.alignment = .center
            stackView.spacing = 4
            let emojiLabel = UILabel()
            emojiLabel.text = emoji
            emojiLabel.font = UIFont.systemFont(ofSize: 50)
            emojiLabel.textAlignment = .center
            let titleLabel = UILabel()
            titleLabel.text = title
            titleLabel.font = UIFont.systemFont(ofSize: 20, weight: .medium)
            titleLabel.textAlignment = .center
            titleLabel.numberOfLines = 0
            stackView.addArrangedSubview(emojiLabel)
            stackView.addArrangedSubview(titleLabel)
            button.addSubview(stackView)
            stackView.snp.makeConstraints { make in
                make.edges.equalToSuperview().inset(4)
            }
            buttonStackView.addArrangedSubview(button)
        }
    }

    private func setupConstraints() {
        buttonStackView.snp.makeConstraints { make in
            make.top.equalTo(view.safeAreaLayoutGuide).offset(16)
            make.leading.trailing.equalToSuperview().inset(16)
            make.bottom.lessThanOrEqualTo(view.safeAreaLayoutGuide).offset(-16)
        }
    }
}

 

 

이렇게 코드를 넣는다면 위치가 맞지 않기 때문에 위치를 맞추기 위해서

스택뷰 정렬과

 

stackView.alignment = .center

emojiLabel.textAlignment = .center
titleLabel.textAlignment = .center

 

버튼 여백을 동일하게 주고 이모지의 높이와 길이를 동일하게 주도록 합니다

 

button.contentEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)

 

 

수정을 햊ㅆ지만 버튼 사이즈가 달라서 전혀 예뻐보이지 않는 버튼들이 완성되었습니다

이런 이유는 컨텐츠 크기나 내용에 따라서 버튼 크기가 달라지기 때문입니다

그래서 각 버튼의 크기를 컨텐츠에 맞춰서 크기를 조절합니다

 

[buttonStackView.arrangedSubviews].forEach { view in
    view.snp.makeConstraints { make in
        make.width.equalTo(buttonStackView.arrangedSubviews[0].snp.width)
        make.height.equalTo(buttonStackView.arrangedSubviews[0].snp.height)
    }
}

 

스택뷰 사이즈를 0번쨰 너비와 높이에 다 맞추고

 

buttonStackView.axis = .horizontal
    buttonStackView.distribution = .fillEqually 
    buttonStackView.spacing = 10

 

fillEqually로 맞추게되면 동일한 버튼 크기가 완성됩니다

 

맞추기 위해 이러한 일련의 과정을 통해서 해결할 수 있습니다

 

import UIKit
import SnapKit
class StackViewController: UIViewController {
  private let buttonStackView = UIStackView()
  private let scheduleButton = UIButton()
  private let projectButton = UIButton()
  private let iepButton = UIButton()
  override func viewDidLoad() {
    super.viewDidLoad()
    setupButtonStackView()
    setupConstraints()
  }
  private func setupButtonStackView() {
    view.addSubview(buttonStackView)
    buttonStackView.axis = .horizontal
    buttonStackView.distribution = .fillEqually
    buttonStackView.spacing = 10
    buttonStackView.alignment = .center
    let buttonData = [
      (":날짜:", "일정"),
      (":연필2:", "프로젝트"),
      (":직소:", "개별화교육")
    ]
    [scheduleButton, projectButton, iepButton].enumerated().forEach { index, button in
      let (emoji, title) = buttonData[index]
      button.backgroundColor = .white
      button.layer.cornerRadius = 12
      button.layer.shadowColor = UIColor.black.cgColor
      button.layer.shadowOpacity = 0.1
      button.layer.shadowOffset = CGSize(width: 0, height: 2)
      button.layer.shadowRadius = 4
      button.contentEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
      //      button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
      let stackView = UIStackView()
      stackView.axis = .vertical
      stackView.alignment = .fill
      stackView.spacing = 4
      let emojiLabel = UILabel()
      emojiLabel.text = emoji
      emojiLabel.font = UIFont.systemFont(ofSize: 50)
      emojiLabel.textAlignment = .center
      emojiLabel.setContentHuggingPriority(.defaultHigh, for: .vertical)
      let titleLabel = UILabel()
      titleLabel.text = title
      titleLabel.font = UIFont.systemFont(ofSize: 20, weight: .medium)
      titleLabel.textAlignment = .center
      titleLabel.numberOfLines = 0
      titleLabel.setContentHuggingPriority(.defaultLow, for: .vertical)
      stackView.addArrangedSubview(emojiLabel)
      stackView.addArrangedSubview(titleLabel)
      button.addSubview(stackView)
      stackView.snp.makeConstraints { make in
        make.edges.equalToSuperview().inset(4)
      }
      buttonStackView.addArrangedSubview(button)
    }
  }
  private func setupConstraints() {
    buttonStackView.snp.makeConstraints { make in
      make.top.equalTo(view.safeAreaLayoutGuide).offset(16)
      make.leading.trailing.equalToSuperview().inset(16)
      make.bottom.lessThanOrEqualTo(view.safeAreaLayoutGuide).offset(-16)
    }
    buttonStackView.arrangedSubviews.forEach { view in
      view.snp.makeConstraints { make in
        make.width.equalTo(buttonStackView.arrangedSubviews[0].snp.width)
        make.height.equalTo(buttonStackView.arrangedSubviews[0].snp.height)
      }
    }
  }
}

 

우선 이 문제를 해결하기 위해서는 스택뷰가 어떤식으로 동작하는지 이해를 해야하고

그리고 그 안에 들어가는 오브젝트들을 어떻게 배치할건지에 대해서

심도있게 고민을 해야 풀 수 있었던 문제였던것 같습니다

 

 

UIStackView.Distribution.equalCentering

 

정리

Content Hugging Priority는 뷰의 크기 조정에서 중요한 역할을 합니다. 이 개념을 잘 활용하면 Auto Layout을 사용할 때 더 원하는 대로 레이아웃을 조정할 수 있어요. 가로와 세로 축을 이해하고 우선순위를 설정함으로써, 뷰가 크기를 어떻게 유지할지 세밀하게 제어할 수 있습니다.

 

https://spin.atomicobject.com/uistackview-distribution/

 

Exploring UIStackView Distribution Types

Exploring the five UIStackView distribution types: Fill, Fill Equally, Fill Proportionally, Equal Spacing, and Equal Centering.

spin.atomicobject.com

https://developer.apple.com/documentation/uikit/uistackview/distribution

 

UIStackView.Distribution | Apple Developer Documentation

The layout that defines the size and position of the arranged views along the stack view’s axis.

developer.apple.com

https://developer.apple.com/documentation/uikit/uiview/1622556-contenthuggingpriority

 

contentHuggingPriority(for:) | Apple Developer Documentation

Returns the priority with which a view resists being made larger than its intrinsic size.

developer.apple.com

 

 

반응형