I am programming something like PONG in IOS Playgrounds.
It is all working but it is crashing after a few seconds.
I don't really know why. Can someone please help me?
The Position of the Ball is changing and The Touch Event is working but it crashes in the draw Function of the UIView
I tried to turn clear Context and all but it didn't work
import UIKit;
import PlaygroundSupport;
import CoreGraphics;
import Dispatch;
func delay (delay:Double)
{
let t = CACurrentMediaTime()
var b = true
while b {
if(CACurrentMediaTime()>t+delay){
b=false;
return
}
}
}
let page = PlaygroundPage.current
var sheight:Double = 700 //Double(UIScreen.main.bounds.height);
var swidth:Double = 900 //Double(UIScreen.main.bounds.width);
let v = UIView(frame: CGRect(x: -100, y: -100, width: swidth, height: sheight))
swidth = Double(v.bounds.width)
sheight = Double(v.bounds.height)
let moveStab = sheight / 300;
var dirX = 1.0
var dirY = 1.0
let bsp:Double = 3.0
var Point1 = 0;
var Point2 = 0;
let psp:Double = 2.0
var up = [false,false]
var down = [false,false]
var ballx = swidth / 2;
var bally = sheight / 2;
let ballRad = 30.0
var p1x = 20.0
var p1y = sheight / 2
var p2x = swidth - 40.0
var p2y = sheight / 2
let pw = 20.0
let ph = 75.0
class Draw: UIView {
func PointInRad (point: CGPoint, point2 : CGPoint, rad : CGFloat) -> Bool{
let x1 = point.x;
let y1 = point.y
let x2 = point2.x
let y2 = point2.y
var dx = x1-x2
var dy = y1 - y2
if (dx < 0){dx=dx * -1}
if (dy < 0){dy=dy * -1}
let e = sqrt(dx*dx + dy*dy)
if(e <= rad){
return true
}
return false
}
func PointInRad (point: CGPoint, x2 : CGFloat, y2: CGFloat, rad: CGFloat) -> Bool {
let x1 = point.x;
let y1 = point.y
var dx = x1-x2
var dy = y1 - y2
if (dx < 0){dx=dx * -1}
if (dy < 0){dy=dy * -1}
let e = sqrt(dx*dx + dy*dy)
if(e <= rad){
return true
}
return false
}
func PointInRad ( x1 : CGFloat, y1 : CGFloat, x2 : CGFloat, y2 : CGFloat, rad: CGFloat) -> Bool{
var dx = x1-x2
var dy = y1 - y2
if (dx < 0){dx=dx * -1}
if (dy < 0){dy=dy * -1}
let e = sqrt(dx*dx + dy*dy)
if(e <= rad){
return true
}
return false
}
func update(){
self.isMultipleTouchEnabled = true;
self.clearsContextBeforeDrawing = true;
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
if(PointInRad(point: touch.location(in: view), x2: 700, y2:400 , rad: 50)){
up[1] = true
}
if(PointInRad(point: touch.location(in: view), x2: 100, y2:400 , rad: 50)){
up[0] = true
}
if(PointInRad(point: touch.location(in: view), x2: 700, y2:500 , rad: 50)){
down[1] = true
}
if(PointInRad(point: touch.location(in: view), x2: 100, y2:500 , rad: 50)){
down[0] = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
if(!PointInRad(point: touch.location(in: view), x2: 700, y2:400 , rad: 50)){
up[1] = false
}
if(!PointInRad(point: touch.location(in: view), x2: 100, y2:400 , rad: 50)){
up[0] = false
}
if(!PointInRad(point: touch.location(in: view), x2: 700, y2:500 , rad: 50)){
down[1] = false
}
if(!PointInRad(point: touch.location(in: view), x2: 100, y2:500 , rad: 50)){
down[0] = false
}}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
if(PointInRad(point: touch.location(in: view), x2: 700, y2:400 , rad: 50)){
up[1] = false
}
if(PointInRad(point: touch.location(in: view), x2: 100, y2:400 , rad: 50)){
up[0] = false
}
if(PointInRad(point: touch.location(in: view), x2: 700, y2:500 , rad: 50)){
down[1] = false
}
if(PointInRad(point: touch.location(in: view), x2: 100, y2:500 , rad: 50)){
down[0] = false
}}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
touchesEnded(touches, with: event)
self.setNeedsDisplay()
}
var n = false;
func drawCircle (color : UIColor, x:CGFloat, y:CGFloat, rad: CGFloat){
// Circle Drawing
let newx = x - rad / 2
let newy = y - rad / 2
let circlePath = UIBezierPath(ovalIn: CGRect(x: newx, y: newy, width: rad, height: rad))
color.setFill()
circlePath.fill()
}
func drawRect ( color : UIColor, x:CGFloat, y:CGFloat, width: CGFloat, height: CGFloat ) {
let circlePath = UIBezierPath(rect: CGRect(x: x, y: y, width: width, height: height))
color.setFill()
circlePath.fill()
}
override func draw(_: CGRect) {
var Path = UIBezierPath(ovalIn: CGRect(x: -100, y: -100, width: 900, height: 700))
let con = UIGraphicsGetCurrentContext();
con?.clear(CGRect(x: -100, y: -100, width: 1000, height: 1000))
// Color Declarations
var color = UIColor.white;
let ButtonN = UIColor(white: 0.5, alpha: 0.5)
let ButtonT = UIColor(white: 0.8, alpha: 0.8)
var p1 = NSString(string: String(Point1))
p1.draw(in: CGRect(x: 325, y: 100, width: 75, height: 100), withAttributes: [NSAttributedString.Key.foregroundColor: UIColor.white,NSAttributedString.Key.font: UIFont(name: "Arial", size: 80) ])
p1 = NSString(string: String(Point2))
p1.draw(in: CGRect(x: 430, y: 100, width: 75, height: 100), withAttributes: [NSAttributedString.Key.foregroundColor: UIColor.white,NSAttributedString.Key.font: UIFont(name: "Arial", size: 80) ])
p1 = NSString(string: String("Pong by Jakob"))
p1.draw(in: CGRect(x: 700, y: 25, width: 100, height: 100), withAttributes: [NSAttributedString.Key.foregroundColor: UIColor.white,NSAttributedString.Key.font: UIFont(name: "Arial", size: 20) ])
var rad = CGFloat(50);
var newx:CGFloat = 100.0
var newy:CGFloat = 400.0
var circlePath = UIBezierPath(ovalIn: CGRect(x: newx, y: newy, width: rad, height: rad))
if up[0] {
ButtonT.setFill();
} else {
ButtonN.setFill();
}
circlePath.fill()
circlePath = UIBezierPath(ovalIn: CGRect(x: 700, y: newy, width: rad, height: rad))
if up[1] {
ButtonT.setFill();
} else {
ButtonN.setFill();
}
circlePath.fill()
circlePath = UIBezierPath(ovalIn: CGRect(x: newx, y: 500, width: rad, height: rad))
if down[0] {
ButtonT.setFill();
} else {
ButtonN.setFill();
}
circlePath.fill()
circlePath = UIBezierPath(ovalIn: CGRect(x: 700, y: 500, width: rad, height: rad))
if down[1] {
ButtonT.setFill();
} else {
ButtonN.setFill();
}
circlePath.fill()
for i in 0 ... 30 {
color = UIColor.white
circlePath = UIBezierPath(rect: CGRect(x: CGFloat(393), y:CGFloat(Double(i)*50), width: CGFloat(14), height: CGFloat(30)))
color.setFill()
circlePath.fill()
}
circlePath = UIBezierPath(rect: CGRect(x: p1x, y: p1y, width: pw, height: ph))
color.setFill()
circlePath.fill()
circlePath = UIBezierPath(rect: CGRect(x: p2x, y: p2y, width: pw, height: ph))
color.setFill()
circlePath.fill()
rad = CGFloat(ballRad);
color = UIColor.white;
newx = (CGFloat(ballx) - rad / 2)
newy = CGFloat(bally) - rad / 2
circlePath = UIBezierPath(ovalIn: CGRect(x: newx, y: newy, width: rad, height: rad))
color.setFill()
circlePath.fill()
}
}
let view = Draw(frame: CGRect(x: 0, y: 0, width: swidth, height: sheight))
view.setNeedsDisplay()
view.update()
let T = Thread(block: {
while true {
if(Int(ballx)<Int(p1x + pw) && bally+ballRad <= p1y + ph && bally - ballRad >= p1y){
dirX *= -1
}
if(ballx<p2x && bally+ballRad <= p2y + ph && bally - ballRad >= p2y){
dirX *= -1
}
if(bally < 0 + ballRad || bally > 600 - ballRad){
dirY *= -1;
}
if (ballx - ballRad < 0){
ballx = 800/2
dirX = 1;
Point2+=1
}
if (ballx + ballRad > 800){
ballx = swidth/2
dirX = -1;
Point1+=1
}
if up[0] {
p1y -= psp
}
if up[1]{
p2y -= psp
}
if down[0] {
p1y += psp
}
if down[1]{
p2y += psp
}
ballx += bsp * dirX
bally += bsp * dirY
view.setNeedsDisplay()
view.updateFocusIfNeeded()
for i in 1 ... 10000 {
print(i)
}
}
})
T.start()
let t = Thread(block: {
while true {
DispatchQueue.main.sync {
view .setNeedsDisplay()
}}
})
t.start()
page.liveView = view;
page.needsIndefiniteExecution = true
page.wantsFullScreenLiveView = true
Because i am on my IPad i dont have any Error message
Related
I'm fairly new to ios programming
I try to draw some arcs for my circular progress bar in UIView.draw(rect:) method.
I'm using UIGraphicsGetCurrentContext to get the context and add arc path and draw in the method of custom UIView called MyProgressView
So I made an object of my custom view and it worked fine
But if I make a couple of more objects, they don't work
UIView.draw(rect:) is called every time whenever it needs to be.
the first one is drawing properly and the rest are not
class MyProgressView: UIView {
var startAngle: Double = 0.0
var endAngle: Double = 0.0 {
didSet{
self.setNeedsDisplay()
}
}
func radian(_ degree: Double) -> Double {
return (degree - 90) * Double.pi / 180.0
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.setLineWidth(10)
context.setStrokeColor(UIColor.white.cgColor)
context.addArc(center: self.center, radius: 30, startAngle: radian(startAngle), endAngle: radian(endAngle), clockwise: false)
context.strokePath()
}
}
I put self.setNeedsDisplay() in didSet of a variable called endAngle
and inside of view controller's viewDidLoad,
let progressBar = MyProgressView()
progressBar.frame = CGRect(x: 0, y: 0, width: view.frame.width / 2, height:view.frame.height / 2)
progressBar.backgroundColor = .systemMint
progressBar.startAngle = 0
progressBar.endAngle = 180
view.addSubview(progressBar)
and it looks fine
but if I add two more, they don't work
let progressBar2 = MyProgressView()
progressBar2.frame = CGRect(x: 150, y: 0, width: view.frame.width / 2, height: view.frame.height / 2)
progressBar2.backgroundColor = .systemPink
progressBar2.startAngle = 90
progressBar2.endAngle = 270
view.addSubview(progressBar2)
let progressBar3 = MyProgressView()
progressBar3.frame = CGRect(x: 0, y: 150, width: view.frame.width / 2, height: view.frame.height / 2)
progressBar3.backgroundColor = .systemIndigo
progressBar3.startAngle = 90
progressBar3.endAngle = 270
view.addSubview(progressBar3)
Can anybody explain what's missing here?
I made it and run in playground, iOS version is 15.4
Thanks
You have 2 problems
1- The method that converts to radians isn't correct
2- You need to use a zero based center point
class MyProgressView: UIView {
var startAngle: Double = 0.0
var endAngle: Double = 0.0
func radian(_ number: Double) -> CGFloat {
return CGFloat(number * .pi / 180)
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.setLineWidth(10)
context.setStrokeColor(UIColor.white.cgColor)
context.addArc(center: CGPoint(x:rect.size.width / 2, y:rect.size.height / 2), radius: 30, startAngle: radian(startAngle), endAngle: radian(endAngle), clockwise: false)
context.strokePath()
}
}
Full demo
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let progressBar = MyProgressView()
progressBar.frame = CGRect(x: 0, y: 0, width: view.frame.width / 2, height:view.frame.height / 2)
progressBar.backgroundColor = .systemMint
progressBar.startAngle = 0
progressBar.endAngle = 180
view.addSubview(progressBar)
let progressBar2 = MyProgressView()
progressBar2.frame = CGRect(x: 150, y: 0, width: view.frame.width / 2, height: view.frame.height / 2)
progressBar2.backgroundColor = .systemPink
progressBar2.startAngle = 90
progressBar2.endAngle = 270
view.addSubview(progressBar2)
let progressBar3 = MyProgressView()
progressBar3.frame = CGRect(x: 0, y: 300, width: view.frame.width / 2, height: view.frame.height / 2)
progressBar3.backgroundColor = .systemIndigo
progressBar3.startAngle = 90
progressBar3.endAngle = 270
view.addSubview(progressBar3)
}
}
class MyProgressView: UIView {
var startAngle: Double = 0.0
var endAngle: Double = 0.0
func radian(_ number: Double) -> CGFloat {
return CGFloat(number * .pi / 180)
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.setLineWidth(10)
context.setStrokeColor(UIColor.white.cgColor)
context.addArc(center: CGPoint(x:rect.size.width / 2, y:rect.size.height / 2), radius: 30, startAngle: radian(startAngle), endAngle: radian(endAngle), clockwise: false)
context.strokePath()
}
}
I simply do the following in code:
let path = UIBezierPath(rect: blurView.bounds)
path.usesEvenOddFillRule = true
path.append(UIBezierPath(rect: CGRect(x: 100, y: 100, width: 100, height: 100)))
path.append(UIBezierPath(rect: CGRect(x: 150, y: 150, width: 100, height: 100)))
//here you can add more paths, but the number is not known
let layer = CAShapeLayer()
layer.path = path.cgPath
layer.fillRule = .evenOdd
blurView.layer.mask = layer
and the effect is following:
Two rectangles overlapping one another. But all I need is to combine area from both rectanges, not to exclude everlapping area. Is it possible?
Using the "even-odd" fill rule is great for "cutting a hole" in a path. However, this code:
// create a big rect
let path = UIBezierPath(rect: blurView.bounds)
// cut a hole in it
path.append(UIBezierPath(rect: CGRect(x: 100, y: 100, width: 100, height: 100)))
// cut a hole overlapping a hole?
path.append(UIBezierPath(rect: CGRect(x: 150, y: 150, width: 100, height: 100)))
will be, as you've seen, problematic.
Depending on what all you are wanting to do, you could use a library such as ClippingBezier which allows you to manipulate paths with boolean actions.
Or, you can use a custom CALayer like this to "invert" multiple paths to use as a "cutout mask":
class BasicCutoutLayer: CALayer {
var rects: [CGRect] = []
func addRect(_ newRect: CGRect) {
rects.append(newRect)
setNeedsDisplay()
}
func reset() {
rects = []
setNeedsDisplay()
}
override func draw(in ctx: CGContext) {
// fill entire layer with solid color
ctx.setFillColor(UIColor.gray.cgColor)
ctx.fill(self.bounds);
rects.forEach { r in
ctx.addPath(UIBezierPath(rect: r).cgPath)
}
// draw clear "cutouts"
ctx.setFillColor(UIColor.clear.cgColor)
ctx.setBlendMode(.sourceIn)
ctx.drawPath(using: .fill)
}
}
To show it in use, we'll use this image:
In a standard UIImageView, overlaid with a blur UIVisualEffectView, and then use the BasicCutoutLayer class with two overlapping rects as the blur view's layer mask:
class BasicCutoutVC: UIViewController {
let myBlurView = UIVisualEffectView()
let myCutoutLayer = BasicCutoutLayer()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
let imgView = UIImageView()
if let img = UIImage(named: "sampleBG") {
imgView.image = img
}
[imgView, myBlurView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
imgView.topAnchor.constraint(equalTo: g.topAnchor),
imgView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
imgView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
imgView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
myBlurView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
myBlurView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
myBlurView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
myBlurView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
])
myBlurView.effect = UIBlurEffect(style: .extraLight)
// set mask for blur view
myBlurView.layer.mask = myCutoutLayer
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// set mask layer frame
myCutoutLayer.frame = myBlurView.bounds
// add two overlapping rects
let v: CGFloat = 160
let c: CGPoint = CGPoint(x: myBlurView.bounds.midX, y: myBlurView.bounds.midY)
var r: CGRect = CGRect(origin: c, size: CGSize(width: v, height: v))
r.origin.x -= v * 0.75
r.origin.y -= v * 0.75
myCutoutLayer.addRect(r)
r.origin.x += v * 0.5
r.origin.y += v * 0.5
myCutoutLayer.addRect(r)
}
}
Before applying the mask, it looks like this:
after applying the mask we get:
As we see, the "overlap" displays as we want.
That was a very simple, basic example. For a more advanced example, take a look at this:
struct MyPath {
var lineWidth: CGFloat = 0
var lineCap: CGLineCap = .butt
var lineJoin: CGLineJoin = .bevel
var isStroked: Bool = true
var isFilled: Bool = true
var pth: UIBezierPath = UIBezierPath()
}
class AdvancedCutoutLayer: CALayer {
var myPaths: [MyPath] = []
func addPath(_ newPath: MyPath) {
myPaths.append(newPath)
setNeedsDisplay()
}
func reset() {
myPaths = []
setNeedsDisplay()
}
override func draw(in ctx: CGContext) {
// fill entire layer with solid color
ctx.setFillColor(UIColor.gray.cgColor)
ctx.fill(self.bounds);
ctx.setBlendMode(.sourceIn)
myPaths.forEach { thisPath in
ctx.setStrokeColor(thisPath.isStroked ? UIColor.clear.cgColor : UIColor.black.cgColor)
ctx.setFillColor(thisPath.isFilled ? UIColor.clear.cgColor : UIColor.black.cgColor)
ctx.setLineWidth(thisPath.isStroked ? thisPath.lineWidth : 0.0)
ctx.setLineCap(thisPath.lineCap)
ctx.setLineJoin(thisPath.lineJoin)
ctx.addPath(thisPath.pth.cgPath)
ctx.drawPath(using: .fillStroke)
}
}
}
along with a subclassed UIVisualEffectView for convenience:
class CutoutBlurView: UIVisualEffectView {
let sl = AdvancedCutoutLayer()
override init(effect: UIVisualEffect?) {
super.init(effect: effect)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
sl.isOpaque = false
layer.mask = sl
}
override func layoutSubviews() {
super.layoutSubviews()
sl.frame = bounds
sl.setNeedsDisplay()
}
func addPath(_ newPath: MyPath) {
sl.addPath(newPath)
}
func reset() {
sl.reset()
}
}
and an example controller:
class AdvancedCutoutVC: UIViewController {
let myView = CutoutBlurView()
var idx: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBlue
let imgView = UIImageView()
if let img = UIImage(named: "sampleBG") {
imgView.image = img
}
[imgView, myView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
imgView.topAnchor.constraint(equalTo: g.topAnchor),
imgView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
imgView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
imgView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
myView.topAnchor.constraint(equalTo: g.topAnchor),
myView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
myView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
myView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
myView.effect = UIBlurEffect(style: .extraLight)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true, block: { _ in
switch self.idx % 4 {
case 1:
self.addSomeOvals()
case 2:
self.addSomeLines()
case 3:
self.addSomeShapes()
default:
self.addSomeRects()
}
self.idx += 1
})
}
func addSomeRects() {
myView.reset()
let w: CGFloat = myView.frame.width / 4.0
let h: CGFloat = myView.frame.height / 4.0
var x: CGFloat = ((myView.frame.width - (w * 5.0 * 0.5)) * 0.5) - (w * 0.25)
var y: CGFloat = ((myView.frame.height - (h * 5.0 * 0.5)) * 0.5) - (h * 0.25)
for _ in 1...5 {
let bz = UIBezierPath(rect: CGRect(x: x, y: y, width: w, height: h))
myView.addPath(MyPath(lineWidth: 0, isStroked: false, isFilled: true, pth: bz))
x += w * 0.5
y += h * 0.5
}
}
func addSomeOvals() {
myView.reset()
let w: CGFloat = myView.frame.width / 4.0
let h: CGFloat = myView.frame.height / 4.0
var x: CGFloat = ((myView.frame.width - (w * 5.0 * 0.5)) * 0.5) - (w * 0.25)
var y: CGFloat = ((myView.frame.height - (h * 5.0 * 0.5)) * 0.5) - (h * 0.25)
for _ in 1...5 {
let bz = UIBezierPath(ovalIn: CGRect(x: x, y: y, width: w, height: h))
myView.addPath(MyPath(lineWidth: 0, isStroked: false, isFilled: true, pth: bz))
x += w * 0.5
y += h * 0.5
}
}
func addSomeLines() {
myView.reset()
let w: CGFloat = myView.frame.width / 2.0
let h: CGFloat = myView.frame.height / 4.0
let x: CGFloat = 80
var y: CGFloat = 80
var lw: CGFloat = 4
for _ in 1...5 {
let bz = UIBezierPath()
bz.move(to: CGPoint(x: x, y: y))
bz.addLine(to: CGPoint(x: x + w, y: y + 20))
myView.addPath(MyPath(lineWidth: lw, lineCap: .round, isStroked: true, isFilled: false, pth: bz))
y += h * 0.5
lw += 10
}
}
func addSomeShapes() {
myView.reset()
var bz: UIBezierPath!
bz = UIBezierPath(rect: CGRect(x: 80, y: 80, width: 80, height: 120))
myView.addPath(MyPath(isStroked: false, isFilled: true, pth: bz))
bz = UIBezierPath(rect: CGRect(x: 120, y: 120, width: 120, height: 60))
myView.addPath(MyPath(isStroked: false, isFilled: true, pth: bz))
bz = UIBezierPath(rect: CGRect(x: 80, y: 220, width: 220, height: 60))
myView.addPath(MyPath(lineWidth: 12, isStroked: true, isFilled: false, pth: bz))
bz = UIBezierPath(ovalIn: CGRect(x: 100, y: 240, width: 220, height: 60))
myView.addPath(MyPath(lineWidth: 12, isStroked: true, isFilled: false, pth: bz))
var r: CGRect = CGRect(x: 40, y: 320, width: myView.frame.width - 80, height: 200)
for _ in 1...4 {
bz = UIBezierPath(rect: r)
myView.addPath(MyPath(lineWidth: 8, isStroked: true, isFilled: false, pth: bz))
r = r.insetBy(dx: 20, dy: 20)
}
}
}
When run, this example will cycle through overlapping rect, overlapping ovals, some varying width lines, and some assorted shapes (just to give an idea):
I would go with ClippingBezier because it is fast, easy to use and neat. It'll be something like this:
let rect1 = CGRect(x: 100, y: 100, width: 200, height: 200)
let rect2 = CGRect(x: 150, y: 200, width: 200, height: 200)
let path0 = UIBezierPath(rect: blurView.bounds)
let path1 = UIBezierPath(rect: rect1)
let path2 = UIBezierPath(rect: rect2)
let unionPathArray = path1.union(with: path2)
let unionPath = UIBezierPath()
if let array = unionPathArray {
array.forEach(unionPath.append)
path0.append(unionPath.reversing())
let layerUnion = CAShapeLayer()
layerUnion.path = path0.cgPath
blurView.layer.mask = layerUnion
}
Output:
EDIT
It appears that this method doesn't work properly when using UIBezierPath(roundedRect:cornerRadius:). To overcome that, here is how we can construct our own func to do that:
extension UIBezierPath {
convenience init(rectangleIn rect: CGRect, cornerRadius: CGFloat) {
self.init()
move(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius))
addArc(withCenter: CGPoint(x: rect.minX + cornerRadius, y: rect.minY + cornerRadius), radius: cornerRadius, startAngle: .pi, endAngle: 3.0 * .pi / 2.0, clockwise: true)
addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY))
addArc(withCenter: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY + cornerRadius), radius: cornerRadius, startAngle: 3.0 * .pi / 2.0, endAngle: 2 * .pi, clockwise: true)
addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadius))
addArc(withCenter: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY - cornerRadius), radius: cornerRadius, startAngle: 0.0, endAngle: .pi / 2.0, clockwise: true)
addLine(to: CGPoint(x: rect.minX + cornerRadius, y: rect.maxY))
addArc(withCenter: CGPoint(x: rect.minX + cornerRadius, y: rect.maxY - cornerRadius), radius: cornerRadius, startAngle: .pi / 2.0, endAngle: .pi, clockwise: true)
//addLine(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius))
close()
}
}
We can also extend the above-mentioned solution to multiple paths. Here is one way to create the union of multiple paths:
extension UIBezierPath {
class func getUnion(of paths: [UIBezierPath]) -> UIBezierPath {
var result = UIBezierPath()
paths.forEach { subPath in
guard let union = result.union(with: subPath) else { return }
let unionCombined = UIBezierPath()
union.forEach(unionCombined.append)
result = unionCombined
}
return result
}
}
Here is an example:
let rect1 = CGRect(x: 100, y: 100, width: 200, height: 180)
let rect2 = CGRect(x: 150, y: 200, width: 200, height: 200)
let rect3 = CGRect(x: 150, y: 500, width: 100, height: 100)
let rect4 = CGRect(x: 150, y: 800, width: 300, height: 100)
let pathBase = UIBezierPath(rect: blurView.bounds)
let path1 = UIBezierPath(rectangleIn: rect1, cornerRadius: 20.0)
let path2 = UIBezierPath(rect: rect2)
let path3 = UIBezierPath(ovalIn: rect3)
let path4 = UIBezierPath(ovalIn: rect4)
let union = UIBezierPath.getUnion(of: [path1, path2, path3, path4])
pathBase.append(union.reversing())
let layerUnion = CAShapeLayer()
layerUnion.path = pathBase.cgPath
blurView.layer.mask = layerUnion
And the output:
I'm trying to make a line graph with no libraries, but I just cmd+c, cmd+v all the code. Yes, I know that I shouldn't do so, but I don't have much time
So I did everything with help of this - https://medium.com/#tstenerson/lets-make-a-line-chart-in-swift-3-5e819e6c1a00
Also added a view to the view controller and called it LineChart
But on line 42 I get an error Thread 1: ECX_BAD_ACCESS (code = EXC_I386_GPFLT)
lineChart.deltaX = 20
I don't know how to fix it
I coded only in ViewController.swift, here it is:
import UIKit
extension String {
func size(withSystemFontSize pointSize: CGFloat) -> CGSize {
return (self as NSString).size(attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: pointSize)])
}
}
extension CGPoint {
func adding(x: CGFloat) -> CGPoint { return CGPoint(x: self.x + x, y: self.y) }
func adding(y: CGFloat) -> CGPoint { return CGPoint(x: self.x, y: self.y + y) }
}
class ViewController: UIViewController {
#IBOutlet var lineChart: LineChart!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let f: (CGFloat) -> CGPoint = {
let noiseY = (CGFloat(arc4random_uniform(2)) * 2 - 1) * CGFloat(arc4random_uniform(4))
let noiseX = (CGFloat(arc4random_uniform(2)) * 2 - 1) * CGFloat(arc4random_uniform(4))
let b: CGFloat = 5
let y = 2 * $0 + b + noiseY
return CGPoint(x: $0 + noiseX, y: y)
}
let xs = [Int](1..<20)
let points = xs.map({f(CGFloat($0 * 10))})
lineChart.deltaX = 20
lineChart.deltaY = 30
lineChart.plot(points)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
class LineChart: UIView {
let lineLayer = CAShapeLayer()
let circlesLayer = CAShapeLayer()
var chartTransform: CGAffineTransform?
#IBInspectable var lineColor: UIColor = UIColor.green {
didSet {
lineLayer.strokeColor = lineColor.cgColor
}
}
#IBInspectable var lineWidth: CGFloat = 1
#IBInspectable var showPoints: Bool = true { // show the circles on each data point
didSet {
circlesLayer.isHidden = !showPoints
}
}
#IBInspectable var circleColor: UIColor = UIColor.green {
didSet {
circlesLayer.fillColor = circleColor.cgColor
}
}
#IBInspectable var circleSizeMultiplier: CGFloat = 3
#IBInspectable var axisColor: UIColor = UIColor.white
#IBInspectable var showInnerLines: Bool = true
#IBInspectable var labelFontSize: CGFloat = 10
var axisLineWidth: CGFloat = 1
var deltaX: CGFloat = 10 // The change between each tick on the x axis
var deltaY: CGFloat = 10 // and y axis
var xMax: CGFloat = 100
var yMax: CGFloat = 100
var xMin: CGFloat = 0
var yMin: CGFloat = 0
var data: [CGPoint]?
override init(frame: CGRect) {
super.init(frame: frame)
combinedInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
combinedInit()
}
func combinedInit() {
layer.addSublayer(lineLayer)
lineLayer.fillColor = UIColor.clear.cgColor
lineLayer.strokeColor = lineColor.cgColor
layer.addSublayer(circlesLayer)
circlesLayer.fillColor = circleColor.cgColor
layer.borderWidth = 1
layer.borderColor = axisColor.cgColor
}
override func layoutSubviews() {
super.layoutSubviews()
lineLayer.frame = bounds
circlesLayer.frame = bounds
if let d = data{
setTransform(minX: xMin, maxX: xMax, minY: yMin, maxY: yMax)
plot(d)
}
}
func setAxisRange(forPoints points: [CGPoint]) {
guard !points.isEmpty else { return }
let xs = points.map() { $0.x }
let ys = points.map() { $0.y }
// МИНИМАЛЬНЫЕ И МАКСИМАЛЬНЫЕ ЗНАЧЕНИЯ
xMax = ceil(xs.max()! / deltaX) * deltaX
yMax = ceil(ys.max()! / deltaY) * deltaY
xMin = 0
yMin = 0
setTransform(minX: xMin, maxX: xMax, minY: yMin, maxY: yMax)
}
func setAxisRange(xMin: CGFloat, xMax: CGFloat, yMin: CGFloat, yMax: CGFloat) {
self.xMin = xMin
self.xMax = xMax
self.yMin = yMin
self.yMax = yMax
setTransform(minX: xMin, maxX: xMax, minY: yMin, maxY: yMax)
}
func setTransform(minX: CGFloat, maxX: CGFloat, minY: CGFloat, maxY: CGFloat) {
let xLabelSize = "\(Int(maxX))".size(withSystemFontSize: labelFontSize)
let yLabelSize = "\(Int(maxY))".size(withSystemFontSize: labelFontSize)
let xOffset = xLabelSize.height + 2
let yOffset = yLabelSize.width + 5
let xScale = (bounds.width - yOffset - xLabelSize.width/2 - 2)/(maxX - minX)
let yScale = (bounds.height - xOffset - yLabelSize.height/2 - 2)/(maxY - minY)
chartTransform = CGAffineTransform(a: xScale, b: 0, c: 0, d: -yScale, tx: yOffset, ty: bounds.height - xOffset)
setNeedsDisplay()
}
override func draw(_ rect: CGRect) {
// draw rect comes with a drawing context, so lets grab it.
// Also, if there is not yet a chart transform, we will bail on performing any other drawing.
// I like guard statements for this because it's kind of like a bouncer to a bar.
// If you don't have your transform yet, you can't enter drawAxes.
guard let context = UIGraphicsGetCurrentContext(), let t = chartTransform else { return }
drawAxes(in: context, usingTransform: t)
}
func drawAxes(in context: CGContext, usingTransform t: CGAffineTransform) {
context.saveGState()
// Make two paths, one for thick lines, one for thin.
let thickerLines = CGMutablePath()
let thinnerLines = CGMutablePath()
// The two line chart axes.
let xAxisPoints = [CGPoint(x: xMin, y: 0), CGPoint(x: xMax, y: 0)]
let yAxisPoints = [CGPoint(x: 0, y: yMin), CGPoint(x: 0, y: yMax)]
// Add each to thicker lines but apply our transform too.
thickerLines.addLines(between: xAxisPoints, transform: t)
thickerLines.addLines(between: yAxisPoints, transform: t)
// Next we go from xMin to xMax by deltaX using stride
for x in stride(from: xMin, through: xMax, by: deltaX) {
// Tick points are the points for the ticks on each axis.
// We check showInnerLines first to see if we are drawing small ticks or full lines.
// Yip for new guys: `let a = someBool ? b : c` is called a ternary operator.
// In English it means "let a = b if somebool is true, or c if it is false."
let tickPoints = showInnerLines ?
[CGPoint(x: x, y: yMin).applying(t), CGPoint(x: x, y: yMax).applying(t)] :
[CGPoint(x: x, y: 0).applying(t), CGPoint(x: x, y: 0).applying(t).adding(y: -5)]
thinnerLines.addLines(between: tickPoints)
if x != xMin { // draw the tick label (it is too buy if you draw it at the origin for both x & y
let label = "\(Int(x))" as NSString // Int to get rid of the decimal, NSString to draw
let labelSize = "\(Int(x))".size(withSystemFontSize: labelFontSize)
let labelDrawPoint = CGPoint(x: x, y: 0).applying(t)
.adding(x: -labelSize.width/2)
.adding(y: 1)
label.draw(at: labelDrawPoint,
withAttributes:
[NSFontAttributeName: UIFont.systemFont(ofSize: labelFontSize),
NSForegroundColorAttributeName: axisColor])
}
}
// Repeat for y.
for y in stride(from: yMin, through: yMax, by: deltaY) {
let tickPoints = showInnerLines ?
[CGPoint(x: xMin, y: y).applying(t), CGPoint(x: xMax, y: y).applying(t)] :
[CGPoint(x: 0, y: y).applying(t), CGPoint(x: 0, y: y).applying(t).adding(x: 5)]
thinnerLines.addLines(between: tickPoints)
if y != yMin {
let label = "\(Int(y))" as NSString
let labelSize = "\(Int(y))".size(withSystemFontSize: labelFontSize)
let labelDrawPoint = CGPoint(x: 0, y: y).applying(t)
.adding(x: -labelSize.width - 1)
.adding(y: -labelSize.height/2)
label.draw(at: labelDrawPoint,
withAttributes:
[NSFontAttributeName: UIFont.systemFont(ofSize: labelFontSize),
NSForegroundColorAttributeName: axisColor])
}
}
// Finally set stroke color & line width then stroke thick lines, repeat for thin.
context.setStrokeColor(axisColor.cgColor)
context.setLineWidth(axisLineWidth)
context.addPath(thickerLines)
context.strokePath()
context.setStrokeColor(axisColor.withAlphaComponent(0.5).cgColor)
context.setLineWidth(axisLineWidth/2)
context.addPath(thinnerLines)
context.strokePath()
context.restoreGState()
// Whenever you change a graphics context you should save it prior and restore it after.
// If we were using a context other than draw(rect) we would have to also end the graphics context.
}
func plot(_ points: [CGPoint]) {
lineLayer.path = nil
circlesLayer.path = nil
data = nil
guard !points.isEmpty else { return }
self.data = points
if self.chartTransform == nil {
setAxisRange(forPoints: points)
}
let linePath = CGMutablePath()
linePath.addLines(between: points, transform: chartTransform!)
lineLayer.path = linePath
if showPoints {
circlesLayer.path = circles(atPoints: points, withTransform: chartTransform!)
}
}
func circles(atPoints points: [CGPoint], withTransform t: CGAffineTransform) -> CGPath {
let path = CGMutablePath()
let radius = lineLayer.lineWidth * circleSizeMultiplier/2
for i in points {
let p = i.applying(t)
let rect = CGRect(x: p.x - radius, y: p.y - radius, width: radius * 2, height: radius * 2)
path.addEllipse(in: rect)
}
return path
}
} // <- I didn't close the LineChart class up top, closing it now
}
In storyboard remove reference outlet link to 'lineChart' and try this:
import UIKit
extension String {
func size(withSystemFontSize pointSize: CGFloat) -> CGSize {
return (self as NSString).size(attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: pointSize)])
}
}
extension CGPoint {
func adding(x: CGFloat) -> CGPoint { return CGPoint(x: self.x + x, y: self.y) }
func adding(y: CGFloat) -> CGPoint { return CGPoint(x: self.x, y: self.y + y) }
}
class ViewController: UIViewController {
// #IBOutlet var lineChart: LineChart! ////////////REMOVED THIS
var lineChart = LineChart(frame: CGRect.zero) ////////////ADDED THIS
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let f: (CGFloat) -> CGPoint = {
let noiseY = (CGFloat(arc4random_uniform(2)) * 2 - 1) * CGFloat(arc4random_uniform(4))
let noiseX = (CGFloat(arc4random_uniform(2)) * 2 - 1) * CGFloat(arc4random_uniform(4))
let b: CGFloat = 5
let y = 2 * $0 + b + noiseY
return CGPoint(x: $0 + noiseX, y: y)
}
let xs = [Int](1..<20)
let points = xs.map({f(CGFloat($0 * 10))})
////////////ADDED THIS
self.lineChart.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
self.view.addSubview(self.lineChart)
lineChart.deltaX = 20
lineChart.deltaY = 30
lineChart.plot(points)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
class LineChart: UIView {
let lineLayer = CAShapeLayer()
let circlesLayer = CAShapeLayer()
var chartTransform: CGAffineTransform?
#IBInspectable var lineColor: UIColor = UIColor.green {
didSet {
lineLayer.strokeColor = lineColor.cgColor
}
}
#IBInspectable var lineWidth: CGFloat = 1
#IBInspectable var showPoints: Bool = true { // show the circles on each data point
didSet {
circlesLayer.isHidden = !showPoints
}
}
#IBInspectable var circleColor: UIColor = UIColor.green {
didSet {
circlesLayer.fillColor = circleColor.cgColor
}
}
#IBInspectable var circleSizeMultiplier: CGFloat = 3
#IBInspectable var axisColor: UIColor = UIColor.white
#IBInspectable var showInnerLines: Bool = true
#IBInspectable var labelFontSize: CGFloat = 10
var axisLineWidth: CGFloat = 1
var deltaX: CGFloat = 10 // The change between each tick on the x axis
var deltaY: CGFloat = 10 // and y axis
var xMax: CGFloat = 100
var yMax: CGFloat = 100
var xMin: CGFloat = 0
var yMin: CGFloat = 0
var data: [CGPoint]?
override init(frame: CGRect) {
super.init(frame: frame)
combinedInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
combinedInit()
}
func combinedInit() {
layer.addSublayer(lineLayer)
lineLayer.fillColor = UIColor.clear.cgColor
lineLayer.strokeColor = lineColor.cgColor
layer.addSublayer(circlesLayer)
circlesLayer.fillColor = circleColor.cgColor
layer.borderWidth = 1
layer.borderColor = axisColor.cgColor
}
override func layoutSubviews() {
super.layoutSubviews()
lineLayer.frame = bounds
circlesLayer.frame = bounds
if let d = data{
setTransform(minX: xMin, maxX: xMax, minY: yMin, maxY: yMax)
plot(d)
}
}
func setAxisRange(forPoints points: [CGPoint]) {
guard !points.isEmpty else { return }
let xs = points.map() { $0.x }
let ys = points.map() { $0.y }
// МИНИМАЛЬНЫЕ И МАКСИМАЛЬНЫЕ ЗНАЧЕНИЯ
xMax = ceil(xs.max()! / deltaX) * deltaX
yMax = ceil(ys.max()! / deltaY) * deltaY
xMin = 0
yMin = 0
setTransform(minX: xMin, maxX: xMax, minY: yMin, maxY: yMax)
}
func setAxisRange(xMin: CGFloat, xMax: CGFloat, yMin: CGFloat, yMax: CGFloat) {
self.xMin = xMin
self.xMax = xMax
self.yMin = yMin
self.yMax = yMax
setTransform(minX: xMin, maxX: xMax, minY: yMin, maxY: yMax)
}
func setTransform(minX: CGFloat, maxX: CGFloat, minY: CGFloat, maxY: CGFloat) {
let xLabelSize = "\(Int(maxX))".size(withSystemFontSize: labelFontSize)
let yLabelSize = "\(Int(maxY))".size(withSystemFontSize: labelFontSize)
let xOffset = xLabelSize.height + 2
let yOffset = yLabelSize.width + 5
let xScale = (bounds.width - yOffset - xLabelSize.width/2 - 2)/(maxX - minX)
let yScale = (bounds.height - xOffset - yLabelSize.height/2 - 2)/(maxY - minY)
chartTransform = CGAffineTransform(a: xScale, b: 0, c: 0, d: -yScale, tx: yOffset, ty: bounds.height - xOffset)
setNeedsDisplay()
}
override func draw(_ rect: CGRect) {
// draw rect comes with a drawing context, so lets grab it.
// Also, if there is not yet a chart transform, we will bail on performing any other drawing.
// I like guard statements for this because it's kind of like a bouncer to a bar.
// If you don't have your transform yet, you can't enter drawAxes.
guard let context = UIGraphicsGetCurrentContext(), let t = chartTransform else { return }
drawAxes(in: context, usingTransform: t)
}
func drawAxes(in context: CGContext, usingTransform t: CGAffineTransform) {
context.saveGState()
// make two paths, one for thick lines, one for thin
let thickerLines = CGMutablePath()
let thinnerLines = CGMutablePath()
// the two line chart axes
let xAxisPoints = [CGPoint(x: xMin, y: 0), CGPoint(x: xMax, y: 0)]
let yAxisPoints = [CGPoint(x: 0, y: yMin), CGPoint(x: 0, y: yMax)]
// add each to thicker lines but apply our transform too.
thickerLines.addLines(between: xAxisPoints, transform: t)
thickerLines.addLines(between: yAxisPoints, transform: t)
// next we go from xMin to xMax by deltaX using stride
for x in stride(from: xMin, through: xMax, by: deltaX) {
// tick points are the points for the ticks on each axis
// we check showInnerLines first to see if we are drawing small ticks or full lines
// tip for new guys: `let a = someBool ? b : c` is called a ternary operator
// in english it means "let a = b if somebool is true, or c if it is false."
let tickPoints = showInnerLines ?
[CGPoint(x: x, y: yMin).applying(t), CGPoint(x: x, y: yMax).applying(t)] :
[CGPoint(x: x, y: 0).applying(t), CGPoint(x: x, y: 0).applying(t).adding(y: -5)]
thinnerLines.addLines(between: tickPoints)
if x != xMin { // draw the tick label (it is too buy if you draw it at the origin for both x & y
let label = "\(Int(x))" as NSString // Int to get rid of the decimal, NSString to draw
let labelSize = "\(Int(x))".size(withSystemFontSize: labelFontSize)
let labelDrawPoint = CGPoint(x: x, y: 0).applying(t)
.adding(x: -labelSize.width/2)
.adding(y: 1)
label.draw(at: labelDrawPoint,
withAttributes:
[NSFontAttributeName: UIFont.systemFont(ofSize: labelFontSize),
NSForegroundColorAttributeName: axisColor])
}
}
// repeat for y
for y in stride(from: yMin, through: yMax, by: deltaY) {
let tickPoints = showInnerLines ?
[CGPoint(x: xMin, y: y).applying(t), CGPoint(x: xMax, y: y).applying(t)] :
[CGPoint(x: 0, y: y).applying(t), CGPoint(x: 0, y: y).applying(t).adding(x: 5)]
thinnerLines.addLines(between: tickPoints)
if y != yMin {
let label = "\(Int(y))" as NSString
let labelSize = "\(Int(y))".size(withSystemFontSize: labelFontSize)
let labelDrawPoint = CGPoint(x: 0, y: y).applying(t)
.adding(x: -labelSize.width - 1)
.adding(y: -labelSize.height/2)
label.draw(at: labelDrawPoint,
withAttributes:
[NSFontAttributeName: UIFont.systemFont(ofSize: labelFontSize),
NSForegroundColorAttributeName: axisColor])
}
}
// finally set stroke color & line width then stroke thick lines, repeat for thin
context.setStrokeColor(axisColor.cgColor)
context.setLineWidth(axisLineWidth)
context.addPath(thickerLines)
context.strokePath()
context.setStrokeColor(axisColor.withAlphaComponent(0.5).cgColor)
context.setLineWidth(axisLineWidth/2)
context.addPath(thinnerLines)
context.strokePath()
context.restoreGState()
// whenever you change a graphics context you should save it prior and restore it after
// if we were using a context other than draw(rect) we would have to also end the graphics context
}
func plot(_ points: [CGPoint]) {
lineLayer.path = nil
circlesLayer.path = nil
data = nil
guard !points.isEmpty else { return }
self.data = points
if self.chartTransform == nil {
setAxisRange(forPoints: points)
}
let linePath = CGMutablePath()
linePath.addLines(between: points, transform: chartTransform!)
lineLayer.path = linePath
if showPoints {
circlesLayer.path = circles(atPoints: points, withTransform: chartTransform!)
}
}
func circles(atPoints points: [CGPoint], withTransform t: CGAffineTransform) -> CGPath {
let path = CGMutablePath()
let radius = lineLayer.lineWidth * circleSizeMultiplier/2
for i in points {
let p = i.applying(t)
let rect = CGRect(x: p.x - radius, y: p.y - radius, width: radius * 2, height: radius * 2)
path.addEllipse(in: rect)
}
return path
}
} // <- I didn't close the LineChart class up top, closing it now
}
My View Controller code contains below code
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let width: CGFloat = 240
let height: CGFloat = 240
let demoView = DemoView(frame: CGRect(x: self.view.frame.size.width/2 - width/2,
y: self.view.frame.size.height/2 - height/2,
width: width,
height: height))
let subView = UIView.init(frame: (CGRect(x: demoView.frame.origin.x - width,
y: demoView.frame.origin.y,
width: width * 2,
height: height * 2)))
self.view.addSubview(demoView)
self.view.addSubview(subView)
subView.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5)
subView.layer.cornerRadius = subView.frame.size.height / 2
}
}
import UIKit
class DemoView: UIView {
var path: UIBezierPath!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.darkGray
}
override func draw(_ rect: CGRect) {
self.createTriangle()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func createTriangle() {
let count : Int = 9
let gap : CGFloat = 3
let yValue : CGFloat = CGFloat(self.frame.size.width - ((CGFloat(count - 1)) * gap)) / CGFloat(count);
for a in 0 ..< count {
let i : CGFloat = CGFloat(a)
let path1: UIBezierPath! = UIBezierPath()
path1.move(to: CGPoint(x: 0.0, y: self.frame.size.height))
path1.addLine(to: CGPoint(x: (yValue * i) > 0 ? (yValue * i) + i*gap : 0, y: (yValue * i) > 0 ? (yValue * i) + i*gap : 0))
path1.addLine(to: CGPoint(x:yValue * (i+1) + i*gap, y: yValue * (i+1) + i*gap))
path1.close()
UIColor.orange.setFill()
path1.fill()
}
}
}
Can anyone help me to achieve this thing?
Edit image :
Is this the result your want ?
The way I did it isn't with triangles but Arcs.
Add createPie() in your DemoView class and call it in draw(:) instead of your createTriangle().
This is my code:
func createPie() {
// 2 vars to configure width of gap/banches
var branchAmount = 10
var gapAngle = CGFloat.pi / 100
let startAngle = 3 * CGFloat.pi / 2
let endAngle = 2 * CGFloat.pi
let branchAngle = (endAngle - startAngle - (CGFloat(branchAmount) - 1) * gapAngle) / CGFloat(branchAmount)
let paths = UIBezierPath()
for i in 0..<branchAmount {
paths.move(to: CGPoint(x: 0.0, y: self.frame.size.height))
paths.addArc(withCenter: CGPoint(x: 0, y: self.frame.size.height),
radius: self.frame.size.height,
startAngle: startAngle + CGFloat(i) * (branchAngle + gapAngle),
endAngle: startAngle + CGFloat(i) * (branchAngle + gapAngle) + branchAngle,
clockwise: true)
}
paths.close()
UIColor.orange.setFill()
paths.fill()
}
Cheers!
EDIT: If you want to add a circular mask you and add this in the end of createPie() (which is no longer really a pie now..):
// Circular mask
let maskLayer = CAShapeLayer()
let maskPath = UIBezierPath(rect: bounds)
maskLayer.fillRule = kCAFillRuleEvenOdd // Circle will be substracted to the mask thanks to this
maskPath.move(to: CGPoint(x: 0.0, y: frame.size.height))
maskPath.addArc(withCenter: CGPoint(x: 0, y: frame.size.height), radius: maskRadius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
maskLayer.path = maskPath.cgPath
layer.mask = maskLayer
It just adds mask composed of the subtraction of bounds and the circle of origin (0, height)
In my project everything is going smoothly and working great except for when I want to add 1 instance of a SKLabelNode when a certain event happens in my game.
Then problem is when the event happens it adds the SKLabelNode more than once and it keeps on doing it into the thousands...
Here is my code:
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate{
var isTouched: Bool = false
let lavaRoom = SKSpriteNode(imageNamed: "LavaRom")
let tileStone1 = SKSpriteNode(imageNamed: "TileStone")
let tileStone2 = SKSpriteNode(imageNamed: "TileStone")
let tileStone3 = SKSpriteNode(imageNamed: "TileStone")
let tileStone4 = SKSpriteNode(imageNamed: "TileStone")
let tileStone5 = SKSpriteNode(imageNamed: "TileStone")
let tileStone6 = SKSpriteNode(imageNamed: "TileStone")
let tileStone7 = SKSpriteNode(imageNamed: "TileStone")
let tileStone8 = SKSpriteNode(imageNamed: "TileStone")
let tileStone9 = SKSpriteNode(imageNamed: "TileStone")
let tileStone10 = SKSpriteNode(imageNamed: "TileStone")
let tileStone11 = SKSpriteNode(imageNamed: "TileStone")
let tileStone12 = SKSpriteNode(imageNamed: "TileStone")
let tileStone13 = SKSpriteNode(imageNamed: "TileStone")
let tileStone14 = SKSpriteNode(imageNamed: "TileStone")
let tileStone15 = SKSpriteNode(imageNamed: "TileStone")
let tileStone16 = SKSpriteNode(imageNamed: "TileStone")
let tileStone17 = SKSpriteNode(imageNamed: "TileStone")
let tileStone18 = SKSpriteNode(imageNamed: "TileStone")
let tileStone19 = SKSpriteNode(imageNamed: "TileStone")
let tileStone20 = SKSpriteNode(imageNamed: "TileStone")
let tileStone21 = SKSpriteNode(imageNamed: "TileStone")
let tileStone22 = SKSpriteNode(imageNamed: "TileStone")
let tileStone23 = SKSpriteNode(imageNamed: "TileStone")
let tileStone24 = SKSpriteNode(imageNamed: "TileStone")
let tileStone25 = SKSpriteNode(imageNamed: "TileStone")
let tileStone26 = SKSpriteNode(imageNamed: "TileStone")
let tileStone27 = SKSpriteNode(imageNamed: "TileStone")
let tileStoneTop1 = SKSpriteNode(imageNamed: "TileStone")
let tileStoneTop2 = SKSpriteNode(imageNamed: "TileStone")
let tileStoneTop3 = SKSpriteNode(imageNamed: "TileStone")
let tileStoneTop4 = SKSpriteNode(imageNamed: "TileStone")
let tileStoneTop5 = SKSpriteNode(imageNamed: "TileStone")
let tileStoneTop6 = SKSpriteNode(imageNamed: "TileStone")
let tileStoneTop7 = SKSpriteNode(imageNamed: "TileStone")
let tileStoneTop8 = SKSpriteNode(imageNamed: "TileStone")
let tileStoneTop9 = SKSpriteNode(imageNamed: "TileStone")
let tileStoneTop10 = SKSpriteNode(imageNamed: "TileStone")
let tileStoneTop11 = SKSpriteNode(imageNamed: "TileStone")
let tileStoneTop12 = SKSpriteNode(imageNamed: "TileStone")
let tileStoneTop13 = SKSpriteNode(imageNamed: "TileStone")
let player = SKSpriteNode(imageNamed: "PlayerBox")
let enemyBox = SKSpriteNode(imageNamed: "TileStone")
let playerInt : UInt32 = 0
let enemyInt : UInt32 = 1
override func didMoveToView(view: SKView) {
let invisibleNode = SKShapeNode(rect: CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height))
invisibleNode.name = "box"
invisibleNode.fillColor = SKColor.redColor()
invisibleNode.strokeColor = SKColor.clearColor()
self.addChild(invisibleNode)
self.backgroundColor = UIColor(hue: 0.0194, saturation: 0.66, brightness: 0.89, alpha: 1.0)
player.size = CGSize(width: 50, height: 50)
player.position = CGPointMake(self.frame.size.width / 2 - 350, 190)
player.anchorPoint = CGPointZero
player.zPosition = 2
addChild(player)
enemyBox.size = CGSize(width: 50, height: 50)
enemyBox.position = CGPointMake(self.frame.size.width / 2, 215)
enemyBox.zPosition = 2
addChild(enemyBox)
tileStone1.size = CGSize(width: 100, height: 100)
tileStone1.anchorPoint = CGPointZero
tileStone1.position = CGPoint(x: 0, y: 90)
tileStone1.zPosition = 2
addChild(tileStone1)
tileStone2.size = CGSize(width: 100, height: 100)
tileStone2.zPosition = 2
tileStone2.anchorPoint = CGPointZero
tileStone2.position = CGPoint(x: 100, y: 90)
addChild(tileStone2)
tileStone3.size = CGSize(width: 100, height: 100)
tileStone3.zPosition = 2
tileStone3.anchorPoint = CGPointZero
tileStone3.position = CGPoint(x: 200, y: 90)
addChild(tileStone3)
tileStone4.size = CGSize(width: 100, height: 100)
tileStone4.zPosition = 2
tileStone4.anchorPoint = CGPointZero
tileStone4.position = CGPoint(x: 300, y: 90)
addChild(tileStone4)
tileStone5.size = CGSize(width: 100, height: 100)
tileStone5.anchorPoint = CGPointZero
tileStone5.position = CGPoint(x: 400, y: 90)
tileStone5.zPosition = 2
addChild(tileStone5)
tileStone6.size = CGSize(width: 100, height: 100)
tileStone6.anchorPoint = CGPointZero
tileStone6.position = CGPoint(x: 500, y: 90)
tileStone6.zPosition = 2
addChild(tileStone6)
tileStone7.size = CGSize(width: 100, height: 100)
tileStone7.anchorPoint = CGPointZero
tileStone7.position = CGPoint(x: 600, y: 90)
tileStone7.zPosition = 2
addChild(tileStone7)
tileStone8.size = CGSize(width: 100, height: 100)
tileStone8.anchorPoint = CGPointZero
tileStone8.position = CGPoint(x: 700, y: 90)
tileStone8.zPosition = 2
addChild(tileStone8)
tileStone9.size = CGSize(width: 100, height: 100)
tileStone9.anchorPoint = CGPointZero
tileStone9.position = CGPoint(x: 800, y: 90)
tileStone9.zPosition = 2
addChild(tileStone9)
tileStone10.size = CGSize(width: 100, height: 100)
tileStone10.anchorPoint = CGPointZero
tileStone10.position = CGPoint(x: 900, y: 90)
tileStone10.zPosition = 2
addChild(tileStone10)
addChild(tileStone11)
tileStone11.size = CGSize(width: 100, height: 100)
tileStone11.anchorPoint = CGPointZero
tileStone11.position = CGPoint(x: 1000, y: 90)
tileStone11.zPosition = 2
tileStone12.size = CGSize(width: 100, height: 100)
tileStone12.anchorPoint = CGPointZero
tileStone12.position = CGPoint(x: 1100, y: 90)
tileStone12.zPosition = 2
addChild(tileStone12)
tileStone13.size = CGSize(width: 100, height: 100)
tileStone13.anchorPoint = CGPointZero
tileStone13.position = CGPoint(x: 1200, y: 90)
tileStone13.zPosition = 2
addChild(tileStone13)
tileStone14.size = CGSize(width: 100, height: 100)
tileStone14.anchorPoint = CGPointZero
tileStone14.position = CGPoint(x: 1300, y: 90)
tileStone14.zPosition = 2
addChild(tileStone14)
tileStone15.size = CGSize(width: 100, height: 100)
tileStone15.anchorPoint = CGPointZero
tileStone15.position = CGPoint(x: 1400, y: 90)
tileStone15.zPosition = 2
addChild(tileStone15)
tileStone16.size = CGSize(width: 100, height: 100)
tileStone16.anchorPoint = CGPointZero
tileStone16.position = CGPoint(x: 1500, y: 90)
tileStone16.zPosition = 2
addChild(tileStone16)
tileStone17.size = CGSize(width: 100, height: 100)
tileStone17.anchorPoint = CGPointZero
tileStone17.position = CGPoint(x: 1600, y: 90)
tileStone17.zPosition = 2
addChild(tileStone17)
tileStone18.size = CGSize(width: 100, height: 100)
tileStone18.anchorPoint = CGPointZero
tileStone18.position = CGPoint(x: 1700, y: 90)
tileStone18.zPosition = 2
addChild(tileStone18)
tileStone19.size = CGSize(width: 100, height: 100)
tileStone19.anchorPoint = CGPointZero
tileStone19.position = CGPoint(x: 1800, y: 90)
tileStone19.zPosition = 2
addChild(tileStone19)
tileStone20.size = CGSize(width: 100, height: 100)
tileStone20.anchorPoint = CGPointZero
tileStone20.position = CGPoint(x: 1900, y: 90)
tileStone20.zPosition = 2
addChild(tileStone20)
tileStone21.size = CGSize(width: 100, height: 100)
tileStone21.anchorPoint = CGPointZero
tileStone21.position = CGPoint(x: 2000, y: 90)
tileStone21.zPosition = 2
addChild(tileStone21)
tileStone22.size = CGSize(width: 100, height: 100)
tileStone22.anchorPoint = CGPointZero
tileStone22.position = CGPoint(x: 2100, y: 90)
tileStone22.zPosition = 2
addChild(tileStone22)
tileStoneTop1.size = CGSize(width: 100, height: 100)
tileStoneTop1.anchorPoint = CGPointZero
tileStoneTop1.position = CGPoint(x: 0, y: 580)
tileStoneTop1.zPosition = 2
addChild(tileStoneTop1)
tileStoneTop2.size = CGSize(width: 100, height: 100)
tileStoneTop2.zPosition = 2
tileStoneTop2.anchorPoint = CGPointZero
tileStoneTop2.position = CGPoint(x: 100, y: 580)
addChild(tileStoneTop2)
tileStoneTop3.size = CGSize(width: 100, height: 100)
tileStoneTop3.zPosition = 2
tileStoneTop3.anchorPoint = CGPointZero
tileStoneTop3.position = CGPoint(x: 200, y: 580)
addChild(tileStoneTop3)
tileStoneTop4.size = CGSize(width: 100, height: 100)
tileStoneTop4.zPosition = 2
tileStoneTop4.anchorPoint = CGPointZero
tileStoneTop4.position = CGPoint(x: 300, y: 580)
addChild(tileStoneTop4)
tileStoneTop5.size = CGSize(width: 100, height: 100)
tileStoneTop5.anchorPoint = CGPointZero
tileStoneTop5.position = CGPoint(x: 400, y: 580)
tileStoneTop5.zPosition = 2
addChild(tileStoneTop5)
tileStoneTop6.size = CGSize(width: 100, height: 100)
tileStoneTop6.anchorPoint = CGPointZero
tileStoneTop6.position = CGPoint(x: 500, y: 580)
tileStoneTop6.zPosition = 2
addChild(tileStoneTop6)
tileStoneTop7.size = CGSize(width: 100, height: 100)
tileStoneTop7.anchorPoint = CGPointZero
tileStoneTop7.position = CGPoint(x: 600, y: 580)
tileStoneTop7.zPosition = 2
addChild(tileStoneTop7)
tileStoneTop8.size = CGSize(width: 100, height: 100)
tileStoneTop8.anchorPoint = CGPointZero
tileStoneTop8.position = CGPoint(x: 700, y: 580)
tileStoneTop8.zPosition = 2
addChild(tileStoneTop8)
tileStoneTop9.size = CGSize(width: 100, height: 100)
tileStoneTop9.anchorPoint = CGPointZero
tileStoneTop9.position = CGPoint(x: 800, y: 580)
tileStoneTop9.zPosition = 2
addChild(tileStoneTop9)
tileStoneTop10.size = CGSize(width: 100, height: 100)
tileStoneTop10.anchorPoint = CGPointZero
tileStoneTop10.position = CGPoint(x: 900, y: 580)
tileStoneTop10.zPosition = 2
addChild(tileStoneTop10)
tileStoneTop11.size = CGSize(width: 150, height: 100)
tileStoneTop11.anchorPoint = CGPointZero
tileStoneTop11.position = CGPoint(x: 1000, y: 580)
tileStoneTop11.zPosition = 2
addChild(tileStoneTop11)
tileStoneTop12.size = CGSize(width: 200, height: 100)
tileStoneTop12.anchorPoint = CGPointZero
tileStoneTop12.position = CGPoint(x: 1100, y: 5780)
tileStoneTop12.zPosition = 2
addChild(tileStoneTop12)
tileStoneTop13.size = CGSize(width: 100, height: 100)
tileStoneTop13.anchorPoint = CGPointZero
tileStoneTop13.position = CGPoint(x: 1100, y: 5780)
tileStoneTop13.zPosition = 2
// addChild(tileStoneTop13)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let touchLocation = touch.locationInNode(self)
let touchedNode = nodeAtPoint(touchLocation)
if touchedNode.name == "box" {
isTouched = true
}
if touchedNode.name == "rerun"{
for node in self.nodesAtPoint(touchLocation) {
if node.name == "rerun" {
isTouched = false
node.removeFromParent()
}
}
}
}
}
let moveFactor:CGFloat = 0.5
override func update(currentTime: CFTimeInterval) {
var tileStoneArray = [tileStone1, tileStone2, tileStone3, tileStone4, tileStone5, tileStone6, tileStone7, tileStone8, tileStone9, tileStone10, tileStone11, tileStone12, tileStone13, tileStone14, tileStone15, tileStone16, tileStone17, tileStone18, tileStone19, tileStone20, tileStone21, tileStone22, tileStoneTop1, tileStoneTop2, tileStoneTop3, tileStoneTop4, tileStoneTop5, tileStoneTop6, tileStoneTop7, tileStoneTop8, tileStoneTop9, tileStoneTop10, tileStoneTop11, tileStoneTop12, tileStoneTop13]
if isTouched == false {
enemyBox.position = CGPoint(x: enemyBox.position.x - 10, y: 215)
}
if enemyBox.position.x < self.frame.minX - 100 {
enemyBox.position = CGPoint(x: self.frame.maxX, y: 215)
}
for tileStone1 in tileStoneArray {
if isTouched == false {
tileStone1.position = CGPoint(x: tileStone1.position.x - 10, y: tileStone1.position.y)
}
if (tileStone1.position.x < self.frame.minX - 100){
tileStone1.anchorPoint = CGPointZero
tileStone1.position = CGPoint(x: self.frame.maxX, y: tileStone1.position.y)
}
}
var score = 0;
func addGreatNode(){
let congratsLabel = SKLabelNode(fontNamed: "")
congratsLabel.name = "rerun"
congratsLabel.text = "Great!"
congratsLabel.fontSize = 65
congratsLabel.position = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height / 2)
congratsLabel.fontColor = SKColor.yellowColor()
addChild(congratsLabel)
}
if isTouched == true {
var xValuesEnemy = enemyBox.position.x
var xValuesPlayer = player.position.x
if xValuesPlayer < xValuesEnemy + 120 && xValuesPlayer > xValuesEnemy - 120 {
addGreatNode()
//This is where it adds the node infinitely
}
else {
}
}
}
}
This is a image of my node count climbing while in my game.
I know that I could improve my code to be more efficient but I'm very inexperienced, but I'm working on it.
Your addGreatNode() function is being called within update: which is called by the scene up to 60 times per second.
A few more tips:
Keep your TileStones in a collection
When adding TileStones to your Scene, use either a for or while loop. This will compact your code. Within each iteration, adjust the position for each Node.
If you want to add a SKLabelNode on touch, that logic should be within touchesBegan:, and not associated with update: