Plotting waveform in circular path - audiokit

Trying to plot file waveform in circular form and looking through the underlying EZAudio's EZAudioPlot class docs tells me this should be possible. However I can't quite wrap my head how to construct the 'points' argument in the linked method. Anyone done this and could point to correct way?
UPDATE:
Looking into this I think I've gotten fairly good grasp how EZAudioPlotting works. The actual waveform drawing is done on EZAudioPlotWaveformLayer which is just simple CAShapeLayer subclass. On creation of EZAudioPlot one EZAudioPlotWaveformLayer is created and reference stored on EZAudioPlot class waveformLayer property.
The actual waveform is CGPath on EZAudioPlotWaveformLayer class and to manipulate it it should go something like below.
// Setup audio data for the plot
let file = EZAudioFile(url: url)
guard let data = file?.getWaveformData(withNumberOfPoints: pointCount) else {return}
// Setup the plot
let waveform = EZAudioPlot()
waveform.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height)
waveform.gain = 10.0
// + any other property tweaking you need
//Get hold of the underlaying layer
let myLayer = waveform.waveformLayer!
// Create the default presentation for the waveforms as CGPath
let path = waveform.createPath(withPoints: waveform.points, pointCount: pointCount, in: EZRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height)).takeRetainedValue()
// ??? Here somehow tweak the CGPath eg. into circular shape ???
newPath = plotPointsIntoCircleOrSomeOtherNiftyFunc(path)
// Stick new path to layer
myLayer.path = newPath
Only thing is that currently I haven't figured any math to actually implement anything meaningful at that pseudo code point

Related

Drawing a circular loader

I have to create a circular loader as per below image (Front is thicker than tail)
I am able to create a circular progress bar and also provided rotation animation.
below is code for circle
func circleFrame() -> CGRect {
var circleFrame = CGRect(x: 0, y: 0, width: 2 * circleRadius, height: 2 * circleRadius)
let circlePathBounds = circlePathLayer.bounds
circleFrame.origin.x = circlePathBounds.midX - circleFrame.midX
circleFrame.origin.y = circlePathBounds.midY - circleFrame.midY
return circleFrame
}
func circlePath() -> UIBezierPath {
return UIBezierPath(ovalIn: circleFrame())
}
Using above code I can create a circle of equal width but not like as displayed image.
Please guide me how to create a loader like above image (tail is thinner than front). Any idea or suggestion would be great.
The simplest approach might be to create the image you want to display and then rotate it, rather than trying to draw it from scratch.
I haven't tried the following tutorial but I'm including it as a sample of how this might be done:
https://bencoding.com/2015/07/27/spinning-uiimageview-using-swift/
Note that the GitHub version (linked on that page) includes a Swift 4 update.

UIBezierPath translation transform gives wrong answer

