Cropping is not working perfectly as per the frame drawn - swift

I am trying to crop a selected portion of NSImage which is fitted as per ProportionallyUpOrDown(AspectFill) Mode.
I am drawing a frame using mouse dragged event like this:
class CropImageView: NSImageView {
var startPoint: NSPoint!
var shapeLayer: CAShapeLayer!
var flagCheck = false
var finalPoint: NSPoint!
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
override var image: NSImage? {
set {
self.layer = CALayer()
self.layer?.contentsGravity = kCAGravityResizeAspectFill
self.layer?.contents = newValue
self.wantsLayer = true
super.image = newValue
}
get {
return super.image
}
}
override func mouseDown(with event: NSEvent) {
self.startPoint = self.convert(event.locationInWindow, from: nil)
if self.shapeLayer != nil {
self.shapeLayer.removeFromSuperlayer()
self.shapeLayer = nil
}
self.flagCheck = true
var pixelColor: NSColor = NSReadPixel(startPoint) ?? NSColor()
shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 1.0
shapeLayer.fillColor = NSColor.clear.cgColor
if pixelColor == NSColor.black {
pixelColor = NSColor.color_white
} else {
pixelColor = NSColor.black
}
shapeLayer.strokeColor = pixelColor.cgColor
shapeLayer.lineDashPattern = [1]
self.layer?.addSublayer(shapeLayer)
var dashAnimation = CABasicAnimation()
dashAnimation = CABasicAnimation(keyPath: "lineDashPhase")
dashAnimation.duration = 0.75
dashAnimation.fromValue = 0.0
dashAnimation.toValue = 15.0
dashAnimation.repeatCount = 0.0
shapeLayer.add(dashAnimation, forKey: "linePhase")
}
override func mouseDragged(with event: NSEvent) {
let point: NSPoint = self.convert(event.locationInWindow, from: nil)
var newPoint: CGPoint = self.startPoint
let xDiff = point.x - self.startPoint.x
let yDiff = point.y - self.startPoint.y
let dist = min(abs(xDiff), abs(yDiff))
newPoint.x += xDiff > 0 ? dist : -dist
newPoint.y += yDiff > 0 ? dist : -dist
let path = CGMutablePath()
path.move(to: self.startPoint)
path.addLine(to: NSPoint(x: self.startPoint.x, y: newPoint.y))
path.addLine(to: newPoint)
path.addLine(to: NSPoint(x: newPoint.x, y: self.startPoint.y))
path.closeSubpath()
self.shapeLayer.path = path
}
override func mouseUp(with event: NSEvent) {
self.finalPoint = self.convert(event.locationInWindow, from: nil)
}
}
and selected this area as shown in picture using black dotted line:
My Cropping Code logic is this:
// resize Image Methods
extension CropProfileView {
func resizeImage(image: NSImage) -> Data {
var scalingFactor: CGFloat = 0.0
if image.size.width >= image.size.height {
scalingFactor = image.size.width/cropImgView.size.width
} else {
scalingFactor = image.size.height/cropImgView.size.height
}
let width = (self.cropImgView.finalPoint.x - self.cropImgView.startPoint.x) * scalingFactor
let height = (self.cropImgView.startPoint.y - self.cropImgView.finalPoint.y) * scalingFactor
let xPos = ((image.size.width/2) - (cropImgView.bounds.midX - self.cropImgView.startPoint.x) * scalingFactor)
let yPos = ((image.size.height/2) - (cropImgView.bounds.midY - (cropImgView.size.height - self.cropImgView.startPoint.y)) * scalingFactor)
var croppedRect: NSRect = NSRect(x: xPos, y: yPos, width: width, height: height)
let imageRef = image.cgImage(forProposedRect: &croppedRect, context: nil, hints: nil)
guard let croppedImage = imageRef?.cropping(to: croppedRect) else {return Data()}
let imageWithNewSize = NSImage(cgImage: croppedImage, size: NSSize(width: width, height: height))
guard let data = imageWithNewSize.tiffRepresentation,
let rep = NSBitmapImageRep(data: data),
let imgData = rep.representation(using: .png, properties: [.compressionFactor: NSNumber(floatLiteral: 0.25)]) else {
return imageWithNewSize.tiffRepresentation ?? Data()
}
return imgData
}
}
With this cropping logic i am getting this output:
I think as image is AspectFill thats why its not getting cropped in perfect size as per selected frame. Here if you look at output: xpositon & width & heights are not perfect. Or probably i am not calculating these co-ordinates properly. Let me know the faults probably i am calculating someting wrong.

Note: the CropImageView class in the question is a subclass of NSImageView but the view is layer-hosting and the image is drawn by the layer, not by NSImageView. imageScaling is not used.
When deciding which scaling factor to use you have to take the size of the image view into account. If the image size is width:120, height:100 and the image view size is width:120, height 80 then image.size.width >= image.size.height is true and image.size.width/cropImgView.size.width is 1 but the image is scaled because image.size.height/cropImgView.size.height is 1.25. Calculate the horizontal and vertical scaling factors and use the largest.
See How to crop a UIImageView to a new UIImage in 'aspect fill' mode?
Here's the calculation of croppedRect assuming cropImgView.size returns self.layer!.bounds.size.
var scalingWidthFactor: CGFloat = image.size.width/cropImgView.size.width
var scalingHeightFactor: CGFloat = image.size.height/cropImgView.size.height
var xOffset: CGFloat = 0
var yOffset: CGFloat = 0
switch cropImgView.layer?.contentsGravity {
case CALayerContentsGravity.resize: break
case CALayerContentsGravity.resizeAspect:
if scalingWidthFactor > scalingHeightFactor {
scalingHeightFactor = scalingWidthFactor
yOffset = (cropImgView.size.height - (image.size.height / scalingHeightFactor)) / 2
}
else {
scalingWidthFactor = scalingHeightFactor
xOffset = (cropImgView.size.width - (image.size.width / scalingWidthFactor)) / 2
}
case CALayerContentsGravity.resizeAspectFill:
if scalingWidthFactor < scalingHeightFactor {
scalingHeightFactor = scalingWidthFactor
yOffset = (cropImgView.size.height - (image.size.height / scalingHeightFactor)) / 2
}
else {
scalingWidthFactor = scalingHeightFactor
xOffset = (cropImgView.size.width - (image.size.width / scalingWidthFactor)) / 2
}
default:
print("contentsGravity \(String(describing: cropImgView.layer?.contentsGravity)) is not supported")
return nil
}
let width = (self.cropImgView.finalPoint.x - self.cropImgView.startPoint.x) * scalingWidthFactor
let height = (self.cropImgView.startPoint.y - self.cropImgView.finalPoint.y) * scalingHeightFactor
let xPos = (self.cropImgView.startPoint.x - xOffset) * scalingWidthFactor
let yPos = (cropImgView.size.height - self.cropImgView.startPoint.y - yOffset) * scalingHeightFactor
var croppedRect: NSRect = NSRect(x: xPos, y: yPos, width: width, height: height)
Bugfix: cropImgView.finalPoint should be the corner of the selection, not the location of mouseUp. In CropImageView set self.finalPoint = newPoint in mouseDragged instead of mouseUp.

