Мне нужен массив текста, который должен прокручиваться в форме кривой, как показано ниже:
Код: но с помощью этого кода я могу создать представление формы кривой зеленого цвета, но как показать массив текста в форме кривой? пожалуйста, помогите мне добиться результата.
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: Мой выходной массив строк отображается в строке, но мне нужен текст формы кривой, как показано выше, с прокруткой. Как написать для этого код? можем ли мы получить это, написав код SwiftUI? или нам нужно использовать какую-либо структуру в SwiftUI? если оно есть, поделитесь.
@workingdogsupportUkraine, спасибо, но я не смог найти подходящего degrees
для кривой, подобной приведенной выше. пожалуйста, помогите найти любое другое решение или поделитесь, пожалуйста, подходящим (Angle(degrees: ...)
Предполагая, что вы хотите, чтобы тексты вращались при прокрутке изображения, это должен быть ScrollTransition.
rotationEffect
не может вращать изображение вокруг центра «воображаемого круга», но мы можем аппроксимировать этот эффект, смещая текст вниз в дополнение к rotationEffect
около его вершины.
ZStack {
Color.green
VStack {
Spacer()
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) {
ForEach(stringArray, id: \.self) { item in
Text(item)
.foregroundColor(.white)
.containerRelativeFrame(.horizontal, count: 3, spacing: 10.0)
.padding(.bottom, 20)
.scrollTransition(.interactive.threshold(.centered)) { content, phase in
// I empirically found these magic numbers to create a
// *reasonably* convincing effect.
// In reality I don't think this should be a linear relationship,
// but I'm too bad at maths to figure out what the correct
// formulas should be
content
.offset(y: 13 * abs(phase.value))
.rotationEffect(.degrees(10 * phase.value), anchor: .top)
}
}
}
}
Spacer()
}
}
.frame(height: 160)
.clipShape(TopProtractorCurveShape())
Если вам нужен эффект табуляции, как на первом снимке экрана, где центрированный текст — это выбранная в данный момент вкладка, вы можете использовать целевое поведение прокрутки .viewAligned
:
ZStack {
Color.green
// this geometry reader is needed to calculate the correct padding for the HStack,
// such that the scroll view "snaps" at the centre of the screen
GeometryReader { geo in
VStack {
Spacer()
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) {
ForEach(stringArray, id: \.self) { item in
Text(item)
.foregroundColor(.white)
.containerRelativeFrame(.horizontal, count: 3, spacing: 10.0)
.padding(.bottom, 20)
.scrollTransition(.interactive.threshold(.centered)) { content, phase in
content
.offset(y: 13 * abs(phase.value))
.rotationEffect(.degrees(10 * phase.value), anchor: .top)
}
}
}
.scrollTargetLayout()
.padding(.horizontal, geo.size.width / 3)
}
.scrollTargetBehavior(.viewAligned)
Spacer()
}
}
}
.frame(height: 160)
.clipShape(TopProtractorCurveShape())
Выход:
Кажется, что вы хотите преобразовать смещение по оси X от прокрутки в угол поворота. Кроме того, вы, вероятно, захотите использовать изогнутый текст для надписей.
Для изогнутого текста решение SwiftUI можно найти в ответе на вопрос SwiftUI: Как обеспечить одинаковое расстояние между буквами в изогнутом текстовом представлении? (это был мой ответ).
Если известна ширина экрана, то радиус дуги изогнутого текста можно вычислить с помощью Пифагора и тригонометрии. GeometryReader
можно использовать для измерения размера экрана.
Если вы хотите, чтобы между метками было одинаковое расстояние, а не чтобы каждая из меток занимала одинаковое пространство (= равную дугу), то хорошо будет использовать HStack
со скрытыми метками, которые действуют как заполнители.
Изогнутые метки затем отображаются как наложение поверх каждой скрытой метки.
Угол поворота применяется с использованием смещения по оси y, равного радиусу дуги, затем применяется rotationEffect
, после чего смещение по оси y снова отменяется. Здесь очень важен порядок модификаторов.
Также необходимо смещение по оси X, чтобы компенсировать смещение прокрутки. Для этого необходимо знать расстояние от метки до середины экрана. Еще один GeometryReader
вокруг каждой метки можно использовать для определения этого расстояния.
Чтобы добиться прокрутки, вы можете поместить HStack
внутри ScrollView
, как вы это делали раньше. Однако мне было сложно реализовать фиксированное позиционирование вокруг центра изображения.
В качестве альтернативы использованию ScrollView
к HStack
можно применить смещение по оси X, а для изменения смещения можно использовать DragGesture
.
Когда перетаскивание заканчивается, метка, ближайшая к центру представления, становится выбранной меткой. Чтобы обеспечить фиксированное позиционирование, можно применить настройку смещения по оси X.
Вот как все это сочетается:
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()
}
}
Огромное спасибо, все супер!!! можем ли мы сделать одну модификацию? изначально мы можем видеть только два значения массива (Панель мониторинга, Посещаемость), если мой stringArray
содержит только эти два, то это идеально, но он также содержит еще 2, но они скрыты, пока мы не прокрутим. Итак, что мне нужно, так это то, что если массив длинный, я имею в виду, например, (10 или 5 значений), тогда нет необходимости в пустом пространстве перед значениями, потому что, если мы показываем только два, а перед пустыми, тогда пользователь может думать, что у нас есть только 2, пока мы не прокрутим. поэтому с одинаковыми пробелами и прокруткой нужно показать, сколько из них может отображаться справа налево, а остальные будут прокручиваться
@Swift Рад, что смог помочь, спасибо, что приняли ответ! Итак, о модификации, если я правильно понимаю: если вариантов будет много и при первом появлении представления выбирается первый, то первый вариант должен быть крайним слева, а не по центру (хотя у него есть выбор). Хорошо, итак... как бы вы представляли себе работу прокрутки, если бы вы хотели выбрать, скажем, второй вариант? Может быть, вы хотели бы, чтобы он вёл себя примерно так этот ответ, но в радиальной форме?
вы можете попробовать использовать
Text(item).rotationEffect(Angle(degrees: ...))
. Отрегулируйте угол по желанию.