ARTICLE AD BOX
My sets: Xcode 26 beta 2 Swift 6.3 I set default thread insolation: MainActor
I can often solve errors with my knowledge, but I can't figure out the Shape with ArithmeticVector implementation.
nonisolated struct MoodBodyShape: Shape {
xcode error: Conformance of 'MoodBodyShape' to protocol 'Animatable' crosses into main actor-isolated code and can cause data races
I can't seem to fix this error. I've asked every AI I know; they try to solve it, but it always ends up throwing a strict concurrency error.
Full code:
import SwiftUI // MARK: - Shape Calculations struct ShapeCalculator { static let points = 100 static func calculateRadii(for shapeValue: Int, baseRadius: Double) -> [Double] { switch shapeValue { case 0: return calculatePolygon(sides: 3, baseRadius: baseRadius) case 1: return calculatePolygon(sides: 5, baseRadius: baseRadius) case 2: return calculatePolygon(sides: 6, baseRadius: baseRadius) case 3: return calculateFlower(bumps: 5, baseRadius: baseRadius) case 4: return calculateCircle(baseRadius: baseRadius) case 5: return calculateFlower(bumps: 6, baseRadius: baseRadius) default: return calculateFlower(bumps: 8, baseRadius: baseRadius) } } static func calculatePolygon(sides: Int, baseRadius: Double) -> [Double] { var rawRadii = [Double]() let sectorAngle = (2 * Double.pi) / Double(sides) for i in 0..<points { let theta = (Double(i) / Double(points)) * 2 * Double.pi var adjustedTheta = theta.truncatingRemainder(dividingBy: sectorAngle) if adjustedTheta < 0 { adjustedTheta += sectorAngle } adjustedTheta -= sectorAngle / 2 let r = (baseRadius * cos(sectorAngle / 2)) / cos(adjustedTheta) rawRadii.append(r) } let smoothAmount = max(5, 40 - sides * 4) return smoothArray(rawRadii, iterations: smoothAmount) } static func calculateCircle(baseRadius: Double) -> [Double] { return Array(repeating: baseRadius, count: points) } static func calculateFlower(bumps: Int, baseRadius: Double) -> [Double] { let amplitude = 6.0 let base = baseRadius - amplitude var radii = [Double]() for i in 0..<points { let theta = (Double(i) / Double(points)) * 2 * Double.pi let r = base + amplitude * cos(Double(bumps) * theta) radii.append(r) } return radii } static func smoothArray(_ arr: [Double], iterations: Int) -> [Double] { var temp = arr let len = temp.count for _ in 0..<iterations { var newArr = [Double]() for i in 0..<len { let prev = temp[(i - 1 + len) % len] let curr = temp[i] let next = temp[(i + 1) % len] newArr.append((prev + curr + next) / 3) } temp = newArr } return temp } } // MARK: - AnimatableVector for Radii Array struct AnimatableRadiiVector: VectorArithmetic, Sendable { var values: [Double] static var zero: AnimatableRadiiVector { AnimatableRadiiVector(values: Array(repeating: 0, count: ShapeCalculator.points)) } static func + (lhs: AnimatableRadiiVector, rhs: AnimatableRadiiVector) -> AnimatableRadiiVector { let result = zip(lhs.values, rhs.values).map { $0 + $1 } return AnimatableRadiiVector(values: result) } static func - (lhs: AnimatableRadiiVector, rhs: AnimatableRadiiVector) -> AnimatableRadiiVector { let result = zip(lhs.values, rhs.values).map { $0 - $1 } return AnimatableRadiiVector(values: result) } mutating func scale(by rhs: Double) { values = values.map { $0 * rhs } } var magnitudeSquared: Double { values.reduce(0) { $0 + $1 * $1 } } } // MARK: - Mood Body Shape nonisolated struct MoodBodyShape: Shape { var radiiVector: AnimatableRadiiVector var animatableData: AnimatableRadiiVector { get { radiiVector } set { radiiVector = newValue } } init(radii: [Double]) { self.radiiVector = AnimatableRadiiVector(values: radii) } func path(in rect: CGRect) -> Path { var path = Path() let cx = rect.midX let cy = rect.midY let angleOffset = -Double.pi / 2 let points = radiiVector.values.count for i in 0..<points { let angle = angleOffset + (Double(i) / Double(points)) * 2 * Double.pi let r = radiiVector.values[i] let x = cx + CGFloat(r * cos(angle)) let y = cy + CGFloat(r * sin(angle)) if i == 0 { path.move(to: CGPoint(x: x, y: y)) } else { path.addLine(to: CGPoint(x: x, y: y)) } } path.closeSubpath() return path } } // MARK: - Mouth Shape nonisolated struct MouthShape: Shape { var progress: Double let size: CGFloat var animatableData: Double { get { progress } set { progress = newValue } } func path(in rect: CGRect) -> Path { let cx = rect.midX let cy = rect.midY let norm = min(max(progress / 6, 0), 1) let mouthWidth = size * 0.12 let sad = size * 0.12 let happy = -size * 0.12 let curve = happy + (sad - happy) * norm let x1 = cx - mouthWidth let x2 = cx + mouthWidth let cx2 = cx let y = cy + size * 0.1 var path = Path() path.move(to: CGPoint(x: x1, y: y)) path.addQuadCurve( to: CGPoint(x: x2, y: y), control: CGPoint(x: cx2, y: y + curve) ) return path } } // MARK: - Main MoodShape View struct MoodShapeView: View { let progress: Double let size: CGFloat private var radii: [Double] { let shapeValue = Int(progress.rounded()) return ShapeCalculator.calculateRadii(for: shapeValue, baseRadius: size / 2) } init(progress: Double, size: CGFloat = 100) { self.progress = progress self.size = size } var body: some View { ZStack { // Body con animación automática MoodBodyShape(radii: radii) .fill(.blue) // Cambié a azul para que se vea mejor en modo claro .animation(.easeInOut(duration: 0.5), value: progress) // Eyes HStack(spacing: size * 0.22) { Circle() .fill(.black) .frame(width: 6, height: 6) Circle() .fill(.black) .frame(width: 6, height: 6) } .offset(y: -size * 0.08) // Mouth con animación automática MouthShape(progress: progress, size: size) .stroke(.black, style: StrokeStyle(lineWidth: 3, lineCap: .round)) .animation(.easeInOut(duration: 0.5), value: progress) } .frame(width: size, height: size) } } // MARK: - Demo View struct MoodShapeDemo: View { @State private var sliderValue: Double = 0 var body: some View { VStack(spacing: 30) { MoodShapeView(progress: sliderValue, size: 200) .background(Color.gray.opacity(0.1)) .cornerRadius(20) VStack(alignment: .leading, spacing: 10) { Text("Mood: \(moodLabel)") .font(.headline) Slider(value: $sliderValue, in: 0...6, step: 1) .padding(.horizontal) Text("Value: \(Int(sliderValue))") .font(.caption) .foregroundColor(.secondary) } .padding() // Botones rápidos para probar HStack { ForEach(0...6, id: \.self) { value in Button("\(value)") { withAnimation { sliderValue = Double(value) } } .buttonStyle(.bordered) } } } .padding() } private var moodLabel: String { switch Int(sliderValue) { case 0: return "😢 Very Sad (Triangle)" case 1: return "😟 Sad (Pentagon)" case 2: return "😐 Meh (Hexagon)" case 3: return "🙂 Good (Flower 5)" case 4: return "😊 Happy (Circle)" case 5: return "😄 Very Happy (Flower 6)" case 6: return "🤩 Ecstatic (Flower 8)" default: return "Unknown" } } } #Preview { MoodShapeDemo() }