I am try draw shape using UIBezietPath but each time I am have trouble with "Curve".
For solving my problem I did use Paint Code, code was be generated well, but it is a static shape , and I try translate this code for dynamic screen size, but each time I have trouble with half of shape.
Can anyone help me translate this code correctly?
Generated code by Paint Code:
//// Color Declarations
let fillColor = NSColor(red: 0.118, green: 0.118, blue: 0.149, alpha: 1)
//// Bezier Drawing
let bezierPath = NSBezierPath()
bezierPath.move(to: NSPoint(x: 8.83, y: 89))
bezierPath.line(to: NSPoint(x: 154.29, y: 89))
bezierPath.curve(to: NSPoint(x: 163.01, y: 81.04), controlPoint1: NSPoint(x: 158.71, y: 89), controlPoint2: NSPoint(x: 162.21, y: 85.39))
bezierPath.curve(to: NSPoint(x: 206.45, y: 45.05), controlPoint1: NSPoint(x: 166.77, y: 60.57), controlPoint2: NSPoint(x: 184.79, y: 45.05))
bezierPath.curve(to: NSPoint(x: 249.89, y: 81.04), controlPoint1: NSPoint(x: 228.11, y: 45.05), controlPoint2: NSPoint(x: 246.12, y: 60.57))
bezierPath.curve(to: NSPoint(x: 258.61, y: 89), controlPoint1: NSPoint(x: 250.68, y: 85.39), controlPoint2: NSPoint(x: 254.19, y: 89))
bezierPath.line(to: NSPoint(x: 405.17, y: 89))
bezierPath.curve(to: NSPoint(x: 414, y: 80.21), controlPoint1: NSPoint(x: 410.05, y: 89), controlPoint2: NSPoint(x: 414, y: 85.06))
bezierPath.line(to: NSPoint(x: 414, y: 8.79))
bezierPath.curve(to: NSPoint(x: 405.17, y: 0), controlPoint1: NSPoint(x: 414, y: 3.94), controlPoint2: NSPoint(x: 410.05, y: 0))
bezierPath.line(to: NSPoint(x: 8.83, y: 0))
bezierPath.curve(to: NSPoint(x: 0, y: 8.79), controlPoint1: NSPoint(x: 3.95, y: 0), controlPoint2: NSPoint(x: 0, y: 3.94))
bezierPath.line(to: NSPoint(x: 0, y: 80.21))
bezierPath.curve(to: NSPoint(x: 8.83, y: 89), controlPoint1: NSPoint(x: 0, y: 85.06), controlPoint2: NSPoint(x: 3.95, y: 89))
bezierPath.close()
fillColor.setFill()
bezierPath.fill()
Shape:
First extend CGPoint and create a scaled(by:) method:
extension CGPoint {
func scaled(by value: CGFloat) -> CGPoint { applying(.init(scaleX: value, y: value)) }
}
Then get the actual superview's bounds width and divide by the width of your path 414.
let value: CGFloat = superview!.bounds.size.width / 414
Now you just need to apply the scale value to every point of your path:
bezierPath.move(to: NSPoint(x: 8.83, y: 89).scaled(by: value))
Related
Im attempting to animate a UIView along an oval using a bezier curve. The code works except that there is a delay between each loop of the animation. I'm at a loss as to what is causing this.
let myView = UIView()
myView.backgroundColor = UIColor.blue
myView.frame = CGRect(x: 0, y:0, width: 30, height: 30)
let path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 200, height: 200))
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = path.cgPath
animation.repeatCount = 1000
animation.duration = 1
myView.layer.add(animation, forKey: "animate along path")
Found a solution. The pause between animation loops only takes place when making a path with the UIBezierPath(ovalIn:) constructor. There's no delay when manually making your own path. So I made my own oval.
Replaced this
let path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 200, height: 200))
with this
let rect = CGRect(x: 0, y: 0, width: 200, height: 200)
let path = UIBezierPath()
path.move(to: CGPoint(x: rect.maxX, y: rect.midY))
path.addQuadCurve(to: CGPoint(x: rect.midX, y: rect.maxY), controlPoint: CGPoint(x: rect.maxX, y: rect.maxY))
path.addQuadCurve(to: CGPoint(x: rect.minX, y: rect.midY), controlPoint: CGPoint(x: rect.minX, y: rect.maxY))
path.addQuadCurve(to: CGPoint(x: rect.midX, y: rect.minY), controlPoint: CGPoint(x: rect.minX, y: rect.minY))
path.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.midY), controlPoint: CGPoint(x: rect.maxX, y: rect.minY))
I want to cast a shadow to the lower left AND the lower right of my Image. But I'm struggling to get the second shadow cast (layer2)...
I've created a subclass of UIImage view and use that in IB:
import UIKit
class ShadowImageView: UIImageView {
var layer2 = CALayer()
override func layoutSubviews() {
super.layoutSubviews()
self.layer2 = CALayer(layer: self.layer)
self.clipsToBounds = false
self.layer.shadowColor = UIColor.gray.cgColor
self.layer.shadowOffset = CGSize(width: -16, height: 16)
self.layer.shadowOpacity = 0.33
self.layer.shadowRadius = 3
self.layer.masksToBounds = false
self.layer2.shadowColor = UIColor.gray.cgColor
self.layer2.shadowOffset = CGSize(width: 16, height: 16)
self.layer2.shadowOpacity = 0.33
self.layer2.shadowRadius = 3
self.layer2.masksToBounds = false
self.layer.insertSublayer(self.layer2, at: 0)
print("\(self.layer)\n")
if let sublayers = self.layer.sublayers {
for layer in sublayers {
print("\(layer)\n")
}
}
}
}
Am I going in the right direction to copy the layer and apply a new shadow to it or should I look in a different direction?
Fixed the shadow problem. I was able to generate double shadows by adding 3 layers on my UIView. The furthest behind (the first sublayer of the view's layer) is the darker shadow, the one on top of that is the lighter shadow and on top of that I have one with the normal color.
I'm 'drawing' the images on the layers by applying a shadow path & color.
The image I'm drawing is coming from a method like listed below, but it's not being generated from a SFSymbols. I would like to generate it during runtime so I end up with a simple method of using symbols to place Neumorphic elements in our apps. The manual generation of these BezierPaths isn't really pleasant.
static var shieldFill: UIBezierPath = {
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 58.94, y: 115.53))
bezierPath.addCurve(to: CGPoint(x: 62.84, y: 114.4), controlPoint1: CGPoint(x: 60.06, y: 115.53), controlPoint2: CGPoint(x: 61.57, y: 115.04))
bezierPath.addCurve(to: CGPoint(x: 101.76, y: 74.46), controlPoint1: CGPoint(x: 91.5, y: 100.49), controlPoint2: CGPoint(x: 101.76, y: 91.89))
bezierPath.addLine(to: CGPoint(x: 101.76, y: 39.94))
bezierPath.addCurve(to: CGPoint(x: 92.04, y: 25.05), controlPoint1: CGPoint(x: 101.76, y: 31.84), controlPoint2: CGPoint(x: 99.41, y: 28.12))
bezierPath.addCurve(to: CGPoint(x: 67.63, y: 16.85), controlPoint1: CGPoint(x: 87.89, y: 23.29), controlPoint2: CGPoint(x: 71.29, y: 17.97))
bezierPath.addCurve(to: CGPoint(x: 58.94, y: 15.53), controlPoint1: CGPoint(x: 64.94, y: 16.11), controlPoint2: CGPoint(x: 61.52, y: 15.53))
bezierPath.addCurve(to: CGPoint(x: 50.24, y: 16.85), controlPoint1: CGPoint(x: 56.35, y: 15.53), controlPoint2: CGPoint(x: 52.93, y: 16.11))
bezierPath.addCurve(to: CGPoint(x: 25.83, y: 25.05), controlPoint1: CGPoint(x: 46.53, y: 17.97), controlPoint2: CGPoint(x: 29.93, y: 23.29))
bezierPath.addCurve(to: CGPoint(x: 16.11, y: 39.94), controlPoint1: CGPoint(x: 18.46, y: 28.12), controlPoint2: CGPoint(x: 16.11, y: 31.84))
bezierPath.addLine(to: CGPoint(x: 16.11, y: 74.46))
bezierPath.addCurve(to: CGPoint(x: 55.03, y: 114.4), controlPoint1: CGPoint(x: 16.11, y: 91.89), controlPoint2: CGPoint(x: 26.46, y: 100.29))
bezierPath.addCurve(to: CGPoint(x: 58.94, y: 115.53), controlPoint1: CGPoint(x: 56.3, y: 115.04), controlPoint2: CGPoint(x: 57.76, y: 115.53))
bezierPath.close()
return bezierPath
}()
I was able to get this very easily:
That seems to be the sort of thing you're after.
The double shadow stems from the fact that in a graphics context, shadow drawing is a state applied to any future drawing. So if you set the context's shadow to be down-and-to-the-left and draw the symbol image, and then set the context's shadow to be down-and-to-the-right and draw the symbol image, you get the double shadow.
It is true that the symbol image itself is drawn twice but that does not necessarily change the way it appears; if I hadn't told you I'd drawn it twice, I don't think you'd have known.
I am trying to draw a shape using CGPath and SKShapeNode. However, whatever the lineJoin or lineCap combinations I try to use, the lines have gaps.
import PlaygroundSupport
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
super.didMove(to: view)
let path = CGMutablePath()
path.move(to: NSPoint(x: -66.585, y: 1.125))
path.addCurve(to: NSPoint(x: -60.585, y: -41.765), control1: NSPoint(x: -66.585, y: 1.125), control2: NSPoint(x: -65.835, y: -32.735))
path.addCurve(to: NSPoint(x: 66.585, y: -20.3150000000001), control1: NSPoint(x: -60.585, y: -41.765), control2: NSPoint(x: -3.39500000000001, y: -1.88499999999999))
path.addCurve(to: NSPoint(x: 56.545, y: 18.235), control1: NSPoint(x: 66.125, y: -7.09500000000003), control2: NSPoint(x: 62.695, y: 6.16499999999996))
path.addCurve(to: NSPoint(x: -66.585, y: 1.125), control1: NSPoint(x: 56.055, y: 19.1849999999999), control2: NSPoint(x: -15.415, y: 41.765))
path.closeSubpath()
let shapeNode = SKShapeNode(path: path)
shapeNode.fillColor = .red
shapeNode.lineWidth = 10
shapeNode.lineJoin = .round
shapeNode.strokeColor = .white
addChild(shapeNode)
}
}
let sceneView = SKView(frame: CGRect(x:0 , y:0, width: 640, height: 480))
if let scene = GameScene(fileNamed: "GameScene") {
scene.scaleMode = .aspectFill
sceneView.presentScene(scene)
}
PlaygroundSupport.PlaygroundPage.current.liveView = sceneView
What am I doing wrong here?
This looks odd to me:
path.move(to: NSPoint(x: -66.585, y: 1.125))
path.addCurve(to: NSPoint(x: -60.585, y: -41.765), control1: NSPoint(x: -66.585, y: 1.125), control2: NSPoint(x: -65.835, y: -32.735))
You move to (-66.58., 1.125). Then you add a curve where the first point is (-60.585, -41.765), and the first control point is the same as the original point you moved to. It seems like that will cause some odd issues. I'd think you'd want to have the point you move to be the first point of the curve, not its control point.
I want to create image view like this.
With Bezier path, the code i tried is
let path = UIBezierPath()
path.move(to: CGPoint(x: myImage.frame.size.width - 10 , y: myImage.frame.size.height))
path.addLine(to: CGPoint(x: myImage.frame.size.width - 20 , y: 20))
path.addLine(to: CGPoint(x: myImage.frame.size.width, y: 20))
path.addArc(withCenter: CGPoint(x: myImage.frame.size.width/2, y: myImage.frame.size.height/2), radius: myImage.frame.size.width/2, startAngle:-CGFloat(M_PI_2), endAngle: CGFloat(M_PI_2), clockwise: false)
//
path.move(to: CGPoint(x: myImage.frame.size.width/2, y: myImage.frame.size.height))
path.close()
UIColor.red.setFill()
path.stroke()
path.reversing()
code
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 153.5, y: 137.5))
bezierPath.addCurve(to: CGPoint(x: 63.5, y: 196.5), controlPoint1: CGPoint(x: 130.5, y: 137.5), controlPoint2: CGPoint(x: 63.5, y: 120.37))
bezierPath.addCurve(to: CGPoint(x: 63.5, y: 310.5), controlPoint1: CGPoint(x: 63.5, y: 260.99), controlPoint2: CGPoint(x: 63.5, y: 218.5))
bezierPath.addCurve(to: CGPoint(x: 88.5, y: 352.5), controlPoint1: CGPoint(x: 63.5, y: 333.5), controlPoint2: CGPoint(x: 73.5, y: 347.5))
bezierPath.addCurve(to: CGPoint(x: 218.5, y: 360.5), controlPoint1: CGPoint(x: 137.23, y: 368.74), controlPoint2: CGPoint(x: 218.5, y: 360.5))
bezierPath.addCurve(to: CGPoint(x: 240.5, y: 309.5), controlPoint1: CGPoint(x: 218.5, y: 360.5), controlPoint2: CGPoint(x: 240.5, y: 337))
bezierPath.addCurve(to: CGPoint(x: 222.5, y: 249.5), controlPoint1: CGPoint(x: 240.5, y: 282), controlPoint2: CGPoint(x: 222.5, y: 249.5))
bezierPath.addCurve(to: CGPoint(x: 240.5, y: 196.5), controlPoint1: CGPoint(x: 222.5, y: 249.5), controlPoint2: CGPoint(x: 240.5, y: 224.5))
bezierPath.addCurve(to: CGPoint(x: 218.5, y: 137.5), controlPoint1: CGPoint(x: 240.5, y: 168.5), controlPoint2: CGPoint(x: 218.5, y: 137.5))
bezierPath.addCurve(to: CGPoint(x: 153.5, y: 137.5), controlPoint1: CGPoint(x: 218.5, y: 137.5), controlPoint2: CGPoint(x: 201.5, y: 137.5))
bezierPath.close()
UIColor.gray.setFill()
bezierPath.fill()
UIColor.black.setStroke()
bezierPath.lineWidth = 1
bezierPath.stroke()
output
I'm relatively new in app development but for interest I want to implement this application on a playground project on Xcode:
http://merowing.info/2015/11/the-beauty-of-imperfection/
All the errors are corrected, but when I press the run Playground button it says "Running" forever without displaying anything. Do you know what I'm doing wrong?
import UIKit
import XCPlayground
import QuartzCore
import PlaygroundSupport
class Animator {
class TargetProxy {
init (target: Animator) {
self.target = target
}
weak var target: Animator!
var totalTime: TimeInterval = 0
#objc func onFire (dl: CADisplayLink) {
totalTime += dl.duration
target.block(totalTime)
}
}
private lazy var displayLink: CADisplayLink = {
return CADisplayLink(target: TargetProxy(target: self), selector: #selector(TargetProxy.onFire))}()
private let block: (TimeInterval) -> Void
init (block: #escaping (TimeInterval) -> Void) {
self.block = block
displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes)
}
deinit {
displayLink.invalidate()
}
}
func createBlobShape() -> UIBezierPath {
let ovalPath = UIBezierPath()
ovalPath.move(to: CGPoint.init(x: 13.71, y: -29.07))
ovalPath.addCurve(to: CGPoint.init(x: 27.92, y: -14), controlPoint1: CGPoint.init(x: 20.64, y: -25.95), controlPoint2: CGPoint.init(x: 24.57, y: -20.72))
ovalPath.addCurve(to: CGPoint.init(x: 33, y: 0.5), controlPoint1: CGPoint.init(x: 30.08, y: -9.68), controlPoint2: CGPoint.init(x: 33, y: -4.64))
ovalPath.addCurve(to: CGPoint.init(x: 20.82, y: 26), controlPoint1: CGPoint.init(x: 333, y: 10.93), controlPoint2: CGPoint.init(x: 27.47, y: 17.84))
ovalPath.addCurve(to: CGPoint.init(x: 0, y: 33), controlPoint1: CGPoint.init(x: 16.02, y: 31.88), controlPoint2: CGPoint.init(x: 7.63, y: 33))
ovalPath.addCurve(to: CGPoint.init(x: -16.72, y: 28.33), controlPoint1: CGPoint.init(x: -6.21, y: 33), controlPoint2: CGPoint.init(x: -11.89, y: 31.29))
ovalPath.addCurve(to: CGPoint.init(x: -23.86, y: 22), controlPoint1: CGPoint.init(x: -19.59, y: 26.57), controlPoint2: CGPoint.init(x: -22.22, y: 24.28))
ovalPath.addCurve(to: CGPoint.init(x: -28, y: 17), controlPoint1: CGPoint.init(x: -25.19, y: 20.16), controlPoint2: CGPoint.init(x: -26.74, y: 19.46))
ovalPath.addCurve(to: CGPoint.init(x: -33, y: 0.5), controlPoint1: CGPoint.init(x: -30.24, y: 12.61), controlPoint2: CGPoint.init(x: -33, y: 5.74))
ovalPath.addCurve(to: CGPoint.init(x: -23.86, y: -23), controlPoint1: CGPoint.init(x: -33, y: -9.63), controlPoint2: CGPoint.init(x: -31.23, y: -17.04))
ovalPath.addCurve(to: CGPoint.init(x: -4.57, y: -33), controlPoint1: CGPoint.init(x: -18.17, y: -27.6), controlPoint2: CGPoint.init(x: -12.51, y: -33))
ovalPath.addCurve(to: CGPoint.init(x: 13.71, y: -29.07), controlPoint1: CGPoint.init(x: 0.32, y: -33), controlPoint2: CGPoint.init(x: 9.53, y: -30.95))
ovalPath.close()
return ovalPath
}
func toRadian(degree: Int) -> Float {
return Float(Double(degree) * (Double.pi / 180.0))
}
let blob = CAShapeLayer()
let blobShape = createBlobShape()
blob.path = blobShape.cgPath
blob.frame = blobShape.bounds
blob.anchorPoint = CGPoint.init(x: 0, y: 0)
blob.fillColor = UIColor.orange.cgColor
var view = UIView(frame: CGRect(x: 0, y: 0, width: 640, height: 480))
view.backgroundColor = UIColor.gray
//grayColor().colorWithAlphaComponent(0.8)
PlaygroundPage.current.liveView = view
blob.position = view.center
view.layer.addSublayer(blob)
let playImageView = UIImageView(image: UIImage(named: "play"))
playImageView.transform = CGAffineTransform.init(scaleX: 0.05, y: 0.05)
playImageView.center = blob.position
view.addSubview(playImageView)
PlaygroundPage.current.needsIndefiniteExecution = true
//XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
let dl = Animator {
let skewBaseTime = $0 * 0.3
let rotation = CATransform3DMakeRotation(CGFloat(acos(cos(skewBaseTime))), 0, 0, 1)
let upscale = 5.0
let scaleAdjustment = 0.1
let scale = CATransform3DMakeScale(CGFloat(upscale + abs(sin(skewBaseTime) * scaleAdjustment)), CGFloat(upscale + abs(cos(skewBaseTime) * scaleAdjustment)), 1)
let skewTransform = CGAffineTransform.init(a: 1.0, b: 0.0, c: CGFloat(cos(skewBaseTime + Double.pi) * 0.1), d: 1.0, tx: 0.0, ty: 0.0)
CATransaction.begin()
CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
view.layer.sublayerTransform = CATransform3DConcat(CATransform3DMakeAffineTransform(skewTransform), scale)
blob.transform = rotation
CATransaction.commit()
}
Your code works. What you need to do is to open the "Assistant Editor" from the "View" menu to see the result.
You should correct:
private lazy var displayLink: CADisplayLink = {
return CADisplayLink(target: TargetProxy(target: self), selector: #selector(TargetProxy.onFire))}()
With:
var displayLink: CADisplayLink {
let target = TargetProxy(target: self)
return CADisplayLink(target: target, selector: #selector(target.onFire))
}
By default, Playgrounds are in sync with iCloud.
Sometimes it can lead to this "stucks on "Running" bug.
Just go to:
System Preferences - Apple ID - Apps on this Mac using iCloud- iCloud Drive - Options- And then uncheck "Playground"
That little trick prevents syncing and resolves the frozen state of Xcode.