Related

Drawing a Tiled Logo over NSImage an 45 degree

I'm trying to draw a logo tiled over an image at 45 degrees.But I always get a spacing on the left side.
var y_offset: CGFloat = logo.size.width * sin(45 * (CGFloat.pi / 180.0))
// the sin of the angle may return zero or negative value,
// it won't work with this formula
if y_offset >= 0 {
var x: CGFloat = 0
while x < size.width {
var y: CGFloat = 0
while y < size.height {
// move to this position
context.saveGState()
context.translateBy(x: x, y: y)
// draw text rotated around its center
context.rotate(by: ((CGFloat(-45) * CGFloat.pi ) / 180))
logo.draw(at:NSPoint(x:x,y:y), from: .zero, operation: .sourceOver, fraction: CGFloat(logotransparency))
// reset
context.restoreGState()
y = y + CGFloat(y_offset)
}
x = x + logo.size.width
}}
}
This is the result what I get.
As you can see there are some spacing present on the left side.I cannot figure out what I'm doing wrong.I have tried setting y to size.height and decrementing it by y_offset in the loop.But I get the same result.
Update:
var dirtyRect:NSRect=NSMakeRect(0, 0, size.width, size.height)
let deg45 = CGFloat.pi / 4
if let ciImage = logo.ciImage {
let ciTiled = ciImage.tiled(at: deg45).cropped(to: dirtyRect)
let color = NSColor.init(patternImage: NSImage.fromCIImage(ciTiled))
color.setFill()
context.fill(dirtyRect)
}
Updated answer
If you need more control over the appearance you can go with manually drawing the overlays. See below code for a fixed version of your original code with two options for spacing.
In production, you would of course want to avoid using ! and move the image loading out of the draw function (even though NSImage(named:) uses a cache).
override func draw(_ dirtyRect: NSRect) {
let bgImage = NSImage(named: "landscape")!
bgImage.draw(in: dirtyRect)
let deg45 = CGFloat.pi / 4
let logo = NSImage(named: "TextTile")!
let context = NSGraphicsContext.current!.cgContext
let h = logo.size.height // (sin(deg45) * logo.size.height) + (cos(deg45) * logo.size.height)
let w = logo.size.width // (sin(deg45) * logo.size.width ) + (cos(deg45) * logo.size.width )
var x: CGFloat = -w
while x < dirtyRect.width + w {
var y: CGFloat = -h
while y < dirtyRect.height + h {
context.saveGState()
context.translateBy(x: x, y: y)
context.rotate(by: deg45)
logo.draw(at:NSPoint(x:0,y:0),
from: .zero,
operation: .sourceOver,
fraction: 1)
context.restoreGState()
y = y + h
}
x = x + w
}
super.draw(dirtyRect)
}
Original answer
You can set a backgroundColor with a patternImage to for the effect of drawing image tiles in a rect.
To tilt the image by some angle, use CIImage's CIAffineTile option with some transformation.
Here is some example code:
import Cocoa
import CoreImage
class ViewController: NSViewController {
override func loadView() {
let size = CGSize(width: 500, height: 500)
let view = TiledView(frame: CGRect(origin: CGPointZero, size: size))
self.view = view
}
}
class TiledView: NSView {
override func draw(_ dirtyRect: NSRect) {
let bgImage = NSImage(named: "landscape")!
bgImage.draw(in: dirtyRect)
let deg45 = CGFloat.pi / 4
if let ciImage = NSImage(named: "TextTile")?.ciImage() {
let ciTiled = ciImage.tiled(at: deg45).cropped(to: dirtyRect)
let color = NSColor.init(patternImage: NSImage.fromCIImage(ciTiled))
color.setFill()
dirtyRect.fill()
}
super.draw(dirtyRect)
}
}
extension NSImage {
// source: https://rethunk.medium.com/convert-between-nsimage-and-ciimage-in-swift-d6c6180ef026
func ciImage() -> CIImage? {
guard let data = self.tiffRepresentation,
let bitmap = NSBitmapImageRep(data: data) else {
return nil
}
let ci = CIImage(bitmapImageRep: bitmap)
return ci
}
static func fromCIImage(_ ciImage: CIImage) -> NSImage {
let rep = NSCIImageRep(ciImage: ciImage)
let nsImage = NSImage(size: rep.size)
nsImage.addRepresentation(rep)
return nsImage
}
}
extension CIImage {
func tiled(at angle: CGFloat) -> CIImage {
// try different transforms here
let transform = CGAffineTransform(rotationAngle: angle)
return self.applyingFilter("CIAffineTile", parameters: [kCIInputTransformKey: transform])
}
}
The result looks like this:

Need help whenIntegrating complex UIView with SwiftUI failed

