Как анимировать сворачивание текста в SwiftUI

#ios #swift #animation #swiftui #transition

#iOS #swift #Анимация #swiftui #переход

Вопрос:

Мне нужна помощь с поведением анимации / перехода SwiftUI. Я должен реализовать сворачивание / расширение Text при переходе lineLimit с нуля на 5 и наоборот.

На данный момент у меня есть рабочий код, вы можете увидеть его ниже.

 import SwiftUI

public struct ContentView: View {
    private let description: String
    
    @State private var isCollapsed: Bool = true
    @State private var isExpandeButtonShow: Bool = false
    
    public init(description: String) {
        self.description = description
    }

    // MARK: - Body

    public var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            text.padding(.top, 26)
                .font(Font.system(size: 12))
            if isExpandeButtonShow {
                collapseButton.padding(.top, 9)
            }
            
            Spacer()
        }
        .padding(.horizontal, 16)
    }

    // MARK: - Private

    private var text: some View {
        Text(description)
            .lineLimit(isCollapsed ? 5 : nil)
            .background(
                GeometryReader { geometry in
                    Color.clear.onAppear {
                        truncateIfNeeded(withGeometry: geometry)
                    }
                }
            )
            .animation(.linear(duration: 4))
            .transition(.opacity)
    }

    private func truncateIfNeeded(withGeometry geometry: GeometryProxy) {
        let total = description.boundingRect(
            with: CGSize(
                width: geometry.size.width,
                height: .greatestFiniteMagnitude
            ),
            options: .usesLineFragmentOrigin,
            attributes: [.font: UIFont.systemFont(ofSize: 12)],
            context: nil
        )

        if total.size.height > geometry.size.height {
            isExpandeButtonShow = true
        }
    }

    private var collapseButton: some View {
        button(title: collapseButtonTitle()) {
            isCollapsed.toggle()
        }
    }
    
    private func button(title: String, handler: @escaping () -> Void) -> some View {
        Button(action: handler) {
            Text(title)
                .foregroundColor(Color.blue)
                .contentShape(Rectangle())
        }
        .buttonStyle(PlainButtonStyle())
    }

    private func collapseButtonTitle() -> String {
        isCollapsed ? "Collapse" : "Expand"
    }
}

 

Это почти делает то, что я хочу. Но есть две причины моей боли от такого поведения.
Во-первых, когда я пытаюсь свернуть текст, анимация / переход не запускается. Он просто немедленно сворачивается. Во-вторых, я хочу, чтобы строка отображалась по строкам с анимацией каждой из них от 0 непрозрачности до 1. Как я могу это сделать?

Любые мысли могут быть полезны. С уважением.

Комментарии:

1. «Я хочу, чтобы строка отображалась по строкам с анимацией каждой из них от 0 непрозрачности до 1» — это можно сделать, только если каждая строка будет находиться в отдельном представлении, а не в одном тексте.

2. Нет ли способа реализовать это поведение? Вы уверены? В целом это звучит совершенно правильно. Но я надеюсь, что в мире SwiftUI есть хороший обходной путь.

Ответ №1:

Что-то подобное вы пытаетесь сделать?

 struct RandomView: View {
    
    @State var isCollapsed: Bool = false
    @State var lineCount: Int = 1
    @State var isAnimating: Bool = false
    
    var body: some View {
        Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
            .font(.largeTitle)
            .lineLimit(lineCount)
            .animation(.linear(duration: 1.0))
            .onTapGesture {
                animateLines()
            }
    }
    
    func animateLines() {
        
        guard !isAnimating else { return } // Check not currently animating
        isCollapsed.toggle()
        isAnimating = true

        let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (timer) in
            
            if isCollapsed {
                //Expand
                lineCount  = 1
                if lineCount >= 10 { // max lines
                    timer.invalidate()
                    isAnimating = false
                }
            } else {
                //Collapse
                lineCount -= 1
                if lineCount <= 1 { // max lines
                    timer.invalidate()
                    isAnimating = false
                }
            }
            
        }
        timer.fire()
    }
}
 

РЕДАКТИРОВАТЬ: если вы хотите анимировать непрозрачность построчно, вам нужно разбить строку на разные текстовые элементы, каждый из которых имеет индивидуальную непрозрачность. Вот так:

 var body: some View {
        VStack {
            Text("Line 1")
                .opacity(lineCount >= 1 ? 1 : 0)
            Text("Line 2")
                .opacity(lineCount >= 2 ? 1 : 0)
            Text("Line 3")
                .opacity(lineCount >= 3 ? 1 : 0)
            Text("Line 4")
                .opacity(lineCount >= 4 ? 1 : 0)
            Text("Line 5")
                .opacity(lineCount >= 5 ? 1 : 0)
        }
        .font(.largeTitle)
        .animation(Animation.linear(duration: 1.0))
        .onTapGesture {
            animateLines()
        }

    }
 

Комментарии:

1. Это почти то, что мне нужно, спасибо. Но как я могу добавить анимацию непрозрачности к вашему примеру?