I need array of text should scroll in curve shape like below:
Code: but with this code i can create green colour curve shape view but how to show array of text in curve shape ? please help me to achieve the output.
struct HomeNew: View {
@State private var stringArray = ["Dashboard", "Attendance", "Feed", "Time table"]
var body: some View {
ZStack{
Color.white
.ignoresSafeArea()
VStack{
ZStack {
Color.appGreen2
Spacer()
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 10) {
ForEach(stringArray, id: \.self) { item in
Text(item)
.foregroundColor(.white)
.padding()
}
}
.padding(.horizontal, 10)
}
}
.frame(height: 160)
.clipShape(TopProtractorCurveShape())
}
}
}
}
struct TopProtractorCurveShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
// Starting from the bottom-left corner
path.move(to: CGPoint(x: 0, y: rect.height))
// Drawing line to the top-left corner
path.addLine(to: CGPoint(x: 0, y: rect.height * 0.4))
// Drawing the protractor-like curve
path.addQuadCurve(to: CGPoint(x: rect.width, y: rect.height * 0.4),
control: CGPoint(x: rect.width / 2, y: rect.height * 0.2))
// Drawing line to the bottom-right corner
path.addLine(to: CGPoint(x: rect.width, y: rect.height))
return path
}
}
o/p: My output string array shows in line, But i need like above screen shot curve shape text with scrolling. How to write code for that? can we get it by writing SwiftUI coding? or do we need to use any framework in swiftui? if it is there please share.
It seems that what you are wanting to do is to convert the x-displacement from scrolling into an angle of rotation. Also, you probably want to use curved text for the labels.
For the curved text, a SwiftUI solution can be found in the answer to SwiftUI: How to have equal spacing between letters in a curved text view? (it was my answer).
If the screen width is known then the arc radius for the curved text can be computed using pythagoras and trigonometry. A GeometryReader
can be used to measure the size of the screen.
If you want to have equal distance between the labels, as opposed to each of the labels occupying an equal space (= equal arc), then it works well to use an HStack
with hidden labels that act as placeholders.
The curved labels are then shown as an overlay over each of the hidden labels.
The angle of rotation is applied by using a y-offset equal to the arc radius, then a rotationEffect
is applied, then the y-offset is undone again. The order of modifiers is very important here.
An x-offset is also needed, to compensate for the scroll displacement. This requires knowing the distance from the label to the middle of the screen. Another GeometryReader
around each label can be used to determine this distance.
To achieve scrolling, you can put the HStack
inside a ScrollView
as you were doing before. However, I found it difficult to implement sticky positioning around the center of the view.
As an alternative to using a ScrollView
, an x-offset can be applied to the HStack
and a DragGesture
can be used to change the offset.
When a drag ends, the label that is nearest to the center of the view becomes the selected label. To give sticky positioning, an adjustment to the x-offset can be applied.
Here is how it all comes together:
struct HomeNew: View {
let stringArray = ["Dashboard", "Attendance", "Feed", "Time table"]
let backgroundHeight: CGFloat = 160
let labelSpacing: CGFloat = 40
@State private var selectedIndex = 0
@State private var xOffset = CGFloat.zero
@GestureState private var dragOffset = CGFloat.zero
var body: some View {
GeometryReader { outer in
let w = outer.size.width
let halfWidth = w / 2
let curveHeight = backgroundHeight * 0.1
let slopeLen = sqrt((halfWidth * halfWidth) + (curveHeight * curveHeight))
let arcRadius = (slopeLen * slopeLen) / (2 * curveHeight)
let arcAngle = 4 * asin((slopeLen / 2) / arcRadius)
HStack(spacing: labelSpacing) {
ForEach(Array(stringArray.enumerated()), id: \.offset) { index, item in
Text(item)
.lineLimit(1)
.fixedSize()
.hidden()
.overlay {
GeometryReader { proxy in
let midX = proxy.frame(in: .global).midX
let offsetFromMiddle = midX - (w / 2)
// https://stackoverflow.com/a/77280669/20386264
CurvedText(string: item, radius: arcRadius)
.foregroundStyle(.white)
.offset(y: -arcRadius)
.rotationEffect(.radians((offsetFromMiddle / w) * arcAngle))
.offset(x: -offsetFromMiddle, y: arcRadius)
.onAppear {
if selectedIndex == index {
xOffset = -offsetFromMiddle
}
}
.onChange(of: dragOffset) { oldVal, newVal in
if newVal == 0 {
let halfWidth = (proxy.size.width + labelSpacing) / 2
if (abs(offsetFromMiddle) < halfWidth) ||
(index == 0 && offsetFromMiddle > 0) ||
(index == stringArray.count - 1 && offsetFromMiddle < 0) {
selectedIndex = index
xOffset -= offsetFromMiddle
}
}
}
}
}
}
}
.offset(x: xOffset + dragOffset)
.animation(.easeInOut, value: xOffset)
.gesture(
DragGesture(minimumDistance: 0)
.updating($dragOffset) { val, state, trans in
state = val.translation.width
}
.onEnded { val in
xOffset += val.translation.width
}
)
.padding(.top, curveHeight + 36)
.frame(width: w)
}
.frame(height: backgroundHeight, alignment: .top)
.background(.green) //appGreen2
.clipShape(TopProtractorCurveShape())
.ignoresSafeArea()
}
}