@@ -12,35 +12,49 @@ public enum WritingDirection {
1212 case LeftToRight, RightToLeft
1313}
1414
15+ public struct TextComponent {
16+ let text : String
17+ let color : Color
18+ let font : Font ?
19+ }
20+
1521public struct SphericText < T: BinaryFloatingPoint > : View {
1622
17- @Binding var offsetDegree : T
23+ let rotateDegree : T
24+ private let textComponents : [ TextComponent ]
1825
19- private let stringTable : [ ( offset: Int , element: String ) ]
20- private var wordSpacing : Double = 30.0
26+ private var textSpacing : Double = 30.0
2127 private var font : Font ?
2228 private var wordColor = Color . white
2329 private var wordBackground = Color . clear
2430 private var hideOpposite = false
2531 private var blurMinors = false
26- private var oppositeRange = 150 ... 210
32+ private var oppositeRange = - 90 ... 90
2733 private var frontMostRange = - 10 ... 10
2834 private var perspective : CGFloat = 0.0
29- private var anchorZ : CGFloat = 100 .0
35+ private var radius : CGFloat = 0 .0
3036 private var writingDirection : WritingDirection = . LeftToRight
3137 @State private var estHeight : CGFloat = 30.0
3238 @State private var estWidth : CGFloat = 30.0
3339
40+
41+ public init ( _ components: [ TextComponent ] , rotateDegree: T ) {
42+ textComponents = components
43+ font = . system( size: 40.0 )
44+ self . rotateDegree = rotateDegree
45+ }
46+
3447 public init ( words: [ String ] , word_spacing: T = 30.0 , font: Font ? = nil , word_color: Color = Color . white, word_background: Color = Color . clear, hide_opposite: Bool = false , degree_offset: Binding < T > = . constant( 0 ) ) {
35- stringTable = words. enumerated ( ) . map { ( i, e) in
36- return ( i, e)
37- }
38- _offsetDegree = degree_offset
39- wordSpacing = Double ( word_spacing)
40- self . font = font ?? . system( size: 20.0 )
48+ textSpacing = Double ( word_spacing)
49+ let defaultFont = font ?? . system( size: 40.0 )
50+ self . font = defaultFont
4151 wordColor = word_color
4252 wordBackground = word_background
4353 hideOpposite = hide_opposite
54+ self . textComponents = words. map { word in
55+ TextComponent ( text: word, color: word_color, font: defaultFont)
56+ }
57+ self . rotateDegree = degree_offset. wrappedValue
4458 }
4559
4660 public init ( _ text: String , _ rotateDegree: Binding < T > = . constant( 0 ) ) {
@@ -69,35 +83,32 @@ public struct SphericText<T:BinaryFloatingPoint>: View {
6983 let frameL = geo. frame ( in: . local)
7084 ZStack ( alignment: . center) {
7185 //hide text to caculate height
72- Sizing {
73- Text ( " A " ) . font ( font) . opacity ( 0.0 )
74- }
75- ForEach ( stringTable, id: \. self. offset) { i, element in
76- let positionInDegree = Double ( offsetDegree) + ( writingDirection == . LeftToRight ? - 1.0 : 1.0 ) * Double( i) * wordSpacing
77- let _ = print ( " degrees: \( positionInDegree) " )
86+ let anchorZ = radius > 0 ? radius : w/ 2.0
87+ ForEach ( 0 ..< textComponents. count, id: \. self) { index in
88+ let component = textComponents [ index]
89+ let positionInDegree = Double ( rotateDegree) + ( writingDirection == . LeftToRight ? 1.0 : - 1.0 ) * Double( index) * textSpacing
7890 let normalizedD = Int ( positionInDegree) % 360
7991 let shouldBlur = blurMinors ? !( frontMostRange ~= abs ( normalizedD) ) : false
8092 let shouldHide = hideOpposite ? ( oppositeRange ~= abs ( normalizedD) ) : false
8193
82- let text = writingDirection == . RightToLeft ? String ( element. reversed ( ) ) : element
83- Text ( text) . frame ( width: estWidth*CGFloat ( text. count) , height: 50 , alignment: Alignment ( horizontal: . center, vertical: . center) )
84- . font ( font)
85- . foregroundColor ( wordColor)
86- . background ( wordBackground)
87- . rotation3DEffect (
94+ let text = writingDirection == . RightToLeft ? String ( component. text. reversed ( ) ) : component. text
95+ VStack {
96+ Text ( text)
97+ . font ( component. font)
98+ . foregroundColor ( component. color)
99+ . border ( Color . blue)
100+ Text ( " \( positionInDegree) " )
101+ } . rotation3DEffect (
88102 . degrees( positionInDegree) ,
89103 axis: ( x: 0.0 , y: 1.0 , z: 0.0 ) ,
90104 anchor: . center,
91105 anchorZ: anchorZ,
92106 perspective: perspective
93107 ) . opacity ( shouldHide ? 0.0 : ( shouldBlur ? 0.5 : 1.0 ) )
94- . blur ( radius: ( shouldBlur ? 1.0 : 0.0 ) )
95- } . frame ( width: w, height: estHeight, alignment: . center)
96-
97- } . frame ( width: frameL. size. width, height: frameL. size. height) . onPreferenceChange ( ViewSizeKey . self) { sizes in
98- estHeight = ( sizes. reduce ( 0 , { $0 + $1. height} ) ) / CGFloat( sizes. count) * CGFloat( 1.1 )
99- estWidth = ( sizes. reduce ( 0 , { $0 + $1. width} ) ) / CGFloat( sizes. count)
100- }
108+ . blur ( radius: ( shouldBlur ? 1.0 : 0.0 ) ) . frame ( alignment: . center)
109+
110+ }
111+ } . frame ( width: frameL. size. width, height: frameL. size. height) . clipped ( )
101112
102113 }
103114 }
@@ -106,7 +117,7 @@ public struct SphericText<T:BinaryFloatingPoint>: View {
106117extension SphericText : Adjustable {
107118 public func wordSpacing( _ spacing: T ) -> SphericText {
108119 setProperty { tmp in
109- tmp. wordSpacing = Double ( spacing)
120+ tmp. textSpacing = Double ( spacing)
110121 }
111122 }
112123
@@ -154,7 +165,7 @@ extension SphericText : Adjustable {
154165
155166 public func radius( _ value: T ) -> SphericText {
156167 setProperty { tmp in
157- tmp. anchorZ = CGFloat ( value)
168+ tmp. radius = CGFloat ( value)
158169 }
159170 }
160171
@@ -172,11 +183,39 @@ extension SphericText : Adjustable {
172183
173184@available ( tvOS, unavailable)
174185struct SphericTextDemo : View {
186+ var demoData : [ TextComponent ] {
187+ let font = Font . system ( size: 48 )
188+ if #available( iOS 15 . 0 , * ) {
189+ return [
190+ TextComponent ( text: " A " , color: . red, font: font) ,
191+ TextComponent ( text: " B " , color: . green, font: font) ,
192+ TextComponent ( text: " C " , color: . blue, font: font) ,
193+ TextComponent ( text: " D " , color: . yellow, font: font) ,
194+ TextComponent ( text: " E " , color: . pink, font: font) ,
195+ TextComponent ( text: " F " , color: . gray, font: font) ,
196+ TextComponent ( text: " G " , color: . purple, font: font) ,
197+ TextComponent ( text: " H " , color: . cyan, font: font) ,
198+ TextComponent ( text: " I " , color: . white, font: font)
199+ ]
200+ } else {
201+ // Fallback on earlier versions
202+ return [
203+ TextComponent ( text: " A " , color: . red, font: font) ,
204+ TextComponent ( text: " B " , color: . green, font: font) ,
205+ TextComponent ( text: " C " , color: . blue, font: font) ,
206+ TextComponent ( text: " D " , color: . yellow, font: font) ,
207+ TextComponent ( text: " E " , color: . pink, font: font) ,
208+ TextComponent ( text: " F " , color: . gray, font: font) ,
209+ TextComponent ( text: " G " , color: . purple, font: font) ,
210+ TextComponent ( text: " I " , color: . white, font: font)
211+ ]
212+ }
213+ }
175214 @State var rotateDeg : CGFloat = 0.0
176215 @State var showModifier : Bool = false
177216 @State var radius : CGFloat = 40.0
178217 @State var perspective : CGFloat = 0.0
179- @State var characters = " ABCDE "
218+ @State var characters = " ABCDEGMS "
180219 @State var wordsInput = " Test \n 100 "
181220 @State var words = [ " Test " , " 100 " ]
182221 @State var blurMinors : Bool = false
@@ -200,12 +239,13 @@ struct SphericTextDemo: View {
200239 let width = geo. size. width/ 2
201240 VStack {
202241 ZStack {
203- SphericText ( characters , $rotateDeg ) . rangeOfOpposite ( in : 145 ... 210 )
204- . radius ( radius )
242+ SphericText ( demoData , rotateDegree : rotateDeg )
243+ . rangeOfOpposite ( in : 145 ... 210 )
205244 . perspective ( perspective)
206245 . blurMinors ( blurMinors)
207246 . hideOpposite ( hideOpposite)
208247 . frame ( width: width)
248+ . border ( Color . blue, width: 1.0 )
209249 }
210250 HStack {
211251 Spacer ( )
@@ -219,7 +259,7 @@ struct SphericTextDemo: View {
219259 }
220260 Divider ( ) . background ( Color . white)
221261 VStack {
222- SphericText ( words: words, degree_offset: $rotateDeg) . wordSpacing ( wordSpacing) . font ( . system( size: 32.0 ) ) . wordColor ( textColor) . wordBackground ( backgroundColor) . hideOpposite ( false ) . perspective ( perspective) . radius ( radius ) . frame ( width: width )
262+ SphericText ( words: words, degree_offset: $rotateDeg) . wordSpacing ( wordSpacing) . font ( . system( size: 32.0 ) ) . wordColor ( textColor) . wordBackground ( backgroundColor) . hideOpposite ( false ) . perspective ( perspective) . frame ( width : width ) . border ( Color . blue , width: 1.0 )
223263 #if os(iOS)
224264 if #available( macOS 11 . 0 , iOS 14 . 0 , * ) {
225265 TextEditor ( text: wordsInputBinding) . textFieldStyle ( RoundedBorderTextFieldStyle ( ) ) . frame ( width: 100 , height: 80 , alignment: . center)
@@ -272,7 +312,7 @@ struct SphericTextDemo: View {
272312 } , label: {
273313 Text ( " Rotate " )
274314 } )
275- Slider ( value: $rotateDeg, in: - 360 .0... 360.0 )
315+ Slider ( value: $rotateDeg, in: 0 .0... 360.0 )
276316 Text ( " \( rotateDeg, specifier: " %.2f " ) " ) . foregroundColor ( . white)
277317 Spacer ( minLength: 20 )
278318 }
0 commit comments