diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/README.md b/README.md index efb8061..419fde5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +## This code is forked from @jasudev and it supports multiline texts. To customize the UI, simply go to AnimateText file and edit lines that are commented on. + + + + # **AnimateText for SwiftUI** This library for animating text. Developed with SwiftUI. This library supports iOS/macOS. diff --git a/Sources/AnimateText/AnimateText.swift b/Sources/AnimateText/AnimateText.swift index db9676d..a03468f 100644 --- a/Sources/AnimateText/AnimateText.swift +++ b/Sources/AnimateText/AnimateText.swift @@ -37,6 +37,9 @@ public struct AnimateText: View { /// Custom user info for the effect. var userInfo: Any? = nil + /// The height of the Text view. + @State private var height: CGFloat = 0 + /// Split text into individual elements. @State private var elements: Array = [] @@ -69,25 +72,42 @@ public struct AnimateText: View { ZStack(alignment: .leading) { if !isChanged { Text(text) - .lineLimit(1) .takeSize($size) + .multilineTextAlignment(.center) }else { - HStack(spacing: 0) { - ForEach(Array(elements.enumerated()), id: \.offset) { index, element in - let data = ATElementData(element: element, - type: self.type, - index: index, - count: elements.count, - value: value, - size: size) - if toggle { - Text(element).modifier(E(data, userInfo)) - }else { - Text(element).modifier(E(data, userInfo)) + GeometryReader { geometry in + VStack(alignment: .leading, spacing: 0) { + ForEach(splitElements(containerWidth: geometry.size.width), id: \.self) { lineElements in + HStack { + Spacer() + HStack(spacing: 0) { + ForEach(Array(lineElements.enumerated()), id: \.offset) { index, element in + let data = ATElementData(element: element, + type: self.type, + index: index, + count: elements.count, + value: value, + size: size) + if toggle { + Text(element).modifier(E(data, userInfo)) + } else { + Text(element).modifier(E(data, userInfo)) + } + } + } + .fixedSize(horizontal: true, vertical: false) + Spacer() + } } } + .onAppear { + height = CGFloat(splitElements(containerWidth: geometry.size.width).count) * 40 // here you can customize the height to align a text + } + .onChange(of: geometry.size.width) { newValue in + height = CGFloat(splitElements(containerWidth: geometry.size.width).count) * 40 // here you can customize the height to align a text + } } - .fixedSize(horizontal: true, vertical: false) + .frame(height: height) } } .onChange(of: text) { _ in @@ -117,6 +137,56 @@ public struct AnimateText: View { self.elements = elements } } + + func splitElements(containerWidth: CGFloat) -> [[String]] { + var lines: [[String]] = [[]] + var currentLineIndex = 0 + var remainingWidth: CGFloat = containerWidth + var currentWord: String = "" + var words: [String] = [] + + // build words + for (index, element) in elements.enumerated() { + if element == " " { + currentWord.append(element) + words.append(currentWord) + currentWord = "" + } else { + // Add the element to the current word + currentWord.append(element) + + // Check if this is the last element + if index == elements.count - 1 { + words.append(currentWord) + } + } + } + + // build sentences, split words into elements + for (index, word) in words.enumerated() { + var letters: [String] = [] + for char in word { + letters.append(String(char)) + } + + let wordWidth = word.width(withConstrainedHeight: 1000, font: .systemFont(ofSize: 40)) // change the size if you change a font on your contentView + + if index == 0 { + lines[currentLineIndex].append(contentsOf: letters) + remainingWidth -= wordWidth + } else { + if wordWidth > remainingWidth { + currentLineIndex += 1 + lines.append(letters) + remainingWidth = containerWidth - wordWidth + } else { + lines[currentLineIndex].append(contentsOf: letters) + remainingWidth -= wordWidth + } + } + } + return lines + } } struct AnimateText_Previews: PreviewProvider { @@ -124,3 +194,11 @@ struct AnimateText_Previews: PreviewProvider { ATAnimateTextPreview() } } + +extension String { + func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat { + let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height) + let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil) + return ceil(boundingBox.width) + } +} diff --git a/Sources/AnimateText/Preview/ATAnimateTextPreview.swift b/Sources/AnimateText/Preview/ATAnimateTextPreview.swift index e4187b9..0818498 100644 --- a/Sources/AnimateText/Preview/ATAnimateTextPreview.swift +++ b/Sources/AnimateText/Preview/ATAnimateTextPreview.swift @@ -41,13 +41,16 @@ public struct ATAnimateTextPreview: View { VStack(alignment: .leading) { Spacer() AnimateText($text, type: type) - .font(.largeTitle) + .font(.largeTitle) // remember that if you change the font, you should change the size of wordWidth in struct "AnimateText" .padding() .background( RoundedRectangle(cornerRadius: 10) .fill(Color.blue.opacity(0.1)) + .frame(height: 300) // modify this if you need ) .padding(.leading) + .padding(.trailing) + Spacer() Divider().padding() HStack { Spacer()