Related
Swift 5, iOS 13
Using addArc to draw a semi-circle and then using rotation3DEffect to rotate it. On the y axis it works perfectly, but in the x axis is seems broken. Am I doing something wrong here.
I got this code to work. But it is a kludge.
I want to us the x axis on the MyArc2 and not have to use an offset+rotationEffect. The first image is what you see with this code as is.
However if I do use x axis it doesn't draw it correctly.
import SwiftUI
struct ContentView: View {
#State var turn:Double = 0
var body: some View {
return VStack {
Image(systemName: "circle")
.foregroundColor(Color.blue)
.onTapGesture {
withAnimation(.linear(duration: 36)) {
self.turn = 360
}
}
Globe2View(turn: $turn)
} // VStack
}
}
struct Globe2View: View {
struct MyArc : Shape {
func path(in rect: CGRect) -> Path {
var p = Path()
p.addArc(center: CGPoint(x: UIScreen.screenWidth/2, y:UIScreen.screenHeight/2), radius: 64, startAngle: .degrees(90), endAngle: .degrees(270), clockwise: true)
return p
}
}
struct MyArc2 : Shape {
func path(in rect: CGRect) -> Path {
var p = Path()
p.addArc(center: CGPoint(x: UIScreen.screenWidth/2, y:UIScreen.screenHeight/2), radius: 64, startAngle: .degrees(90), endAngle: .degrees(270), clockwise: true)
return p
}
}
#Binding var turn:Double
var body: some View {
ZStack {
ForEach(0..<12) { loop in
MyArc()
.stroke(Color.blue, lineWidth: 2)
.rotation3DEffect(.degrees(self.turn + Double((loop * 30))), axis: (x: 0, y: -1, z: 0), anchor: UnitPoint.center, anchorZ: 0, perspective: 0)
}
ForEach(0..<12) { loop in
MyArc2()
.stroke(Color.green, lineWidth: 2)
.rotation3DEffect(.degrees(self.turn + Double((loop * 30))), axis: (x: 0, y: -1, z: 0), anchor: UnitPoint.center, anchorZ: 0, perspective: 0)
.rotationEffect(.degrees(90))
.offset(x: 20, y: 20)
}
}
}
}
If I change the last few lines so they look like this...
struct MyArc2 : Shape {
func path(in rect: CGRect) -> Path {
var p = Path()
p.addArc(center: CGPoint(x: UIScreen.screenWidth/2, y:UIScreen.screenHeight/2), radius: 64, startAngle: .degrees(0), endAngle: .degrees(180), clockwise: true)
return p
}
}
ForEach(0..<12) { loop in
MyArc2()
.stroke(Color.green, lineWidth: 2)
.rotation3DEffect(.degrees(self.turn + Double((loop * 30))), axis: (x: 1, y: 0, z: 0), anchor: UnitPoint.center, anchorZ: 0, perspective: 0)
// .rotationEffect(.degrees(90))
// .offset(x: 20, y: 20)
}
It is not clear why it is needed MyArc2 as it is the same, but the following, with simple fix in shape, just works as expected (to my mind).
Tested with Xcode 11.4 / iOS 13.4
struct Globe2View: View {
struct MyArc : Shape {
func path(in rect: CGRect) -> Path {
var p = Path()
// used provided dynamic rect instead of hardcoded NSScreen
p.addArc(center: CGPoint(x: rect.width/2, y:rect.height/2), radius: 64, startAngle: .degrees(90), endAngle: .degrees(270), clockwise: true)
return p
}
}
#Binding var turn:Double
var body: some View {
ZStack {
ForEach(0..<12) { loop in
MyArc()
.stroke(Color.blue, lineWidth: 2)
.rotation3DEffect(.degrees(self.turn + Double((loop * 30))), axis: (x: 0, y: -1, z: 0), anchor: UnitPoint.center, anchorZ: 0, perspective: 0)
}
ForEach(0..<12) { loop in
MyArc()
.stroke(Color.green, lineWidth: 2)
.rotation3DEffect(.degrees(self.turn + Double((loop * 30))), axis: (x: 0, y: -1, z: 0), anchor: UnitPoint.center, anchorZ: 0, perspective: 0)
.rotationEffect(.degrees(90))
}
}
}
}
I have a rudimentary ClockView build with SwiftUI. :) My question is if applying drop shadows to the clock-hands is easily possible with this approach or if i need a different layout and grouping of the elements? I've tried to find a way to add shadows to Path, but got stuck. Thank you.
Code
import SwiftUI
struct ClockView: View {
#Binding var time : TimeValue
let hoursHandWidth = 10
let minutesHandWidth = 10
let secondsHandWidth = 10
var body: some View {
return(
ZStack {
// clock-face
Path { path in
let seconds : Path = Path(CGRect(x: 100, y: -0.5, width: 10, height: 1))
for i in 0...60 {
…
}
}
.fill(Color(red: 0.3, green: 0.3, blue: 0.3, opacity: 1.0))
Path { path in
let hours : Path = Path(CGRect(x: 100, y: -1, width: 12, height: 2))
for i in 0...12 {
…
}
}
.fill(Color(red: 0.3, green: 0.3, blue: 0.3, opacity: 1.0))
Path { path in
let threehours : Path = Path(CGRect(x: 95, y: -2, width: 20, height: 4))
for i in 0...4 {
…
}
}
.fill(Color.red)
// clock-hands
Path { path in
let minutehand : Path = Path(CGRect(x: 0, y: -(minutesHandWidth/2), width: 95, height: minutesHandWidth))
path.addPath(minutehand, …)
}
.fill(Color.blue)
Path { path in
let hourhand : Path = Path(CGRect(x: 0, y: -(hoursHandWidth/2), width: 72, height: hoursHandWidth))
path.addPath(hourhand, …)
}
.fill(Color.blue)
Path { path in
let secondshand : Path = Path(CGRect(x: 0, y: -(secondsHandWidth/2), width: 97, height: secondsHandWidth))
path.addPath(secondshand, …)
}
.fill(Color.yellow)
.animation(.easeInOut)
Path { path in
path.addPath(Path(CGPath(ellipseIn: CGRect(x: -5, y: -5, width: 10, height: 10), transform: nil)))
}
.fill(Color.black)
}
.offset(CGSize(width: 150, height: 150))
.frame(width: 300, height: 300, alignment: .topLeading)
.scaleEffect(1.2)
.background(Color(red: 0.8, green: 0.8, blue: 0.8, opacity: 1.0))
)
}
}
Look
** Update **
I've applied .shadow(..) as user Asperi recommended:
Path { path in
let secondshand : Path = Path(CGRect(x: 0, y: -(secondsHandWidth/2), width: 97, height: secondsHandWidth))
path.addPath(secondshand, transform: .init(rotationAngle: 2.0 * CGFloat.pi * CGFloat(time.seconds-15)/60.0))
}
.fill(Color.yellow)
.shadow(color: .black, radius: 8, x: 1, y: 1)
But the result is not exactly what i expected :)
I've moved the transformation (rotation) from the subpath to the resulting path. Now it looks good.
Old:
Path { path in
let secondshand : Path = Path(CGRect(x: 0, y: -(secondsHandWidth/2), width: 97, height: secondsHandWidth))
path.addPath(secondshand, transform: .init(rotationAngle: 2.0 * CGFloat.pi * CGFloat(time.seconds-15)/60.0))
}
.fill(Color.yellow)
.shadow(color: .black, radius: 2)
New:
Path { path in
let secondshand : Path = Path(CGRect(x: 0, y: -(secondsHandWidth/2), width: 97, height: secondsHandWidth))
path.addPath(secondshand)
}
.fill(Color.yellow)
.shadow(color: .black, radius: 2)
.transformEffect(CGAffineTransform(rotationAngle: 2.0 * CGFloat.pi * CGFloat(time.seconds-15)/60.0))
Look (new):
Here is an example
Path { path in
let secondshand : Path = Path(CGRect(x: 0, y: -(secondsHandWidth/2), width: 97, height: secondsHandWidth))
path.addPath(secondshand)
}
.fill(Color.yellow)
.shadow(color: .black, radius: 8, x: 4, y: 1)
I need to create a shape from several other shapes, and ideally, I'd get a single shape struct at the end which would have two shapes stacked as ZStack would. I haven't noticed an obvious implementation of this anywhere, so maybe somebody has some ideas on how to implement it?
Here's what I want to have:
struct CustomShape: Shape {
func path(in rect: CGRect) -> Path {
// Add shapes here???? For example, combine Rectangle with Circle?
}
}
You can use func addPath(_ path: Path, transform: CGAffineTransform = .identity) to create a new Shape like this :
struct CustomShape: Shape {
func path(in rect: CGRect) -> Path {
var customShape = Path { p in
p.move(to: CGPoint(x: 0, y: 0))
p.addQuadCurve(to: CGPoint(x: 0, y: 100),
control: CGPoint(x: 0, y: 0))
p.addCurve(to: CGPoint(x: 100, y: 400),
control1: CGPoint(x: 0, y: 200),
control2: CGPoint(x: 100, y: 200))
p.addCurve(to: CGPoint(x: 200, y: 100),
control1: CGPoint(x: 100, y: 200),
control2: CGPoint(x: 200, y: 200))
p.addQuadCurve(to: CGPoint(x: 200, y: 0),
control: CGPoint(x: 200, y: 0))
}
let rectangle = Rectangle()
.path(in: customShape.boundingRect)
.offsetBy(dx: 100, dy: 0)
customShape.addPath(rectangle, transform: .init(scaleX: 0.5, y: 0.35))
return customShape
}
}
And use it like this :
struct SwiftUIView: View {
var body: some View {
CustomShape()
}
}
I have a simple task: rotate UIView on user touch. Top of my view must always pointed on finger touch.
I tried to do it with next code:
func handlePan(recognizer: UIPanGestureRecognizer) {
let currentPoint = recognizer.locationInView(self.superview)
let dx = currentPoint.x - self.center.x
let dy = currentPoint.y - self.center.y - self.radius
let angle = atan2(dy, dx)
var deg = angle * CGFloat(2.0 / M_PI)
if deg < 0 {
deg += 360.0
}
let transform = CGAffineTransformRotate(self.rotateView.transform, angle)
self.rotateView.transform = transform
println("x: \(currentPoint.x), y: \(currentPoint.y), angle: \(angle), deg: \(deg)")
}
But I have a wrong angle as you can see below
x: 235.0, y: 426.5, angle: 0.616667, deg: 0.392582
x: 221.0, y: 431.5, angle: 0.761626, deg: 0.484866
x: 212.0, y: 433.5, angle: 0.858078, deg: 0.54627
x: 203.0, y: 436.5, angle: 0.973115, deg: 0.619504
x: 196.0, y: 437.5, angle: 1.05952, deg: 0.67451
x: 187.0, y: 439.5, angle: 1.18336, deg: 0.753351
x: 179.0, y: 439.5, angle: 1.29117, deg: 0.821982
x: 168.0, y: 439.5, angle: 1.45047, deg: 0.9234
x: 143.0, y: 439.5, angle: 1.82228, deg: 1.1601
x: 122.0, y: 437.5, angle: 2.10547, deg: 1.34038
x: 108.0, y: 435.5, angle: 2.26738, deg: 1.44346
x: 96.0, y: 429.5, angle: 2.42129, deg: 1.54144
x: 80.0, y: 423.5, angle: 2.5815, deg: 1.64343
x: 73.0, y: 418.5, angle: 2.66274, deg: 1.69515
x: 68.0, y: 415.5, angle: 2.71183, deg: 1.7264
x: 60.0, y: 409.5, angle: 2.79456, deg: 1.77907
x: 56.0, y: 406.5, angle: 2.83288, deg: 1.80347
x: 47.0, y: 397.5, angle: 2.9309, deg: 1.86587
x: 40.0, y: 384.0, angle: 3.05294, deg: 1.94356
x: 26.0, y: 359.0, angle: -3.03503, deg: 358.068
x: 18.0, y: 342.0, angle: -2.92442, deg: 358.138
x: 12.0, y: 326.0, angle: -2.83205, deg: 358.197
x: 8.0, y: 312.0, angle: -2.75807, deg: 358.244
x: 6.0, y: 303.0, angle: -2.71317, deg: 358.273
x: 5.0, y: 296.0, angle: -2.67881, deg: 358.295
x: 3.0, y: 286.0, angle: -2.63395, deg: 358.323
x: 2.0, y: 281.0, angle: -2.61273, deg: 358.337
x: 2.0, y: 277.0, angle: -2.59407, deg: 358.349
x: 2.0, y: 272.0, angle: -2.57132, deg: 358.363
x: 2.0, y: 269.0, angle: -2.55798, deg: 358.372
x: 3.0, y: 266.0, angle: -2.54192, deg: 358.382
x: 4.0, y: 264.0, angle: -2.53029, deg: 358.389
x: 6.0, y: 262.0, angle: -2.51563, deg: 358.398
x: 8.0, y: 260.0, angle: -2.5009, deg: 358.408
x: 11.0, y: 254.0, angle: -2.46631, deg: 358.43
x: 13.0, y: 250.0, angle: -2.44352, deg: 358.444
x: 16.0, y: 244.0, angle: -2.4098, deg: 358.466
x: 19.0, y: 238.0, angle: -2.3767, deg: 358.487
x: 24.0, y: 228.0, angle: -2.32303, deg: 358.521
x: 25.0, y: 222.0, angle: -2.29921, deg: 358.536
x: 28.0, y: 214.0, angle: -2.26265, deg: 358.56
x: 28.0, y: 206.0, angle: -2.2387, deg: 358.575
x: 29.0, y: 199.0, angle: -2.21521, deg: 358.59
x: 30.0, y: 191.0, angle: -2.19018, deg: 358.606
x: 31.0, y: 182.0, angle: -2.16401, deg: 358.622
x: 32.0, y: 174.0, angle: -2.14163, deg: 358.637
x: 34.0, y: 168.0, angle: -2.12118, deg: 358.65
x: 35.0, y: 161.0, angle: -2.10286, deg: 358.661
x: 36.0, y: 155.0, angle: -2.08731, deg: 358.671
x: 36.0, y: 152.0, angle: -2.08147, deg: 358.675
x: 37.0, y: 149.0, angle: -2.07233, deg: 358.681
x: 39.0, y: 146.0, angle: -2.05992, deg: 358.689
x: 39.0, y: 146.0, angle: -2.05992, deg: 358.689
And I don't know why my angle is always ~385.
Anyone have any idea what I do wrong?
Glancing at your code, it looks like you're converting from radians to degrees incorrectly. 360 degrees = 2π radians, so...
var deg = angle * CGFloat(180.0 / M_PI)
should fix things.
Hope this helps.
$(function () {
var gaugeOptions = {
chart: {
type: 'solidgauge'
},
title: null,
pane: {
center: ['50%', '85%'],
size: '140%',
startAngle: -90,
endAngle: 90,
background: {
backgroundColor: (Highcharts.theme && Highcharts.theme.background2) || '#EEE',
innerRadius: '60%',
outerRadius: '100%',
shape: 'arc'
}
},
tooltip: {
enabled: false
},
// the value axis
yAxis: {
lineWidth: 0,
minorTickInterval: null,
tickPixelInterval: 400,
tickWidth: 0,
title: {
y: -70
},
labels: {
y: 16
}
},
plotOptions: {
solidgauge: {
dataLabels: {
y: 5,
borderWidth: 0,
useHTML: true
}
}
}
};
// The speed gauge
$('#container-speed').highcharts(Highcharts.merge(gaugeOptions, {
yAxis: {
min: 0,
max: 100,
title: {
text: 'Speed'
}
},
series: [{
name: 'Speed',
data: [75]
}]
}));
});
Using the Highchart's solidgauge. Is there a way to have a gradient color up to the value showing on the gauge?
For example if my gauge is from 0 to 100, and at a specific time the value we are showing is 75, I want to show it as a gradient color transitioning from a green left section to small red right section. Of course the remaining 25% of the gauge in this example should not have any color just showing the background color of the gauge.
Is there a way to do that?