관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - SwiftUI] Text에 Lottie 넣는 방법, Text에 애니메이션 넣는 방법 (Text, Lottie, NSTextAttachment) 본문

iOS 응용 (SwiftUI)

[iOS - SwiftUI] Text에 Lottie 넣는 방법, Text에 애니메이션 넣는 방법 (Text, Lottie, NSTextAttachment)

jake-kim 2024. 9. 2. 01:05

Text에 Lottie 넣기

Text에 Lottie 넣는 아이디어

  • Text와 LottieView를 붙이는 것
  • 문자열이 있을때, 이 문자열을 띄어쓰기 단위로 쪼개고 Lottie를 넣고 싶은 위치 혹은 문자열을 정하여 그 옆에 LottieView를 넣는 것
  • 구조
// Lottie를 마지막에 넣고싶고, 문구가 들어갈 뷰의 width를 200라 할 경우 아이디어

VStack {
  문자열을 " " 단위로 분해하여 배열로 나누기
  라인별로 들어갈 문자열을 나누어서 lines배열에 저장 (width값을 계산하면서 200보다 크면 새로운 line에 저장)
  ForEach문으로 Text를 집어넣고, 마지막 인덱스라면 HStack으로 Text와 LottieView를 삽입
}

직접 구현

SPM 으로 설치

  • 뷰 준비
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)
        }
    }
}

 

완성) 

 

* 전체 코드: https://github.com/JK0369/AttachLottieInText

Comments