Skip to content

Instantly share code, notes, and snippets.

@notoroid
Created February 14, 2021 02:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save notoroid/5f9aef867129222b072faaa2d4c717ba to your computer and use it in GitHub Desktop.
Save notoroid/5f9aef867129222b072faaa2d4c717ba to your computer and use it in GitHub Desktop.
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