Как сделать функции векторного рисования более краткими в Swift - PullRequest
0 голосов
/ 06 февраля 2020

Я сделал кнопку, соответствующую стилю остальной части моего приложения, но она слишком громоздкая. Я попробовал несколько вещей, но это настолько мало, насколько я смог получить.

class ButtonPath {
    private let wiggle: Int = 3
    func points(height: Int) ->
        ((Int,Int),(Int,Int),(Int,Int),
         (Int,Int),(Int,Int),(Int,Int),
         (Int,Int),(Int,Int),(Int,Int),
         (Int,Int),(Int,Int),(Int,Int))
    {
        func rand() -> Int { Int.random(in: -wiggle...wiggle) }
        func r2(x: Int) -> Int { Int.random(in: -x...x) }
        let screen: CGRect = UIScreen.main.bounds
        let widthMx = CGFloat(0.9)
        let origin = (x:15, y:15)
        let width = Int(screen.width * widthMx)

        // Corner points
        let tl   = (x: origin.x + rand(),   y: origin.x + rand()) // tl = Top Left, etc.
        let tr   = (x: origin.x + width + rand(),      y: origin.y + rand())
        let bl   = (x: origin.x + rand(),   y: origin.y + height + rand())
        let br   = (x: origin.x + width + rand(),      y: origin.y + height + rand())

        // Arc controls, we're drawing a rectangle counter-clockwise from the top left
        let a1c1 = (x: origin.x + rand(),   y: Int(Double(origin.y+height+rand()) * 0.3)) // a1c1 = Arc 1 Control 1
        let a1c2 = (x: origin.x + rand(),   y: Int(Double(origin.y+height+rand()) * 0.6))
        let a2c1 = (x: Int(Double(origin.x+width+rand()) * 0.3), y: origin.y + height + rand())
        let a2c2 = (x: Int(Double(origin.x+width+rand()) * 0.6), y: origin.y + height + rand())
        let a3c1 = (x: origin.x + width + rand(),                y: Int(Double(origin.y + height+rand()) * 0.6))
        let a3c2 = (x: origin.x + width + rand(),                y: Int(Double(origin.y + height+rand()) * 0.3))
        let a4c1 = (x: Int(Double(origin.x+width+rand()) * 0.6), y: origin.y + rand())
        let a4c2 = (x: Int(Double(origin.x+width+rand()) * 0.6), y: origin.y + rand())

        return (
            t1: tl, tr: tr, b1: bl, br: br,
            a1c1: a1c1, a1c2: a1c2, a2c1: a2c1,
            a2c2:a2c2, a3c1:a3c1, a3c2:a3c2, a4c1:a4c1, a4c2:a4c2
        )
    }
    func path (height:Int) -> Path {
        let (tl, tr, bl, br, a1c1, a1c2, a2c1, a2c2, a3c1, a3c2, a4c1, a4c2) = points(height: height)
        return Path { path in
            path.move( to: CGPoint(x: tl.0, y: tl.1) )
            path.addCurve( to: CGPoint(x: bl.0, y: bl.1), control1: CGPoint(x: a1c1.0, y: a1c1.1), control2: CGPoint(x: a1c2.0, y: a1c2.1))
            path.addCurve( to: CGPoint(x: br.0, y: br.1), control1: CGPoint(x: a2c1.0, y: a2c1.1), control2: CGPoint(x: a2c2.0, y: a2c2.1))
            path.addCurve( to: CGPoint(x: tr.0, y: tr.1), control1: CGPoint(x: a3c1.0, y: a3c1.1), control2: CGPoint(x: a3c2.0, y: a3c2.1))
            path.addCurve( to: CGPoint(x: tl.0-2, y: tl.1), control1: CGPoint(x: a4c1.0, y: a4c1.1), control2: CGPoint(x: a4c2.0, y: a4c2.1))
        }
    }
}

Есть ли функция, которую я мог бы написать, чтобы вывести значение углов, дуг, путей и т. Д. c

1 Ответ

3 голосов
/ 06 февраля 2020

Вы можете упростить свой код несколькими способами:

  • Просто используйте CGFloat везде вместо постоянного преобразования между Int и Double.
  • Добавьте немного fileprivate операторы для выполнения математики на CGPoint. Или добавьте их по всему проекту, если они вам нужны в других файлах.
  • Выделите метод, который покачивает точку.
  • Выделите метод, который dr aws извилистая строка из текущей указать на другую точку.

Вы, очевидно, используете тип Path. Итак, я делаю вывод, что вы используете SwiftUI.

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

Кроме того, поскольку SwiftUI может запрашивать View (включая * 1033) * или Path) для его body в любое время, и сколько угодно раз важно, чтобы код определялся c. Это означает, что вы не используете SystemRandomNumberGenerator. Итак, давайте начнем с определения детерминированных c RandomNumberGenerator:

struct Xorshift128Plus: RandomNumberGenerator {
    var seed: (UInt64, UInt64)

