쇼핑 아이템 cell UI 구현 과정에서 필요했던 AttributedString과 새롭게 배운 것들을 정리해보았다.

AttributedString은 다시봐도 신기한 것 같다. 마지막으로 다크모드까지 살짝 대응할 수 있어서 더욱 재밌는 과정이었던 것 같다.

  1. AttributedString
  2. StackView
  3. DarkMode

1. AttributedString

초기 접근법

기존 가격실제 판매 가격을 보여주는 UI를 만들어야하는데, 처음에는 UILabel 두 개를 사용하여서 하려고 했다.

Decoding된 값들은 스토리보드에서 잘 디자인된 UILabel에 잘 들어갔다. 하지만 문제점은 실제 판매 가격 값이 optional 값이여서 기존 가격 UILabel의 text에 "" 을 넣어줬지만, 조금은 여백이 생겨버리는 문제가 발생했다.

그래서 optional binding을 통해서 기존 가격 이 nil이면 앞에 있는 label을 removeFromSuperView() 메소드를 이용해 제거하려고 했지만, tableView cell은 재사용되기 때문에 없어져버린 UILabel에 할당하려고하는 일이 발생해버린다.

그래서 attributedString을 사용해야겠다는 생각을 하였다.

NSMutableAttributedString

A mutable string object that also contains attributes (such as visual style, hyperlinks, or accessibility data) associated with various portions of its text content.

NSAttributedString은 String에 특정 효과나 속성을 변경시키는 일을 해준다. 그리고 하나의 String에 여러가지 효과나 속성을 줄 수 있다.

attribute는 여기다, 생각하다 라는 의미가 있다. 그래서 attributedString은 생각되는 여겨지는 String이라고 생각하면 될 것 같다. 또한 명사로는 특질, 특성, 속성이라는 뜻이 있다.

전체적으로 순서는 다음과 같다.

  1. NSMutableAttributedString 객체 선언
  2. append(_:) 또는 addAttribute(_:value:range:)

NSMutableAttributedString

Declaration

class NSMutableAttributedString : NSAttributedString

addAttribute(_:value:range:)

Declaration

Adds an attribute with the given name and value to the characters in the specified range.

func addAttribute(_ name: NSAttributedString.Key, value: Any, range: NSRange)

append(_:)

Adds the characters and attributes of a given attributed string to the end of the receiver.

Declaration

func append(_ attrString: NSAttributedString)

접근 방법

먼저 NSMutableAttributedString 객체를 만들고 addAttribute() 메소드로 속성을 주고, append() 메소드를 통해 NSAttributedString 를 추가할 수 있다.

var spaceBetweenPrices: String