When I attempt to translate a path, to move it to an origin of {0, 0}, the resulting path bounds is in error. (Or, my assumptions are in error).
e.g. the path gives the following bounds info:
let bezier = UIBezierPath(cgPath: svgPath)
print(bezier.bounds)
// (0.0085, 0.7200, 68.5542, 41.1379)
print(bezier.cgPath.boundingBoxOfPath)
// (0.0085, 0.7200, 68.5542, 41.1379)
print(bezier.cgPath.boundingBox)
// (-1.25, -0.1070, 70.0360, 41.9650)
I (attempt to) move the path to the origin:
let origin = bezier.bounds.origin
bezier.apply(CGAffineTransform(translationX: -origin.x, y: -origin.y))
print(bezier.bounds)
// (0.0, -2.7755, 68.5542, 41.1379)
As you can see, the x origin component is correct at 0. But, the y component (-2.7755) has gone all kittywumpus. It should be 0, non?
The same thing happens when I perform the transform on the cgPath property.
Does anyone know what kind of circumstances could cause a UIBezierPath/CGPath to behave like this when translated? After reading the Apple docs, it seems that UIBezierPath/CGPath do not hold a transform state; the points are transformed immediately when the transform is called.
Thanks for any help.
Background:
The path data is from Font-Awesome SVGs, via PocketSVG. All files parse, and most draw OK. But a small subset exhibit the above translation issue. I'd like to know if I'm doing something fundamentally wrong or silly before I go ferreting through the SVG parsing, path-building code looking for defects.
BTW I am not drawing at this stage or otherwise dealing with a context; I am building paths prior to drawing.
[edit]
To check that PocketSVG was giving me properly formed data, I passed the same SVG to SwiftSVG, and got the same path data as PocketSVG, and the same result:
let svgURL = Bundle.main.url(forResource: "fa-mars-stroke-h", withExtension: "svg")!
var bezier = UIBezierPath.pathWithSVGURL(svgURL)!
print(bezier.bounds)
// (0.0085, 0.7200, 68.5542, 41.1379)
let origin = bezier.bounds.origin
let translation = CGAffineTransform(translationX: -origin.x, y: -origin.y)
bezier.apply(translation)
print(bezier.bounds)
// (0.0, -2.7755, 68.5542, 41.1379)
Once again, that y component should be 0, but is not. Very weird.
On a whim, I thought I'd try to apply a transformation again. And, it worked!
let translation2 = CGAffineTransform(translationX: -bezier.bounds.origin.x, y: -bezier.bounds.origin.y)
bezier.apply(translation2)
print(bezier.bounds)
// (0.0, 0.0, 68.5542491336633, 41.1379438254997)
Baffling! Am I overlooking something really basic here?
I have tried the same as you and is working for me in Xcode 8.3.2 / iOS 10
I struggled myself with the same problem, I managed to solve it by using the following snippet of code (Swift 5). I tested on an organic bezier shape and it works as expected:
extension CGRect {
var center: CGPoint { return CGPoint(x: midX, y: midY) }
}
extension UIBezierPath {
func center(inRect rect:CGRect) {
let rectCenter = rect.center
let bezierCenter = self.bounds.center
let translation = CGAffineTransform(translationX: rectCenter.x - bezierCenter.x, y: rectCenter.y - bezierCenter.y)
self.apply(translation)
}
}
Usage example:
override func viewDidLoad() {
super.viewDidLoad()
let bezier = UIBezierPath() // replace this with your bezier object
let shape = CAShapeLayer()
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.clear.cgColor
shape.bounds = self.view.bounds
shape.position = self.view.bounds.center
bezier.center(inRect: shape.bounds)
shape.path = bezier.cgPath
self.view.layer.addSublayer(shape)
}
It will display the shape in the center of the screen.

Two UIBezierPaths intersection as a UIBezierPath

I have two UIBezierPaths, one that represents the polygon of part of an image and the other is a path to be drawn on top of it.
I need to find the intersection between them so that only the point that are inside this intersection area will be colored.
Is there a method in UIBezierPath that can find intersection points - or a new path - between two paths ?
I'm not aware of a method for getting a new path that's the intersection of two paths, but you can fill or otherwise draw in the intersection by using the clipping property of each path.
In this example, there are two paths, a square and a circle:
let path1 = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 100, height: 100))
let path2 = UIBezierPath(ovalIn: CGRect(x:50, y:50, width: 100, height: 100))
I make a renderer to draw these, but you could be doing this in drawRect or wherever:
let renderer = UIGraphicsImageRenderer(bounds: CGRect(x: 0, y: 0, width: 200, height: 200))
let image = renderer.image {
context in
// You wouldn't actually stroke the paths, this is just to illustrate where they are
UIColor.gray.setStroke()
path1.stroke()
path2.stroke()
// You would just do this part
path1.addClip()
path2.addClip()
UIColor.red.setFill()
context.fill(context.format.bounds)
}
The resulting image looks like this (I've stroked each path for clarity as indicated in the code comments, in actual fact you'd only do the fill part):
I wrote a UIBezierPath library that will let you cut a given closed path into sub shapes based on an intersecting path. It'll do essentially exactly what you're looking for: https://github.com/adamwulf/ClippingBezier
NSArray<UIBezierPath*>* componentShapes = [shapePath uniqueShapesCreatedFromSlicingWithUnclosedPath:scissorPath];
Alternatively, you can also just find the intersection points:
NSArray* intersections = [scissorPath findIntersectionsWithClosedPath:shapePath andBeginsInside:nil];

