Как показать строку текста массива в форме кривой в SwiftUI?

Мне нужен массив текста, который должен прокручиваться в форме кривой, как показано ниже:

Код: но с помощью этого кода я могу создать представление формы кривой зеленого цвета, но как показать массив текста в форме кривой? пожалуйста, помогите мне добиться результата.

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? если оно есть, поделитесь.

вы можете попробовать использовать Text(item).rotationEffect(Angle(degrees: ...)). Отрегулируйте угол по желанию.

workingdog support Ukraine 19.07.2024 07:19

@workingdogsupportUkraine, спасибо, но я не смог найти подходящего degrees для кривой, подобной приведенной выше. пожалуйста, помогите найти любое другое решение или поделитесь, пожалуйста, подходящим (Angle(degrees: ...)

Swift 19.07.2024 07:58
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
0
2
109
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Предполагая, что вы хотите, чтобы тексты вращались при прокрутке изображения, это должен быть 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()
    }
}

Animation

Огромное спасибо, все супер!!! можем ли мы сделать одну модификацию? изначально мы можем видеть только два значения массива (Панель мониторинга, Посещаемость), если мой stringArray содержит только эти два, то это идеально, но он также содержит еще 2, но они скрыты, пока мы не прокрутим. Итак, что мне нужно, так это то, что если массив длинный, я имею в виду, например, (10 или 5 значений), тогда нет необходимости в пустом пространстве перед значениями, потому что, если мы показываем только два, а перед пустыми, тогда пользователь может думать, что у нас есть только 2, пока мы не прокрутим. поэтому с одинаковыми пробелами и прокруткой нужно показать, сколько из них может отображаться справа налево, а остальные будут прокручиваться

Swift 19.07.2024 17:26

@Swift Рад, что смог помочь, спасибо, что приняли ответ! Итак, о модификации, если я правильно понимаю: если вариантов будет много и при первом появлении представления выбирается первый, то первый вариант должен быть крайним слева, а не по центру (хотя у него есть выбор). Хорошо, итак... как бы вы представляли себе работу прокрутки, если бы вы хотели выбрать, скажем, второй вариант? Может быть, вы хотели бы, чтобы он вёл себя примерно так этот ответ, но в радиальной форме?

Benzy Neez 19.07.2024 18:02

Другие вопросы по теме