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.
Related
I'm trying to achive this layout
[LAYOUT TO ACHIEVE]
and right now I have gradient part done, blur part done, and also bezier path mostly done. My question is that I can't replace TAIL from bottom right part to top left part (to be similar like in the picture).
[LAYOUT ACHIEVED]
My Code I wrote for acheiving:
public final class BubbleView: UIView {
public var configuration: Configuration = Configuration.defaultConfiguration {
didSet {
setNeedsLayout()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
super.backgroundColor = .clear
}
public override func draw(_ rect: CGRect) {
let hasBlurSubview = subviews.compactMap({$0}).contains(where: {$0 is UIVisualEffectView })
guard !hasBlurSubview else {
return
}
let lineWidth = configuration.lineWidth
let bezierPath = UIBezierPath()
bezierPath.lineWidth = lineWidth
let bottom = rect.height - lineWidth
let right = rect.width - lineWidth
let top = 0.0
let left = 0.0
bezierPath.move(to: CGPoint(x: right - 22, y: bottom))
bezierPath.addLine(to: CGPoint(x: 17 + lineWidth, y: bottom))
bezierPath.addCurve(to: CGPoint(x: left, y: bottom - 18), controlPoint1: CGPoint(x: 7.61 + lineWidth, y: bottom), controlPoint2: CGPoint(x: left, y: bottom - 7.61))
bezierPath.addLine(to: CGPoint(x: left, y: 17 + lineWidth))
bezierPath.addCurve(to: CGPoint(x: 17 + borderWidth, y: top), controlPoint1: CGPoint(x: left, y: 7.61 + lineWidth), controlPoint2: CGPoint(x: 7.61 + lineWidth, y: top))
bezierPath.addLine(to: CGPoint(x: right - 21, y: top))
bezierPath.addCurve(to: CGPoint(x: right - 4, y: 17 + lineWidth), controlPoint1: CGPoint(x: right - 11.61, y: top), controlPoint2: CGPoint(x: right - 4, y: 7.61 + lineWidth))
bezierPath.addLine(to: CGPoint(x: right - 4, y: bottom - 11))
bezierPath.addCurve(to: CGPoint(x: right, y: bottom), controlPoint1: CGPoint(x: right - 4, y: bottom - 1), controlPoint2: CGPoint(x: right, y: bottom))
bezierPath.addLine(to: CGPoint(x: right + 0.05, y: bottom - 0.01))
bezierPath.addCurve(to: CGPoint(x: right - 11.04, y: bottom - 4.04), controlPoint1: CGPoint(x: right - 4.07, y: bottom + 0.43), controlPoint2: CGPoint(x: right - 8.16, y: bottom - 1.06))
bezierPath.addCurve(to: CGPoint(x: right - 22, y: bottom), controlPoint1: CGPoint(x: right - 16, y: bottom), controlPoint2: CGPoint(x: right - 19, y: bottom))
bezierPath.close()
let blurEffect = UIVisualEffectView(effect: UIBlurEffect(style: .light))
blurEffect.alpha = configuration.blurAlpha
blurEffect.frame = bezierPath.bounds
let mask = CAShapeLayer()
mask.path = bezierPath.cgPath
blurEffect.layer.mask = mask
insertSubview(blurEffect, at: 0)
let gradient = CAGradientLayer()
gradient.frame = CGRect(origin: CGPoint.zero, size: size)
gradient.colors = configuration.borderGradientColors.map({$0.cgColor})
gradient.startPoint = configuration.borderGradientStartPoint
gradient.endPoint = configuration.borderGradientEndPoint
let shape = CAShapeLayer()
shape.lineWidth = lineWidth
shape.path = bezierPath.cgPath
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.clear.cgColor
gradient.mask = shape
blurEffect.layer.addSublayer(gradient)
}
}
public class Configuration {
static var defaultConfiguration: Configuration {
return .init(
lineWidth: 2.0,
blurAlpha: 0.95,
borderGradientColors: [Assets.Colors.white05.color, Assets.Colors.white01.color],
borderGradientStartPoint: CGPoint(x: 0.0, y: 0.5),
borderGradientEndPoint: CGPoint(x: 1.0, y: 0.5)
)
}
var lineWidth: CGFloat
var blurAlpha: CGFloat
var borderGradientColors: [UIColor]
var borderGradientStartPoint: CGPoint
var borderGradientEndPoint: CGPoint
init(
lineWidth: CGFloat,
blurAlpha: CGFloat,
borderGradientColors: [UIColor],
borderGradientStartPoint: CGPoint,
borderGradientEndPoint: CGPoint
) {
self.lineWidth = lineWidth
self.blurAlpha = blurAlpha
self.borderGradientColors = borderGradientColors
self.borderGradientStartPoint = borderGradientStartPoint
self.borderGradientEndPoint = borderGradientEndPoint
}
}
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))
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 think I am having kind of a blackout but I am not able to simply work with BezierPaths within Swift. Want I want to do is something like on the screenshot:
So it's just something like a tachometer with three colours:
What I do have is the following:
diagram.layer.borderWidth = 4.0
diagram.layer.borderColor = UIColor.black.cgColor
let pi = CGFloat(Float.pi)
let start:CGFloat = 0.0
let end :CGFloat = pi
let radius : CGFloat = diagram.frame.width/3
let middle = CGPoint(x: diagram.frame.size.width/2, y:diagram.frame.size.height/2)
let path: UIBezierPath = UIBezierPath();
path.addArc(
withCenter: CGPoint(x: middle.x, y: middle.y),
radius: radius,
startAngle: start,
endAngle: end,
clockwise: false
)
let layer = CAShapeLayer()
layer.fillColor = UIColor.clear.cgColor
layer.strokeColor = UIColor.red.cgColor
layer.path = path.cgPath
diagram.layer.addSublayer(layer)
That is just the first arc, but it makes me crazy. It is not centred, which is odd because I am using the size of my Uiview for it. My UIView is obviously called "diagram".
Does anyone have a great example with Swift how to do something like on the screenshot?
Thanks for reading!
=======
This is just code relating to the answer of andykkt
func drawCompareGauge(frame: CGRect = CGRect(x: 0, y: 0, width: 180, height: 92), leftPercentage: CGFloat = 0.5) {
//// General Declarations
let context = UIGraphicsGetCurrentContext()
//// Color Declarations
let leftColor = UIColor(red: 0.278, green: 0.757, blue: 0.749, alpha: 1.000)
let rightColor = UIColor(red: 0.557, green: 0.400, blue: 0.867, alpha: 1.000)
//// Variable Declarations
let leftDashed: CGFloat = 247 * leftPercentage
//// rightRing Drawing
let rightRingPath = UIBezierPath()
rightRingPath.move(to: CGPoint(x: frame.minX + 11.5, y: frame.minY + 91.5))
rightRingPath.addCurve(to: CGPoint(x: frame.minX + 42.73, y: frame.minY + 28.82), controlPoint1: CGPoint(x: frame.minX + 11.5, y: frame.minY + 65.89), controlPoint2: CGPoint(x: frame.minX + 23.76, y: frame.minY + 43.15))
rightRingPath.addCurve(to: CGPoint(x: frame.minX + 90, y: frame.minY + 13), controlPoint1: CGPoint(x: frame.minX + 55.88, y: frame.minY + 18.89), controlPoint2: CGPoint(x: frame.minX + 72.25, y: frame.minY + 13))
rightRingPath.addCurve(to: CGPoint(x: frame.minX + 168.5, y: frame.minY + 91.5), controlPoint1: CGPoint(x: frame.minX + 133.35, y: frame.minY + 13), controlPoint2: CGPoint(x: frame.minX + 168.5, y: frame.minY + 48.15))
rightColor.setStroke()
rightRingPath.lineWidth = 23
context?.saveGState()
//CGContextSetLineDash(context, 0, [247, 247], 2)
//context!.setLineDash(phase: <#T##CGFloat#>, lengths: <#T##[CGFloat]#>)
context?.setLineDash(phase: 0, lengths: [247,247])
//problem
rightRingPath.stroke()
context?.restoreGState()
//// leftRing Drawing
let leftRingPath = UIBezierPath()
leftRingPath.move(to: CGPoint(x: frame.minX + 11.5, y: frame.minY + 91.5))
leftRingPath.addCurve(to: CGPoint(x: frame.minX + 42.73, y: frame.minY + 28.82), controlPoint1: CGPoint(x: frame.minX + 11.5, y: frame.minY + 65.89), controlPoint2: CGPoint(x: frame.minX + 23.76, y: frame.minY + 43.15))
leftRingPath.addCurve(to: CGPoint(x: frame.minX + 90, y: frame.minY + 13), controlPoint1: CGPoint(x: frame.minX + 55.88, y: frame.minY + 18.89), controlPoint2: CGPoint(x: frame.minX + 72.25, y: frame.minY + 13))
leftRingPath.addCurve(to: CGPoint(x: frame.minX + 168.5, y: frame.minY + 91.5), controlPoint1: CGPoint(x: frame.minX + 133.35, y: frame.minY + 13), controlPoint2: CGPoint(x: frame.minX + 168.5, y: frame.minY + 48.15))
leftColor.setStroke()
leftRingPath.lineWidth = 23
context?.saveGState()
//context!.setLineDash(context, 0, [leftDashed, 247], 2)
//Problem
context?.setLineDash(phase: 0, lengths: [leftDashed,247])
leftRingPath.stroke()
context?.restoreGState()
//// Text Drawing
let textRect = CGRect(x: frame.minX + 7, y: frame.minY + 74, width: 11, height: 21)
let textTextContent = NSString(string: "L")
let textStyle = NSMutableParagraphStyle()
textStyle.alignment = .left
let textFontAttributes = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: UIFont.systemFontSize), NSAttributedStringKey.foregroundColor: UIColor.white, NSAttributedStringKey.paragraphStyle: textStyle]
let textTextHeight: CGFloat = textTextContent.boundingRect(with: CGSize(width: textRect.width, height: CGFloat.infinity), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: textFontAttributes, context: nil).size.height
context?.saveGState()
context?.clip(to: textRect)
textTextContent.draw(in: CGRect(x: textRect.minX, y: textRect.minY + (textRect.height - textTextHeight) / 2, width: textRect.width, height: textTextHeight), withAttributes: textFontAttributes)
context?.restoreGState()
//// Text 2 Drawing
let text2Rect = CGRect(x: frame.minX + 164, y: frame.minY + 76, width: 11, height: 17)
let text2TextContent = NSString(string: "R")
let text2Style = NSMutableParagraphStyle()
text2Style.alignment = .left
let text2FontAttributes = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: UIFont.systemFontSize), NSAttributedStringKey.foregroundColor: UIColor.white, NSAttributedStringKey.paragraphStyle: text2Style]
let text2TextHeight: CGFloat = text2TextContent.boundingRect(with: CGSize(width: text2Rect.width, height: CGFloat.infinity), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: text2FontAttributes, context: nil).size.height
context?.saveGState()
context?.clip(to: text2Rect)
//CGContextClipToRect(context!, text2Rect)
text2TextContent.draw(in: CGRect(x: text2Rect.minX, y: text2Rect.minY + (text2Rect.height - text2TextHeight) / 2, width: text2Rect.width, height: text2TextHeight), withAttributes: text2FontAttributes)
context?.restoreGState()
}
I draw it from "PaintCode" as an example, you can use paintcode to draw how you want it to be.
#IBDesignable class GaugeView: UIView {
#IBInspectable var leftPercentage: CGFloat = 0.708 {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var otherPercentage: CGFloat = 0.206 {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
// Drawing code
drawCompareGauge(frame: bounds, leftPercentage: leftPercentage, otherPercentage: otherPercentage)
}
func drawCompareGauge(frame: CGRect = CGRect(x: 0, y: 0, width: 180, height: 92), leftPercentage: CGFloat = 0.708, otherPercentage: CGFloat = 0.206) {
//// General Declarations
guard let context = UIGraphicsGetCurrentContext() else { return }
//// Color Declarations
let leftColor = UIColor(red: 0.278, green: 0.757, blue: 0.749, alpha: 1.000)
let rightColor = UIColor(red: 0.557, green: 0.400, blue: 0.867, alpha: 1.000)
let otherColor = UIColor(red: 0.800, green: 0.320, blue: 0.320, alpha: 1.000)
//// Variable Declarations
let leftDashed: CGFloat = 247 * leftPercentage
let otherDashed: CGFloat = 247 * otherPercentage
//// rightRing Drawing
let rightRingPath = UIBezierPath()
rightRingPath.move(to: CGPoint(x: frame.minX + 11.5, y: frame.minY + 91.5))
rightRingPath.addCurve(to: CGPoint(x: frame.minX + 42.73, y: frame.minY + 28.82), controlPoint1: CGPoint(x: frame.minX + 11.5, y: frame.minY + 65.89), controlPoint2: CGPoint(x: frame.minX + 23.76, y: frame.minY + 43.15))
rightRingPath.addCurve(to: CGPoint(x: frame.minX + 90, y: frame.minY + 13), controlPoint1: CGPoint(x: frame.minX + 55.88, y: frame.minY + 18.89), controlPoint2: CGPoint(x: frame.minX + 72.25, y: frame.minY + 13))
rightRingPath.addCurve(to: CGPoint(x: frame.minX + 168.5, y: frame.minY + 91.5), controlPoint1: CGPoint(x: frame.minX + 133.35, y: frame.minY + 13), controlPoint2: CGPoint(x: frame.minX + 168.5, y: frame.minY + 48.15))
rightColor.setStroke()
rightRingPath.lineWidth = 23
context.saveGState()
context.setLineDash(phase: 0, lengths: [247, 247])
rightRingPath.stroke()
context.restoreGState()
//// leftRing Drawing
let leftRingPath = UIBezierPath()
leftRingPath.move(to: CGPoint(x: frame.minX + 11.5, y: frame.minY + 91.5))
leftRingPath.addCurve(to: CGPoint(x: frame.minX + 42.73, y: frame.minY + 28.82), controlPoint1: CGPoint(x: frame.minX + 11.5, y: frame.minY + 65.89), controlPoint2: CGPoint(x: frame.minX + 23.76, y: frame.minY + 43.15))
leftRingPath.addCurve(to: CGPoint(x: frame.minX + 90, y: frame.minY + 13), controlPoint1: CGPoint(x: frame.minX + 55.88, y: frame.minY + 18.89), controlPoint2: CGPoint(x: frame.minX + 72.25, y: frame.minY + 13))
leftRingPath.addCurve(to: CGPoint(x: frame.minX + 168.5, y: frame.minY + 91.5), controlPoint1: CGPoint(x: frame.minX + 133.35, y: frame.minY + 13), controlPoint2: CGPoint(x: frame.minX + 168.5, y: frame.minY + 48.15))
leftColor.setStroke()
leftRingPath.lineWidth = 23
context.saveGState()
context.setLineDash(phase: 0, lengths: [leftDashed, 247])
leftRingPath.stroke()
context.restoreGState()
//// leftRing 2 Drawing
let leftRing2Path = UIBezierPath()
leftRing2Path.move(to: CGPoint(x: frame.minX + 11.5, y: frame.minY + 91.5))
leftRing2Path.addCurve(to: CGPoint(x: frame.minX + 42.73, y: frame.minY + 28.82), controlPoint1: CGPoint(x: frame.minX + 11.5, y: frame.minY + 65.89), controlPoint2: CGPoint(x: frame.minX + 23.76, y: frame.minY + 43.15))
leftRing2Path.addCurve(to: CGPoint(x: frame.minX + 90, y: frame.minY + 13), controlPoint1: CGPoint(x: frame.minX + 55.88, y: frame.minY + 18.89), controlPoint2: CGPoint(x: frame.minX + 72.25, y: frame.minY + 13))
leftRing2Path.addCurve(to: CGPoint(x: frame.minX + 168.5, y: frame.minY + 91.5), controlPoint1: CGPoint(x: frame.minX + 133.35, y: frame.minY + 13), controlPoint2: CGPoint(x: frame.minX + 168.5, y: frame.minY + 48.15))
otherColor.setStroke()
leftRing2Path.lineWidth = 23
context.saveGState()
context.setLineDash(phase: 0, lengths: [otherDashed, 247])
leftRing2Path.stroke()
context.restoreGState()
}
I have changed the code to class and support latest xcode & swift. You can just place a this view on view controller to see the result and change leftPercentage animate gauge.
==============================
I have updated the code to display 3 different value as gauge, I hope this help you to figure it out. Please see below gif for this control in action.
After spending a whole day trying to work out how to do it I have finally worked out a way of having non-rectangular buttons.
All three buttons at the bottom are on one button. The hitTest then works out which UIBezierPath the click was within then calls a function.
import UIKit
class ViewController: UIViewController {
let path = UIBezierPath()
let path2 = UIBezierPath()
let path3 = UIBezierPath()
let path4 = UIBezierPath()
#IBAction func clicked(_ sender: Any) {
print("Clicked")
}
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
drawShape()
let gesture = UITapGestureRecognizer(target: self, action: #selector (self.checkAction(sender:)))
self.button.addGestureRecognizer(gesture)
}
func drawShape(){
let buttonWidth = button.frame.width
let buttonHeight = buttonWidth * 1.23
path.move(to: CGPoint(x: 0, y: buttonHeight*0.85))
path.addLine(to: CGPoint(x: 0, y: buttonHeight))
path.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight))
path.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.68))
path.close()
path2.move(to: CGPoint(x: 0, y: buttonHeight*0.68))
path2.addLine(to: CGPoint(x: 0, y: buttonHeight*0.85))
path2.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.68))
path2.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.35))
path2.close()
path3.move(to: CGPoint(x: 0, y: buttonHeight*0.50))
path3.addLine(to: CGPoint(x: 0, y: buttonHeight*0.68))
path3.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.35))
path3.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.06))
path3.close()
path4.move(to: CGPoint(x: 0, y: buttonHeight*0.45))
path4.addLine(to: CGPoint(x: 0, y: buttonHeight*0.50))
path4.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.06))
path4.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.01))
path4.close()
}
func checkAction(sender : UITapGestureRecognizer) {
let location = sender.location(in: button)
print(location)
hitTest(tapLocation: location)
}
public func hitTest(tapLocation:CGPoint){
if path.contains(tapLocation){
print("Button3")
}
if path2.contains(tapLocation){
print("Button2")
}
if path3.contains(tapLocation){
print("Button1")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Then in the file linked to the button:
import UIKit
#IBDesignable
class PushButtonView: UIButton {
override func draw(_ rect: CGRect) {
let buttonWidth = self.frame.width
let buttonHeight = buttonWidth * 1.23
let color1 = hexStringToUIColor(hex: "#e0dfd5")
let color2 = hexStringToUIColor(hex: "#ef6461")
let color3 = hexStringToUIColor(hex: "#e4b363")
let color4 = hexStringToUIColor(hex: "#313638")
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: buttonHeight*0.85))
path.addLine(to: CGPoint(x: 0, y: buttonHeight))
path.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight))
path.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.68))
path.close()
color4.setFill()
path.fill()
let path2 = UIBezierPath()
path2.move(to: CGPoint(x: 0, y: buttonHeight*0.68))
path2.addLine(to: CGPoint(x: 0, y: buttonHeight*0.85))
path2.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.68))
path2.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.35))
path2.close()
color3.setFill()
path2.fill()
let path3 = UIBezierPath()
path3.move(to: CGPoint(x: 0, y: buttonHeight*0.50))
path3.addLine(to: CGPoint(x: 0, y: buttonHeight*0.68))
path3.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.35))
path3.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.06))
path3.close()
color2.setFill()
path3.fill()
let path4 = UIBezierPath()
path4.move(to: CGPoint(x: 0, y: buttonHeight*0.45))
path4.addLine(to: CGPoint(x: 0, y: buttonHeight*0.50))
path4.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.06))
path4.addLine(to: CGPoint(x: buttonWidth, y: buttonHeight*0.01))
path4.close()
color1.setFill()
path4.fill()
}
func hexStringToUIColor (hex:String) -> UIColor {
var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
if (cString.hasPrefix("#")) {
cString.remove(at: cString.startIndex)
}
if ((cString.characters.count) != 6) {
return UIColor.gray
}
var rgbValue:UInt32 = 0
Scanner(string: cString).scanHexInt32(&rgbValue)
return UIColor(
red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
alpha: CGFloat(1.0)
)
}
}
Everything works! It may not be the best way of doing it but after 5 hours of trying different things this is whats worked best. However I am now stuck with the problem of having no idea how to manipulate those shapes I have drawn in the external file. I am sure that this has been asked and answered before but I literally have no idea was to search for.
For example if it was javascript I would assume a 'path' drawn within the object 'button' would be accessed by self.button.path (or something similar), however, my understanding is Swift3 doesn't work like that. Am I correct? So if I wanted to change the backgroundColor of the NOTES button/shape with an animate how would I get this effect:
UIView.animate(withDuration: 1, animations: {
self.button.path.color = UIColor.red
}, completion: { finished in
if(finished){
//callFunction()
}
})
Is this possible, or once a path is drawn is it not a changeable object and has to be deleted and redrawn?