Custom figure doesn't display in Today Extension - swift

I make progress circle in today extension. I create additional class for it. I can see it in storyboard with help of IBDesignable. But circle doesn't appear in today extension on real device ( iPhone 5s, iPad 3) or simulator. How can I fix it?
import UIKit
#IBDesignable
class CPUView: UIView {
// MARK: - colors
#IBInspectable var firstColor: UIColor = UIColor.blackColor()
#IBInspectable var secondColor: UIColor = UIColor.greenColor()
#IBInspectable var thirdColor: UIColor = UIColor.yellowColor()
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
self.addCircle(10, capRadius: 0.0)
}
func addOval(lineWidth: CGFloat, path: CGPath, strokeColor: UIColor, fillColor: UIColor, strokeStart: CGFloat, strokeEnd: CGFloat) {
let shape = CAShapeLayer()
shape.lineWidth = lineWidth
shape.path = path
shape.strokeColor = strokeColor.CGColor
shape.fillColor = fillColor.CGColor
shape.strokeStart = strokeStart
shape.strokeEnd = strokeEnd
layer.addSublayer(shape)
}
func addCircle(arcRadius: CGFloat, capRadius: CGFloat) {
let X = CGRectGetMidX(self.bounds)
let Y = CGRectGetMidY(self.bounds)
let firstPathCircle = UIBezierPath(ovalInRect: CGRectMake(X - arcRadius/2, Y - arcRadius/2, arcRadius, arcRadius)).CGPath
self.addOval(0.1, path: firstPathCircle, strokeColor: UIColor.redColor(), fillColor: UIColor.yellowColor(), strokeStart: 0.0, strokeEnd: 0.5)
}
}
UPDATE
I tried to write the code in drawRect but I got the same result
override func drawRect(rect: CGRect) {
// Drawing code
let x = CGRectGetMidX(self.bounds)
let y = CGRectGetMidY(self.bounds)
let radius = CGFloat()
let path = UIBezierPath(ovalInRect: CGRectMake(x - radius/2, y - radius, 100, 100)).CGPath
let shape = CAShapeLayer()
shape.lineWidth = 0.0
shape.fillColor = UIColor.greenColor().CGColor
shape.path = path
layer.addSublayer(shape)
}

I wrote the following code and it works for me
override func viewDidLoad() {
super.viewDidLoad()
self.preferredContentSize = CGSize(width: self.view.frame.width, height: 200.0)
}

Related

The background mask cuts off part of the foreground layer

Recently ran into a problem in one of my tasks. I tried to solve this problem for two days. Finally I came here.
I have a custom progress view. Don't look at the colors, they are so awful just for the debugging process to see better.
[the look I have now] 1
[the look I want to have] 2
As you can see, there is a red dot at the end of the progress layer, but it is cut at the top and bottom ...
And it shouldn't be like that ...
Also I will leave here all the code I have, maybe someone
can help me.
Thank you all in advance for your time.
import UIKit
class PlainProgressBar: UIView {
//MARK: - Private
//colour of progress layer : default -> white
private var color: UIColor = .white {
didSet { setNeedsDisplay() }
}
// progress numerical value
private var progress: CGFloat = 0.0 {
didSet { setNeedsDisplay() }
}
private var height : CGFloat = 0.0 {
didSet{ setNeedsDisplay() }
}
private let progressLayer = CALayer()
private let dotLayer = CALayer()
private let backgroundMask = CAShapeLayer()
private func setupLayers() {
layer.addSublayer(progressLayer)
layer.addSublayer(dotLayer)
}
//MARK:- Public
// set color for progress layer
func setColor(progressLayer color: UIColor){
self.color = color
}
func set(progress: CGFloat){
if progress > 1.0{
self.progress = 1.0
}
if progress < 0.0{
self.progress = 0.0
}
if progress >= 0.0 && progress <= 1.0{
self.progress = progress
}
}
func set(height: CGFloat){
self.height = height
}
override init(frame: CGRect) {
super.init(frame: frame)
setupLayers()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupLayers()
}
//Main draw function for view
override func draw(_ rect: CGRect) {
backgroundMask.path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: CGSize(width: rect.width, height: height)), cornerRadius: rect.height * 0.25).cgPath
layer.mask = backgroundMask
let dotOnProgressLayer = CGRect(origin: .zero, size: CGSize(width: 10.0, height: 10.0))
let progressRect = CGRect(origin: .zero, size: CGSize(width: rect.width * progress, height: height))
progressLayer.frame = progressRect
progressLayer.backgroundColor = color.cgColor
dotLayer.frame = dotOnProgressLayer
dotLayer.cornerRadius = dotLayer.bounds.width * 0.5
dotLayer.backgroundColor = #colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1).cgColor
dotLayer.position = CGPoint(x: progressLayer.frame.width ,y: progressLayer.frame.height / 2)
}
}
progressView.set(height: 4.5)
progressView.setColor(progressLayer: UIColor(ciColor: .green))
You are masking the views layer with
layer.mask = backgroundMask
So anything outside the mask will no be drawn.

