How we can increase the size of the selected date title circle in FSCalendar libraryas we can see in the image I want the circle bigger than current size
calendar.appearance.borderRadius = .someValue // I tried all Value of enum But it not works
Override FSCalendarCell layoutSubviews method.
Here is my code.
override func layoutSubviews() {
super.layoutSubviews()
let titleHeight: CGFloat = self.bounds.size.height * 4.1 / 5
var diameter: CGFloat = min(self.bounds.size.height * 5.2 / 8, self.bounds.size.width)
diameter = diameter > FSCalendarStandardCellDiameter ? (diameter - (diameter-FSCalendarStandardCellDiameter) * 0.5) : diameter
shapeLayer.frame = CGRect(x: (bounds.size.width - diameter) / 2,
y: (titleHeight - diameter) / 2,
width: diameter, height: diameter)
let path = UIBezierPath(roundedRect: shapeLayer.bounds, cornerRadius: shapeLayer.bounds.width * 0.5 * appearance.borderRadius).cgPath
if shapeLayer.path != path {
shapeLayer.path = path
}
}
Change the value titleHeight, diameter to increase/decrease the size of selected date circle.
Related
I'm building an app where I want to display profilePictures of people "near you" in a hexagon beehive style.
The full beehive should be draggable, like google maps for example.
My question is if this is something I can do with just using UIKit, or if it would be easier to use UIKit and SpriteKit together.
I hope someone could point me at the right direction and or have some ideas on how this could be made. Thank you for your time!
Update:
Just to make my question a bit more clear.
This is how my view looks like atm
And this is what I want to achieve
In the first image I´ve just set the X and Y pos of the UIImage center middle.
I want to create some sort of function that can get an array of different profilePics and then put the out in this pattern.
UIKit alone can do the job: you should try to setup a mask with CALayer on a UIImageView for instance.
The draggable behavior thing can be achieved either with a UIScrollView by adding and arranging all your image subviews in it, or with a UICollectionView with a custom flow, but it may be much harder to set up.
For the hexagon views, you'll find here an interesting example you can adapt for your usage: http://sapandiwakar.in/make-hexagonal-view-on-ios/
Here is an adaption of Sapan Diwakar solution in Swift 4.2 and using extensions:
extension UIBezierPath {
convenience init(roundedPolygonPathInRect rect: CGRect, lineWidth: CGFloat, sides: NSInteger, cornerRadius: CGFloat = 0, rotationOffset: CGFloat = 0) {
self.init()
let theta: CGFloat = 2.0 * CGFloat.pi / CGFloat(sides) // How much to turn at every corner
let width = min(rect.size.width, rect.size.height) // Width of the square
let center = CGPoint(x: rect.origin.x + width / 2.0, y: rect.origin.y + width / 2.0)
// Radius of the circle that encircles the polygon
// Notice that the radius is adjusted for the corners, that way the largest outer
// dimension of the resulting shape is always exactly the width - linewidth
let radius = (width - lineWidth + cornerRadius - (cos(theta) * cornerRadius)) / 2.0
// Start drawing at a point, which by default is at the right hand edge
// but can be offset
var angle = CGFloat(rotationOffset)
let corner = CGPoint(x: center.x + (radius - cornerRadius) * cos(angle), y: center.y + (radius - cornerRadius) * sin(angle))
move(to: CGPoint(x: corner.x + cornerRadius * cos(angle + theta), y: corner.y + cornerRadius * sin(angle + theta)))
for _ in 0 ..< sides {
angle += theta
let corner = CGPoint(x: center.x + (radius - cornerRadius) * cos(angle), y: center.y + (radius - cornerRadius) * sin(angle))
let tip = CGPoint(x: center.x + radius * cos(angle), y: center.y + radius * sin(angle))
let start = CGPoint(x: corner.x + cornerRadius * cos(angle - theta), y: corner.y + cornerRadius * sin(angle - theta))
let end = CGPoint(x: corner.x + cornerRadius * cos(angle + theta), y: corner.y + cornerRadius * sin(angle + theta))
addLine(to: start)
addQuadCurve(to: end, controlPoint: tip)
}
close()
}
}
extension UIImageView {
func setupHexagonMask(lineWidth: CGFloat, color: UIColor, cornerRadius: CGFloat) {
let path = UIBezierPath(roundedPolygonPathInRect: bounds, lineWidth: lineWidth, sides: 6, cornerRadius: cornerRadius, rotationOffset: CGFloat.pi / 2.0).cgPath
let mask = CAShapeLayer()
mask.path = path
mask.lineWidth = lineWidth
mask.strokeColor = UIColor.clear.cgColor
mask.fillColor = UIColor.white.cgColor
layer.mask = mask
let border = CAShapeLayer()
border.path = path
border.lineWidth = lineWidth
border.strokeColor = color.cgColor
border.fillColor = UIColor.clear.cgColor
layer.addSublayer(border)
}
}
And then you can just use it like that:
let image = UIImageView(frame: CGRect(x: 30, y: 30, width: 300, height: 300))
image.contentMode = .scaleAspectFill
image.image = UIImage(named: "lenna.png")
image.setupHexagonMask(lineWidth: 5.0, color: .white, cornerRadius: 20.0)
view.addSubview(image)
EDIT: As I told you, the easiest way is to use a UIScrollView to display your map, and with simple math you can display your hexagons the way you want.
Here is a small example you must adapt to match your requirements. For example you should be extra careful with performance. This example should not be used as is, if you have many images, you should load them on the fly and remove them when you don't show them. And you can even think using a background rendering if it takes too much fps...
Assuming view is a UIScrollView:
let hexaDiameter : CGFloat = 150
let hexaWidth = hexaDiameter * sqrt(3) * 0.5
let hexaWidthDelta = (hexaDiameter - hexaWidth) * 0.5
let hexaHeightDelta = hexaDiameter * 0.25
let spacing : CGFloat = 5
let rows = 10
let firstRowColumns = 6
view.contentSize = CGSize(width: spacing + CGFloat(firstRowColumns) * (hexaWidth + spacing),
height: spacing + CGFloat(rows) * (hexaDiameter - hexaHeightDelta + spacing) + hexaHeightDelta)
for y in 0..<rows {
let cellsInRow = y % 2 == 0 ? firstRowColumns : firstRowColumns - 1
let rowXDelta = y % 2 == 0 ? 0.0 : (hexaWidth + spacing) * 0.5
for x in 0..<cellsInRow {
let image = UIImageView(frame: CGRect(x: rowXDelta + CGFloat(x) * (hexaWidth + spacing) + spacing - hexaWidthDelta,
y: CGFloat(y) * (hexaDiameter - hexaHeightDelta + spacing) + spacing,
width: hexaDiameter,
height: hexaDiameter))
image.contentMode = .scaleAspectFill
image.image = UIImage(named: "lenna.png")
image.setupHexagonMask(lineWidth: 5.0, color: .white, cornerRadius: 10.0)
view.addSubview(image)
}
}
I am trying to display an image as the content of a CALayer slightly zoomed in by changing its bounds to a bigger size. (This is so that I can pan over it later.)
For some reason however setting the bounds does not change them or trigger an animation to do so.
This is the code I use to change the bounds:
self.imageLayer.bounds = CGRect(x: 0, y: 0, width: 10, height: 10)
I have a function to compute the CGRect, but this dummy one leads to exactly the same result of the size not changing.
I have also determined, that while I can't see the size change, if I check the bounds of the layer right after setting it, it correctly has the value I set it to.
The following code is executed after setting the bounds. I couldn't find anything in it, that changes them back.
self.imageLayer.add(self.generatePanAnimation(), forKey: "pan")
func generatePanAnimation() -> CAAnimation {
var positionA = CGPoint(x: (self.bounds.width / 2), y: self.bounds.height / 2)
var positionB = CGPoint(x: (self.bounds.width / 2), y: self.bounds.height / 2)
positionA = self.generateZoomedPosition()
positionB = self.generateZoomedPosition()
let panAnimation = CABasicAnimation(keyPath: "position")
if self.direction == .AtoB {
panAnimation.fromValue = positionA
panAnimation.toValue = positionB
} else {
panAnimation.fromValue = positionB
panAnimation.toValue = positionA
}
panAnimation.duration = self.panAndZoomDuration
self.panAnimation = panAnimation
return panAnimation
}
func generateZoomedPosition() -> CGPoint {
let maxRight = self.zoomedImageLayerBounds.width / 2
let maxLeft = self.bounds.width - (self.zoomedImageLayerBounds.height / 2)
let maxUp = self.zoomedImageLayerBounds.height / 2
let maxDown = self.bounds.height - (self.zoomedImageLayerBounds.height / 2)
let horizontalFactor = CGFloat(arc4random()) / CGFloat(UINT32_MAX)
let verticalFactor = CGFloat(arc4random()) / CGFloat(UINT32_MAX)
let randomX = maxLeft + horizontalFactor * (maxRight - maxLeft)
let randomY = maxDown + verticalFactor * (maxUp - maxDown)
return CGPoint(x: randomX, y: randomY)
}
I even tried setting the bounds as shown below, but it didn't help.
CATransaction.begin()
CATransaction.setValue(true, forKey: kCATransactionDisableActions)
self.imageLayer.bounds = CGRect(x: 0, y: 0, width: 10, height: 10)
CATransaction.commit()
I really hope someone has an idea. Thanks a lot!
The way to change the apparent drawing size of a layer is not to change its bounds but to change its transform. To make the layer look larger, including its drawing, apply a scale transform.
In my UIViewController I have a single subclass of UIView in which I will draw a tic tac toe board. Somehow the dividers (the "#" shape) I'm drawing using UIBezierPath() are not dividing the board evenly. Instead of 1/3-1/3-1/3, the vertical dividers are closer to 45%-45%-10% even though the dimensions printouts make sense. What am I missing? Thanks
In my subclass:
import UIKit
#IBDesignable class GameBoardView: UIView {
override func drawRect(rect: CGRect) {
// set up gameBoard dimensions everytime drawRect() is called
setUpGameBoardCells()
self.frame = CGRectMake(gameBoardPosX, gameBoardPosY, gameBoardLength, gameBoardLength)
print("gameBoard.frame: x=\(self.frame.origin.x), y=\(self.frame.origin.y), h=\(self.frame.height), w=\(self.frame.width)\n")
// draw dividers & cells
var divider = UIBezierPath(rect: CGRect(x: cellWidth, y: 0, width: dividerWidth, height: gameBoardLength))
divider.lineWidth = 1
UIColor.orangeColor().setStroke()
divider.stroke()
divider = UIBezierPath(rect: CGRect(x: cellWidth * 2 + dividerWidth, y: 0, width: dividerWidth, height: gameBoardLength))
divider.stroke()
}
}
And this is how I set up the dimensions to handle any sized screens:
var screenSize = CGRect()
let screenMargin: CGFloat = 20 // to the edge
var gameBoardIsPortrait = Bool()
var gameBoardLength = CGFloat()
var gameBoardPosX = CGFloat()
var gameBoardPosY = CGFloat()
let cellsPerRow: Int = 3
var cellWidth = CGFloat()
let dividerWidthGuide: CGFloat = 0.02 // guideline % of gameBoardLength
var dividerWidth = CGFloat()
let debugPrint = true
func setUpGameBoardCells() {
screenSize = UIScreen.mainScreen().bounds
// gameBoard is a square
gameBoardIsPortrait = (screenSize.height >= screenSize.width ? true : false)
gameBoardLength = min(screenSize.height, screenSize.width) - screenMargin * 2
gameBoardPosX = (screenSize.width - gameBoardLength) / 2
gameBoardPosY = (screenSize.height - gameBoardLength) / 2
// want cells & dividers on gameBoard to be whole numbers
dividerWidth = round(gameBoardLength * dividerWidthGuide)
let cellsTotalWidth: Int = Int(gameBoardLength) - Int(dividerWidth) * (cellsPerRow - 1)
let dividerWidthFudge: CGFloat = (cellsTotalWidth % cellsPerRow == 1 ? -1 : (cellsTotalWidth % cellsPerRow == 2 ? 1 : 0))
dividerWidth += dividerWidthFudge
cellWidth = CGFloat((cellsTotalWidth - Int(dividerWidthFudge) * (cellsPerRow - 1)) / cellsPerRow)
if debugPrint {
print("setUpCellDivision()->\nscreen: h=\(screenSize.height), w=\(screenSize.width)")
print("gameBoardIsPortrait=\(gameBoardIsPortrait), gameBoardLength=\(gameBoardLength), gameBoardPosX=\(gameBoardPosX), gameBoardPosY=\(gameBoardPosY)")
print("cellWidth=\(cellWidth), dividerWidth=\(dividerWidth)\n")
}
}
What is bizarre is that in xcode it looks right:
But in simulator it looks like this:
The problem is the setting of the frame from within drawRect. This would especially be a problem if you have any auto-layout constraints defined for the view.
The laying out of a view and the drawing of that view are two different steps, and you should therefore separate that logic.
Personally, I'd set up auto-layout constraints on the view to make sure that it is square, centered, and had the correct spacing. Then the view rendering is simplified:
#IBDesignable class GameBoardView: UIView {
override func drawRect(rect: CGRect) {
setUpGameBoardCells()
UIColor.orangeColor().setStroke()
var divider = UIBezierPath(rect: CGRect(x: cellWidth, y: 0, width: dividerWidth, height: bounds.size.height))
divider.lineWidth = 1
divider.stroke()
divider = UIBezierPath(rect: CGRect(x: cellWidth * 2 + dividerWidth, y: 0, width: dividerWidth, height: bounds.size.height))
divider.lineWidth = 1
divider.stroke()
}
let cellsPerRow = 3
let dividerWidthGuide: CGFloat = 0.02 // guideline % of gameBoardLength
var cellWidth: CGFloat!
var cellHeight: CGFloat!
var dividerWidth: CGFloat!
func setUpGameBoardCells() {
let gameBoardLength = min(bounds.size.height, bounds.size.width)
dividerWidth = round(gameBoardLength * dividerWidthGuide)
let cellsTotalWidth: Int = Int(gameBoardLength) - Int(dividerWidth) * (cellsPerRow - 1)
let dividerWidthFudge: CGFloat = (cellsTotalWidth % cellsPerRow == 1 ? -1 : (cellsTotalWidth % cellsPerRow == 2 ? 1 : 0))
dividerWidth! += dividerWidthFudge
cellWidth = CGFloat((cellsTotalWidth - Int(dividerWidthFudge) * (cellsPerRow - 1)) / cellsPerRow)
}
}
That yields:
Clearly, just repeat for your horizontal separators, too.
The simplest way would be adding UIViews(as separator lines) on yellow UIView and constraint them properly. You don't have to write too much of code.I would suggest to avoid the code in such cases.
You can try this. Draw the lines by calculating the frame of the view. The following will resize according to the frame.
func drawRect(frame frame: CGRect = CGRect(x: 52, y: 30, width: 90, height: 75)) {
//// Bezier Drawing
let bezierPath = UIBezierPath()
UIColor.blackColor().setStroke()
bezierPath.lineWidth = 1
bezierPath.stroke()
//// Rectangle Drawing
let rectanglePath = UIBezierPath(rect: CGRect(x: frame.minX + floor(frame.width * 0.32778) + 0.5, y: frame.minY + floor(frame.height * 0.06000) + 0.5, width: floor(frame.width * 0.35000) - floor(frame.width * 0.32778), height: floor(frame.height * 0.92667) - floor(frame.height * 0.06000)))
UIColor.blackColor().setStroke()
rectanglePath.lineWidth = 1
rectanglePath.stroke()
//// Rectangle 3 Drawing
let rectangle3Path = UIBezierPath(rect: CGRect(x: frame.minX + floor(frame.width * 0.68333) + 0.5, y: frame.minY + floor(frame.height * 0.06000) + 0.5, width: floor(frame.width * 0.70556) - floor(frame.width * 0.68333), height: floor(frame.height * 0.92667) - floor(frame.height * 0.06000)))
UIColor.blackColor().setStroke()
rectangle3Path.lineWidth = 1
rectangle3Path.stroke()
//// Rectangle 5 Drawing
let rectangle5Path = UIBezierPath(rect: CGRect(x: frame.minX + floor(frame.width * 0.07222) + 0.5, y: frame.minY + floor(frame.height * 0.63333) + 0.5, width: floor(frame.width * 0.92778) - floor(frame.width * 0.07222), height: floor(frame.height * 0.66000) - floor(frame.height * 0.63333)))
UIColor.blackColor().setStroke()
rectangle5Path.lineWidth = 1
rectangle5Path.stroke()
//// Rectangle 6 Drawing
let rectangle6Path = UIBezierPath(rect: CGRect(x: frame.minX + floor(frame.width * 0.07222) + 0.5, y: frame.minY + floor(frame.height * 0.31333) + 0.5, width: floor(frame.width * 0.92778) - floor(frame.width * 0.07222), height: floor(frame.height * 0.34000) - floor(frame.height * 0.31333)))
UIColor.blackColor().setStroke()
rectangle6Path.lineWidth = 1
rectangle6Path.stroke()
}
It seems as if your constraints aren't set up correctly. Try redoing the constraints to see if that fixes things.
Is there an easy way to rotate a NSImage in a Mac OSX app? Or just set the orientation from portrait to landscape using Swift?
I am playing around with CATransform3DMakeAffineTransform but I can't get it to work.
CATransform3DMakeAffineTransform(CGAffineTransformMakeRotation(CGFloat(M_PI) * 90/180))
It's the first time for me to work with transformations. So please be patient with me :) Maybe I'm working on a wrong approach...
Can anybody help me please?
Thanks!
public extension NSImage {
public func imageRotatedByDegreess(degrees:CGFloat) -> NSImage {
var imageBounds = NSZeroRect ; imageBounds.size = self.size
let pathBounds = NSBezierPath(rect: imageBounds)
var transform = NSAffineTransform()
transform.rotateByDegrees(degrees)
pathBounds.transformUsingAffineTransform(transform)
let rotatedBounds:NSRect = NSMakeRect(NSZeroPoint.x, NSZeroPoint.y, pathBounds.bounds.size.width, pathBounds.bounds.size.height )
let rotatedImage = NSImage(size: rotatedBounds.size)
//Center the image within the rotated bounds
imageBounds.origin.x = NSMidX(rotatedBounds) - (NSWidth(imageBounds) / 2)
imageBounds.origin.y = NSMidY(rotatedBounds) - (NSHeight(imageBounds) / 2)
// Start a new transform
transform = NSAffineTransform()
// Move coordinate system to the center (since we want to rotate around the center)
transform.translateXBy(+(NSWidth(rotatedBounds) / 2 ), yBy: +(NSHeight(rotatedBounds) / 2))
transform.rotateByDegrees(degrees)
// Move the coordinate system bak to normal
transform.translateXBy(-(NSWidth(rotatedBounds) / 2 ), yBy: -(NSHeight(rotatedBounds) / 2))
// Draw the original image, rotated, into the new image
rotatedImage.lockFocus()
transform.concat()
self.drawInRect(imageBounds, fromRect: NSZeroRect, operation: NSCompositingOperation.CompositeCopy, fraction: 1.0)
rotatedImage.unlockFocus()
return rotatedImage
}
var image = NSImage(named:"test.png")!.imageRotatedByDegreess(CGFloat(90)) //use only this values 90, 180, or 270
}
Updated for Swift 3:
public extension NSImage {
public func imageRotatedByDegreess(degrees:CGFloat) -> NSImage {
var imageBounds = NSZeroRect ; imageBounds.size = self.size
let pathBounds = NSBezierPath(rect: imageBounds)
var transform = NSAffineTransform()
transform.rotate(byDegrees: degrees)
pathBounds.transform(using: transform as AffineTransform)
let rotatedBounds:NSRect = NSMakeRect(NSZeroPoint.x, NSZeroPoint.y, pathBounds.bounds.size.width, pathBounds.bounds.size.height )
let rotatedImage = NSImage(size: rotatedBounds.size)
//Center the image within the rotated bounds
imageBounds.origin.x = NSMidX(rotatedBounds) - (NSWidth(imageBounds) / 2)
imageBounds.origin.y = NSMidY(rotatedBounds) - (NSHeight(imageBounds) / 2)
// Start a new transform
transform = NSAffineTransform()
// Move coordinate system to the center (since we want to rotate around the center)
transform.translateX(by: +(NSWidth(rotatedBounds) / 2 ), yBy: +(NSHeight(rotatedBounds) / 2))
transform.rotate(byDegrees: degrees)
// Move the coordinate system bak to normal
transform.translateX(by: -(NSWidth(rotatedBounds) / 2 ), yBy: -(NSHeight(rotatedBounds) / 2))
// Draw the original image, rotated, into the new image
rotatedImage.lockFocus()
transform.concat()
self.draw(in: imageBounds, from: NSZeroRect, operation: NSCompositingOperation.copy, fraction: 1.0)
rotatedImage.unlockFocus()
return rotatedImage
}
}
class SomeClass: NSViewController {
var image = NSImage(named:"test.png")!.imageRotatedByDegreess(degrees: CGFloat(90)) //use only this values 90, 180, or 270
}
Thank for this solution, however it did not worked perfectly for me.
As you may have noticed that pathBounds is not used anywhere. In my opinion is has to be used like so:
let rotatedBounds:NSRect = NSMakeRect(NSZeroPoint.x, NSZeroPoint.y , pathBounds.bounds.size.width, pathBounds.bounds.size.height )
Otherwise the image will be rotated but cropped to a square bounds.
Letting IKImageView do the heavy lifting:
import Quartz
extension NSImage {
func imageRotated(by degrees: CGFloat) -> NSImage {
let imageRotator = IKImageView()
var imageRect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
let cgImage = self.cgImage(forProposedRect: &imageRect, context: nil, hints: nil)
imageRotator.setImage(cgImage, imageProperties: [:])
imageRotator.rotationAngle = CGFloat(-(degrees / 180) * CGFloat(M_PI))
let rotatedCGImage = imageRotator.image().takeUnretainedValue()
return NSImage(cgImage: rotatedCGImage, size: NSSize.zero)
}
}
Here's a simple Swift (4+) solution to drawing an image that is rotated around the center:
extension NSImage {
/// Rotates the image by the specified degrees around the center.
/// Note that if the angle is not a multiple of 90°, parts of the rotated image may be drawn outside the image bounds.
func rotated(by angle: CGFloat) -> NSImage {
let img = NSImage(size: self.size, flipped: false, drawingHandler: { (rect) -> Bool in
let (width, height) = (rect.size.width, rect.size.height)
let transform = NSAffineTransform()
transform.translateX(by: width / 2, yBy: height / 2)
transform.rotate(byDegrees: angle)
transform.translateX(by: -width / 2, yBy: -height / 2)
transform.concat()
self.draw(in: rect)
return true
})
img.isTemplate = self.isTemplate // preserve the underlying image's template setting
return img
}
}
This one works also for non-square images, Swift 5.
extension NSImage {
func rotated(by degrees : CGFloat) -> NSImage {
var imageBounds = NSRect(x: 0, y: 0, width: size.width, height: size.height)
let rotatedSize = AffineTransform(rotationByDegrees: degrees).transform(size)
let newSize = CGSize(width: abs(rotatedSize.width), height: abs(rotatedSize.height))
let rotatedImage = NSImage(size: newSize)
imageBounds.origin = CGPoint(x: newSize.width / 2 - imageBounds.width / 2, y: newSize.height / 2 - imageBounds.height / 2)
let otherTransform = NSAffineTransform()
otherTransform.translateX(by: newSize.width / 2, yBy: newSize.height / 2)
otherTransform.rotate(byDegrees: degrees)
otherTransform.translateX(by: -newSize.width / 2, yBy: -newSize.height / 2)
rotatedImage.lockFocus()
otherTransform.concat()
draw(in: imageBounds, from: CGRect.zero, operation: NSCompositingOperation.copy, fraction: 1.0)
rotatedImage.unlockFocus()
return rotatedImage
}
}
Building on #FrankByte.com's code, this version should extend correctly in both x and y on any image and any rotation.
extension NSImage {
func rotated(by degrees: CGFloat) -> NSImage {
let sinDegrees = abs(sin(degrees * CGFloat.pi / 180.0))
let cosDegrees = abs(cos(degrees * CGFloat.pi / 180.0))
let newSize = CGSize(width: size.height * sinDegrees + size.width * cosDegrees,
height: size.width * sinDegrees + size.height * cosDegrees)
let imageBounds = NSRect(x: (newSize.width - size.width) / 2,
y: (newSize.height - size.height) / 2,
width: size.width, height: size.height)
let otherTransform = NSAffineTransform()
otherTransform.translateX(by: newSize.width / 2, yBy: newSize.height / 2)
otherTransform.rotate(byDegrees: degrees)
otherTransform.translateX(by: -newSize.width / 2, yBy: -newSize.height / 2)
let rotatedImage = NSImage(size: newSize)
rotatedImage.lockFocus()
otherTransform.concat()
draw(in: imageBounds, from: CGRect.zero, operation: NSCompositingOperation.copy, fraction: 1.0)
rotatedImage.unlockFocus()
return rotatedImage
}
}
I'm creating a simple player app. There is a circle, that shows a progress of playing a song.
What is the best way to draw this circle in Swift and make a mask? I assume I can draw a 2 circles putting the width stroke to the thickness I want and without filling it. And the white one has to be masked according to some parameter. I don't have an idea, how to mask it in a proper way.
I came up with this solution recently:
class CircularProgressView: UIView {
private let floatPi = CGFloat(M_PI)
private var progressColor = UIColor.greenColor()
private var progressBackgroundColor = UIColor.grayColor()
#IBInspectable var percent: CGFloat = 0.11 {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var lineWidth: CGFloat = 18
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
let origo = CGPointMake(frame.size.width / 2, frame.size.height / 2)
let radius: CGFloat = frame.size.height / 2 - lineWidth / 2
CGContextSetLineWidth(context, lineWidth)
CGContextMoveToPoint(context, frame.width / 2, lineWidth / 2)
CGContextAddArc(context, origo.x, origo.y, radius, floatPi * 3 / 2, floatPi * 3 / 2 + floatPi * 2 * percent, 0)
progressColor.setStroke()
let lastPoint = CGContextGetPathCurrentPoint(context)
CGContextStrokePath(context)
CGContextMoveToPoint(context, lastPoint.x, lastPoint.y)
CGContextAddArc(context, origo.x, origo.y, radius, floatPi * 3 / 2 + floatPi * 2 * percent, floatPi * 3 / 2, 0)
progressBackgroundColor.setStroke()
CGContextStrokePath(context)
}
}
You just have to set a correct frame to it (via code or interface builder), and set the percent property.
This solution is not using mask or two circles, just two arcs, the first start at 12 o clock and goes to 2 * Pi * progress percent, and the other arc is drawn from the end of the previous arc to 12 o clock.
Important: the percent property has to be between 0 and 1!