Draw multiple circles in view | ForEach - swift

What I am trying to achieve
I am learning swift. I am trying to draw a line with multiple circle on it.
Then I want to be able to modify the Color of a circle when the user tap the circle.
And eventually do a drag and drop for moving the circle. Such as a custom bezier curve.
Currently, I am stuck on drawing multiple circle on the line.
What I trying to do :
1 - I create an array containing CGPoint
public var PointArray:[CGPoint]=[]
2 - Then doing an Identifiable struct
private struct Positions: Identifiable {
var id: Int
let point: CGPoint
}
3 - With CurveCustomInit, I fill the array. I got this error
No exact matches in call to instance method 'append'
I have done a lot, but I never success at using a ForEach function in one view.
I am using struct shapes as I want to customise shapes and then reuse the component. And also add a gesture function to each circle.
There is the whole code :
Note that a GeometryReader is sending the size of the view
import SwiftUI
public var PointArray:[CGPoint]=[]
public var PointArrayInit:Bool = false
private struct Positions: Identifiable {
var id: Int
let point: CGPoint
}
struct Arc: Shape {
var startAngle: Angle
var endAngle: Angle
var clockwise: Bool
var centerCustom:CGPoint
func path(in rect: CGRect) -> Path {
let rotationAdjustment = Angle.degrees(90)
let modifiedStart = startAngle - rotationAdjustment
let modifiedEnd = endAngle - rotationAdjustment
var path = Path()
path.addArc(center: centerCustom, radius: 20, startAngle: modifiedStart, endAngle: modifiedEnd, clockwise: !clockwise)
return path
}
}
struct CurveCustomInit: Shape {
private var Divider:Int = 10
func path(in rect: CGRect) -> Path {
var path = Path()
let xStep:CGFloat = DrawingZoneWidth / CGFloat(Divider)
let yStep:CGFloat = DrawingZoneHeight / 2
var xStepLoopIncrement:CGFloat = 0
path.move(to: CGPoint(x: 0, y: yStep))
var incr = 0
for _ in 0...Divider {
let Point:CGPoint = CGPoint(x: xStepLoopIncrement, y: yStep)
let value = Positions(id:incr, point:Point )
PointArray.append(value)
path.addLine(to: Point)
xStepLoopIncrement += xStep
incr += 1
}
PointArrayInit = true
return (path)
}
}
struct TouchCurveBasic: View {
var body: some View {
if !PointArrayInit {
// initialisation
CurveCustomInit()
.stroke(Color.red, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
.frame(width: 300, height: 300)
.overlay(Arc(startAngle: .degrees(0),endAngle: .degrees(360),clockwise:true).stroke(Color.blue, lineWidth: 10))
} else {
// Update point
}
}
}
struct TouchCurveBasic_Previews: PreviewProvider {
static var previews: some View {
TouchCurveBasic()
}
}
What I am trying to achieve :

Your array PointArray contains values of type CGFloat but you are trying to append a value of type Positions. That's why you're getting this error.

Related

SwiftUI: Update struct View when calling mutating func

New to Switch. I need to implement an Analog Clock with auto-updating every second to the current time (like a typical analog clock).
Drawing the Views etc is no problem. But I'm having troubles auto-updating the time.
View
struct ClockView: View {
var viewModel: ClockViewModel
var clock:ClockViewModel.TransformedClock
var size: CGFloat
#State var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
Circle()
.fill(.white)
.frame(width: size, height: size)
.overlay{
GeometryReader{ geometry in
let radius = geometry.size.width/2
let midX = geometry.safeAreaInsets.top + radius
let midY = geometry.safeAreaInsets.leading + radius
ClockFace(size: size, radius: radius, x: midX, y: midY)
ClockHand(type: .second, angle: clock.secondAngle, x: midX, y: midY, length: radius-size*0.04, size: size)
.onReceive(timer){ _ in updateAngleHere}
}
}
}
}
struct ClockHand:View{
let type: ClockHandType
#State var angle: Double
var x: CGFloat
var y: CGFloat
var length: CGFloat
var size: CGFloat
var body: some View {
RoundedRectangle(cornerRadius: 5)
.fill(type.getColor())
.frame(width: size*type.getWidth(), height: length )
.position(x: x, y: y)
.offset(y: -length/2)
.rotationEffect(Angle.degrees(angle))
}
mutating func update(angle: Double) {
print("calling \(self.angle)")
self.angle = angle
}
}
ViewModel
class ClockViewModel: ObservableObject {
#Published private var model: ClockModel
init(){
model = ClockViewModel.initClocks()
}
static func initClock()->ClockModel {
let places = [
"Europe/Bern",
]
return ClockModel(number: places.count){ index in
places[index]
}
}
var activeClock: TransformedClock{
TransformedClock(clock: model.activeWorldClock!)
}
struct TransformedClock:Identifiable{
var id:Int
var secondAngle:Double
var clock:ClockModel.WorldClock
init(clock:ClockModel.WorldClock){
let second = clock.calendar.component(.second, from: Date())
self.clock = clock
self.id = clock.id
self.secondAngle = Double(second)/60.0*360
}
}
}
Model
struct ClockModel{
var worldClocks: [WorldClock]
init(number: Int, timeFactory: (Int)->String){
worldClocks = [WorldClock]()
for index in 0..<number{
let place = timeFactory(index)
var calendar = Calendar.current
worldClocks.append(
WorldClock(
id: index,
calendar: calendar,
place: formatPlaceString(place)
)
)
}
}
struct WorldClock:Identifiable {
var calendar: Calendar
var place: String
}
}
Find my code above. First of all, I just would like to move the ClockHand for seconds to different positions. For this, I've been told to use a timer and make use of onReceive. My first idea was to call a mutating func of that struct in the closure - but since I can't seem to specifically call it on that struct, I guess it's the wrong option.
So, I need to find a way to teach my second-ClockFace struct to update / redraw itself, whenever I call the mutating func.
Examples I've found only shown functions outside a struct...
Any inut would be much appreciated.