    mutating func next() -> UInt64 {
        // Based on https://stackoverflow.com/a/40385556/77567
        // The state must be seeded so that it is not everywhere zero.
        if seed == (0, 0) { seed = (0, 1) }
        var x = seed.0
        x ^= x << 23
        x ^= x >> 17
        x ^= seed.1
        x ^= seed.1 >> 26
        seed = (seed.1, x)
        return seed.0 &+ seed.1
    }
}

Нам также понадобятся эти арифметические c операторы для CGPoint:

fileprivate func *(s: CGFloat, p: CGPoint) -> CGPoint { .init(x: s * p.x, y: s * p.y) }
fileprivate func +(p: CGPoint, q: CGPoint) -> CGPoint { .init(x: p.x + q.x, y: p.y + q.y) }
fileprivate func -(p: CGPoint, q: CGPoint) -> CGPoint { .init(x: p.x - q.x, y: p.y - q.y) }

Теперь мы готовы реализовать метод, который покачивает точку:

extension CGPoint {
    fileprivate func wiggled<RNG: RandomNumberGenerator>(by wiggle: CGFloat, using rng: inout RNG) -> CGPoint {
        return .init(
            x: x + CGFloat.random(in: -wiggle...wiggle, using: &rng),
            y: y + CGFloat.random(in: -wiggle...wiggle, using: &rng))
    }
}

Обратите внимание, что мы обобщены c над типом RandomNumberGenerator, потому что мы принимаем его inout. Мы могли бы жестко кодировать Xorshift128Plus или любую другую конкретную реализацию RandomNumberGenerator вместо generi c. Но зачем себя ограничивать?

Имея точечный вигглер, мы можем реализовать метод addWigglyLine на Path:

extension Path {
    fileprivate mutating func addWigglyLine<RNG: RandomNumberGenerator>(
        to p3: CGPoint, wiggle: CGFloat, rng: inout RNG)
    {
        let p0 = currentPoint ?? .zero
        let v = p3 - p0
        let p1 = (p0 + (1/3) * v).wiggled(by: wiggle, using: &rng)
        let p2 = (p0 + (2/3) * v).wiggled(by: wiggle, using: &rng)
        addCurve(to: p3, control1: p1, control2: p2)
    }
}

И, наконец, мы можем реализовать WigglyRect, a обычай Shape. Опять же, мы должны быть обобщенными c над RandomNumberGenerator. Обратите внимание, что, поскольку нам нужно дважды упомянуть начальную / конечную точку, мы храним ее в p0, чтобы мы могли аккуратно закрыть путь.

struct WigglyRect<RNG: RandomNumberGenerator>: Shape {
    var rng: RNG
    var wiggle: CGFloat = 8

    func path(in frame: CGRect) -> Path {
        var rng = self.rng
        func p(_ x: CGFloat, _ y: CGFloat) -> CGPoint {
            return CGPoint(x: x, y: y).wiggled(by: wiggle, using: &rng)
        }

        let rect = frame.insetBy(dx: wiggle, dy: wiggle)
        let (x0, x1, y0, y1) = (rect.minX, rect.maxX, rect.minY, rect.maxY)
        let p0 = p(x0, y0)
        var path = Path()
        path.move(to: p0)
        path.addWigglyLine(to: p(x1, y0), wiggle: wiggle, rng: &rng)
        path.addWigglyLine(to: p(x1, y1), wiggle: wiggle, rng: &rng)
        path.addWigglyLine(to: p(x0, y1), wiggle: wiggle, rng: &rng)
        path.addWigglyLine(to: p0, wiggle: wiggle, rng: &rng)
        path.closeSubpath()
        return path
    }
}

Я тестировал на macOS. Чтобы использовать ваш код, я изменил использование UIScreen.main.bounds на CGRect(x: 0, y: 0, width: 300, height: 600). Вот код предварительного просмотра:

struct ContentView: View {
    @State var seed: (UInt64, UInt64) = makeSeed()

    var body: some View {
        VStack(spacing: 20) {
            ButtonPath().path(height: 100)
                .stroke()
                .frame(height: 100)
                .background(Color.pink.opacity(0.2))
            WigglyRect(rng: Xorshift128Plus(seed: seed))
                .stroke()
                .frame(height: 100)
                .background(Color.pink.opacity(0.2))
            Spacer()
            Button("Reseed") {
                self.seed = Self.makeSeed()
            }
        } //
            .padding()
    }

    private static func makeSeed() -> (UInt64, UInt64) {
        (UInt64.random(in: .min ... .max), UInt64.random(in: .min ... .max))
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .previewLayout(PreviewLayout.fixed(width: 300, height: 600))
    }
}

Вот как это выглядит:

demo of wiggly buttons

Ваш код dr aws верхняя форма , Код в этом ответе dr aws нижняя форма. Я помещаю розовые фоны позади каждой дорожки, показывая рамку дорожки. Обратите внимание, что ваш код не делает путь соответствующим его границам.

...