Swift Change UIView Image

I am trying to make a basic game for iOS10 using swift 3 and scenekit. In one part of my games code I have a function that adds fishes to the screen, and gives each one a certain tag so i can find them later:
let fish = UIImageView(frame: CGRect(x: 0, y: 0, width: CGFloat(fishsize.0), height: CGFloat(fishsize.1)))
fish.contentMode = .scaleAspectFit
fish.center = CGPoint(x: CGFloat(fishsize.0) * -0.6, y: pos)
fish.animationImages = fImg(Fishes[j].size, front: Fishes[j].front)
fish.animationDuration = 0.7
fish.startAnimating()
fish.tag = j + 200
self.view.insertSubview(fish, belowSubview: big1)
What I would like is to be able to, at a certain point, recall the fish and
Change the images shown, and
Stop the animation.
Is this possible? I've been trying it with var fish = view.viewWithTag(killer+200)! but from this I can't seem to change any image properties of the new variable fish.
Any help would be much appreciated.
Tom
Try to cast the UIView to UIImageView like this.
if let fish = view.viewWithTag(killer+200) as? UIImageView {
//Perform your action on imageView object
}

How to create a ring with 3D effect using Sprite Kit?

I want to create a ring with a 3D effect using Sprite Kit. (SEE IMAGES)
I tried subclassing a SKNode and adding two nodes as children. (SEE CODE)
One node was a complete SKShapeNode ellipse, and the other was half ellipse using SKCropNode with a higher zPosition.
It looks good, but the SKCropNode increases the app CPU usage from 40% to 99%.
Any ideas on how to reduce the SKCropNode performance cost, or any alternative to create the same ring 3D effect?
class RingNode: SKNode {
let size: CGSize
init(size: CGSize, color: SKColor)
{
self.size = size
self.color = color
super.init()
ringPartsSetup()
}
private func ringPartsSetup() {
// LEFT PART (half ellipse)
let ellipseNodeLeft = getEllipseNode()
let leftMask = SKSpriteNode(texture: nil, color: SKColor.blackColor(), size: CGSize(
width: ellipseNodeLeft.frame.size.width/2,
height: ellipseNodeLeft.frame.size.height))
leftMask.anchorPoint = CGPoint(x: 0, y: 0.5)
leftMask.position = CGPoint(x: -ellipseNodeLeft.frame.size.width/2, y: 0)
let leftNode = SKCropNode()
leftNode.addChild(ellipseNodeLeft)
leftNode.maskNode = leftMask
leftNode.zPosition = 10 // Higher zPosition for 3D effect
leftNode.position = CGPoint(x: -leftNode.frame.size.width/4, y: 0)
addChild(leftNode)
// RIGHT PART (complete ellipse)
let rightNode = getEllipseNode()
rightNode.position = CGPoint(x: 0, y: 0)
rightNode.zPosition = 5
addChild(rightNode)
}
private func getEllipseNode() -> SKShapeNode {
let ellipseNode = SKShapeNode(ellipseOfSize: CGSize(
width: size.width,
height: size.height))
ellipseNode.strokeColor = SKColor.blackColor()
ellipseNode.lineWidth = 5
return ellipseNode
}
}
You've got the right idea with your two-layer approach and the half-slips on top. But instead of using a shape node inside a crop node, why not just use a shape node whose path is a half-ellipse? Create one using either CGPath or UIBezierPath API — use a circular arc with a transform to make it elliptical — then create your SKShapeNode from that path.
You may try converting your SKShapeNode to an SKSpriteNode. You can use SKView textureFromNode: (but we aware of issues with scale that require you to use it only after the node has been added to the view and at least one update cycle has run), or from scratch using an image (created programatically with a CGBitmapContext, of course).

Resources