SwiftUI, create a circle for each CGpoint in Array

I have an array of CGPoint and I'm trying to draw a little circle for each CGPoint location in this array (Using SwiftUI).
I have tried different approaches but have not been successful, I'm looking for some help.
my array : CGPoint is called "allFaceArray"
first try(doesn't working) throws an error : Generic struct 'ForEach' requires that 'CGPoint' conform to 'Hashable'
struct FaceFigure : View {
#ObservedObject var ce : CameraEngine
var size: CGSize
var body: some View {
if !ce.allFaceArray.isEmpty{
ZStack{
ForEach(ce.allFaceArray, id: \.self) { point in
Circle().stroke(lineWidth: 2.0).fill(Color.green)
}
}
}
}
}
second try I make a custom shape
struct MakeCircle: Shape {
var arrayViso: [CGPoint]
func path(in rect: CGRect) -> Path {
var path = Path()
for punti in arrayViso{
let radius = 2
path.addArc(
center: punti,
radius: CGFloat(radius),
startAngle: Angle(degrees: 0),
endAngle: Angle(degrees: 360),
clockwise: true)
}
return path
}
}
and I use it like this:
struct FaceFigure : View {
#ObservedObject var ce : CameraEngine
var size: CGSize
var body: some View {
if !ce.allFaceArray.isEmpty{
ZStack{
MakeCircle(arrayViso: ce.allFaceArray)
.stroke(lineWidth: 2.0)
.fill(Color.green)
}
}
}
}
but like this every points is connect each other with a line I don't know why...
thanks for the help
For your second approach, you can create a separate path for each point in your array and add them to the path you already defined as var path = Path(). This way your second approach would work fine
struct MakeCircle: Shape {
var arrayViso: [CGPoint]
func path(in rect: CGRect) -> Path {
var path = Path()
for punti in arrayViso{
var circle = Path()
let radius = 2
circle.addArc(
center: punti,
radius: CGFloat(radius),
startAngle: Angle(degrees: 0),
endAngle: Angle(degrees: 360),
clockwise: true)
path.addPath(circle)
}
return path
}
}
When you find Generic struct 'ForEach' requires that 'CGPoint' conform to 'Hashable', making CGPoint conform to Hashable would be one way to solve it.
Please use this extension and re-try your first try:
extension CGPoint: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(x)
hasher.combine(y)
}
}
The first method can be used. Although you can't directly call ForEach() for CGPoint arrays. Instead you can use ForEach with a Range<T> like follows
struct ContentView: View {
var allFaceArray = [
CGPoint(x: 0, y: 0),
CGPoint(x: 30, y: 50),
CGPoint(x: 100, y: 100),
CGPoint(x: 100, y: 500),
]
var body: some View {
ZStack {
ForEach(0..<allFaceArray.count) { x in //<- Passing the count of the array as a range
Circle().stroke(lineWidth: 2.0).fill(Color.green)
.position(allFaceArray[x])
}
}
}
}
The output of the above code is,

