Подводные камни при рисовании лепестков цветов в SwiftUI: практика использования протокола Shape

Подробная запись всего процесса рисования пользовательских форм лепестков с использованием протокола SwiftUI Shape Protocol, включая практические советы, такие как addQuadCurve и система координат.

Предыстория

Недавно я захотел перенести своё мини-приложение WeChat "Вызов лепестковых слов" на iOS. В этой игре вы составляете слова, нажимая на буквы на лепестках. Рисовать лепестки в CSS было просто, но при переходе на SwiftUI возникли сложности.

Мои знания SwiftUI довольно поверхностны - до этого я создал лишь несколько простых приложений. Я не знал, с чего начать рисование лепестка. Поискав "SwiftUI лепесток" в Google, я нашёл эту статью. Хотя решение там было не совсем тем, что я хотел, оно дало отправную точку.

Что такое Shape Protocol?

Проще говоря, Shape Protocol в SwiftUI - это инструмент для создания собственных фигур. Вам нужно реализовать метод path(in rect: CGRect) -> Path, который определяет, как рисовать фигуру.

Первая попытка: Рисование дуг с помощью addArc

В статье использовался метод addArc:

struct DaisyPental: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        path.move(to: CGPoint(x: rect.minX, y: rect.midY))
        path.addArc(
            center: CGPoint(x: rect.maxX, y: rect.midY), 
            radius: rect.maxX*(1/11),
            startAngle: .degrees(90),
            endAngle: .degrees(270),
            clockwise: true
        )
        return path
    }
}

Чтобы понять код, я протестировал его в Xcode с красным фоном:

struct ShapTest: View {
    var body: some View {
        DaisyPental()
            .fill(.yellow.gradient)
            .frame(width: 110, height: 20)
            .background(.red)  // Красный фон для наглядности
    }
}

Система координат SwiftUI неинтуитивна

Важное замечание:

  • Начало координат вверху слева: Не внизу
  • Ось X: Положительное направление вправо (нормально)
  • Ось Y: Положительное направление вниз (неинтуитивно!)

Следовательно:

  • rect.minX, rect.minY → Верхний левый угол
  • rect.maxX, rect.maxY → Нижний правый угол
  • rect.midX, rect.midY → Центр

Экспериментируйте с изменением значений в path.move, наблюдая превью в Xcode.

Углы в addArc ещё более неинтуитивны

Определение углов в addArc особенное:

  • 0° → Вправо
  • 90° → Вниз (Не вверх!)
  • 180° → Влево
  • 270° → Вверх

Это отличается от стандартной математической системы координат.

addQuadCurve: Инструмент для кривых

Я понял, что addArc не подходит для лепестка. Увидев addQuadCurve на YouTube, я попробовал его:

path.addQuadCurve(
    to: CGPoint,      // Конечная точка
    control: CGPoint  // Контрольная точка
)
  • Начальная точка: Текущее положение
  • Конечная точка: Куда должна идти кривая
  • Контрольная точка: Определяет кривизну

Изменение контрольной точки визуально меняет кривую.

Моя финальная реализация лепестка

После экспериментов я получил эту форму:

import SwiftUI

struct PetalShape: Shape {
    func path(in rect: CGRect) -> Path {
        Path { path in
            // Начальная точка
            path.move(to: CGPoint(x: rect.minX + 30, y: rect.minY + 30))
            
            // Левая кривая лепестка
            path.addQuadCurve(
                to: CGPoint(x: rect.minX + 30, y: rect.minY - 30),
                control: CGPoint(x: rect.minX - 10, y: rect.minY)
            )
            
            // Кончик лепестка
            path.addQuadCurve(
                to: CGPoint(x: rect.maxX * 1.8, y: rect.minY),
                control: CGPoint(x: rect.midX + 40, y: -rect.maxY + 30)
            )
            
            // Правая сторона (возврат к началу)
            path.addQuadCurve(
                to: CGPoint(x: rect.minX + 30, y: rect.minY + 30),
                control: CGPoint(x: rect.midX + 40, y: rect.maxY - 30)
            )
        }
    }
}

Пример использования:

struct ShapeTest: View {
    var body: some View {
        PetalShape()
            .fill(.yellow.gradient)
            .frame(width: 110, height: 110)
    }
}

Следующий шаг: Сборка целого цветка

С одним лепестком собрать цветок просто:

  1. Дублируйте несколько лепестков
  2. Поворачивайте с помощью rotationEffect
  3. Настраивайте положение через offset
  4. Собирайте в целый цветок

Извлечённые уроки

Ключевые выводы:

  • Система координат: Особенно неинтуитивна
  • addQuadCurve > addArc: Лучше для естественных кривых
  • Контрольная точка критична: Эксперименты необходимы
  • Превью незаменимо: Постоянно используйте Canvas

В заключение

Я создал форму лепестка и продолжаю разработку "Вызова лепестковых слов" для iOS, планируя скорый релиз.

Написал эту статью, чтобы задокументировать процесс. Если вы работаете с кастомными фигурами в SwiftUI, надеюсь, это поможет!

Ссылки