-
SwiftUI Text에 stroke 적용하기 (feat. UIKit)SwiftUI 2025. 1. 2. 18:49
안녕하세요. 그린입니다 🍏
이번 포스팅에서는 SwiftUI Text에 stroke를 적용하는 두번째 방법에 대해 알아보겠습니다 🙋🏻
이전 첫번째 방법에 대해 기술한적이 있어요.
해당 방법에서는 SwiftUI Text 자체에 shadow를 적절히 주어 표현했었는데요.
한계가 있었습니다.
stroke 두께를 늘릴수록 shadow가 크게 적용되고 그에 따라 사실 상 aliasing, 계단 현상으로 디자인이 매끄럽지 못하게 나타나는 한계가 있었어요 🥲
그래서, 사실 SwiftUI만으로는 매끄럽게 구현하기가 어려워 이번 두번째 방법을 소개해보려 합니다!
SwiftUI Text에 stroke 적용하기 (UIKit을 곁들인)
이번 방법은 UIKit을 곁들여 SwiftUI에서 보다 매끄럽고 편하게 사용하는 방법입니다.
코드를 먼저 보면서 하나씩 살펴보겠습니다 😃
가장 크게 세가지 핵심적인 컴포넌트가 필요합니다.
1️⃣ StrokedLabel - UILabel을 통해 실제 외곽선 드로잉을 담당
2️⃣ StrokedText - UIKit의 StrokedLabel을 SwiftUI에서 사용할 수 있도록 래핑
2️⃣ StrokedText Extension - SwiftUI 스타일의 뷰 모디파이어 지원
그럼 하나씩 살펴볼까요?
StrokedLabel
public class StrokedLabel: UILabel { var strokeWidth: CGFloat = 5.0 { didSet { setNeedsDisplay() } } var strokeColor: UIColor = .black { didSet { setNeedsDisplay() } } public override func drawText(in rect: CGRect) { let context = UIGraphicsGetCurrentContext() context?.setLineWidth(strokeWidth) context?.setLineJoin(.round) // 외곽선 그리기 context?.setTextDrawingMode(.stroke) textColor = strokeColor super.drawText(in: rect) // 외곽선 내부 채우기 context?.setTextDrawingMode(.fill) textColor = strokeColor.withAlphaComponent(0) super.drawText(in: rect) // 실제 텍스트 그리기 textColor = .white super.drawText(in: rect) } }
먼저 UILabel을 상속받아 외곽선을 그리는 커스텀한 UILabel을 만들어야 합니다.
drawText(in:) 메서드를 오버라이드하여 다음과 같이 세 단계로 텍스트를 그려줘요.
1️⃣ 외곽선 그리기 (.stroke 모드)
2️⃣ 외곽선 내부 채우기 (.fill 모드)
3️⃣ 실제 텍스트 그리기
이렇게 커스텀한 UILabel이 준비되었다면 SwiftUI에서 사용할 수 있도록 만듭니다.
StrokedText
public struct StrokedText: UIViewRepresentable { let text: String let strokeWidth: CGFloat let strokeColor: UIColor let foregroundColor: UIColor let font: UIFont var numberOfLines: Int var kerning: CGFloat var lineHeight: CGFloat? var textAlignment: NSTextAlignment // UIViewRepresentable 구현 public func makeUIView(context: Context) -> StrokedLabel { let label = StrokedLabel() updateUIView(label, context: context) return label } public func updateUIView(_ label: StrokedLabel, context: Context) { // 기본 속성 설정 label.strokeWidth = strokeWidth label.strokeColor = strokeColor label.font = font // AttributedString 설정 let attributedString = NSMutableAttributedString(string: text) let range = NSRange(location: 0, length: text.count) var attributes: [NSAttributedString.Key: Any] = [ .kern: kerning, .foregroundColor: foregroundColor, .font: font ] // 줄 높이 설정 if let lineHeight = lineHeight { let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.minimumLineHeight = lineHeight paragraphStyle.maximumLineHeight = lineHeight paragraphStyle.alignment = textAlignment paragraphStyle.lineBreakMode = .byTruncatingTail attributes[.paragraphStyle] = paragraphStyle } attributedString.addAttributes(attributes, range: range) label.attributedText = attributedString label.numberOfLines = numberOfLines label.textAlignment = textAlignment } }
UIViewRepresentable을 구현해줘요.
여기서 기본 속성이나 AttributedString을 설정하고 행간, 자간에 대해 설정해주고 표현해줍니다.
더 나아가서 SwiftUI에 올려서 사용할 때 라인 수나 행/자간 등 추가적인 속성을 뷰 모디파이어로 설정할 수 있도록 해당 StrokedText를 확장해 뷰 모디파이어를 구현합니다.
StrokedText Extension
public extension StrokedText { func lineLimit(_ limit: Int?) -> StrokedText { var view = self view.numberOfLines = limit ?? 0 return view } func multilineTextAlignment(_ alignment: TextAlignment) -> StrokedText { var view = self switch alignment { case .leading: view.textAlignment = .left case .trailing: view.textAlignment = .right case .center: view.textAlignment = .center } return view } func kerning(_ value: CGFloat) -> StrokedText { var view = self view.kerning = value return view } func lineHeight(_ value: CGFloat) -> StrokedText { var view = self view.lineHeight = value return view } }
간단하죠?
그럼 SwiftUI 환경에서 사용해볼까요?
import SwiftUI struct ContentView: View { var body: some View { StrokedText( text: "Hello, Green!", strokeWidth: 5, strokeColor: .black, foregroundColor: .white, font: .systemFont(ofSize: 24, weight: .bold) ) .lineLimit(1) .multilineTextAlignment(.center) .kerning(2) } }
이렇게 간단히 해당 커스텀 컴포넌트를 호출하여 뷰 모디파이어도 적용하고 사용할 수 있어요!
이렇게 입맛에 맞도록 stroke 색상이나 두께 그리고 text 색상들도 자유롭게 변경할 수 있습니다 😃
마무리
SwiftUI에서 이런 기본적인것을 제공해주면 얼마나 좋을까요? 🥲
점차 발전은 해가는것 같지만 정말 기초적인 이런 부분들의 제공은 아직 없는것 같아서 아쉽습니다.
'SwiftUI' 카테고리의 다른 글
SwiftUI의 Custom Grid로 카테고리 뷰 구현하기 (33) 2024.12.23 UIGestureRecognizerRepresentable 사용하기 (34) 2024.12.16 SwiftUI에서 인터랙티브 푸시 네비게이션 사용하기 (31) 2024.12.12 SwiftUI - 키보드 노출 여부에 따른 뷰 오프셋 조정 (30) 2024.11.29 SwiftUI - refreshable ScrollView (26) 2024.11.22