var attributedString = NSMutableAttributedString()
if let normalPrice = item.nPrice {
    spaceBetweenPrices = "  "
    attributedString = NSMutableAttributedString(string: normalPrice)
    attributedString.addAttribute(.strikethroughStyle, value: 2, range: NSMakeRange(0, attributedString.length))
}
spaceBetweenPrices = ""
attributedString.append(NSAttributedString(string: "\(spaceBetweenPrices)\(item.sPrice)", attributes: [.font: UIFont.systemFont(ofSize: 17, weight: .heavy), .foregroundColor: #colorLiteral(red: 0.1703471243, green: 0.7560165524, blue: 0.737252295, alpha: 1)]))
self.normalPriceLabel.attributedText = attributedString

String 가운데 줄 긋기

attributedString.addAttribute(.strikethroughStyle, value: 2, range: NSMakeRange(0, attributedString.length))

속성값 있는 NSAttributedString append

attributes Dictionary의 Key값들은 addAttribute를 case를 참고하면 된다.

append(NSAttributedString(string: "\(spaceBetweenPrices)\(item.sPrice)", attributes: [.font: UIFont.systemFont(ofSize: 17, weight: .heavy), .foregroundColor: #colorLiteral(red: 0.1703471243, green: 0.7560165524, blue: 0.737252295, alpha: 1)]))

아쉬운 점은 NSMutableAttributedString로 만들면 정렬방법을 선택할 수 없다는 것이다.

2. StackView

stackView는 잘쓰면 정말 편하지만, 가끔 정말 원하는대로 되지 않아서 너무 화가나게하는 UI 중 하나인 것 같다. 거기다 스토리보드와 함께 쓰다보니 더욱 더 다루기 힘들었던 것 같다.

접근 방법

String 배열로 넘어오는 badge property를 stackView에 반영하는 것. 또한 badge마다 정해진 색깔이 있으므로 String 값에 따라 enum을 통해 badge 색깔을 바꿔준다. UILabel로는 inset을 줄 수 없어서 UITextView를 사용하기로 하였고 UITextView에 요소들을 바꿔줄게 많기 때문에, CustomBadgeTextView를 만들어서 처리하도록 하였다.

주의할점

badge: [String]? 을 받아온대로 UITextView만들어서 추가하면 tableView는 재사용되기 때문에 StackView에 모든 원소들을 한번씩 모두 제거해줘야한다.

StackView.arrangedSubviews.forEach({ $0.removeFromSuperview() })

실수

스토리보드에서 UITextView를 디자인한 후에 badge 값에 따라서 StackView에 addArrangedSubview 메소드를 이용해 badge 값을 넣어주었다. 여기서 꽤 오랫동안 막혔는데, 스토리보드에서 했던 frame을 구현하지 않았는데, 계속 programmatical하게 만든 UITextView를 StackView에 추가하고 있었기에 화면에 보이지 않은 것이었다. UITextView는 기본적으로 interaction이 가능하고 numberOfLine이 0이기 때문에 rect가 꽤 크다.

trick

badge들을 추가하려고 하는데, stackView 안에 요소가 없으면 또 먹지를 않는다…

그래서 trick을 사용했다. default로 스토리보드에서 하나의 element를 갖게 하고 모든 원소들을 제거한 후에 받아온 badge [String]에 맞게 StackView에 추가해주었다.

전체 코드

badgeStackView.arrangedSubviews.forEach({ $0.removeFromSuperview() })

if let badges = item.badge {
    var badgeViews = [UITextView]()
    
    badges.forEach { (badgeString) in
        let badgeTextView = CustomBadgeTextView(text: badgeString)
        badgeTextView.textColor = .white
        
        switch Badge.fromString(badgeString) {
        case .EventPrice:
            badgeTextView.backgroundColor = UIColor(named: "BadgePurple")
        case .FreeGift:
            badgeTextView.backgroundColor = UIColor(named: "BadgeYellow")
        case .SpecialPrice:
            badgeTextView.backgroundColor = UIColor(named: "BadgePurple")
        case .SoldOut:
            badgeTextView.backgroundColor = UIColor(named: "BadgeDarkGray")
        case .UNKNOWN: break
        @unknown default: break
        }
        
        badgeViews.append(badgeTextView)
    }
    
    badgeViews.forEach({ badgeStackView.addArrangedSubview($0)})
} else {
    // trick
    badgeStackView.addArrangedSubview(CustomBadgeTextView(text: "none"))
}

3. DarkMode

시뮬레이터에서는 Light Mode여서 인지하지 못하였는데, 내 핸드폰에서 돌려보니 다크모드가 적용되어서 수정해야할 부분들이 보였다. 기본적으로 주어지는 systemColor로 적용되어있는 UI요소들은 문제가 없었지만, 다른 색깔들은 Dark Mode에서는 바뀌지만 일반 UIColor들은 바뀌지 않아서 조금 어색하였다.

Light와 Dark Mode 둘다 처리하기 위해서 Assets에서 custom Color를 만들어 줘야한다. 이제 light mode 색감 디자인 후에 dark모드 또한 색감을 위해 디자인해야할 것 같다.

References

태그: ,

카테고리:

업데이트:

댓글남기기