I'm trying to draw a part of an n-sided polygon with every user click. Right now this is what I have come up with but it doesn't seem to work. currentDrawn is the index of the last element drawn. N is the total sides count. layout is my box to draw in.
if(Double(currentDrawn) > n){
return
}else{
if(currentDrawn == 0){
UIGraphicsBeginImageContext(layout.bounds.size)
layout.draw(layout.bounds)
UIColor.green.setFill();
UIColor.green.setStroke();
let ctx=UIGraphicsGetCurrentContext()
ctx?.addEllipse(in: CGRect(x: 0, y: 0, width: 200, height: 200));
ctx?.drawPath(using: .stroke);
layout.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
currentDrawn += 1
let i=Double(0);
var x: Double,y: Double,t: Double;
t=2 * Double.pi * (i/n);
x=cos(t)*r;
y=sin(t)*r;
lastLineX = x + 100;
lastLineY = y + 100;
}else{
UIGraphicsBeginImageContext(layout.bounds.size)
layout.draw(layout.bounds)
let ctx=UIGraphicsGetCurrentContext()
ctx?.move(to: CGPoint(x: lastLineX, y: lastLineY));
UIColor.green.setFill();
UIColor.green.setStroke();
let i=Double(currentDrawn );
var x: Double,y: Double,t: Double;
t=2 * Double.pi * (i/n);
x=cos(t)*r + 100;
y=sin(t)*r + 100;
ctx?.addLine(to: CGPoint(x: x, y: y));
ctx?.drawPath(using: .fillStroke);
lastLineX = x;
lastLineY = y;
currentDrawn+=1;
layout.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
}
Related
I'm trying to make Concentration game (with matching cards) and my task is to set frames for the array of 12 UIButtons with for-loop (I tried to realize it with switches, but it wan't right as my mentor said).
So maybe you can help me with this. The code added below works correctly, but l have to escape of switch and make it works with the help of for-loop and universal formulas.
func setFrames() {
for i in 0...11{
var x = CGFloat()
var y = CGFloat()
switch i {
case 0...2:
y = 50
case 3...5:
y = (7/36 * view.bounds.height + 50);
case 6...8:
y = (7/18 * view.bounds.height + 50);
default:
y = (7/12 * view.bounds.height + 50);
}
switch i {
case 0, 3, 6, 9:
x = 1/18 * view.bounds.width; //20
case 1, 4, 7, 10:
x = 3/8 * view.bounds.width; //3/8
default:
x = 3/4 * view.bounds.width - 20;
}
buttonsArray[i].frame = CGRect(x: x, y: y, width: (1/4 * view.bounds.width), height: (1/6 * view.bounds.height))
}
}
You can use something like so:
func placeBtnsIn(frame: CGRect, padding: CGFloat) {
let numCols = 3
let numRows = 4
let buttonWidth = (frame.width - CGFloat(numCols - 1) * padding) / CGFloat(numCols)
let buttonHeight = (frame.height - CGFloat(numRows - 1) * padding) / CGFloat(numRows)
var x: CGFloat
var y: CGFloat
for row in 0...(numRows - 1) {
y = frame.origin.y + (buttonHeight + padding) * CGFloat(row)
for col in 0...(numCols - 1) {
x = frame.origin.x + (buttonWidth + padding) * CGFloat(col)
let button = UIButton()
button.frame = CGRect(x: x, y: y, width: buttonWidth, height: buttonHeight)
self.addSubview(button)
}
}
}
Then in your code use it like so:
placeBtnsIn(frame: yourFrame, padding: 5)
Where yourFrame is a rectangle in which buttons should be drawn, and padding is the distance between button "cells"
The goal is to fill a polygon using Core Graphics in Swift.
The following code does just that if all of the endpoints are known.
func drawPolySegment() {
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 512, height: 512))
let img = renderer.image { ctx in
let endpoints = [
CGPoint(x: 200, y: 175),
CGPoint(x: 270, y: 170),
CGPoint(x: 300, y: 100)
]
ctx.cgContext.addLines(between: endpoints)
UIColor.yellow.setFill()
ctx.cgContext.drawPath(using: .fillStroke)
}
imageView.image = img
}
However, the endpoints for the desired shape are not easily obtainable and so a more generic approach was opted for using rotate(by:), move(to:), addline(to:), translate(by:) methods as follows:
func drawPolygon() {
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 512, height: 512))
let img = renderer.image { ctx in
let apexes: CGFloat = 3 //5 //6
let length: CGFloat = 500 //190 //140
let angle: CGFloat = 2 * π / apexes
let zero: CGFloat = 0
UIColor.brown.setStroke()
UIColor.yellow.setFill()
ctx.cgContext.setLineWidth(10)
ctx.cgContext.setLineCap(.round)
ctx.cgContext.translateBy(x: 256, y: 5)
for segment in 0..<Int(apexes * 2) {
if segment == 0 {
ctx.cgContext.rotate(by: angle)
} else if segment % 2 == 0 {
ctx.cgContext.rotate(by: 2 * angle)
} else {
ctx.cgContext.rotate(by: -angle)
}
ctx.cgContext.move(to: CGPoint(x: zero, y: zero))
ctx.cgContext.addLine(to: CGPoint(x: length, y: zero))
ctx.cgContext.translateBy(x: length, y: zero)
}
ctx.cgContext.drawPath(using: .fillStroke)
}
imageView.image = img
}
The code above generates a beautifully outlined shape, but will not fill with colour. The values commented out for "apexes" and "length" are valid for the 512 x 512 rendering space created. Why does the shape fill for the first code sample and not the second? What is missing from the second code sample to make the shape fill?
You aren't drawing a polygon, you're drawing 3 separate unconnected lines. Each move(to:) starts a new polygon.
You can fix this by only doing the move(to:) for the first segment:
for segment in 0..<Int(apexes * 2) {
if segment == 0 {
ctx.cgContext.rotate(by: angle)
ctx.cgContext.move(to: CGPoint(x: zero, y: zero))
} else if segment % 2 == 0 {
ctx.cgContext.rotate(by: 2 * angle)
} else {
ctx.cgContext.rotate(by: -angle)
}
ctx.cgContext.addLine(to: CGPoint(x: length, y: zero))
ctx.cgContext.translateBy(x: length, y: zero)
}
Results:
apexes: 3, length: 500
apexes: 5, length: 190
apexes: 6, length: 140
I have a tileMapNode in which I want to assign a physics body to some tiles.
I use the following function:
static func addPhysicsBody(to tileMap: SKTileMapNode, and tileInfo: String){
let tileSize = tileMap.tileSize
let halfWidth = CGFloat(tileMap.numberOfColumns) / 2 * tileSize.width
let halfHeight = CGFloat(tileMap.numberOfRows) / 2 * tileSize.height
for column in 0..<tileMap.numberOfColumns{
for row in 0..<tileMap.numberOfRows{
let tileDefinition = tileMap.tileDefinition(atColumn: column, row: row)
let isCorrectTile = tileDefinition?.userData?[tileInfo] as? Bool
if isCorrectTile ?? false && tileInfo == "wall"{
let x = CGFloat(column) * tileSize.width - halfWidth
let y = CGFloat(row) * tileSize.height - halfHeight
let rect = CGRect(x: 0, y: 0, width: tileSize.width, height: tileSize.height)
let tileNode = SKShapeNode(rect: rect)
tileNode.position = CGPoint(x: x, y: y)
tileNode.physicsBody = SKPhysicsBody.init(rectangleOf: tileSize, center: CGPoint(x: tileSize.width / 2, y: tileSize.height / 2))
tileNode.physicsBody!.isDynamic = false
tileNode.physicsBody!.restitution = 0.0
tileNode.physicsBody!.categoryBitMask = Constants.PhysicsCategories.wall
tileNode.physicsBody!.collisionBitMask = Constants.PhysicsCategories.player | Constants.PhysicsCategories.npc
tileMap.addChild(tileNode)
}
}
}
}
This all is working well, but if I run the app now, the tiles where I assigned a physics body to, have white borders. view.showphysics is set to false.
Anyone an idea why there are white borders around the tiles? It looks like this:
Okay so I want to rotate CGPoint(A) 50 degrees around CGPoint(B) is there a good way to do that?
CGPoint(A) = CGPoint(x: 50, y: 100)
CGPoint(B) = CGPoint(x: 50, y: 0)
Here's what I want to do:
This is really a maths question. In Swift, you want something like:
func rotatePoint(target: CGPoint, aroundOrigin origin: CGPoint, byDegrees: CGFloat) -> CGPoint {
let dx = target.x - origin.x
let dy = target.y - origin.y
let radius = sqrt(dx * dx + dy * dy)
let azimuth = atan2(dy, dx) // in radians
let newAzimuth = azimuth + byDegrees * CGFloat(M_PI / 180.0) // convert it to radians
let x = origin.x + radius * cos(newAzimuth)
let y = origin.y + radius * sin(newAzimuth)
return CGPoint(x: x, y: y)
}
There are lots of ways to simplify this, and it's a perfect case for an extension to CGPoint, but I've left it verbose for clarity.
public extension CGFloat {
///Returns radians if given degrees
var radians: CGFloat{return self * .pi / 180}
}
public extension CGPoint {
///Rotates point by given degrees
func rotate(origin: CGPoint? = CGPoint(x: 0.5, y: 0.5), _ byDegrees: CGFloat) -> CGPoint {
guard let origin = origin else {return self}
let rotationSin = sin(byDegrees.radians)
let rotationCos = cos(byDegrees.radians)
let x = (self.x * rotationCos - self.y * rotationSin) + origin.x
let y = (self.x * rotationSin + self.y * rotationCos) + origin.y
return CGPoint(x: x, y: y)
}
}
Usage
var myPoint = CGPoint(x: 40, y: 50).rotate(45)
var myPoint = CGPoint(x: 40, y: 50).rotate(origin: CGPoint(x: 0, y: 0), 45)
I'm looking for a way to programmatically create stars, sunburst, and other "spiky" effects using UIBezierPath.
UIBezierPath *sunbeamsPath = [UIBezierPath bezierPath];
[sunbeamsPath moveToPoint: CGPointMake(x, y)];
Are there any algorithms that can generate points for sunburst like shapes programmatically, without paths overlapping?
I'm also interested in an irregular shape sunburst like the one below:
I would imagine that such algorithm would take a certain number of rays, then roughly divide the circle in a number of segments and generate points for such segment in a clockwise direction. Does an algorithm like the one I'm describing already exists or will I have to write one by myself?
Thank you!
I know this old, but I was curious about the first part of this question myself, and going off jrturton's post, I created a custom UIView that generates a UIBezierPath from center of the view. Even animated it spinning for bonus points. Here is the result:
The code I used is here:
- (void)drawRect:(CGRect)rect {
CGFloat radius = rect.size.width/2.0f;
[self.fillColor setFill];
[self.strokeColor setStroke];
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
CGPoint centerPoint = CGPointMake(rect.origin.x + radius, rect.origin.y + radius);
CGPoint thisPoint = CGPointMake(centerPoint.x + radius, centerPoint.y);
[bezierPath moveToPoint:centerPoint];
CGFloat thisAngle = 0.0f;
CGFloat sliceDegrees = 360.0f / self.beams / 2.0f;
for (int i = 0; i < self.beams; i++) {
CGFloat x = radius * cosf(DEGREES_TO_RADIANS(thisAngle + sliceDegrees)) + centerPoint.x;
CGFloat y = radius * sinf(DEGREES_TO_RADIANS(thisAngle + sliceDegrees)) + centerPoint.y;
thisPoint = CGPointMake(x, y);
[bezierPath addLineToPoint:thisPoint];
thisAngle += sliceDegrees;
CGFloat x2 = radius * cosf(DEGREES_TO_RADIANS(thisAngle + sliceDegrees)) + centerPoint.x;
CGFloat y2 = radius * sinf(DEGREES_TO_RADIANS(thisAngle + sliceDegrees)) + centerPoint.y;
thisPoint = CGPointMake(x2, y2);
[bezierPath addLineToPoint:thisPoint];
[bezierPath addLineToPoint:centerPoint];
thisAngle += sliceDegrees;
}
[bezierPath closePath];
bezierPath.lineWidth = 1;
[bezierPath fill];
[bezierPath stroke];
}
And you can download a sample project here:
https://github.com/meekapps/Sunburst
I'm not aware of an algorithm to create these but I do have some advice - create your bezier path such that (0,0) is the centre of the sunburst, then define however many points you need to draw one "beam" of your sunburst going upwards, returning to (0,0)
Then, for as many beams as you want, perform a loop: apply a rotation transform (2 pi / number of beams) to your sunbeam points (CGPointApplyTransform), and add them to the path.
Once you are finished, you can translate and scale the path for drawing.
I used a similar process to draw star polygons recently and it was very simple. Credit to Rob Napier's book for the idea.
Swift version for this
import UIKit
extension Int {
var degreesToRadians: Double { return Double(self) * .pi / 180 }
var radiansToDegrees: Double { return Double(self) * 180 / .pi }
}
extension FloatingPoint {
var degreesToRadians: Self { return self * .pi / 180 }
var radiansToDegrees: Self { return self * 180 / .pi }
}
class SunBurstView: UIView {
override func draw(_ rect: CGRect) {
let radius: CGFloat = rect.size.width / 2.0
UIColor.red.setFill()
UIColor.blue.setStroke()
let bezierPath = UIBezierPath()
let centerPoint = CGPoint(x: rect.origin.x + radius, y: rect.origin.y + radius)
var thisPoint = CGPoint(x: centerPoint.x + radius, y: centerPoint.y)
bezierPath.move(to: centerPoint)
var thisAngle: CGFloat = 0.0
let sliceDegrees: CGFloat = 360.0 / self.beams / 2.0
for _ in 0..<self.beams {
let x = radius * CGFloat(cosf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.x
let y = radius * CGFloat(sinf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.y
thisPoint = CGPoint(x: x, y: y)
bezierPath.addLine(to: thisPoint)
thisAngle += sliceDegrees
let x2 = radius * CGFloat(cosf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.x
let y2 = radius * CGFloat(sinf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.y
thisPoint = CGPoint(x: x2, y: y2)
bezierPath.addLine(to: thisPoint)
bezierPath.addLine(to: centerPoint)
thisAngle += sliceDegrees
}
bezierPath.close()
bezierPath.lineWidth = 1
bezierPath.fill()
bezierPath.stroke()
}
}
I noticed that the Swift version didn't compile for me or take up enough of the screen, so here's Reinier's answer in Swift 4 adjusted for a rectangular view.
extension Int {
var degreesToRadians: Double { return Double(self) * .pi / 180 }
var radiansToDegrees: Double { return Double(self) * 180 / .pi }
}
extension FloatingPoint {
var degreesToRadians: Self { return self * .pi / 180 }
var radiansToDegrees: Self { return self * 180 / .pi }
}
class SunBurstView: UIView {
var beams: CGFloat = 20
override func draw(_ rect: CGRect) {
self.clipsToBounds = false
self.layer.masksToBounds = false
let radius: CGFloat = rect.size.width * 1.5
UIColor.orange.withAlphaComponent(0.3).setFill()
UIColor.clear.setStroke()
let bezierPath = UIBezierPath()
let centerPoint = CGPoint(x: rect.origin.x + (radius / 3), y: rect.origin.y + (radius / 1.5))
var thisPoint = CGPoint(x: centerPoint.x + radius, y: centerPoint.y)
bezierPath.move(to: centerPoint)
var thisAngle: CGFloat = 0.0
let sliceDegrees: CGFloat = 360.0 / self.beams / 2.0
for _ in 0...Int(beams) {
let x = radius * CGFloat(cosf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.x
let y = radius * CGFloat(sinf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.y
thisPoint = CGPoint(x: x, y: y)
bezierPath.addLine(to: thisPoint)
thisAngle += sliceDegrees
let x2 = radius * CGFloat(cosf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.x
let y2 = radius * CGFloat(sinf(Float((thisAngle + sliceDegrees).degreesToRadians))) + centerPoint.y
thisPoint = CGPoint(x: x2, y: y2)
bezierPath.addLine(to: thisPoint)
bezierPath.addLine(to: centerPoint)
thisAngle += sliceDegrees
}
bezierPath.close()
bezierPath.lineWidth = 1
bezierPath.fill()
bezierPath.stroke()
}
}