I tried to test a waving animated UIView that is runloop based on SwiftUI using ''UIViewRepresentable'' but it does not appear to be animating at all.
Using UIViewRepresentable Protocol to connect swiftui to UIView.
Swift UI Code:
import SwiftUI
struct WaveView: UIViewRepresentable {
func makeUIView(context: Context) -> WaveUIView {
WaveUIView(frame: .init(x: 0, y: 0, width: 300, height: 300))
}
func updateUIView(_ view: WaveUIView, context: Context) {
view.start()
}
}
struct WaveView_Previews: PreviewProvider {
static var previews: some View {
WaveView()
}
}
The "Waving" UIView that I tested working on UIViewController way of doing it.
import Foundation
import UIKit
class WaveUIView:UIView {
/// wave curvature (default: 1.5)
open var waveCurvature: CGFloat = 1.5
/// wave speed (default: 0.6)
open var waveSpeed: CGFloat = 0.6
/// wave height (default: 5)
open var waveHeight: CGFloat = 5
/// real wave color
open var realWaveColor: UIColor = UIColor.red {
didSet {
self.realWaveLayer.fillColor = self.realWaveColor.cgColor
}
}
/// mask wave color
open var maskWaveColor: UIColor = UIColor.red {
didSet {
self.maskWaveLayer.fillColor = self.maskWaveColor.cgColor
}
}
/// float over View
open var overView: UIView?
/// wave timmer
fileprivate var timer: CADisplayLink?
/// real aave
fileprivate var realWaveLayer :CAShapeLayer = CAShapeLayer()
/// mask wave
fileprivate var maskWaveLayer :CAShapeLayer = CAShapeLayer()
/// offset
fileprivate var offset :CGFloat = 0
fileprivate var _waveCurvature: CGFloat = 0
fileprivate var _waveSpeed: CGFloat = 0
fileprivate var _waveHeight: CGFloat = 0
fileprivate var _starting: Bool = false
fileprivate var _stoping: Bool = false
/**
Init view
- parameter frame: view frame
- returns: view
*/
override init(frame: CGRect) {
super.init(frame: frame)
var frame = self.bounds
frame.origin.y = frame.size.height
frame.size.height = 0
maskWaveLayer.frame = frame
realWaveLayer.frame = frame
// test
self.backgroundColor = UIColor.blue
}
/**
Init view with wave color
- parameter frame: view frame
- parameter color: real wave color
- returns: view
*/
public convenience init(frame: CGRect, color:UIColor) {
self.init(frame: frame)
self.realWaveColor = color
self.maskWaveColor = color.withAlphaComponent(0.4)
realWaveLayer.fillColor = self.realWaveColor.cgColor
maskWaveLayer.fillColor = self.maskWaveColor.cgColor
self.layer.addSublayer(self.realWaveLayer)
self.layer.addSublayer(self.maskWaveLayer)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/**
Add over view
- parameter view: overview
*/
open func addOverView(_ view: UIView) {
overView = view
overView?.center = self.center
overView?.frame.origin.y = self.frame.height - (overView?.frame.height)!
self.addSubview(overView!)
}
/**
Start wave
*/
open func start() {
if !_starting {
_stop()
_starting = true
_stoping = false
_waveHeight = 0
_waveCurvature = 0
_waveSpeed = 0
timer = CADisplayLink(target: self, selector: #selector(wave))
timer?.add(to: RunLoop.current, forMode: RunLoop.Mode.common)
}
}
/**
Stop wave
*/
open func _stop(){
if (timer != nil) {
timer?.invalidate()
timer = nil
}
}
open func stop(){
if !_stoping {
_starting = false
_stoping = true
}
}
/**
Wave animation
*/
#objc func wave() {
// when view is not visible
// if overView?.window == nil {
// print("not playing cause not visible")
// return
// }
if _starting {
print("started")
if _waveHeight < waveHeight {
_waveHeight = _waveHeight + waveHeight/100.0
var frame = self.bounds
frame.origin.y = frame.size.height-_waveHeight
frame.size.height = _waveHeight
maskWaveLayer.frame = frame
realWaveLayer.frame = frame
_waveCurvature = _waveCurvature + waveCurvature / 100.0
_waveSpeed = _waveSpeed + waveSpeed / 100.0
} else {
_starting = false
}
}
if _stoping {
if _waveHeight > 0 {
_waveHeight = _waveHeight - waveHeight/50.0
var frame = self.bounds
frame.origin.y = frame.size.height
frame.size.height = _waveHeight
maskWaveLayer.frame = frame
realWaveLayer.frame = frame
_waveCurvature = _waveCurvature - waveCurvature / 50.0
_waveSpeed = _waveSpeed - waveSpeed / 50.0
} else {
_stoping = false
_stop()
}
}
offset += _waveSpeed
let width = frame.width
let height = CGFloat(_waveHeight)
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y: height))
var y: CGFloat = 0
let maskpath = CGMutablePath()
maskpath.move(to: CGPoint(x: 0, y: height))
let offset_f = Float(offset * 0.045)
let waveCurvature_f = Float(0.01 * _waveCurvature)
for x in 0...Int(width) {
y = height * CGFloat(sinf( waveCurvature_f * Float(x) + offset_f))
path.addLine(to: CGPoint(x: CGFloat(x), y: y))
maskpath.addLine(to: CGPoint(x: CGFloat(x), y: -y))
}
if (overView != nil) {
let centX = self.bounds.size.width/2
let centY = height * CGFloat(sinf(waveCurvature_f * Float(centX) + offset_f))
let center = CGPoint(x: centX , y: centY + self.bounds.size.height - overView!.bounds.size.height/2 - _waveHeight - 1 )
overView?.center = center
}
path.addLine(to: CGPoint(x: width, y: height))
path.addLine(to: CGPoint(x: 0, y: height))
path.closeSubpath()
self.realWaveLayer.path = path
maskpath.addLine(to: CGPoint(x: width, y: height))
maskpath.addLine(to: CGPoint(x: 0, y: height))
maskpath.closeSubpath()
self.maskWaveLayer.path = maskpath
}
}
I expect the SwiftUI to have the view animating and correctly have the frame/border changes according animation. But it is not animating at all right now.
Following is the animated view with UIViewController:
override func viewWillAppear(_ animated: Bool) {
cardView.start()
}
func viewdidload(){
let frame = CGRect(x: 0, y: 0, width: self.view.bounds.size.width * 0.8, height: view.bounds.height * 0.5)
cardView = HomeCardView(frame: frame, color: .gray)
cardView.addOverView(someUIView())
cardView.realWaveColor = UIColor.white.withAlphaComponent(0.7)
cardView.maskWaveColor = UIColor.white.withAlphaComponent(0.3)
cardView.waveSpeed = 1.2
cardView.waveHeight = 10
view.addSubview(cardView)
}
You forget to addOverview in update uimethod
func updateUIView(_ view: WaveUIView, context: Context) {
let overView: UIView = UIView(frame: CGRect.init(x: 0, y: 0, width: 300, height: 300))
overView.backgroundColor = UIColor.green
view.addOverView(overView)
view.start()
}

SwiftChart Add Range Highlighting

