김경록의 앱 개발 여정

[Swift UIKit] 공백이 포함된 커스텀 뷰 구현기 본문

Trouble Shooting

[Swift UIKit] 공백이 포함된 커스텀 뷰 구현기

Kim Roks 2025. 1. 10. 23:22

개요

사용 할 TableViewCell의 모습

팀 프로젝트에서 Cell의 디자인이 개선되었습니다.

복잡한 UI였는데 이를 구현하고 개선하는 과정을 담았습니다.

포스팅은 메모장 말투로 진행하겠습니다!

 

구현해야 할 UI에서 주의해야 할 부분

1. 제목이 줄바꿈이 되고 카테고리 아래서부터 시작하는 점 

2. 레시피의 경우 배경색, 글자색, cornerRadius까지 포함

3. 제목과 카테고리 부분이 폰트가 다름

 

최초 아이디어 : AttributedString 사용

func configureMainText(category: String, mainText: String) -> NSAttributedString {
        let attributedString = NSMutableAttributedString()
        // 카테고리 글씨 속성 설정
        let categoryAttributes: [NSAttributedString.Key: Any] = [
            .backgroundColor: UIColor(hex: "#FAE0E4"),
            .foregroundColor: UIColor(hex: "#E84D65"),
            .font: UIFont.boldSystemFont(ofSize: 12),
        ]
        
        let categoryAttributedString = NSMutableAttributedString(string: category, attributes: categoryAttributes)
        
        attributedString.append(categoryAttributedString)
        // 본문 글씨 속성 설정
        let mainAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.boldSystemFont(ofSize: 16),
            .foregroundColor: UIColor.black
        ]
        let mainAttributedString = NSAttributedString(string: mainText, attributes: mainAttributes)
        attributedString.append(mainAttributedString)
        
        return attributedString
    }
    
    
    //단순 사용 예시임, 외부에서 값 주입받아서 적용 권장
    lazy var titleLabel: UILabel = {
        let label = UILabel()
        
        label.numberOfLines = 2
        label.attributedText = configureMainText(category: String, mainText: String)
        return label
    }()

 

  • 두가지 AttributedString을 합쳐주는 형식으로 구현
  • 하지만 해당 방식은 카테고리 부분의  cornerRadius를 포함할 수 없음
  • 또한 글씨의 높낮이가 조금 어긋나게 됨
  • 우선 구현을 위해 하드코딩 된 부분이 있지만 치명적 단점으로 인해 개선 진행 X

 

AttributedString으로 구현된 셀의 모습

나쁘진 않지만, 요구사항을 충족시킬 수 없었다.

 

두 번째 아이디어: 카테고리 부분과 제목을 각 다른 레이블을 사용하여 구현

실패한 예시

 

  • 제목의 왼쪽 시작점을 카테고리의 오른쪽에 맞추게 되면 위와 같이 열바꿈이 의도와는 다르게 구현됨
  • 그래서 카테고리와 제목의 시작점을 같게 두되, 제목의 텍스트 자체에 공백을 추가하는 방향으로 결정 
  • 단, 카테고리의 길이는 유동적이므로 하드코딩으론 불가능,  넓이를 유동적으로 계산하여 적용하도록 결정

1. 카테고리 뷰의 넓이를 구하고 그에 맞는 공백을 채워줘야 함

    private func calculateLabelWidth(for label: UILabel) -> CGFloat {
        guard let text = label.text else { return 0 }
        let font = label.font ?? UIFont...(각 폰트)
        let size = (text as NSString).size(withAttributes: [.font: font])
        return size.width + 5 // 텍스트 패딩 포함
    }
    
    private func createDynamicSpace(forWidth width: CGFloat, withFont font: UIFont) -> String {
        let spaceCharWidth = " ".size(withAttributes: [.font: font]).width // 공백 문자 하나의 넓이
        let spaceCount = Int(ceil(width / spaceCharWidth)) // 필요한 공백 문자 수 계산
        return String(repeating: " ", count: spaceCount)
    }
  • 각 폰트별로 넓이가 다를 수 있으므로 폰트를 기반으로 넓이를 구함
  •  
  • VC에서 바인딩할 때 사용할 수 있도록 internal Methods 정의
    func configureTitleLabelText(_ text: String) {
        let categoryWidth = calculateLabelWidth(for: categoryLabel)
        let space = createDynamicSpace(forWidth: categoryWidth, withFont: titleLabel.font)
        let titleText = text
        titleLabel.text = space + titleText
    }

 

2. 카테고리 뷰의 반복작업 감소 및 주의사항 체크

여러 카테고리들이 존재해서 반복 작업을 줄이기 위한 UILabel 확장 구현
enum categorys: Int {
    case hobby = 1
    case it
    case health
    ...
}


extension UILabel {
    func setCategory(with id: Int) {
        applyCommonStyle()
        guard let category = categorys(rawValue: id) else {
            print("Invalid category value")
            return
        }
        switch category {
            //Text 뒤 문자는 일괄적이고 자연스러운 공백을 위한 유니코드임
        case .hobby:
            text = "취미\u{2003}"
            backgroundColor = UIColor...
            textColor = UIColor...
        case .it:
            text = "IT\u{2003}"
            backgroundColor = UIColor....
            textColor = UIColor...
            ....
}

 

  • API는 Category ID를 Int형으로 배출해 주므로 그에 맞는 label을 생성할 수 있도록 개선
  • 기본적인 폰트, cornerRadius 등의 공통부분은 함수로 선언하여 같은 동작을 최소화
  • 원하는 텍스트 이후에 공백대신 공백을 나타내주는 유니코드를 삽입하여 자연스럽게 표현 ( 이 부분은 이게 더 자연스럽다고 판단해서 채택)

 

만족스러운 완성본

 

 

더미데이터를 통한 결괏값 확인

카테고리 레이블의 길이와 상관없이 자연스러운 ui를 그리도록 개선 완료하였다.

 

 

이후 한번 더 개선!