Sample of using SwifUI's rotationEffect modifier.
import SwiftUI | |
enum MechanicalArm { | |
static let colors: [Color] = [Color(UIColor(red: 0.349, green: 0.671, blue: 0.914, alpha: 1.000)) /*.red, .green, .yellow, .blue, .yellow*/] | |
static let manipulatorJointEdge = CGFloat(16.5); static let manipulatorRootEdge = CGFloat(37.5) | |
static let manipulatorWidth = CGFloat(9); static let manipulatorFirstLength = CGFloat(60); static let manipulatorOtherLength = CGFloat(37.5) | |
static let armLength = CGFloat(105); static let armWidth = CGFloat(37.5); static let armJointEdge = CGFloat(16.5) | |
static let standWidth = CGFloat(75); static let standHeighth = CGFloat(60) | |
static let defaultArmAngle = Double(0); static let defaultWristAngle = Double(-45); static let defaultFingerAngle = Double(40) | |
} | |
struct ArmShape: Shape { | |
func path(in rect: CGRect) -> Path { | |
let bezierPath = UIBezierPath() | |
bezierPath.move(to: CGPoint(x: rect.maxX - 5, y: rect.minY + 14)) | |
bezierPath.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - 19)) | |
bezierPath.addCurve(to: CGPoint(x: rect.minX + 0.50000 * rect.width, y: rect.maxY), controlPoint1: CGPoint(x: rect.maxX, y: rect.maxY - 8.51), controlPoint2: CGPoint(x: rect.minX + 0.77614 * rect.width, y: rect.maxY)) | |
bezierPath.addCurve(to: CGPoint(x: rect.minX, y: rect.maxY - 19), controlPoint1: CGPoint(x: rect.minX + 0.22386 * rect.width, y: rect.maxY), controlPoint2: CGPoint(x: rect.minX, y: rect.maxY - 8.51)) | |
bezierPath.addLine(to: CGPoint(x: rect.minX + 5, y: rect.minY + 14)) | |
bezierPath.addCurve(to: CGPoint(x: rect.minX + 9.2, y: rect.minY + 4), controlPoint1: CGPoint(x: rect.minX + 5, y: rect.minY + 10.08), controlPoint2: CGPoint(x: rect.minX + 6.61, y: rect.minY + 6.54)) | |
bezierPath.addCurve(to: CGPoint(x: rect.minX + 0.50000 * rect.width, y: rect.minY), controlPoint1: CGPoint(x: rect.minX + 11.72, y: rect.minY + 1.53), controlPoint2: CGPoint(x: rect.minX + 0.39957 * rect.width, y: rect.minY)) | |
bezierPath.addCurve(to: CGPoint(x: rect.maxX - 5, y: rect.minY + 14), controlPoint1: CGPoint(x: rect.minX + 0.70347 * rect.width, y: rect.minY), controlPoint2: CGPoint(x: rect.maxX - 5, y: rect.minY + 6.27)) | |
bezierPath.close() | |
return Path(bezierPath.cgPath) | |
} | |
} | |
struct ContentView: View { | |
@State var armAngle = MechanicalArm.defaultArmAngle | |
@State var wristAngle = MechanicalArm.defaultWristAngle | |
@State var fingerAngle = MechanicalArm.defaultFingerAngle | |
func fingerView(length: CGFloat) -> some View { | |
Rectangle().foregroundColor(MechanicalArm.colors[3 % MechanicalArm.colors.count]).frame(width: MechanicalArm.manipulatorWidth, height: length) | |
.overlay( | |
Circle().foregroundColor(MechanicalArm.colors[4 % MechanicalArm.colors.count]).frame(width: MechanicalArm.manipulatorJointEdge, height: MechanicalArm.manipulatorJointEdge) | |
.offset(x: 0, y: MechanicalArm.manipulatorJointEdge * 0.5) | |
, alignment: .bottom) | |
} | |
static let manipulatorVector: [Double] = [1,-1] | |
func manipulatorView() -> some View { | |
Rectangle().overlay(ZStack(alignment: Alignment(horizontal: .center, vertical: .bottom)){ | |
ForEach(0..<Self.manipulatorVector.count) { index in | |
fingerView(length: MechanicalArm.manipulatorFirstLength).overlay( | |
fingerView(length: MechanicalArm.manipulatorOtherLength).overlay( | |
fingerView(length: MechanicalArm.manipulatorOtherLength) | |
.rotationEffect(Angle(degrees: -1 * Self.manipulatorVector[index] * fingerAngle), anchor: .bottom) | |
.padding(MechanicalArm.manipulatorOtherLength) | |
, alignment: .bottom) | |
.rotationEffect(Angle(degrees: -1 * Self.manipulatorVector[index] * (80-fingerAngle) ), anchor: .bottom) | |
.padding(MechanicalArm.manipulatorFirstLength) | |
, alignment: .bottom) | |
.rotationEffect(Angle(degrees: Self.manipulatorVector[index] * (fingerAngle * 0.5 + 25) ), anchor: .bottom ) | |
} | |
} | |
, alignment: .bottom) | |
.foregroundColor(.clear).frame(width: MechanicalArm.manipulatorRootEdge, height: MechanicalArm.manipulatorRootEdge) | |
} | |
func mechanicalArmView() -> some View { | |
ArmShape().foregroundColor(MechanicalArm.colors[1 % MechanicalArm.colors.count]).frame(width: MechanicalArm.armWidth, height: MechanicalArm.armLength) | |
.overlay( | |
Circle().foregroundColor(MechanicalArm.colors[2 % MechanicalArm.colors.count]).frame(width: MechanicalArm.armJointEdge, height: MechanicalArm.armJointEdge) | |
.offset(x: 0, y: 7.5) | |
, alignment: .bottom) | |
} | |
var body: some View { | |
VStack { | |
Button("Reset Parameter") { | |
withAnimation { | |
self.armAngle = MechanicalArm.defaultArmAngle | |
self.wristAngle = MechanicalArm.defaultWristAngle | |
self.fingerAngle = MechanicalArm.defaultFingerAngle | |
} | |
} | |
Spacer() | |
Rectangle().overlay( | |
mechanicalArmView().overlay( | |
mechanicalArmView().overlay( | |
manipulatorView().foregroundColor(.red) | |
.rotationEffect(Angle(degrees: wristAngle), anchor: .bottom) | |
.padding(MechanicalArm.armLength) | |
, alignment: .bottom) | |
.rotationEffect(Angle(degrees: armAngle - 90), anchor: .bottom) | |
.padding(MechanicalArm.armLength) | |
, alignment: .bottom) | |
.rotationEffect(Angle(degrees: armAngle * 1.5 + 45), anchor: .bottom) | |
.padding(MechanicalArm.standHeighth) | |
, alignment: .bottom) | |
.foregroundColor(.clear).frame(width: MechanicalArm.standWidth, height: MechanicalArm.standHeighth) | |
.background( MechanicalArm.colors[0 % MechanicalArm.colors.count]) | |
Group { | |
HStack { Text("Arm: \(armAngle)"); Spacer() }; Slider(value: $armAngle, in: -45...45); Divider() | |
HStack { Text("Wrist: \(wristAngle)"); Spacer() }; Slider(value: $wristAngle, in: -80...80); Divider() | |
HStack { Text("Finger: \(fingerAngle)"); Spacer() }; Slider(value: $fingerAngle, in: 30...60); Divider() | |
} | |
} | |
.padding(15) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment