Is it possible to overload += operator in Swift? - ios

Is it possible to overload += operator in Swift to accept for example CGFloat arguments? If so, how?
My approach (below) does not work.
infix operator += { associativity left precedence 140 }
public func +=(inout left: CGFloat, right: CGFloat) {
left = left + right
}
(Edit) Important:
The coding approch above actually works. Please see my answer below for explanation why I thought it did not.

I am sorry, my bad. The operator += does not need to be overloaded for CGFloat arguments as such overload is included in Swift. I was trying to do something like
let a: CGFloat = 1.5
a += CGFloat(2.1)
This failed because I cannot asign to let and the error displayed by XCode confused me.
And of course, approach like in my original question (below) works for overloading operators.
infix operator += { associativity left precedence 140 }
public func +=(inout left: CGFloat, right: CGFloat) {
left = left + right
}
Please feel free to vote to close this question.

The += operator for CGFloat is already available, so you just have to use it - the only thing you can do is override the existing operator if you want it to behave in a different way, but that's of course discouraged.
Moreover, the += operator itself already exists, so there is no need to declare it again with this line:
infix operator += { associativity left precedence 140 }
You should declare a new operator only if it's a brand new one, such as:
infix operator <^^> { associativity left precedence 140 }
However, if you want to overload the += operator for other type(s) for which it is not defined, this is the correct way:
func += (inout lhs: MyType, rhs: MyType) {
lhs = // Your implementation here
}

Related

Identify slow code to optimize build time

I'm using these Compilation Swift Flag to identify codes that slow down the compilation time:
-Xfrontend -warn-long-function-bodies=100
-Xfrontend -warn-long-expression-type-checking=100
Then after building, I get warnings like these:
Instance method 'startFadePositionTitle()' took 2702ms to type-check (limit: 500ms)
for this part of the code:
func startFadePositionTitle() -> CGFloat {
let value: CGFloat = ((backgroundImage.frame.height/2 - contentTitle.frame.height/2) - navbarView.frame.height)/2
return value
}
Can someone explains me what is wrong in this method and what could I possibly improve?
You should break it to smaller chunks, then Swift can do type check more easily. Also the more you tell, the less Swift has to think. So you can help compiler and tell it anything you already know:
func beginFadePositionTitle() -> CGFloat {
let n: CGFloat = 2
let a: CGFloat = self.backgroundImage.frame.height/n
let b: CGFloat = self.contentTitle.frame.height/n
let ab: CGFloat = a - b
let c: CGFloat = self.navbarView.frame.height
let abc: CGFloat = ab - c
return abc/n
}
Instance method 'beginFadePositionTitle()' took 1ms to type-check (limit: 1ms)
This is the result when you tell everything to compiler. See the difference?
I recommend to try this one
func startFadePositionTitle() -> CGFloat {
return ((backgroundImage.frame.height - contentTitle.frame.height)/2.0 -
navbarView.frame.height)/2.0
}
with my Xcode 11.2 / Catalina (tested assuming that all those frames are CGRects), there is no such warning with those compiler flags. In the corner case it is possible to use CGFloat(2.0) in corresponding places, but I think it is superfluous.
You are calculating value every time when you call this method startFadePositionTitle
You can specify that once and after than you can use freely.
let value: CGFloat = ((backgroundImage.frame.height/2 - contentTitle.frame.height/2) - navbarView.frame.height)/2
Use that in viewDidLoad method
And optimize your code like that
func startFadePositionTitle() -> CGFloat {
return value
}
Floating point division and square root take considerably longer to compute than addition and multiplication. The latter two are computed directly while the former are usually computed with an iterative algorithm. The most common approach is to use a division-free Newton-Raphson iteration to get an approximation to the reciprocal of the denominator (division) or the reciprocal square root, and then multiply by the numerator (division) or input argument (square root).
Source :
Why is float division slow?
So i have made it to one division:
((x - y)/2 - z)/2 = (x-y-2z)/4
i.e. You can just write something like this
(backgroundImage.frame.height - contentTitle.frame.height - 2.0*navbarView.frame.height)/4.0
So small change to function would be something like this
func startFadePositionTitle() -> CGFloat {
return (backgroundImage.frame.height - contentTitle.frame.height - 2.0*navbarView.frame.height)/4.0
}

