Как создать собственную фигуру в SwiftUI с закругленными верхними углами и неровным нижним краем, напоминающим рваную бумагу?

Я работаю со SwiftUI, и мне нужно реализовать собственную форму, имитирующую определенный дизайн. Форма представляет собой прямоугольник, но с характерными особенностями: верхние углы закруглены, а нижний край напоминает ряд скругленных треугольников, что придает ему вид рваной бумаги.

ссылка

Может ли кто-нибудь подсказать мне, как создать эту конкретную форму, или предложить лучший подход для достижения эффекта рваной бумаги на нижнем крае прямоугольника в SwiftUI?

Вот что я пробовал до сих пор:

struct CustomShape: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()

        let width = rect.size.width
        let height = rect.size.height

        let bottomLeft = CGPoint(x: 0, y: height)
        let bottomRight = CGPoint(x: width, y: height)
        let topLeft = CGPoint(x: 0, y: 0)
        let topRight = CGPoint(x: width, y: 0)

        path.move(to: bottomRight)

        path.addLine(to: CGPoint(x: bottomRight.x, y: topRight.y))

        path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y))
        
        path.addLine(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y))
        
        let triangleWidth: CGFloat = 11
        let triangleHeight: CGFloat = 6

        var x: CGFloat = 0
        while x <= rect.width {
            let startX = x
            let endX = x + triangleWidth
            let midX = (startX + endX) / 2

            path.move(to: CGPoint(x: startX, y: rect.maxY))
            path.addLine(to: CGPoint(x: midX, y: rect.maxY - triangleHeight))
            path.addLine(to: CGPoint(x: endX, y: rect.maxY))

            x += triangleWidth
        }

        return path
    }
}

какашки

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
410
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я параметризую эту форму радиусом верхних углов, угловым радиусом треугольников и размером треугольников.

Данному rect может не соответствовать целое число таких треугольников. Непонятно, как в этом случае должна выглядеть фигура, поэтому я буду считать ширину треугольника минимальной шириной. Фактические треугольники могут быть шире, чтобы вместить целое число треугольников в заданный rect.

Вот код. Основная идея состоит в том, чтобы использовать addArc(tangent1End:tangent2End:radius:), чтобы каждая линия, которую мы рисуем, имела закругленный угол. См. также объяснение в комментариях.

struct CustomShape: Shape {
    let topCornersRadius: CGFloat
    let trianglesCornerRadius: CGFloat
    let triangleHeight: CGFloat
    let minTriangleWidth: CGFloat
    
    func path(in rect: CGRect) -> Path {
        Path { path in
            // calculate the minimum width of the triangles,
            // such that it is at least minTriangleWidth, and
            // rect.width fits an integer number of such triangles
            let triangleCount = Int(rect.width / minTriangleWidth)
            let actualTriangleWidth = rect.width / CGFloat(triangleCount)
            
            // start from the top left
            path.move(to: .init(x: rect.minX + topCornersRadius, y: rect.minY))
            // draw the rounded corner for the top left corner
            path.addArc(
                tangent1End: rect.origin,
                tangent2End: .init(x: rect.minX, y: rect.minX + topCornersRadius),
                radius: topCornersRadius
            )
            
            // draw the left side of the shape
            var tangent1End = CGPoint(x: rect.minX, y: rect.maxY)
            var tangent2End = tangent1End.applying(.init(translationX: actualTriangleWidth / 2, y: -triangleHeight))
            path.addArc(tangent1End: tangent1End, tangent2End: tangent2End, radius: trianglesCornerRadius / 2)
            
            // draw most of the triangles
            // each iteration draws one side of a triangle
            // we don't draw the right side of the last triangle in the loop,
            // because it will have a different tangent2End
            for i in 0..<(2 * triangleCount - 1) {
                tangent1End = tangent2End
                tangent2End = tangent1End.applying(.init(
                    translationX: actualTriangleWidth / 2,
                    y: triangleHeight * (i.isMultiple(of: 2) ? 1 : -1)
                ))
                path.addArc(tangent1End: tangent1End, tangent2End: tangent2End, radius: trianglesCornerRadius)
            }
            
            // draw the right side of the last triangle
            path.addArc(tangent1End: tangent2End, tangent2End: .init(x: rect.maxX, y: rect.minY), radius: trianglesCornerRadius / 2)
            
            // draw the right side of the shape
            path.addArc(
                tangent1End: .init(x: rect.maxX, y: rect.minY),
                tangent2End: rect.origin,
                radius: topCornersRadius
            )
            
            // connects the top right corner of the shape to the top left corner
            path.closeSubpath()
        }
    }
}

Пример вывода:

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