How to draw a radial slice of a circle in SwiftUI ? (ie. pie chart like)

I am trying to create a slice of a circle in SwiftUI.
I am playing around with trim but it behaves oddly.
It picks the amount starting from the outmost point of the area.
Such that 0.5 is half a circle ok. But 0.25 of a circle is not a quarter slice (as a naive programming newbie would guess) but rather the shape filled in a non logical way. There also doesnt seem a way to modify this.
It would be sensible to define a Circle by saying Circle(radius: x, angle: y) instead such that angle: 360 would be a full circle filled.
Does one need to program this oneself in SwiftUI?
import SwiftUI
struct CircleView: View {
var body: some View {
let trimValue: CGFloat = 0.25
Circle()
.trim(from: 0, to: trimValue)
.fill(Color.red)
}
}
struct CircleView_Previews: PreviewProvider {
static var previews: some View {
CircleView()
}
}
Using a Path with an arc is the normal way to do this:
struct PieSegment: Shape {
var start: Angle
var end: Angle
func path(in rect: CGRect) -> Path {
var path = Path()
let center = CGPoint(x: rect.midX, y: rect.midY)
path.move(to: center)
path.addArc(center: center, radius: rect.midX, startAngle: start, endAngle: end, clockwise: false)
return path
}
}
struct ContentView: View {
#State var fileData: Data?
var body: some View {
PieSegment(start: .zero, end: .degrees(45))
}
}

Can't use 'shape' as Type in SwiftUI