IOS Swift Binary operator '<=' cannot be applied to operands of type 'CGFloat?' and 'Int' but '==' works

I understand the difference between CGFloat and Int but strangely I was able to use in an if loop
if image?.size.width == 200 {
// CODE
}
where image is an unwrapped UIImage and the size.width is of CGFloat whereas the number 200 is of type Int and this can be run on Xcode with no compiler errors.
However with:
if image?.size.width <= 200 {
// CODE
}
Xcode returns the error:
Binary operator '<=' cannot be applied to operands of type 'CGFloat?'
and 'Int'
and Xcode can not run. I have looked through Apple docs on CGFloat and their '==' and '<=' operator specifications and they make sure that both sides have to be CGFloats and I dont have any extensions handling == operators between CGFloats and Int (Unless its in a cocoapod?)
My question is why does one work and the other is an error?
You misunderstood why the first case worked: 200 is a numeric literal, the compiler saw that you are checking this literal for equality against an Optional<CGFloat> so it interpreted 200 as a CGFloat. Since an Optional<CGFloat> is either nil or an actual number, you can test if it's equal to 200.
The second case is about ordering of the two values. The compiler does not have a rule about ranking nil against an actual value. You have to make that decision yourself.
In your case, I think this is what you actually want:
if let width = image?.size.width, width <= 200 {
// CODE
}
It is just because you didn't unwrapped your float value ignorer to compare.
You cannot compare optional to non-optional for getting order.
See Even if you have both side CGFloat one as CGFLoat? and other non-optional CGFloat you can equat "==" them but you can't get get order by "<", ">", ">=", "<=" operators

How two UInt8 variables are added in iOS Swift?

Say there are two variables:
let number1 : UInt8 = 100;
let number2 : UInt8 = 100;
You add and print them
print(number1 + number2) //This prints 200
Now define one more
let number3 : UInt8 = 200;
And try to add now
print(number1 + number3) // Throws execution was interrupted
I understand that the sum of number1 and number3 would be out of range of UInt8 but explicit casting also does not help, for example following line also gives the same error:
print(UInt8(number1 + number3)) // Throws execution was interrupted
The way I found was to do the following:
print(Int(number1) + Int(number3))
Is there a better way of adding UInt8 number when their sum goes out of range?
Girish K Gupta,
UInt8 has max range 0 to 255. Which you can check using UInt8.min and UInt8.max. Basically 0 to 2 power 8.
Issue with print(number1 + number3) will return 300. 300 is greater then 255 hence crash.
When you add two UInt8 result will be by default casted to UInt8 hence the crash
Finally when you Int(number1) + Int(number3) you are forcefully casting number1 and number3 to Int.
When you use Int, range of its value depends on the platform on which you are running it either 32 bit or 64 bit. for example its range can be -2,147,483,648 to 2,147,483,647 for 32 bit.
When you add Int to Int result will be typecasted to Int. And believe me 300 is way inside the range :)
As per your question is there a better way to do it :)
Apple's docs clearly specifies and instructs to use Int rather then UInt8 or UInt32 or even UInt64 until and unless using UInt8, UInt32 or UInt64 is absolutely essential.
Here is the quote from apple's doc :)
“Use UInt only when you specifically need an unsigned integer type
with the same size as the platform’s native word size. If this is not
the case, Int is preferred, even when the values to be stored are
known to be non-negative. A consistent use of Int for integer values
aids code interoperability, avoids the need to convert between
different number types, and matches integer type inference,”
Excerpt From: Apple Inc. “The Swift Programming Language (Swift 2.2).”
iBooks. https://itun.es/in/jEUH0.l
So best thing for you to do :) follow apples instruction :) Change the number1,number2 and number3 to Int :) Problem solved :)
Hence no crash :)
As you've said casting both UInt8 variables to Int overrides the default exception on overflow as the resulting Int now has room to fit the sum.
To avoid casting the variables for every operation we would like to overload the operator like this:
func + (left: UInt8, right: UInt8) -> Int {
return Int(left) + Int(right)
}
However this will give us a compiler error as the + operator is already defined for adding two UInt8's.
What we could do instead is to define a custom operator, say ^+ to mean addition of two UInt8's but add them as Int's like so:
infix operator ^+ { associativity left precedence 140 }
func ^+ (left: UInt8, right: UInt8) -> Int {
return Int(left) + Int(right)
}
Then we can use it in our algorithms:
print(number1 ^+ number3) // Prints 300
If you however want the result to just overflow you can use the overflow operators from the standard library:
print(number1 &+ number3) // Prints 44

