iOS 응용 (SwiftUI)
[iOS - SwiftUI] Text에 Lottie 넣는 방법, Text에 애니메이션 넣는 방법 (Text, Lottie, NSTextAttachment)
jake-kim
2024. 9. 2. 01:05
Text에 Lottie 넣기
- Text에 정적인 이미지를 넣는 방법은 기존 방법이 존재
- NSTextAttachment를 사용하여 AttributedText를 Text 컴포넌트에 대입하는 방법
- Text에 바로 이어 붙이는 방법
- 하지만 Lottie와 같은 애니메이션을 Text에 넣는 방법은 알려진 방법이 x
Text에 Lottie 넣는 아이디어
- Text와 LottieView를 붙이는 것
- 문자열이 있을때, 이 문자열을 띄어쓰기 단위로 쪼개고 Lottie를 넣고 싶은 위치 혹은 문자열을 정하여 그 옆에 LottieView를 넣는 것
- 구조
// Lottie를 마지막에 넣고싶고, 문구가 들어갈 뷰의 width를 200라 할 경우 아이디어
VStack {
문자열을 " " 단위로 분해하여 배열로 나누기
라인별로 들어갈 문자열을 나누어서 lines배열에 저장 (width값을 계산하면서 200보다 크면 새로운 line에 저장)
ForEach문으로 Text를 집어넣고, 마지막 인덱스라면 HStack으로 Text와 LottieView를 삽입
}
직접 구현
- Lottie를 사용하기 위해서 lottie-ios 라이브러리 사용
- 뷰 준비
import SwiftUI
import Lottie
struct ContentView: View {
let text = "iOS 앱 개발 알아가기, 긴 텍스트가 있고 문자 마지막에 Lottie 애니메이션을 붙일 수 있는 방법을 알아봅시다. 예제 텍스트입니다."
let maxWidth: CGFloat = 200
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading, spacing: 0) {
// TODO..
}
.padding()
}
}
}
- TODO 부분에 text, width 정보를 받아서 Text와 Lottie를 만드는 핵심 코드 준비
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading, spacing: 0) {
buildTextLines(from: text, maxWidth: maxWidth, availableWidth: geometry.size.width) // <-
}
.padding()
}
}
func buildTextLines(
from text: String,
maxWidth: CGFloat,
availableWidth: CGFloat
) -> some View {
// TODO...
}
- buildTextLines 구현 -1) 문자열을 " " 단위로 분해하여 배열로 나누기
let words = text.split(separator: " ").map(String.init)
- buildTextLines 구현 -2) 라인별로 들어갈 문자열을 나누어서 lines배열에 저장 (width값을 계산하면서 200보다 크면 새로운 line에 저장)
var currentLine = ""
var lines: [String] = []
for word in words {
let potentialLine = currentLine.isEmpty ? word : "\(currentLine) \(word)"
let textWidth = potentialLine.width(usingFont: .systemFont(ofSize: 17))
if textWidth <= maxWidth {
currentLine = potentialLine
} else {
lines.append(currentLine)
currentLine = word
}
}
lines.append(currentLine)
// width 계산하는 문자열 관련 메소드 사용
extension String {
func width(usingFont font: UIFont) -> CGFloat {
let attributes = [NSAttributedString.Key.font: font]
let size = (self as NSString).size(withAttributes: attributes)
return size.width
}
}
- buildTextLines 구현 -3) ForEach문으로 Text를 집어넣고, 마지막 인덱스라면 HStack으로 Text와 LottieView를 삽입
return ForEach(lines.indices, id: \.self) { index in
let line = lines[index]
if index == lines.count - 1 {
HStack(spacing: 0) {
Text(line)
LottieView(animation: .named("sample"))
.playing(loopMode: .loop)
.frame(width: 30, height: 30)
.offset(x: -5)
}
.offset(y: -3) // 글자 간격을 맞추기 위한 임의의 값
} else {
Text(line)
.frame(maxWidth: availableWidth, alignment: .leading)
}
}
- buildTextLines 전체 코드
func buildTextLines(
from text: String,
maxWidth: CGFloat,
availableWidth: CGFloat
) -> some View {
let words = text.split(separator: " ").map(String.init)
var currentLine = ""
var lines: [String] = []
for word in words {
let potentialLine = currentLine.isEmpty ? word : "\(currentLine) \(word)"
let textWidth = potentialLine.width(usingFont: .systemFont(ofSize: 17))
if textWidth <= maxWidth {
currentLine = potentialLine
} else {
lines.append(currentLine)
currentLine = word
}
}
lines.append(currentLine)
return ForEach(lines.indices, id: \.self) { index in
let line = lines[index]
if index == lines.count - 1 {
HStack(spacing: 0) {
Text(line)
LottieView(animation: .named("sample"))
.playing(loopMode: .loop)
.frame(width: 30, height: 30)
.offset(x: -5)
}
.offset(y: -3) // 글자 간격을 맞추기 위한 임의의 값
} else {
Text(line)
.frame(maxWidth: availableWidth, alignment: .leading)
}
}
}
완성)