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
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.
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,
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))
}
}
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).
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
}
}