Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.4k views
in Technique[技术] by (71.8m points)

word wrap - Wrapping multiline text inside a shape in SwiftUI

As the title states, I am looking for a way to wrap multiline text inside of a shape in SwiftUI (see image below). I tried using .clipToShape(MyShape()), but that just renders any text not within the shape invisible.

I have accomplished this in the past using UIKit and exclusion paths, but would like to find a way to achieve the same effect with SwiftUI.

Any advice would be greatly appreciated.

example image of text clipped to circle


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I found a numeric way to do so, but it is probably not the best one.

It should work on IOS and macOS. I tested it on macOS with swiftUI.

The first thing we do is to find out how many rectangles with the hight of the font size are fitting in the circle with its diameter. Then we figure their width out. The last step is grabbing for each rectangle the amount of characters fitting in and adding them into an array. By converting the hole array back to a String we add after each rectangle an " " to get the correct multiline alignment.

func createCircularText(text: String, verticalSpacing: Double,  circleDiameter: Double, FontSize: Double) -> String {
    
    var Text = text
    
    var circularText = String()
 
    var CountOfWordLines = Int()
   
    var widthOfWordLine = [Int]()
    
    var widthOfWordLineSorted = [Int]()
    
    var array = [String]()
    
    let heigthOfWordLines = FontSize + verticalSpacing

    
    var Dnum = (((1/heigthOfWordLines) * circleDiameter) - 2.0)
    Dnum.round(.up)
    CountOfWordLines = Int(Dnum)
    
    
    for n in 1...(CountOfWordLines / 2) {

        
                let num0 = circleDiameter / 2.0
                let num1 = pow(num0, 2.0)
                let num2 = (Double(n) * heigthOfWordLines)
                let num3 = pow(num2,2.0)
                let num4 = num1 - num3
                let num5 = sqrt(Double(num4))
                let num = Int((num5 / 10) * 3)
                widthOfWordLine.append(Int(num))
        
    }
    
    widthOfWordLineSorted.append(contentsOf: widthOfWordLine.sorted { $1 > $0 })
    widthOfWordLine.removeFirst()
    widthOfWordLineSorted.append(contentsOf: widthOfWordLine)
    widthOfWordLine.removeAll()
    
    for n in widthOfWordLineSorted {
        
        
        array.append(String(Text.prefix(n)))

       if Text.isEmpty {} else {
        let t = Text.dropFirst(n)
        Text = String(t)
        }
    }
    
    
    circularText = array.joined(separator: "
")
    

    return circularText 
}

In our view we embed the function like this:

@State var text = "your text"
@State var CircularText = String()


// body:

    ZStack {
                    
        Circle().frame(width: 200)
        
         Text(CircularText)
         .multilineTextAlignment(.center)

            }

.onAppear(perform: {
CircularText = createCircularText(text: text, verticalSpacing: 3.0, circleDiameter: 200, FontSize: 12)
 })

I just tested it with the font size 12, but it should perform with any other as well quite ok.

By changing the diameter you will notice that the text becomes a bit oval, to fix that please change the verticalSpacing. As smaller the number gets, as taller the circle gets, and the other way around. But feel free to fix that issue.

Also, please make sure that your text is long enough.

enter image description here


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...