How to generate a custom UIView that is above a UIImageView that has a circular hole punched in the middle that can see through to the UIImageView?

I am trying to punch a circular hole through a UIView that is above a UIImageView, whereby the hole can see through to the image below (I would like to interact with this image through the hole with a GestureRecognizer later). I have 2 problems, I cannot get the circular hole to centre to the middle of the UIImageView (it is currently centred to the top left of the screen), and that the effect that I am getting is the opposite to what i am trying to achieve (Everything outside of the circle is visible). Below is my code. Please can someone advise?
Result:
class UploadProfileImageViewController: UIViewController {
var scrollView: ReadyToUseScrollView!
let container: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let imgView: UIImageView = {
let imgView = UIImageView()
imgView.backgroundColor = UIColor.black
imgView.contentMode = .scaleAspectFit
imgView.image = UIImage.init(named: "soldier")!
imgView.translatesAutoresizingMaskIntoConstraints = false
return imgView
}()
var overlay: UIView!
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
private func setup(){
view.backgroundColor = UIColor.white
setupViews()
}
override func viewDidLayoutSubviews() {
overlay.center = imgView.center
print("imgView.center: \(imgView.center)")
overlay.layer.layoutIfNeeded() // I have also tried view.layoutIfNeeded()
}
private func setupViews(){
let s = view.safeAreaLayoutGuide
view.addSubview(imgView)
imgView.topAnchor.constraint(equalTo: s.topAnchor).isActive = true
imgView.leadingAnchor.constraint(equalTo: s.leadingAnchor).isActive = true
imgView.trailingAnchor.constraint(equalTo: s.trailingAnchor).isActive = true
imgView.heightAnchor.constraint(equalTo: s.heightAnchor, multiplier: 0.7).isActive = true
overlay = Overlay.init(frame: .zero, center: imgView.center)
print("setup.imgView.center: \(imgView.center)")
view.addSubview(overlay)
overlay.translatesAutoresizingMaskIntoConstraints = false
overlay.topAnchor.constraint(equalTo: s.topAnchor).isActive = true
overlay.leadingAnchor.constraint(equalTo: s.leadingAnchor).isActive = true
overlay.trailingAnchor.constraint(equalTo: s.trailingAnchor).isActive = true
overlay.bottomAnchor.constraint(equalTo: imgView.bottomAnchor).isActive = true
}
private func deg2rad( number: Double) -> CGFloat{
let rad = number * .pi / 180
return CGFloat.init(rad)
}
}
class Overlay: UIView{
var path: UIBezierPath!
var viewCenter: CGPoint?
init(frame: CGRect, center: CGPoint) {
super.init(frame: frame)
self.viewCenter = center
setup()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup(){
backgroundColor = UIColor.black.withAlphaComponent(0.8)
guard let path = createCirclePath() else {return}
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.fillRule = CAShapeLayerFillRule.evenOdd
self.layer.mask = shapeLayer
}
private func createCirclePath() -> UIBezierPath?{
guard let center = self.viewCenter else{return nil}
let circlePath = UIBezierPath()
circlePath.addArc(withCenter: center, radius: 200, startAngle: 0, endAngle: deg2rad(number: 360), clockwise: true)
return circlePath
}
private func deg2rad( number: Double) -> CGFloat{
let rad = number * .pi / 180
return CGFloat.init(rad)
}
}
CONSOLE:
setup.imgView.center: (0.0, 0.0)
imgView.center: (207.0, 359.0)
Try getting rid of the overlay you have and instead add the below UIView. It's basically a circular UIView with a giant black border, but it takes up the whole screen so the user can't tell. FYI, you need to use .frame to position items on the screen. The below puts the circle in the center of the screen. If you want the center of the image, replace self.view.frame with self. imgView.frame... Play around with circleSize and borderSize until you get the circle size you want.
let circle = UIView()
let circleSize: CGFloat = self.view.frame.height * 2 //must be bigger than the screen
let x = (self.view.frame.width / 2) - (circleSize / 2)
let y = (self.view.frame.height / 2) - (circleSize / 2)
circle.frame = CGRect(x: x, y: y, width: circleSize, height: circleSize)
let borderSize = (circleSize / 2) * 0.9 //the size of the inner circle will be circleSize - borderSize
circle.backgroundColor = .clear
circle.layer.cornerRadius = circle.frame.height / 2
circle.layer.borderColor = UIColor.black.cgColor
circle.layer.borderWidth = borderSize
view.addSubview(circle)

UIProgressView setProgress issue

I've encountered an issue using a UIProgressView where low values (1% - about 10%) look off. You can see with the example above that 97% looks accurate while 2% does not.
Here's the code for setting colors:
self.progressView.trackTintColor = UIColor.green.withAlphaComponent(0.3)
self.progressView.tintColor = UIColor.green.withAlphaComponent(1.0)
But, if I comment out the trackTintColor or the tintColor, then the 2% looks correct. Why when using these together does it cause this issue? Just an Xcode bug? Has anyone resolved this before?
I've experienced the same issue in my project. For me it's fixed by using progressTintColor instead of tintColor.
progressView.progressTintColor = UIColor.green.withAlphaComponent(1.0)
progressView.trackTintColor = UIColor.green.withAlphaComponent(0.3)
you need to create color image
SWIFT 3 Example:
class ViewController: UIViewController {
#IBOutlet weak var progressView: UIProgressView!
#IBAction func lessButton(_ sender: UIButton) {
let percentage = 20
let invertedValue = Float(100 - percentage) / 100
progressView.setProgress(invertedValue, animated: true)
}
#IBAction func moreButton(_ sender: UIButton) {
let percentage = 80
let invertedValue = Float(100 - percentage) / 100
progressView.setProgress(invertedValue, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
//create gradient view the size of the progress view
let gradientView = GradientView(frame: progressView.bounds)
//convert gradient view to image , flip horizontally and assign as the track image
progressView.trackImage = UIImage(view: gradientView).withHorizontallyFlippedOrientation()
//invert the progress view
progressView.transform = CGAffineTransform(scaleX: -1.0, y: -1.0)
progressView.progressTintColor = UIColor.black
progressView.progress = 1
}
}
extension UIImage{
convenience init(view: UIView) {
UIGraphicsBeginImageContext(view.frame.size)
view.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.init(cgImage: (image?.cgImage)!)
}
}
#IBDesignable
class GradientView: UIView {
private var gradientLayer = CAGradientLayer()
private var vertical: Bool = false
override func draw(_ rect: CGRect) {
super.draw(rect)
// Drawing code
//fill view with gradient layer
gradientLayer.frame = self.bounds
//style and insert layer if not already inserted
if gradientLayer.superlayer == nil {
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = vertical ? CGPoint(x: 0, y: 1) : CGPoint(x: 1, y: 0)
gradientLayer.colors = [UIColor.green.cgColor, UIColor.red.cgColor]
gradientLayer.locations = [0.0, 1.0]
self.layer.insertSublayer(gradientLayer, at: 0)
}
}
}