I'm using the Swift Chart. I'd like to modify it to allow the user to select a range. The idea is to touch, swipe left/right, and then lift your finger. This should highlight the area swiped and provide a way to get the beginning and ending values of the swipe. I expect I'll need to modify the touchesBegan() and touchesEnded() events, but I don't know how.
Here's what I did to make this work:
I added range selection variables to the class
// Range selection
open var leftRangePoint: UITouch!
open var rightRangePoint: UITouch!
open var leftRangeLocation: CGFloat = 0
open var rightRangeLocation: CGFloat = 0
I modified touchesBegan()
leftRangePoint = touches.first!
leftRangeLocation = leftRangePoint.location(in: self).x
And added a routine to touchesEnded()
handleRangeTouchesEnded(touches, event: event)
Here's the full code:
// Chart.swift
//
// Created by Giampaolo Bellavite on 07/11/14.
// Copyright (c) 2014 Giampaolo Bellavite. All rights reserved.
import UIKit
public protocol ChartDelegate: class {
func didTouchChart(_ chart: Chart, indexes: [Int?], x: Float, left: CGFloat)
func didFinishTouchingChart(_ chart: Chart)
func didEndTouchingChart(_ chart: Chart)
}
typealias ChartPoint = (x: Float, y: Float)
public enum ChartLabelOrientation {
case horizontal
case vertical
}
#IBDesignable open class Chart: UIControl {
#IBInspectable
open var identifier: String?
open var series: [ChartSeries] = [] {
didSet {
setNeedsDisplay()
}
}
open var xLabels: [Float]?
open var xLabelsFormatter = { (labelIndex: Int, labelValue: Float) -> String in
String(Int(labelValue))
}
open var xLabelsTextAlignment: NSTextAlignment = .left
open var xLabelsOrientation: ChartLabelOrientation = .horizontal
open var xLabelsSkipLast: Bool = true
open var xLabelsSkipAll: Bool = true
open var yLabels: [Float]?
open var yLabelsFormatter = { (labelIndex: Int, labelValue: Float) -> String in
String(Int(labelValue))
}
open var yLabelsOnRightSide: Bool = false
open var labelFont: UIFont? = UIFont.systemFont(ofSize: 12)
#IBInspectable
open var labelColor: UIColor = UIColor.black
#IBInspectable
open var axesColor: UIColor = UIColor.gray.withAlphaComponent(0.3)
#IBInspectable
open var gridColor: UIColor = UIColor.gray.withAlphaComponent(0.3)
open var showXLabelsAndGrid: Bool = true
open var showYLabelsAndGrid: Bool = true
open var bottomInset: CGFloat = 20
open var topInset: CGFloat = 20
#IBInspectable
open var lineWidth: CGFloat = 2
weak open var delegate: ChartDelegate?
open var minX: Float?
open var minY: Float?
open var maxX: Float?
open var maxY: Float?
open var highlightLineColor = UIColor.gray
open var highlightLineWidth: CGFloat = 0.5
open var areaAlphaComponent: CGFloat = 0.1
open var leftRangePoint: UITouch!
open var rightRangePoint: UITouch!
open var leftRangeLocation: CGFloat = 0
open var rightRangeLocation: CGFloat = 0
fileprivate var highlightShapeLayer: CAShapeLayer!
fileprivate var layerStore: [CAShapeLayer] = []
fileprivate var drawingHeight: CGFloat!
fileprivate var drawingWidth: CGFloat!
fileprivate var min: ChartPoint!
fileprivate var max: ChartPoint!
typealias ChartLineSegment = [ChartPoint]
override public init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
convenience public init() {
self.init(frame: .zero)
commonInit()
}
private func commonInit() {
backgroundColor = UIColor.clear
contentMode = .redraw // redraw rects on bounds change
}
override open func draw(_ rect: CGRect) {
#if TARGET_INTERFACE_BUILDER
drawIBPlaceholder()
#else
drawChart()
#endif
}
open func add(_ series: ChartSeries) {
self.series.append(series)
}
open func add(_ series: [ChartSeries]) {
for s in series {
add(s)
}
}
open func removeSeriesAt(_ index: Int) {
series.remove(at: index)
}
open func removeAllSeries() {
series = []
}
open func valueForSeries(_ seriesIndex: Int, atIndex dataIndex: Int?) -> Float? {
if dataIndex == nil { return nil }
let series = self.series[seriesIndex] as ChartSeries
return series.data[dataIndex!].y
}
fileprivate func drawIBPlaceholder() {
let placeholder = UIView(frame: self.frame)
placeholder.backgroundColor = UIColor(red: 0.93, green: 0.93, blue: 0.93, alpha: 1)
let label = UILabel()
label.text = "Chart"
label.font = UIFont.systemFont(ofSize: 28)
label.textColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.2)
label.sizeToFit()
label.frame.origin.x += frame.width/2 - (label.frame.width / 2)
label.frame.origin.y += frame.height/2 - (label.frame.height / 2)
placeholder.addSubview(label)
addSubview(placeholder)
}
fileprivate func drawChart() {
drawingHeight = bounds.height - bottomInset - topInset
drawingWidth = bounds.width
let minMax = getMinMax()
min = minMax.min
max = minMax.max
highlightShapeLayer = nil
// Remove things before drawing, e.g. when changing orientation
for view in self.subviews {
view.removeFromSuperview()
}
for layer in layerStore {
layer.removeFromSuperlayer()
}
layerStore.removeAll()
// Draw content
for (index, series) in self.series.enumerated() {
// Separate each line in multiple segments over and below the x axis
let segments = Chart.segmentLine(series.data as ChartLineSegment, zeroLevel: series.colors.zeroLevel)
segments.forEach({ segment in
let scaledXValues = scaleValuesOnXAxis( segment.map({ return $0.x }) )
let scaledYValues = scaleValuesOnYAxis( segment.map({ return $0.y }) )
if series.line {
drawLine(scaledXValues, yValues: scaledYValues, seriesIndex: index)
}
if series.area {
drawArea(scaledXValues, yValues: scaledYValues, seriesIndex: index)
}
})
}
drawAxes()
if showXLabelsAndGrid && (xLabels != nil || series.count > 0) {
drawLabelsAndGridOnXAxis()
}
if showYLabelsAndGrid && (yLabels != nil || series.count > 0) {
drawLabelsAndGridOnYAxis()
}
}
fileprivate func getMinMax() -> (min: ChartPoint, max: ChartPoint) {
// Start with user-provided values
var min = (x: minX, y: minY)
var max = (x: maxX, y: maxY)
// Check in datasets
for series in self.series {
let xValues = series.data.map({ (point: ChartPoint) -> Float in
return point.x })
let yValues = series.data.map({ (point: ChartPoint) -> Float in
return point.y })
let newMinX = xValues.min()!
let newMinY = yValues.min()!
let newMaxX = xValues.max()!
let newMaxY = yValues.max()!
if min.x == nil || newMinX < min.x! { min.x = newMinX }
if min.y == nil || newMinY < min.y! { min.y = newMinY }
if max.x == nil || newMaxX > max.x! { max.x = newMaxX }
if max.y == nil || newMaxY > max.y! { max.y = newMaxY }
}
// Check in labels
if xLabels != nil {
let newMinX = (xLabels!).min()!
let newMaxX = (xLabels!).max()!
if min.x == nil || newMinX < min.x! { min.x = newMinX }
if max.x == nil || newMaxX > max.x! { max.x = newMaxX }
}
if yLabels != nil {
let newMinY = (yLabels!).min()!
let newMaxY = (yLabels!).max()!
if min.y == nil || newMinY < min.y! { min.y = newMinY }
if max.y == nil || newMaxY > max.y! { max.y = newMaxY }
}
if min.x == nil { min.x = 0 }
if min.y == nil { min.y = 0 }
if max.x == nil { max.x = 0 }
if max.y == nil { max.y = 0 }
return (min: (x: min.x!, y: min.y!), max: (x: max.x!, max.y!))
}
fileprivate func scaleValuesOnXAxis(_ values: [Float]) -> [Float] {
let width = Float(drawingWidth)
var factor: Float
if max.x - min.x == 0 {
factor = 0
} else {
factor = width / (max.x - min.x)
}
let scaled = values.map { factor * ($0 - self.min.x) }
return scaled
}
fileprivate func scaleValuesOnYAxis(_ values: [Float]) -> [Float] {
let height = Float(drawingHeight)
var factor: Float
if max.y - min.y == 0 {
factor = 0
} else {
factor = height / (max.y - min.y)
}
let scaled = values.map { Float(self.topInset) + height - factor * ($0 - self.min.y) }
return scaled
}
fileprivate func scaleValueOnYAxis(_ value: Float) -> Float {
let height = Float(drawingHeight)
var factor: Float
if max.y - min.y == 0 {
factor = 0
} else {
factor = height / (max.y - min.y)
}
let scaled = Float(self.topInset) + height - factor * (value - min.y)
return scaled
}
fileprivate func getZeroValueOnYAxis(zeroLevel: Float) -> Float {
if min.y > zeroLevel {
return scaleValueOnYAxis(min.y)
} else {
return scaleValueOnYAxis(zeroLevel)
}
}
fileprivate func drawLine(_ xValues: [Float], yValues: [Float], seriesIndex: Int) {
// YValues are "reverted" from top to bottom, so 'above' means <= level
let isAboveZeroLine = yValues.max()! <= self.scaleValueOnYAxis(series[seriesIndex].colors.zeroLevel)
let path = CGMutablePath()
path.move(to: CGPoint(x: CGFloat(xValues.first!), y: CGFloat(yValues.first!)))
for i in 1..<yValues.count {
let y = yValues[i]
path.addLine(to: CGPoint(x: CGFloat(xValues[i]), y: CGFloat(y)))
}
let lineLayer = CAShapeLayer()
lineLayer.frame = self.bounds
lineLayer.path = path
if isAboveZeroLine {
lineLayer.strokeColor = series[seriesIndex].colors.above.cgColor
} else {
lineLayer.strokeColor = series[seriesIndex].colors.below.cgColor
}
lineLayer.fillColor = nil
lineLayer.lineWidth = lineWidth
lineLayer.lineJoin = kCALineJoinBevel
self.layer.addSublayer(lineLayer)
layerStore.append(lineLayer)
}
fileprivate func drawArea(_ xValues: [Float], yValues: [Float], seriesIndex: Int) {
// YValues are "reverted" from top to bottom, so 'above' means <= level
let isAboveZeroLine = yValues.max()! <= self.scaleValueOnYAxis(series[seriesIndex].colors.zeroLevel)
let area = CGMutablePath()
let zero = CGFloat(getZeroValueOnYAxis(zeroLevel: series[seriesIndex].colors.zeroLevel))
area.move(to: CGPoint(x: CGFloat(xValues[0]), y: zero))
for i in 0..<xValues.count {
area.addLine(to: CGPoint(x: CGFloat(xValues[i]), y: CGFloat(yValues[i])))
}
area.addLine(to: CGPoint(x: CGFloat(xValues.last!), y: zero))
let areaLayer = CAShapeLayer()
areaLayer.frame = self.bounds
areaLayer.path = area
areaLayer.strokeColor = nil
if isAboveZeroLine {
areaLayer.fillColor = series[seriesIndex].colors.above.withAlphaComponent(areaAlphaComponent).cgColor
} else {
areaLayer.fillColor = series[seriesIndex].colors.below.withAlphaComponent(areaAlphaComponent).cgColor
}
areaLayer.lineWidth = 0
self.layer.addSublayer(areaLayer)
layerStore.append(areaLayer)
}
fileprivate func drawAxes() {
let context = UIGraphicsGetCurrentContext()!
context.setStrokeColor(axesColor.cgColor)
context.setLineWidth(0.5)
// horizontal axis at the bottom
context.move(to: CGPoint(x: CGFloat(0), y: drawingHeight + topInset))
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: drawingHeight + topInset))
context.strokePath()
// horizontal axis at the top
context.move(to: CGPoint(x: CGFloat(0), y: CGFloat(0)))
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: CGFloat(0)))
context.strokePath()
// horizontal axis when y = 0
if min.y < 0 && max.y > 0 {
let y = CGFloat(getZeroValueOnYAxis(zeroLevel: 0))
context.move(to: CGPoint(x: CGFloat(0), y: y))
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: y))
context.strokePath()
}
// vertical axis on the left
context.move(to: CGPoint(x: CGFloat(0), y: CGFloat(0)))
context.addLine(to: CGPoint(x: CGFloat(0), y: drawingHeight + topInset))
context.strokePath()
// vertical axis on the right
context.move(to: CGPoint(x: CGFloat(drawingWidth), y: CGFloat(0)))
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: drawingHeight + topInset))
context.strokePath()
}
fileprivate func drawLabelsAndGridOnXAxis() {
let context = UIGraphicsGetCurrentContext()!
context.setStrokeColor(gridColor.cgColor)
context.setLineWidth(0.5)
var labels: [Float]
if xLabels == nil {
// Use labels from the first series
labels = series[0].data.map({ (point: ChartPoint) -> Float in
return point.x })
} else {
labels = xLabels!
}
let scaled = scaleValuesOnXAxis(labels)
let padding: CGFloat = 5
scaled.enumerated().forEach { (i, value) in
let x = CGFloat(value)
let isLastLabel = x == drawingWidth
// Add vertical grid for each label, except axes on the left and right
if x != 0 && x != drawingWidth {
context.move(to: CGPoint(x: x, y: CGFloat(0)))
if xLabelsSkipAll {
let height: CGFloat = bounds.height - 20.0
context.addLine(to: CGPoint(x: x, y: height))
} else {
context.addLine(to: CGPoint(x: x, y: bounds.height))
}
context.strokePath()
}
if (xLabelsSkipLast && isLastLabel) || xLabelsSkipAll {
// Do not add label at the most right position
return
}
// Add label
let label = UILabel(frame: CGRect(x: x, y: drawingHeight, width: 0, height: 0))
label.font = labelFont
label.text = xLabelsFormatter(i, labels[i])
label.textColor = labelColor
// Set label size
label.sizeToFit()
// Center label vertically
label.frame.origin.y += topInset
if xLabelsOrientation == .horizontal {
// Add left padding
label.frame.origin.y -= (label.frame.height - bottomInset) / 2
label.frame.origin.x += padding
// Set label's text alignment
label.frame.size.width = (drawingWidth / CGFloat(labels.count)) - padding * 2
label.textAlignment = xLabelsTextAlignment
} else {
label.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2))
// Adjust vertical position according to the label's height
label.frame.origin.y += label.frame.size.height / 2
// Adjust horizontal position as the series line
label.frame.origin.x = x
if xLabelsTextAlignment == .center {
// Align horizontally in series
label.frame.origin.x += ((drawingWidth / CGFloat(labels.count)) / 2) - (label.frame.size.width / 2)
} else {
// Give some space from the vertical line
label.frame.origin.x += padding
}
}
self.addSubview(label)
}
}
fileprivate func drawLabelsAndGridOnYAxis() {
let context = UIGraphicsGetCurrentContext()!
context.setStrokeColor(gridColor.cgColor)
context.setLineWidth(0.5)
var labels: [Float]
if yLabels == nil {
labels = [(min.y + max.y) / 2, max.y]
if yLabelsOnRightSide || min.y != 0 {
labels.insert(min.y, at: 0)
}
} else {
labels = yLabels!
}
let scaled = scaleValuesOnYAxis(labels)
let padding: CGFloat = 5
let zero = CGFloat(getZeroValueOnYAxis(zeroLevel: 0))
scaled.enumerated().forEach { (i, value) in
let y = CGFloat(value)
// Add horizontal grid for each label, but not over axes
if y != drawingHeight + topInset && y != zero {
context.move(to: CGPoint(x: CGFloat(0), y: y))
context.addLine(to: CGPoint(x: self.bounds.width, y: y))
if labels[i] != 0 {
// Horizontal grid for 0 is not dashed
context.setLineDash(phase: CGFloat(0), lengths: [CGFloat(5)])
} else {
context.setLineDash(phase: CGFloat(0), lengths: [])
}
context.strokePath()
}
let label = UILabel(frame: CGRect(x: padding, y: y, width: 0, height: 0))
label.font = labelFont
label.text = yLabelsFormatter(i, labels[i])
label.textColor = labelColor
label.sizeToFit()
if yLabelsOnRightSide {
label.frame.origin.x = drawingWidth
label.frame.origin.x -= label.frame.width + padding
}
// Labels should be placed above the horizontal grid
label.frame.origin.y -= label.frame.height
self.addSubview(label)
}
UIGraphicsEndImageContext()
}
fileprivate func drawHighlightLineFromLeftPosition(_ left: CGFloat) {
if let shapeLayer = highlightShapeLayer {
// Use line already created
let path = CGMutablePath()
path.move(to: CGPoint(x: left, y: 0))
path.addLine(to: CGPoint(x: left, y: drawingHeight + topInset))
shapeLayer.path = path
} else {
// Create the line
let path = CGMutablePath()
path.move(to: CGPoint(x: left, y: CGFloat(0)))
path.addLine(to: CGPoint(x: left, y: drawingHeight + topInset))
let shapeLayer = CAShapeLayer()
shapeLayer.frame = self.bounds
shapeLayer.path = path
shapeLayer.strokeColor = highlightLineColor.cgColor
shapeLayer.fillColor = nil
shapeLayer.lineWidth = highlightLineWidth
highlightShapeLayer = shapeLayer
layer.addSublayer(shapeLayer)
layerStore.append(shapeLayer)
}
}
func handleTouchEvents(_ touches: Set<UITouch>, event: UIEvent!) {
let point = touches.first!
let left = point.location(in: self).x
let x = valueFromPointAtX(left)
if left < 0 || left > (drawingWidth as CGFloat) {
// Remove highlight line at the end of the touch event
if let shapeLayer = highlightShapeLayer {
shapeLayer.path = nil
}
delegate?.didFinishTouchingChart(self)
return
}
drawHighlightLineFromLeftPosition(left)
if delegate == nil {
return
}
var indexes: [Int?] = []
for series in self.series {
var index: Int? = nil
let xValues = series.data.map({ (point: ChartPoint) -> Float in
return point.x })
let closest = Chart.findClosestInValues(xValues, forValue: x)
if closest.lowestIndex != nil && closest.highestIndex != nil {
// Consider valid only values on the right
index = closest.lowestIndex
}
indexes.append(index)
}
delegate!.didTouchChart(self, indexes: indexes, x: x, left: left)
}
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
handleTouchEvents(touches, event: event)
leftRangePoint = touches.first!
leftRangeLocation = leftRangePoint.location(in: self).x
}
override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
handleTouchEvents(touches, event: event)
delegate?.didEndTouchingChart(self)
handleRangeTouchesEnded(touches, event: event)
}
func handleRangeTouchesEnded(_ touches: Set<UITouch>, event: UIEvent!) {
rightRangePoint = touches.first!
rightRangeLocation = rightRangePoint.location(in: self).x
// Make sure left is actually to the left
if rightRangeLocation < leftRangeLocation {
let rangePoint = leftRangePoint
let rangeLocation = leftRangeLocation
leftRangePoint = rightRangePoint
leftRangeLocation = rightRangeLocation
rightRangePoint = rangePoint
rightRangeLocation = rangeLocation
}
// Highlight the range
let layer = CAShapeLayer()
let width = rightRangeLocation - leftRangeLocation
layer.path = UIBezierPath(rect: CGRect(x: leftRangeLocation, y: topInset, width: width, height: drawingHeight)).cgPath
layer.fillColor = UIColor.red.cgColor
layer.opacity = 0.3
self.layer.addSublayer(layer)
}
override open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
handleTouchEvents(touches, event: event)
}
fileprivate func valueFromPointAtX(_ x: CGFloat) -> Float {
let value = ((max.x-min.x) / Float(drawingWidth)) * Float(x) + min.x
return value
}
fileprivate func valueFromPointAtY(_ y: CGFloat) -> Float {
let value = ((max.y - min.y) / Float(drawingHeight)) * Float(y) + min.y
return -value
}
fileprivate class func findClosestInValues(_ values: [Float],
forValue value: Float
) -> (
lowestValue: Float?,
highestValue: Float?,
lowestIndex: Int?,
highestIndex: Int?
) {
var lowestValue: Float?, highestValue: Float?, lowestIndex: Int?, highestIndex: Int?
values.enumerated().forEach { (i, currentValue) in
if currentValue <= value && (lowestValue == nil || lowestValue! < currentValue) {
lowestValue = currentValue
lowestIndex = i
}
if currentValue >= value && (highestValue == nil || highestValue! > currentValue) {
highestValue = currentValue
highestIndex = i
}
}
return (
lowestValue: lowestValue,
highestValue: highestValue,
lowestIndex: lowestIndex,
highestIndex: highestIndex
)
}
fileprivate class func segmentLine(_ line: ChartLineSegment, zeroLevel: Float) -> [ChartLineSegment] {
var segments: [ChartLineSegment] = []
var segment: ChartLineSegment = []
line.enumerated().forEach { (i, point) in
segment.append(point)
if i < line.count - 1 {
let nextPoint = line[i+1]
if point.y >= zeroLevel && nextPoint.y < zeroLevel || point.y < zeroLevel && nextPoint.y >= zeroLevel {
// The segment intersects zeroLevel, close the segment with the intersection point
let closingPoint = Chart.intersectionWithLevel(point, and: nextPoint, level: zeroLevel)
segment.append(closingPoint)
segments.append(segment)
// Start a new segment
segment = [closingPoint]
}
} else {
// End of the line
segments.append(segment)
}
}
return segments
}
fileprivate class func intersectionWithLevel(_ p1: ChartPoint, and p2: ChartPoint, level: Float) -> ChartPoint {
let dy1 = level - p1.y
let dy2 = level - p2.y
return (x: (p2.x * dy1 - p1.x * dy2) / (dy1 - dy2), y: level)
}
}

