Is a variable or a function? - swift

I see some code on github.
private static var allMemes:[MemeModel]{
return getMemeStorage().memes
}
Is this a variable or a function or other?
Thanks

Computed Properties
In addition to stored properties, classes, structures, and
enumerations can define computed properties, which do not actually
store a value. Instead, they provide a getter and an optional setter
to retrieve and set other properties and values indirectly.
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
Ref :
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html

Related

Is it correct to convert from computed property to lazy variable under constant struct?

I wanted to change my code to use lazy variable instead of Computed property.
this code works
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 10.0, height = 10.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point{
get{
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
}}
let shape = Rect()
print(shape.center)
But this code produces an error
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 10.0, height = 10.0
}
struct Rect {
var origin = Point()
var size = Size()
lazy var center = Point(x: origin.x + (size.width / 2), y: origin.y + (size.height / 2))
}
let shape = Rect()
print(shape.center)
I can fix the error if I changed let shape = Rect() to variable type var shape = Rect() OR when I change Rect to class instead of struct
What is wrong with my code and why does it give me error?
Notice that getting a lazy property potentially changes the struct value. Its state could change from "the lazy property has not been computed" to "the lazy property has been computed". This is why the getters of lazy properties are mutating.
A let constant does not allow its value to change, so you can't use mutating members on it. vars do, so that's why using a var makes your code compile.
Changing Rect to a class makes it a reference type. This means that the let constant shape stores a reference to an instance of Rect, and this reference cannot be changed (e.g. you can't do shape = someOtherRect afterwards). However, the instance of Rect can still be changed (remember that the let constant is only storing a reference to it), and so getting a lazy property is fine on shape, when Rect is a class. Doing this will only potentially change the instance of Rect.
In my opinion, it is not appropriate to change Rect.center to a lazy property. After the first use of its getter, center will stay unchanged even if you change origin or size:
var shape = Rect()
print(shape.center)
shape.size = Size(width: 100, height: 100)
// now shape.center is incorrect!

Making Pie Chart with Slice space(Space between each color in Pie Chart)

I'm working on a project in which i need to implement the pie chart like above. I need to add a space between the slices as picture attached. Need to have a space in slices with arc and values with percentage.
I've tried to implement it but i couldn't succeed. I use get arc shapes wrong.
Please help me. Thanks.
import UIKit
private extension CGFloat {
/// Formats the CGFloat to a maximum of 1 decimal place.
var formattedToOneDecimalPlace : String {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 1
return formatter.string(from: NSNumber(value: self.native)) ?? "\(self)"
}
}
/// Defines a segment of the pie chart
struct Segment {
/// The color of the segment
var color : UIColor
/// The name of the segment
var name : String
/// The value of the segment
var value : CGFloat
}
class PieChartView: UIView {
/// An array of structs representing the segments of the pie chart
var segments = [Segment]() {
didSet { setNeedsDisplay() } // re-draw view when the values get set
}
/// Defines whether the segment labels should be shown when drawing the pie chart
var showSegmentLabels = true {
didSet { setNeedsDisplay() }
}
/// Defines whether the segment labels will show the value of the segment in brackets
var showSegmentValueInLabel = false {
didSet { setNeedsDisplay() }
}
/// The font to be used on the segment labels
var segmentLabelFont = UIFont.systemFont(ofSize: 14) {
didSet {
textAttributes[NSAttributedStringKey.font] = segmentLabelFont
setNeedsDisplay()
}
}
private let paragraphStyle : NSParagraphStyle = {
var p = NSMutableParagraphStyle()
p.alignment = .center
return p.copy() as! NSParagraphStyle
}()
private lazy var textAttributes : [NSAttributedStringKey : Any] = {
return [NSAttributedStringKey.paragraphStyle : self.paragraphStyle, NSAttributedStringKey.font : self.segmentLabelFont]
}()
override init(frame: CGRect) {
super.init(frame: frame)
isOpaque = false // when overriding drawRect, you must specify this to maintain transparency.
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
// get current context
let ctx = UIGraphicsGetCurrentContext()
// radius is the half the frame's width or height (whichever is smallest)
let radius = min(frame.width, frame.height) * 0.5
// center of the view
let viewCenter = CGPoint(x: bounds.size.width * 0.5, y: bounds.size.height * 0.5)
// enumerate the total value of the segments by using reduce to sum them
let valueCount = segments.reduce(0, {($0 + $1.value)})
// the starting angle is -90 degrees (top of the circle, as the context is flipped). By default, 0 is the right hand side of the circle, with the positive angle being in an anti-clockwise direction (same as a unit circle in maths).
var startAngle = -CGFloat.pi * 0.5
// loop through the values array
for segment in segments {
// set fill color to the segment color
ctx?.setFillColor(segment.color.cgColor)
// update the end angle of the segment
let endAngle = startAngle + .pi * 2 * (segment.value / valueCount)
// move to the center of the pie chart
ctx?.move(to: viewCenter)
// add arc from the center for each segment (anticlockwise is specified for the arc, but as the view flips the context, it will produce a clockwise arc)
ctx?.addArc(center: viewCenter, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false)
// fill segment
ctx?.fillPath()
// ctx?.setStrokeColor(UIColor.white.cgColor)
// ctx?.strokePath()
if showSegmentLabels { // do text rendering
// get the angle midpoint
let halfAngle = startAngle + (endAngle - startAngle) * 0.5;
// the ratio of how far away from the center of the pie chart the text will appear
let textPositionValue : CGFloat = 0.65
// get the 'center' of the segment. It's slightly biased to the outer edge, as it's wider.
let segmentCenter = CGPoint(x: viewCenter.x + radius * textPositionValue * cos(halfAngle), y: viewCenter.y + radius * textPositionValue * sin(halfAngle))
// text to render – the segment value is formatted to 1dp if needed to be displayed.
// Previous
//let textToRender = showSegmentValueInLabel ? "\(segment.name) \(segment.value.formattedToOneDecimalPlace))" : segment.name
// Change
let textToRender = showSegmentValueInLabel ? "\(segment.value.formattedToOneDecimalPlace)%" : segment.name
// get the color components of the segement color
guard let colorComponents = segment.color.cgColor.components else { return }
// get the average brightness of the color
let averageRGB = (colorComponents[0] + colorComponents[1] + colorComponents[2]) / 3
// if too light, use black. If too dark, use white
textAttributes[NSAttributedStringKey.foregroundColor] = (averageRGB > 0.7) ? UIColor.black : UIColor.white
// the bounds that the text will occupy
var renderRect = CGRect(origin: .zero, size: textToRender.size(withAttributes: textAttributes))
// center the origin of the rect
renderRect.origin = CGPoint(x: segmentCenter.x - renderRect.size.width * 0.5, y: segmentCenter.y - renderRect.size.height * 0.5)
// draw text in the rect, with the given attributes
textToRender.draw(in: renderRect, withAttributes: textAttributes)
}
// update starting angle of the next segment to the ending angle of this segment
startAngle = endAngle
}
}
}
In your draw(_:), implement this.
override func draw(_ rect: CGRect) {
let anglePI2 = (CGFloat.pi * 2)
let center = CGPoint.init(x: bounds.size.width / 2, y: bounds.size.height / 2)
let radius = min(bounds.size.width, bounds.size.height) / 2;
let lineWidth: CGFloat = 1;
let ctx = UIGraphicsGetCurrentContext()
ctx?.setLineWidth(lineWidth)
var currentAngle: CGFloat = 0
var totalValue: CGFloat = segments.reduce(0) { $0 + $1.value }
if totalValue <= 0 {
totalValue = 1
}
let iRange = 0 ..< segments.count
for i in iRange {
let segment = segments[i]
// calculate percent
let percent = segment.value / totalValue
let angle = anglePI2 * percent
ctx?.beginPath()
ctx?.move(to: center)
ctx?.addArc(center: center, radius: radius - lineWidth, startAngle: currentAngle, endAngle: currentAngle + angle, clockwise: false)
ctx?.closePath()
ctx?.setFillColor(segment.color.cgColor)
ctx?.fillPath()
ctx?.beginPath()
ctx?.move(to: center)
ctx?.addArc(center: center, radius: radius - (lineWidth / 2), startAngle: currentAngle, endAngle: currentAngle + angle, clockwise: false)
ctx?.closePath()
ctx?.setStrokeColor(UIColor.white.cgColor)
ctx?.strokePath()
currentAngle += angle
}
}
Explanation
- Calculate pi equivalent to 360 deg.
- Calculate center.
- Calculate radius of the blocks.
- Calculate totalValue of [Segment]. This is used when calculating the percent of the slice in the circle.
- Loop through segment, calculate percentage of the current segment, make arc for filling (radius - lineWidth), remake arc for stroking path(radius - lineWidth / 2).
Here is the result
In my view controller, I added the data (For your info). Like this
scv.segments = [
Segment.init(color: UIColor.init(red: 0xfe/0xff, green: 0x4a/0xff, blue: 0x49/0xff, alpha: 1), value: 20),
Segment.init(color: UIColor.init(red: 0x2a/0xff, green: 0xb7/0xff, blue: 0xca/0xff, alpha: 1), value: 15),
Segment.init(color: UIColor.init(red: 0xfe/0xff, green: 0xd7/0xff, blue: 0x66/0xff, alpha: 1), value: 15),
Segment.init(color: UIColor.init(red: 0x4a/0xff, green: 0x4e/0xff, blue: 0x4d/0xff, alpha: 1), value: 50)
]
Updated
Here is a complete code for your question and additional requirement as requested.
struct Segment {
// the color of a given segment
var color: UIColor
// the value of a given segment – will be used to automatically calculate a ratio
var value: CGFloat
}
class SliceView: UIView {
/// An array of structs representing the segments of the pie chart
var segments = [Segment]() {
didSet {
totalValue = segments.reduce(0) { $0 + $1.value }
setupLabels()
setNeedsDisplay() // re-draw view when the values get set
layoutLabels();
}
}
private var totalValue: CGFloat = 1;
private var labels: [UILabel] = []
override init(frame: CGRect) {
super.init(frame: frame)
isOpaque = false // when overriding drawRect, you must specify this to maintain transparency.
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
let anglePI2 = (CGFloat.pi * 2)
let center = CGPoint.init(x: bounds.size.width / 2, y: bounds.size.height / 2)
let radius = min(bounds.size.width, bounds.size.height) / 2;
let lineWidth: CGFloat = 1;
let ctx = UIGraphicsGetCurrentContext()
ctx?.setLineWidth(lineWidth)
var currentAngle: CGFloat = 0
if totalValue <= 0 {
totalValue = 1
}
let iRange = 0 ..< segments.count
for i in iRange {
let segment = segments[i]
// calculate percent
let percent = segment.value / totalValue
let angle = anglePI2 * percent
ctx?.beginPath()
ctx?.move(to: center)
ctx?.addArc(center: center, radius: radius - lineWidth, startAngle: currentAngle, endAngle: currentAngle + angle, clockwise: false)
ctx?.closePath()
ctx?.setFillColor(segment.color.cgColor)
ctx?.fillPath()
ctx?.beginPath()
ctx?.move(to: center)
ctx?.addArc(center: center, radius: radius - (lineWidth / 2), startAngle: currentAngle, endAngle: currentAngle + angle, clockwise: false)
ctx?.closePath()
ctx?.setStrokeColor(UIColor.white.cgColor)
ctx?.strokePath()
currentAngle += angle
}
}
override func layoutSubviews() {
super.layoutSubviews()
self.layoutLabels()
}
private func setupLabels() {
var diff = segments.count - labels.count;
if diff >= 0 {
for _ in 0 ..< diff {
let lbl = UILabel()
self.addSubview(lbl)
labels.append(lbl)
}
} else { // diff < 0 (minus values)
// loop until diff is 0
//
while diff != 0 {
var lbl: UILabel!
// if there is no more labels to remove
// break the loop
if labels.count <= 0 {
break;
}
// get the last label
lbl = labels.removeLast()
if lbl.superview != nil {
lbl.removeFromSuperview()
}
// increment the minus value by 1
diff += 1;
}
}
for i in 0 ..< segments.count {
let lbl = labels[i]
lbl.textColor = UIColor.white
// Change here for your text display
// I currently display percent of each pies
lbl.text = String.init(format: "%0.1f", segments[i].value)
lbl.font = UIFont.init(name: "ArialRoundedMT-Bold", size: 12)
}
}
func layoutLabels() {
let anglePI2 = CGFloat.pi * 2
let center = CGPoint.init(x: bounds.size.width / 2, y: bounds.size.height / 2)
let radius = min(bounds.size.width / 2, bounds.size.height / 2) / 2
var currentAngle: CGFloat = 0;
let iRange = 0 ..< labels.count
for i in iRange {
let lbl = labels[i]
let percent = segments[i].value / totalValue
let intervalAngle = anglePI2 * percent;
lbl.frame = .zero;
lbl.sizeToFit()
let x = center.x + radius * cos(currentAngle + (intervalAngle / 2))
let y = center.y + radius * sin(currentAngle + (intervalAngle / 2))
lbl.center = CGPoint.init(x: x, y: y)
currentAngle += intervalAngle
}
}
}
The result is

Ways in using a struct to get and set a value?

The problem I'm having with structs is trying to get and set values.
I try to store my ivars as such:
let origin.x = 10, origin.y = 10
However, I'm confused as to how to utilize the getter and setters. I have an origin, but what should I put into my newCenter parameter?
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point(x: 10, y: 10)
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
This appears to be mostly correct - nothing need to be "put into" the newCenter parameter. However, you are incorrectly dividing by size.width instead of size.height when calculating the y position.
When you use foo.center = bar, bar will be the value of newCenter.

Swift binary operator '+' cannot be applied to two CGFloat operands

I am writing a function in Swift to detect which hexagon I am clicking on. But I ran into a peculiar error message stating that I cannot add two CGFloats. Whatever I did, e.g. changing let to var, declare and assign separately, did not work. I guess there must be something else wrong, but I cannot find it. The code is as follows:
func pointToCoordinate(position: CGPoint) -> (Int, Int) {
var gridHeight = 2 * singleRadius / sqrt(CGFloat(3)) * 1.5
var gridWidth = 2 * singleRadius
var halfWidth = singleRadius
var relativePosition = CGPoint(x: position.x - totalRadius / 2, y: position.y - totalRadius / 2)
println((relativePosition.y / gridHeight))
var row = -(cgfloatToInt)(relativePosition.y / gridHeight)
var column: Int
println((relativePosition.x + halfWidth * CGFloat(row + 1)) / (gridWidth))
column = cgfloatToInt((relativePosition.x + halfWidth * CGFloat(row + 1)) / (gridWidth))
// var innerY: CGFloat = CGFloat(relativePosition.y) + CGFloat(gridHeight * row)
var innerX = relativePosition.x
var innerY = relativePosition.y
innerY = innerY - CGFloat(gridHeight * row)
println((innerX, innerY))
return (column, row)
}
The error message is wrong. The problem is that you are trying to multiply an Int and a CGFloat.
Replace:
innerY = innerY - CGFloat(gridHeight * row)
with:
innerY = innerY - gridHeight * CGFloat(row)
The answer above is for the current version of your code. For the commented out version that corresponds to the error message you posted:
Replace:
var innerY: CGFloat = CGFloat(relativePosition.y) + CGFloat(gridHeight * row)
with
var innerY: CGFloat = CGFloat(relativePosition.y) + gridHeight * CGFloat(row)
Looking through all the answers, castings, conversion and extensions, I think the best solution yet to say for Swift is Operator Overload. Code sample below for Swift 3.0:
/// overloads -/+ between a cgfloat on the left and an int/double on the right
func - (left: CGFloat, right: Double) -> CGFloat {
return left - CGFloat(right);
}
func - (left: CGFloat, right: Int) -> CGFloat {
return left - CGFloat(right);
}
func + (left: CGFloat, right: Double) -> CGFloat {
return left + CGFloat(right);
}
func + (left: CGFloat, right: Int) -> CGFloat {
return left + CGFloat(right);
}
Put this into a global place OUTSIDE of any class. See magic occurs.
Indeed, there is something else wrong, this works:
import QuartzCore
let a:CGFloat = 1
let b:CGFloat = 2
let c = a + b
var innerY: CGFloat = CGFloat(1.0) + CGFloat(2.0)
and CGFloat implements the FloatingPointType type
Late to join. Based on vacawama's answer, you all know the problem is not having the same data type.
CGFloat needs to be written multiple times sometimes. I found this easier via extension. So sharing an answer. You can create a simple extension like below. Ofcourse, this extension can be further modified.
import CoreGraphics
extension Int {
var cgf: CGFloat { return CGFloat(self) }
var f: Float { return Float(self) }
}
extension Float {
var cgf: CGFloat { return CGFloat(self) }
}
extension Double {
var cgf: CGFloat { return CGFloat(self) }
}
extension CGFloat {
var f: Float { return Float(self) }
}
Now we can write
innerY = innerY - gridHeight * row.cgf
iOS 15, Swift 5.x
I had this problem, was solved by unwrapping the value... the floats in my case were optionals.

Symmetry in using structures for conversion. What is best practice?

I've been playing with Swift and I encoded an obvious conversion structure:
struct MutableAngle {
var degrees : CGFloat
var radians : CGFloat {
return degrees * CGFloat(M_PI) / 180.0
}
init(inRadians : CGFloat) {
degrees = inRadians * 180.0 / CGFloat(M_PI)
}
init(inDegrees : CGFloat) {
degrees = inDegrees
}
}
Now this is fine but inelegant since it doesn't treat degrees and radians symmetrically although it does give mutability. This is really a structure which should be called Degrees and which can provide radians. For instance, I can write:
var angle : MutableAngle
angle.degrees = 45.0
but not
var angle : MutableAngle
angle.radians = 0.75
Here's a final version:
struct Angle {
let degrees : CGFloat
let radians : CGFloat
init(inRadians : CGFloat ) {
radians = inRadians
degrees = radians * CGFloat (180 / M_PI)
}
init(inDegrees : Float ) {
degrees = inDegrees
radians = degrees * CGFloat (M_PI / 180)
}
}
Use as follows:
var alpha = Angle(inDegrees: 45)
alpha.degrees // returns 45
alpha.radians // returns 0.7853982
// alpha.radians = 0.9 ... is now illegal with let constants
// must use constructor ... provided alpha was defined using 'var'
// i.e. the struct itself is mutable
alpha = Angle(inRadians: 0.9)
alpha.radians // returns 0.7853982
alpha.degrees // returns 45
Switching from var to let makes it mutable/immutable depending on how alpha is defined and I'm now obliged to use the constructors which is good. So its symmetric. It also has the merit that a calculation is not required every time I want to use the radians.
Two things here:
In Swift you don't need a separate mutable type for value types - that's handled by whoever is instantiating the type by using let or var.
Your radians computed property only has a getter - you can do what you want with both a setter and a getter.
My implementation:
struct Angle {
var degrees : CGFloat = 0
var radians : CGFloat {
get {
return degrees * CGFloat(M_PI) / 180.0
}
set {
degrees = newValue * 180.0 / CGFloat(M_PI)
}
}
init(inRadians : CGFloat) {
radians = inRadians
}
init(inDegrees : CGFloat) {
degrees = inDegrees
}
}
Useage:
// immutable
let angle = Angle(inDegrees: 180)
println(angle.radians)
// next line gives an error: can't assign to an immutable instance
angle.radians = angle.radians * 2
// mutable copy
var mutableAngle = angle
mutableAngle.degrees = 10
println(mutableAngle.radians)
// 0.1745...
mutableAngle.radians = CGFloat(M_PI)
println(mutableAngle.degrees)
// 180.0
A possible solution is to use enum with associated value:
enum MutableAngle {
case Radian(CGFloat)
case Degree(CGFloat)
init(radians:CGFloat) {
self = .Radian(radians)
}
init(degrees:CGFloat) {
self = .Degree(degrees)
}
var radians:CGFloat {
get {
switch self {
case .Radian(let val): return val
case .Degree(let val): return val * CGFloat(M_PI) / 180.0
}
}
set {
self = .Radian(newValue)
}
}
var degrees:CGFloat {
get {
switch self {
case .Degree(let val): return val
case .Radian(let val): return val * 180.0 / CGFloat(M_PI)
}
}
set {
self = .Degree(newValue)
}
}
}
var angle = MutableAngle(radians: 1)
angle.degrees // -> 57.2957795130823
angle.degrees = 180
angle.radians // -> 3.14159265358979