Drawing an NSBezierPath Shape with NSView Subclass

I'm trying to draw a shape that is created with NSBezierPath on an NSView canvas. I've created a subclass of NSView.
// NSView //
import Cocoa
class DisplayView: NSView {
var path: NSBezierPath
var fillColor: NSColor
var strokeColor: NSColor
var weight: CGFloat
init(frame: CGRect, path: NSBezierPath, fillColor: NSColor, strokeColor: NSColor, weight: CGFloat){
self.path = path
self.fillColor = fillColor
self.strokeColor = strokeColor
self.weight = weight
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ dirtyRect: NSRect) {
path.lineWidth = weight
fillColor.set()
path.fill()
strokeColor.set()
path.stroke()
}
}
// NSViewController //
import Cocoa
class ViewController: NSViewController {
// MARK: - IBOutlet
#IBOutlet weak var canvasView: NSView!
override func viewDidLoad() {
super.viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
let path = Shapes.circle(maxSize: 100) // a path from a separate class
let rect = CGRect(x: 20, y: 20, width: 200, height: 200)
let pathView = DisplayView(frame: rect, path: path, fillColor: NSColor.green, strokeColor: NSColor.white, weight: 6.0)
canvasView.addSubview(pathView)
}
}
And I get the following result. How come the edges are broken by half the line weight on two sides? The path object is only a size of 100 pts x 100 pts. Thanks.
UPDATE
The following is the code for making a path object.
class Shapes {
static func circle(maxSize: CGFloat) -> NSBezierPath {
let oval = NSBezierPath.init(ovalIn: CGRect(x: 0.0 * maxSize, y: 0.0 * maxSize, width: 1.0 * maxSize, height: 1.0 * maxSize))
oval.close()
return oval
}
}
You have created your bezier path with an origin of 0,0. As a result, the thicker border gets clipped by the view it is rendered in (your DisplayView class).
You need to create your path so the origin is (weight / 2 + 1) instead of 0.
Or you can apply a translate transform to the graphics context and shift the origin by (weight / 2 + 1.
override func draw(_ dirtyRect: NSRect) {
let ctx = NSGraphicsContext.current!.cgContext
ctx.saveGState()
let offset = weight / 2 + 1
ctx.translateBy(x: offset, y: offset)
path.lineWidth = weight
fillColor.set()
path.fill()
strokeColor.set()
path.stroke()
ctx.restoreGState()
}

How do I create a circle with CALayer?

I have the code below tested, but when I give it constraints it becomes a little small circle:
override func drawRect(rect: CGRect) {
var path = UIBezierPath(ovalInRect: rect)
fillColor.setFill()
path.fill()
//set up the width and height variables
//for the horizontal stroke
let plusHeight:CGFloat = 300.0
let plusWidth:CGFloat = 450.0
//create the path
var plusPath = UIBezierPath()
//set the path's line width to the height of the stroke
plusPath.lineWidth = plusHeight
//move the initial point of the path
//to the start of the horizontal stroke
plusPath.moveToPoint(CGPoint(
x:self.bounds.width/2 - plusWidth/2 + 0.5,
y:self.bounds.height/2 + 0.5))
//add a point to the path at the end of the stroke
plusPath.addLineToPoint(CGPoint(
x:self.bounds.width/2 + plusWidth/2 + 0.5,
y:self.bounds.height/2 + 0.5))
}
Change radius and fillColor as you want. :)
import Foundation
import UIKit
class CircleLayerView: UIView {
var circleLayer: CAShapeLayer!
override func draw(_ rect: CGRect) {
super.draw(rect)
if circleLayer == nil {
circleLayer = CAShapeLayer()
let radius: CGFloat = 150.0
circleLayer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 2.0 * radius, height: 2.0 * radius), cornerRadius: radius).cgPath
circleLayer.position = CGPoint(x: self.frame.midX - radius, y: self.frame.midY - radius)
circleLayer.fillColor = UIColor.blue.cgColor
self.layer.addSublayer(circleLayer)
}
}
}
The rect being passed into drawRect is the area that needs to be updated, not the size of the drawing. In your case, you would probably just ignore the rect being passed in and set the circle to the size you want.
//// Oval Drawing
var ovalPath = UIBezierPath(ovalInRect: CGRectMake(0, 0, 300, 300))
UIColor.whiteColor().setFill()
ovalPath.fill()
The only correct way to do it:
private lazy var whiteCoin: CAShapeLayer = {
let c = CAShapeLayer()
c.path = UIBezierPath(ovalIn: bounds).cgPath
c.fillColor = UIColor.white.cgColor
layer.addSublayer(c)
return c
}()
That literally makes a layer, as you wanted.
In iOS you must correctly resize any layers you are in charge of, when the view is resized/redrawn.
How do you do that? It is the very raison d'etre of layoutSubviews.
class ProperExample: UIView {
open override func layoutSubviews() {
super.layoutSubviews()
whiteCoin.frame = bounds
}
}
It's that simple.
class ProperExample: UIView {
open override func layoutSubviews() {
super.layoutSubviews()
whiteCoin.frame = bounds
}
private lazy var whiteCoin: CAShapeLayer = {
let c = CAShapeLayer()
c.path = UIBezierPath(ovalIn: bounds).cgPath
c.fillColor = UIColor.white.cgColor
layer.addSublayer(c)
return c
}()
}