'[(UIView)]' is not identical to 'UInt8' when using += in Xcode 6 beta 5. Use append method instead?

I was using += to an a UIView to an array and that no longer seems to work. The line
dropsFound += hitView
Gives an error '[(UIView)]' is not identical to 'UInt8'
Here is part of the method. Note that as of Xcode 6 beta 5, hitTest now returns an optional, so it was necessary to say
hitView?.superview
instead of
hitView.superview
in the 'if' statement.
func removeCompletedRows() -> Bool {
println(__FUNCTION__)
var dropsToRemove = [UIView]()
for var y = gameView.bounds.size.height - DROP_SIZE.height / 2; y > 0; y -= DROP_SIZE.height {
var rowIsComplete = true
var dropsFound = [UIView]()
for var x = DROP_SIZE.width / 2; x <= gameView.bounds.size.width - DROP_SIZE.width / 2; x += DROP_SIZE.width {
let hitView = gameView.hitTest(CGPointMake(x, y), withEvent: nil)
if hitView?.superview === gameView {
dropsFound += hitView
} else {
rowIsComplete = false
break
}
}
... remainder of method omitted
That changed in the last release. From the beta 5 release notes:
The += operator on arrays only concatenates arrays, it does not append an element. This resolves ambiguity working with Any, AnyObject and related types.
So if the left side of += is an array, the right now must be as well.
so:
dropsFound.append(hitView)
Or if you really wanted to use += you could probably do:
dropsFound += [hitView]
But that would be a little silly. Use append like the error message suggests.
The solution appears to be that you need to use the append method for the array rather than +=. I don't know the reason for this, so another answer might be more appropriate.
Instead of
dropsFound += hitView
use
dropsFound.append(hitView!)
Again, note that the UIView returned from hitTest is an optional as of Xcode 6 beta 5.
I verified that this is a general problem with arrays with the following playground sample. A bug report has been posted to Apple.
var s: [String] = []
// s += "hello" // error: '[String]' is not identical to 'UInt8'
s.append("hello")
s
There is an additional complexity if you are trying to append a tuple, and perhaps other types.
// line below no longer works in Xcode 6 beta 5
// and you will also get an error trying to append the tuple directly
// which is probably a bug
// possibleFlipsArray += (x, y)
// possibleFlipsArray.append((x, y))
let tempTuple = (x, y)
possibleFlipsArray.append(tempTuple)
This probably deserves its own question, though I think it is just another bug, so again I've posted it to Apple.
adding object in array dropsFound += hitView,in this way, is removed in last release.
You can add element in array by using this syntax dropsFound += [hitView] or dropsFound.append(hitView)

Swift function that takes in array giving error: '#lvalue $T24' is not identical to 'CGFloat'

So I'm writing a lowpass accelerometer function to moderate the jitters of the accelerometer. I have a CGFloat array to represent the data and i want to damp it with this function:
// Damps the gittery motion with a lowpass filter.
func lowPass(vector:[CGFloat]) -> [CGFloat]
{
let blend:CGFloat = 0.2
// Smoothens out the data input.
vector[0] = vector[0] * blend + lastVector[0] * (1 - blend)
vector[1] = vector[1] * blend + lastVector[1] * (1 - blend)
vector[2] = vector[2] * blend + lastVector[2] * (1 - blend)
// Sets the last vector to be the current one.
lastVector = vector
// Returns the lowpass vector.
return vector
}
In this case, lastVector is defined as follows up at the top of my program:
var lastVector:[CGFloat] = [0.0, 0.0, 0.0]
The three lines in the form vector[a] = ... give me the errors. Any ideas as to why i am getting this error?
That code seems to compile if you pass the array with the inout modifier:
func lowPass(inout vector:[CGFloat]) -> [CGFloat] {
...
}
I'm not sure whether that's a bug or not. Instinctively, if I pass an array to a function I expect to be able to modify it. If I pass with the inout modifier, I'd expect to be able to make the original variable to point to a new array - similar to what the & modifier does in C and C++.
Maybe the reason behind is that in Swift there are mutable and immutable arrays (and dictionaries). Without the inout it's considered immutable, hence the reason why it cannot be modified.
Addendum 1 - It's not a bug
#newacct says that's the intended behavior. After some research I agree with him. But even if not a bug I originally considered it wrong (read up to the end for conclusions).
If I have a class like this:
class WithProp {
var x : Int = 1
func SetX(newVal : Int) {
self.x = newVal
}
}
I can pass an instance of that class to a function, and the function can modify its internal state
var a = WithProp()
func Do1(p : WithProp) {
p.x = 5 // This works
p.SetX(10) // This works too
}
without having to pass the instance as inout.
I can use inout instead to make the a variable to point to another instance:
func Do2(inout p : WithProp) {
p = WithProp()
}
Do2(&a)
With that code, from within Do2 I make the p parameter (i.e. the a variable) point to a newly created instance of WithProp.
The same cannot be done with an array (and I presume a dictionary as well). To change its internal state (modify, add or remove an element) the inout modifier must be used. That was counterintuitive.
But everything gets clarified after reading this excerpt from the swift book:
Swift’s String, Array, and Dictionary types are implemented as structures. This means that strings, arrays, and dictionaries are copied when they are assigned to a new constant or variable, or when they are passed to a function or method.
So when passed to a func, it's not the original array, but a copy of it - Hence any change made to it (even if possible) wouldn't be done on the original array.
So, in the end, my original answer above is correct and the experienced behavior is not a bug
Many thanks to #newacct :)
Since Xcode 6 beta 3, modifying the contents of an Array is a mutating operation. You cannot modify a constant (i.e. let) Array; you can only modify a non-constant (i.e. var) Array.
Parameters to a function are constants by default. Therefore, you cannot modify the contents of vector since it is a constant. Like other parameters, there are two ways to be able to change a parameter:
Declare it var, in which case you can assign to it, but it is still passed by value, so any changes to the parameter has no effect on the calling scope.
Declare it inout, in which case the parameter is passed by reference, and any changes to the parameter is just like you made the changes on the variable in the calling scope.
You can see in the Swift standard library that all the functions that take an Array and mutate it, like sort(), take the Array as inout.
P.S. this is just like how arrays work in PHP by the way
Edit: The following worked for Xcode Beta 2. Apparently, the syntax and behavior of arrays has changed in Beta 3. You can no longer modify the contents of an array with subscripts if it is immutable (a parameter not declared inout or var):
Not valid with the most recent changes to the language
The only way I could get it to work in the play ground was change how you are declaring the arrays. I suggest trying this (works in playground):
import Cocoa
let lastVector: CGFloat[] = [0.0,0.0,0.0]
func lowPass(vector:CGFloat[]) -> CGFloat[] {
let blend: CGFloat = 0.2
vector[0] = vector[0] * blend + lastVector[0] * ( 1 - blend)
vector[1] = vector[1] * blend + lastVector[1] * ( 1 - blend)
vector[2] = vector[2] * blend + lastVector[2] * ( 1 - blend)
return vector
}
var test = lowPass([1.0,2.0,3.0]);
Mainly as a followup for future reference, #newacct's answer is the correct one. Since the original post showed a function that returns an array, the correct answer to this question is to tag the parameter with var:
func lowPass(var vector:[CGFloat]) -> [CGFloat] {
let blend:CGFloat = 0.2
// Smoothens out the data input.
vector[0] = vector[0] * blend + lastVector[0] * (1 - blend)
vector[1] = vector[1] * blend + lastVector[1] * (1 - blend)
vector[2] = vector[2] * blend + lastVector[2] * (1 - blend)
// Sets the last vector to be the current one.
lastVector = vector
// Returns the lowpass vector.
return vector
}

Resources