Connect points in grid with lines

I need to put line that will connect every 2 points in the grid when someone tap between those points , so they can be connected . I manage to create the point grid :
func drawPointGrid() {
let points: CGFloat = 5
let cellWidth = bounds.width / points
let cellHeight = bounds.height / points
for i in 0..<Int(points) {
for j in 0..<Int(points) {
let circleX: CGFloat = ((CGFloat(i) + 0.5) * cellWidth)
let circleY: CGFloat = ((CGFloat(j) + 0.5) * cellHeight)
let centerCirclePath = UIBezierPath(ovalIn: CGRect(x: circleX, y: circleY, width: diameter, height: diameter))
let customlayer = CAShapeLayer()
customlayer.path = centerCirclePath.cgPath
customlayer.fillColor = UIColor.black.cgColor
layer.addSublayer(customlayer)
}
}
}
This is my visual point grid :
I manage to make line on the view , but only when I click for start point and click again for end point, so the line to be created , but I need this line to be created between every 2 points on horizontal and vertical when user tap between them :
override func draw(_ rect: CGRect) {
drawPointGrid()
tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(showMoreActions))
tapGestureRecognizer.numberOfTapsRequired = 1
addGestureRecognizer(tapGestureRecognizer)
}
// draw line from point to point that are clicked
var firstPoint: CGPoint?
var secondPoint: CGPoint?
func showMoreActions(touch: UITapGestureRecognizer) {
let touchPoint = touch.location(in: self)
guard let _ = firstPoint else {
firstPoint = touchPoint
return
}
guard let _ = secondPoint else {
secondPoint = touchPoint
addLine(start: firstPoint!,end: secondPoint!)
firstPoint = nil
secondPoint = nil
return
}
}
func addLine(start: CGPoint,end:CGPoint) {
let line = CAShapeLayer()
let linePath = UIBezierPath()
linePath.move(to: start)
linePath.addLine(to: end)
line.path = linePath.cgPath
line.strokeColor = UIColor.black.cgColor
line.lineWidth = 2
line.lineJoin = kCALineJoinRound
layer.addSublayer(line)
}
I've written a function called findEndPoints that finds the CGPoint coordinates of the proper line end points given a touchPoint in the view. I did most of the work as type Double to keep from having to worry about converting between that and CGFloat.
I also moved the setup of the touchGestureRecognizer to a setup routine that is called from the inits since you only want to do that once and draw could be called more than once.
class DotsView: UIView {
let diameter = CGFloat(5)
func setup() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(showMoreActions))
tapGestureRecognizer.numberOfTapsRequired = 1
addGestureRecognizer(tapGestureRecognizer)
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func draw(_ rect: CGRect) {
drawPointGrid()
}
// draw line between points
func showMoreActions(touch: UITapGestureRecognizer) {
let touchPoint = touch.location(in: self)
let (start, end) = findEndPoints(touchPt: touchPoint)
addLine(start: start, end: end)
}
func addLine(start: CGPoint,end:CGPoint) {
let line = CAShapeLayer()
let linePath = UIBezierPath()
linePath.move(to: start)
linePath.addLine(to: end)
line.path = linePath.cgPath
line.strokeColor = UIColor.black.cgColor
line.lineWidth = 2
line.lineJoin = kCALineJoinRound
layer.addSublayer(line)
}
func drawPointGrid() {
let points: CGFloat = 5
let diameter: CGFloat = 5
let cellWidth = bounds.width / points
let cellHeight = bounds.height / points
for i in 0..<Int(points) {
for j in 0..<Int(points) {
let circleX: CGFloat = ((CGFloat(i) + 0.5) * cellWidth)
let circleY: CGFloat = ((CGFloat(j) + 0.5) * cellHeight)
let centerCirclePath = UIBezierPath(ovalIn: CGRect(x: circleX, y: circleY, width: diameter, height: diameter))
let customlayer = CAShapeLayer()
customlayer.path = centerCirclePath.cgPath
customlayer.fillColor = UIColor.black.cgColor
layer.addSublayer(customlayer)
}
}
}
func findEndPoints(touchPt: CGPoint) -> (pt1: CGPoint, pt2: CGPoint) {
let points = 5.0
let cellWidth = Double(bounds.width) / points
let cellHeight = Double(bounds.height) / points
// convert touch point to grid coordinates
let gridX = Double(touchPt.x) / cellWidth - 0.5
let gridY = Double(touchPt.y) / cellHeight - 0.5
// snap to nearest point in the grid
let snapX = round(gridX)
let snapY = round(gridY)
// find distance from touch to snap point
let distX = abs(gridX - snapX)
let distY = abs(gridY - snapY)
// start second point on top of first
var secondX = snapX
var secondY = snapY
if distX < distY {
// this is a vertical line
if secondY > gridY {
secondY -= 1
} else {
secondY += 1
}
} else {
// this is a horizontal line
if secondX > gridX {
secondX -= 1
} else {
secondX += 1
}
}
let halfdot = Double(diameter) / 2
// convert line points to view coordinates
let pt1 = CGPoint(x: (snapX + 0.5) * cellWidth + halfdot, y: (snapY + 0.5) * cellHeight + halfdot)
let pt2 = CGPoint(x: (secondX + 0.5) * cellWidth + halfdot, y: (secondY + 0.5) * cellHeight + halfdot)
return (pt1, pt2)
}
}