I'm trying to create a Set of cards. Each card has an object (shape) assigned, that is then shown with a different colour and appears in different numbers on each card. -> as a template I've created the struct SetCard
Already within the definition of the struct SetCard the var shape: Shape returns the error message:
Value of protocol type 'Shape' cannot conform to 'View'; only struct/enum/class types can conform to protocols"
import Foundation
import SwiftUI
var setCardSet: Array<SetCard> = []
var counter: Int = 0
func createSet() -> Array<SetCard> {
for object in 0..<3 {
var chosenShape = objectLibrary[object]
for color in 0..<3 {
var chosenColor = colors[color]
for numberOfShapes in 0..<3 {
counter += 1
setCardSet.append(SetCard(id: counter, shape: chosenShape, numberOfShapes: numberOfShapes, colorOfShapes: chosenColor))
}
}
}
return setCardSet
}
struct SetCard: Identifiable {
var id: Int
var shape: Shape
var numberOfShapes: Int
var colorOfShapes: Color
// var shadingOfShapes: Double
}
struct GameObject {
var name: String
var object: Shape
}
let objectLibrary: [Shape] = [Diamond(), Oval()]
let colors: [Color] = [Color.green, Color.red, Color.purple]
At a later step, the individual objects are shown and "stacked" on the same card:
import SwiftUI
struct ContentView: View {
let observed: Array<SetCard> = createSet()
var body: some View {
VStack (observed) { card in
CardView(card: card).onTapGesture {
withAnimation(.linear(duration: 0.75)) {
print(card.id)
}
}
.padding(2)
}
}
}
struct CardView: View {
var card: SetCard
var body: some View {
ZStack {
VStack{
ForEach(0 ..< card.numberOfShapes+1) {_ in
card.shape
}
}
}
}
}
let gradientStart = Color(red: 239.0 / 255, green: 120.0 / 255, blue: 221.0 / 255)
let gradientEnd = Color(red: 239.0 / 255, green: 172.0 / 255, blue: 120.0 / 255)
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
What am I doing wrong here?
Definition of shapes:
import SwiftUI
struct Diamond: Shape {
func path(in rect: CGRect) -> Path {
let height = min(rect.width, rect.height)/4
let length = min(rect.width, rect.height)/3
let center = CGPoint(x: rect.midX, y: rect.midY)
let top: CGPoint = CGPoint(x: center.x + length, y: center.y)
let left: CGPoint = CGPoint(x: center.x, y: center.y - height)
let bottom: CGPoint = CGPoint(x: center.x - length, y: center.y)
let right: CGPoint = CGPoint(x: center.x, y: center.y + height)
var p = Path()
p.move(to: top)
p.addLine(to: left)
p.addLine(to: bottom)
p.addLine(to: right)
p.addLine(to: top)
return p
}
}
struct Oval: Shape {
func path(in rect: CGRect) -> Path {
let height = min(rect.width, rect.height)/4
let length = min(rect.width, rect.height)/3
let center = CGPoint(x: rect.midX, y: rect.midY)
let centerLeft = CGPoint(x: center.x - length + (height/2), y: center.y)
let centerRight = CGPoint(x: center.x + length - (height/2), y: center.y)
let bottomRight = CGPoint(x: centerRight.x, y: center.y - height)
let topLeft = CGPoint(x: centerLeft.x, y: center.y + height)
var p = Path()
p.move(to: topLeft)
p.addArc(center: centerLeft, radius: height, startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 270), clockwise: false)
p.addLine(to: bottomRight)
p.addArc(center: centerRight, radius: height, startAngle: Angle(degrees: 270), endAngle: Angle(degrees: 90), clockwise: false)
p.addLine(to: topLeft)
return p
}
}
You need to use one type for shape in your model, like
struct SetCard: Identifiable {
var id: Int
var shape: AnyShape // << here !!
var numberOfShapes: Int
var colorOfShapes: Color
// var shadingOfShapes: Double
}
The AnyShape declaration and demo of usage can be observed in my different answer https://stackoverflow.com/a/62605936/12299030
And, of course, you have to update all other dependent parts to use it (I skipped that for simplicity).

Simple SwiftUI Arc endAngle animation not working, only jump from one state to another

I'm trying to make a simple animation of the appearance of the arc.
This is very simple, and I don't have any idea why is not working.
I make this struct
struct Arc: Shape {
var center: CGPoint
var radius: CGFloat
var endAngle: Double
func path(in rect: CGRect) -> Path {
var path = Path()
path.addArc(center: center, radius: radius, startAngle: .degrees(180), endAngle: .degrees(endAngle), clockwise: false)
return path
}
}
And after that load it like this
struct TestView: View {
#State var endAngle: Double = 180
var body: some View {
Arc(center: CGPoint(x: 250, y: 250), radius: 100, endAngle: self.endAngle)
.stroke(Color.orange, lineWidth: 5)
.onAppear() {
withAnimation(Animation.linear(duration: 20)) {
self.endAngle = 0
}
}
}
}
But is not animate, only jump from 180 to 0.
I try OnTapGesture too, but also dosn't work.
I don't now, why this dosn't work
My ContentView is simple
struct ContentView: View {
var body: some View {
TestView()
}
}
How can I fix it?
Just add animatable data to your Arc, to indicate which parameter should be animated, as below
Tested with Xcode 11.4 / iOS 13.4
struct Arc: Shape {
var center: CGPoint
var radius: CGFloat
var endAngle: Double
var animatableData: CGFloat { // << here !!
get { CGFloat(endAngle) }
set { endAngle = Double(newValue) }
}
func path(in rect: CGRect) -> Path {
var path = Path()
path.addArc(center: center, radius: radius, startAngle: .degrees(180), endAngle: .degrees(endAngle), clockwise: false)
return path
}
}