Вы можете упростить свой код несколькими способами:
- Просто используйте
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))
}
}
Вот как это выглядит:
Ваш код dr aws верхняя форма , Код в этом ответе dr aws нижняя форма. Я помещаю розовые фоны позади каждой дорожки, показывая рамку дорожки. Обратите внимание, что ваш код не делает путь соответствующим его границам.