iPad Pro Simulator not displaying layer.mask

I have tested the following code on all simulators and it works fine except on the iPad Pro. On the the iPad Pro is does not display. The gradient layer will work fine, it is only when I try and apply a mask that it will not appear on the iPad Pro Simulator:
func createOverlay()
{
if !(gradientLayer != nil)
{
self.gradientLayer = CAGradientLayer()
self.layer.addSublayer(gradientLayer)
}
gradientLayer.frame = self.bounds
print(gradientLayer.frame)
gradientLayer.colors = [appColour.CGColor, appColourDark.CGColor]
//--------FROM HERE ON DOES NOT WORK ON IPAD PRO, NO CRASH BUT LAYER DOES NOT APPEAR
self.alpha = maskAlpha
let maskLayer = CAShapeLayer()
let path = CGPathCreateMutable()
let rect: CGRect = CGRect(x: xOffset - offset, y: yOffset - offset, width: circleWidth + (offset * 2), height: circleHeight + (offset * 2))
let bPath = UIBezierPath(ovalInRect: rect)
CGPathAddRect(path, nil, CGRectMake(0, 0, self.frame.width, self.frame.height))
CGPathAddPath(path, nil, bPath.CGPath)
maskLayer.backgroundColor = UIColor.blackColor().CGColor
maskLayer.path = path
maskLayer.fillRule = kCAFillRuleEvenOdd
self.layer.mask = maskLayer
self.clipsToBounds = true
}
I am hoping this is just a simulator issue but if you see something in my code that might be causing a problem please let me know.
I have tried replacing the gradient layer with a normal layer but it still does not display.
Here is full code, it is a custom sub-class of UIView and is the top layer of a view controller setup in IB:
import UIKit
protocol TipSpeechDelegate
{
func stopSpeaking()
}
#IBDesignable
class HoleMaskView: UIView
{
var xOffset : CGFloat = 0.0
var yOffset : CGFloat = 0.0
var circleWidth: CGFloat = 0.0
var circleHeight: CGFloat = 0.0
var maskAlpha: CGFloat = 0.9
var offset: CGFloat = 10.0
var inset: CGFloat = 8.0
var tipText: String = ""
var myLabel: UILabel?
var gradientLayer: CAGradientLayer!
var relativeCorner: RelativeCornerType = RelativeCornerType.upperLeftCorner
var delegate: TipSpeechDelegate!
override func layoutSubviews()
{
super.layoutSubviews()
}
override func drawRect(rect: CGRect)
{
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(HoleMaskView.handleTap(_:)))
self.addGestureRecognizer(gestureRecognizer)
}
func drawTipText()
{
createOverlay()
let height: CGFloat = self.frame.size.height
if (self.myLabel != nil)
{
myLabel?.removeFromSuperview()
}
if (yOffset < height/2)
{
self.myLabel = UILabel(frame: CGRect(x: inset, y: (yOffset + circleHeight), width: self.frame.width-(inset*2), height: self.frame.height-(yOffset + circleHeight)))
}
else
{
self.myLabel = UILabel(frame: CGRect(x: inset, y: 0, width: self.frame.width-(inset*2), height: yOffset))
}
self.myLabel?.text = tipText
self.myLabel?.textColor = UIColor.whiteColor()
self.myLabel?.font = UIFont(name: "Avenir-Medium", size: 20.0)
self.myLabel?.textAlignment = .Center
self.myLabel?.lineBreakMode = .ByWordWrapping
self.myLabel?.numberOfLines = 0
self.myLabel?.setNeedsLayout()
self.addSubview(myLabel!)
}
func updateTipText(text: String, circle: CGRect)
{
self.tipText = text
yOffset = circle.origin.y
xOffset = circle.origin.x
circleWidth = circle.size.width
circleHeight = circle.size.height
self.drawTipText()
}
func tipText(text: String, rFrame: CGRect, inView: UIView) -> Bool
{
showTipMask()
let convertedPoint = inView.convertPoint(rFrame.origin, toView: self)
self.tipText = text
yOffset = convertedPoint.y
xOffset = convertedPoint.x
circleWidth = rFrame.size.width
circleHeight = rFrame.size.height
self.drawTipText()
return true
}
func tipText(text: String, button: UIButton) -> Bool
{
if button.hidden
{
return false
}
showTipMask()
let convertedPoint = button.superview!.convertPoint(button.frame.origin, toView: self)
self.tipText = text
yOffset = convertedPoint.y
xOffset = convertedPoint.x
circleWidth = button.frame.size.width
circleHeight = button.frame.size.height
self.drawTipText()
return true
}
func tipText(text: String, label: UILabel) -> Bool
{
if label.hidden
{
return false
}
showTipMask()
let convertedPoint = label.superview!.convertPoint(label.frame.origin, toView: self)
self.tipText = text
yOffset = convertedPoint.y
xOffset = convertedPoint.x
circleWidth = label.frame.size.width
circleHeight = label.frame.size.height
self.drawTipText()
return true
}
func tipText(text: String, textView: UITextView) -> Bool
{
if textView.hidden
{
return false
}
showTipMask()
let convertedPoint = textView.superview!.convertPoint(textView.frame.origin, toView: self)
self.tipText = text
yOffset = convertedPoint.y
xOffset = convertedPoint.x
circleWidth = textView.frame.size.width
circleHeight = textView.frame.size.height
self.drawTipText()
return true
}
func tipText(text: String) -> Bool
{
showTipMask()
self.tipText = text
yOffset = 0.0
xOffset = self.frame.size.width/2
circleWidth = 0.0
circleHeight = 0.0
self.drawTipText()
return true
}
func tipText(text: String, view: UIView) -> Bool
{
if view.hidden
{
return false
}
showTipMask()
let convertedPoint = view.superview!.convertPoint(view.frame.origin, toView: self)
self.tipText = text
yOffset = convertedPoint.y
xOffset = convertedPoint.x
circleWidth = view.frame.size.width
circleHeight = view.frame.size.height
self.drawTipText()
return true
}
func tipText(text: String, stepper: UIStepper) -> Bool
{
if stepper.hidden
{
return false
}
showTipMask()
let convertedPoint = stepper.superview!.convertPoint(stepper.frame.origin, toView: self)
self.tipText = text
yOffset = convertedPoint.y
xOffset = convertedPoint.x
circleWidth = stepper.frame.size.width
circleHeight = stepper.frame.size.height
self.drawTipText()
return true
}
func showTipMask()
{
self.alpha = alphaHide
self.hidden = false
UIView.animateWithDuration(0.5, animations:
{
self.alpha = alphaShow
}
)
}
func handleTap(gestureRecognizer: UIGestureRecognizer)
{
if delegate != nil
{
delegate.stopSpeaking()
}
print("tapped internal")
UIView.animateWithDuration(0.25, delay: 0.0, options: UIViewAnimationOptions.TransitionNone, animations:
{
() -> Void in
self.alpha = alphaHide
},
completion:
{
(finished: Bool) -> Void in
self.hidden = true
}
)
}
func createOverlay()
{
if !(gradientLayer != nil)
{
self.gradientLayer = CAGradientLayer()
self.layer.addSublayer(gradientLayer)
}
gradientLayer.frame = self.bounds
print(gradientLayer.frame)
gradientLayer.colors = [appColour.CGColor, appColourDark.CGColor]
self.alpha = maskAlpha
let maskLayer = CAShapeLayer()
let path = CGPathCreateMutable()
let rect: CGRect = CGRect(x: xOffset - offset, y: yOffset - offset, width: circleWidth + (offset * 2), height: circleHeight + (offset * 2))
let bPath = UIBezierPath(ovalInRect: rect)
CGPathAddRect(path, nil, CGRectMake(0, 0, self.frame.width, self.frame.height))
CGPathAddPath(path, nil, bPath.CGPath)
maskLayer.backgroundColor = UIColor.blackColor().CGColor
maskLayer.path = path
maskLayer.fillRule = kCAFillRuleEvenOdd
self.layer.mask = maskLayer
self.clipsToBounds = true
}
}
Thanks
Greg
I am facing the same issue.
Currently I
turn on the Debug -> Optimize Rendering for Window Scale
scale it down to 50%
then the graph appears correctly.
But one of my colleges told me that he has to turn off the ORWS option. Wired.