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

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.

Related

Where have misused a type?

I'm having a beginner types problem in swift and the error message isn't very helpful. I get the error
The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
from the following code:
import Foundation
import SwiftUI
import CoreGraphics
struct SwiftUIView: View {
let alphabet = ["a","b","c","d","e"]
var body: some View {
GeometryReader { geometry in
let clockPadding = CGFloat(20)
let width = min(geometry.size.width, geometry.size.height)
let diam = width - 2*clockPadding
let centre = width/2
let radius = diam/2
let theta: CGFloat = 2*CGFloat.pi/CGFloat(alphabet.count)
ForEach(0..<alphabet.count) { i in
let x = centre + radius * cos(i*theta)
let y = centre + radius * sin(i*theta)
Text(alphabet[i])
.position(x: x, y: y)
}
}
}
}
In fact the error is still there even when I make the trig functions take a constant, changing the ForEach to:
ForEach(0..<alphabet.count) { i in
let x = centre + radius * cos(0*theta)
let y = centre + radius * sin(0*theta)
Text(alphabet[0])
.position(x: x, y: y)
}
What I'm trying to do is arrange the letter evenly around a circle.
The compiler is having trouble with the + and * in your x and y assignments. You need to let it know they're CGFloats, and then you need to turn i into a CGFloat as well (which is a bug in your code, but the + and * overloads are just too hard on the compiler and it couldn't help you find it).
ForEach(0..<alphabet.count) { i in
let x: CGFloat = centre + radius * cos(CGFloat(i)*theta)
let y: CGFloat = centre + radius * sin(CGFloat(i)*theta)
Text(alphabet[i])
.position(x: x, y: y)
}
As with most SwiftUI compile problems, the way to debug this is to take things out until it works, and then add things in very slowly. I took out the let x, let y and position lines, and it compiles, so I know the top part is fine. Then I added back in just the let x line and I got the error (Cannot convert value of type 'Int' to expected argument type 'CGFloat'). I fixed the bug for x and y and it compiled (without position). When I added the .position, it was too complicated again, so I added CGFloat types to x and y.
Another approach that often helps is to split things up into smaller functions. For example:
struct SwiftUIView: View {
let alphabet = ["a","b","c","d","e"]
private typealias Layout = (centre: CGFloat, radius: CGFloat, theta: CGFloat)
private func makeLayout(for geometry: GeometryProxy) -> Layout {
let clockPadding = CGFloat(20)
let width = min(geometry.size.width, geometry.size.height)
let diam = width - 2*clockPadding
let centre = width/2
let radius = diam/2
let theta = 2*CGFloat.pi/CGFloat(alphabet.count)
return (centre: centre, radius: radius, theta: theta)
}
private func positionedText(_ string: String, for l: Layout, at index: Int) -> some View {
let i = CGFloat(index)
return Text(string)
.position(x: l.centre + l.radius * cos(i*l.theta),
y: l.centre + l.radius * sin(i*l.theta))
}
var body: some View {
GeometryReader { geometry in
let layout = makeLayout(for: geometry)
ForEach(0..<alphabet.count) { i in
positionedText(alphabet[i], for: layout, at: i)
}
}
}
}
If you wrote it that way, the compiler would have given you a much more useful error message for the missing CGFloat conversion.

Is a variable or a function?

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

SpriteKit action like "Skew" or "Distort"

Is it possible to create an SKAction for SKSpriteNode in SpriteKit that generates the same effect as "Photoshop" with the Edit->Transform->Distort option?
Example:
I solve with this implementation:
Swift 5
extension SKSpriteNode {
func addSkew(value: CGFloat = -1){
var effectNode = SKEffectNode()
effectNode.shouldRasterize = true
effectNode.shouldEnableEffects = true
effectNode.addChild(SKSpriteNode(texture: texture))
effectNode.zPosition = 1
let transform = CGAffineTransform(a: 1 , b: 0,
c: value, d: 1,
tx: 0 , ty: 0)
let transformFilter = CIFilter(name: "CIAffineTransform")!
transformFilter.setValue(transform, forKey: "inputTransform")
effectNode.filter = transformFilter
addChild(effectNode)
texture = nil
}
}
You can create a skew using a 1x1 warp mesh. This is supported in iOS10.0+.
This extension receives the skew angle in degrees, and distorts around the anchor point of the given sprite.
Swift 4.2
extension SKWarpGeometryGrid {
public static var skewPosGridZero:[float2] {
get {
return [float2(0.0, 0.0), float2(1.0, 0.0),
float2(0.0, 1.0), float2(1.0, 1.0)]
}
}
public static func skewXPosGrid(_ skewX: CGFloat, node:SKSpriteNode? = nil) -> [float2] {
let anchorY:Float = Float(node?.anchorPoint.y ?? 0.5)
var skewPosGrid = skewPosGridZero
let offsetX = Float(tan(skewX.degToRad()) * (node == nil ? 1.0 : (node!.size.height/node!.size.width)) )
skewPosGrid[2][0] += offsetX * (1.0 - anchorY)
skewPosGrid[3][0] += offsetX * (1.0 - anchorY)
skewPosGrid[0][0] -= offsetX * anchorY
skewPosGrid[1][0] -= offsetX * anchorY
return skewPosGrid
}
public static func skewYPosGrid(_ skewY: CGFloat, node:SKSpriteNode? = nil) -> [float2] {
let anchorX:Float = Float(node?.anchorPoint.x ?? 0.5)
var skewPosGrid = skewPosGridZero
let offsetY = Float(tan(skewY.degToRad()) * (node == nil ? 1.0 : (node!.size.width/node!.size.height)) )
skewPosGrid[1][1] += offsetY * (1.0 - anchorX)
skewPosGrid[3][1] += offsetY * (1.0 - anchorX)
skewPosGrid[0][1] -= offsetY * anchorX
skewPosGrid[2][1] -= offsetY * anchorX
return skewPosGrid
}
public static func skewX(_ angle: CGFloat, node:SKSpriteNode? = nil) -> SKWarpGeometryGrid {
return SKWarpGeometryGrid(columns: 1, rows: 1, sourcePositions: skewPosGridZero, destinationPositions: skewXPosGrid(angle, node:node))
}
public static func skewY(_ angle: CGFloat, node:SKSpriteNode? = nil) -> SKWarpGeometryGrid {
return SKWarpGeometryGrid(columns: 1, rows: 1, sourcePositions: skewPosGridZero, destinationPositions: skewYPosGrid(angle, node:node))
}
public static func skewZero() -> SKWarpGeometryGrid {
return SKWarpGeometryGrid(columns: 1, rows: 1)
}
}
Example animation:
let spriteNode = SKSpriteNode(imageNamed: "tex")
spriteNode.anchorPoint = CGPoint(x:0.25, y:1.0)
let skewA = SKWarpGeometryGrid.skewX(-45.0, node: spriteNode)
let skewB = SKWarpGeometryGrid.skewX(45.0, node: spriteNode)
spriteNode.warpGeometry = skewB
if let skewActionA = SKAction.warp(to: skewA, duration: 3.0),
let skewActionB = SKAction.warp(to: skewB, duration: 3.0){
// Individual easing
skewActionA.timingMode = .easeInEaseOut
skewActionB.timingMode = .easeInEaseOut
spriteNode.run(SKAction.repeatForever(SKAction.sequence([skewActionA,skewActionB])))
}
The list of available SKAction's is here: https://developer.apple.com/reference/spritekit/skaction
There is none to do exactly what you describe. Instead, you can export multiple sprite images from a photo editing tool like Photoshop, and use an animation action like class func animate(with: [SKTexture], timePerFrame: TimeInterval).
This is a little more work, but should achieve the desired effect.

How to apply impulse to the node on touch angle

I want the node to move in the right direction but the impulse to be with set strength.
let node: SKSpriteNode!;
node = SKSpriteNode(color: UIColor.greenColor(), size: CGSizeMake(50, 50));
node.physicsBody = SKPhysicsBody(rectangleOfSize: node.size);
node.physicsBody?.affectedByGravity = false;
node.physicsBody?.allowsRotation = false;
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
node.physicsBody?.velocity = CGVectorMake(0, 0);
// ver 1
node.physicsBody?.applyImpulse(CGVectorMake((0.4) * (location.x - node.position.x), (0.4) * (location.y - node.position.y)), atPoint: CGPointMake(position.x,position.y));
// ver 2
let offset:CGPoint = self.vecSub(location, b: ghost.position);
let direction: CGPoint = self.vecNormalize(offset);
var len: CGPoint = self.vecMult(direction, b: 40);
let impulseVector:CGVector = CGVectorMake(len.x, len.y);
ghost.physicsBody?.applyImpulse(impulseVector);
}
func vecAdd(a: CGPoint, b:CGPoint) -> CGPoint {
return CGPointMake(a.x + b.x, a.y + b.y);
}
func vecSub(a: CGPoint, b:CGPoint) -> CGPoint {
return CGPointMake(a.x - b.x, a.y - b.y);
}
func vecMult(a: CGPoint, b:CGFloat) -> CGPoint {
return CGPointMake(a.x * b, a.y * b);
}
func vecLenght(a:CGPoint)->CGFloat{
return CGFloat( sqrtf( CFloat(a.x) * CFloat(a.x) + CFloat(a.y) * CFloat(a.y)));
}
func vecNormalize(a:CGPoint)->CGPoint{
let len : CGFloat = self.vecLenght(a);
return CGPointMake(a.x / len, a.y / len);
}
version 1 is horrible
version 2 is okay, but is too expensive
version 3: something not expensive and to apply impulse with 15-100 strength, because if the touch is at the edges of the screen the node should move only 15-100 of its current possition without reaching the touch position
Both the methods you've detailed above work so I'm not exactly sure what you're problem is. Also, I'm not sure method two is it's too expensive, are you having frame rate drops using it?
But I've got another way you could do what you're after, it's basically the second version but cleaned up. But firstly I just wanted to point out a couple things with your current code:
1. You don't need to ; to the end of each line.
2. Instead of using vecAdd, vecSub etc you could overload the +, -, * and / operators which would make your code cleaner and clearer. This way, the operators would also be global so you could use them anywhere else you need to manipulate vectors.
Anyway, here's my attempt at it:
Firstly, extend CGVector to add the functionality you need. Things like length, which you were defining as functions, could be properties of CGVector:
extension CGVector {
init(p1: CGPoint, p2: CGPoint) {
self.dx = p1.x - p2.x
self.dy = p1.y - p2.y
}
var length: CGFloat {
get { return hypot(dx, dy) }
set {
normalise()
dx *= newValue
dy *= newValue
}
}
mutating func normalise() {
dx /= length
dy /= length
}
}
Secondly, using the new methods:
var vec = CGVector(p1: location, p2: ghost.position)
vec.length = 40
ghost.physicsBody!.applyImpulse(vec)
Alternatively, if you wanted the size of the impulse to relate to how far away from the ghost the user pressed, use the following:
vec.length *= 0.1
Hope that helps!

Moving multiple sprite nodes at once on swift

Can I make an array of SK nodes of which one is selected randomly and brought from the top to bottom of the screen. For example say I have 25 or so different platforms that will be falling out of the sky on a portrait iPhone. I need it to randomly select one of the platforms from the array to start and then after a certain amount of time/ or pixel space randomly select another to continue the same action until reaching the bottom etc. Im new to swift but have a pretty decent understanding of it. I haven't been able to find out how to create an array of SKsprite nodes yet either. Could someone help with this?
So far the only way I've been able to get any sort of effect similar to what I've wanted is by placing each of the nodes off the screen and adding them to a dictionary and making them move like this
class ObstacleStatus {
var isMoving = false
var timeGapForNextRun = Int(0)
var currentInterval = Int(0)
init(isMoving: Bool, timeGapForNextRun: Int, currentInterval: Int) {
self.isMoving = isMoving
self.timeGapForNextRun = timeGapForNextRun
self.currentInterval = currentInterval
}
func shouldRunBlock() -> Bool {
return self.currentInterval > self.timeGapForNextRun
}
and
func moveBlocks(){
for(blocks, ObstacleStatus) in self.blockStatuses {
var thisBlock = self.childNodeWithName(blocks)
var thisBlock2 = self.childNodeWithName(blocks)
if ObstacleStatus.shouldRunBlock() {
ObstacleStatus.timeGapForNextRun = randomNum()
ObstacleStatus.currentInterval = 0
ObstacleStatus.isMoving = true
}
if ObstacleStatus.isMoving {
if thisBlock?.position.y > blockMaxY{
thisBlock?.position.y -= CGFloat(self.fallSpeed)
}else{
thisBlock?.position.y = self.origBlockPosistionY
ObstacleStatus.isMoving = false
}
}else{
ObstacleStatus.currentInterval++
}
}
}
using this for the random function
func randomNum() -> Int{
return randomInt(50, max: 300)
}
func randomInt(min: Int, max:Int) -> Int {
return min + Int(arc4random_uniform(UInt32(max - min + 1)))
}
All this has been doing for me is moving the pieces down at random timed intervals often overlapping them, But increasing the min or max of the random numbers doesn't really have an affect on the actual timing of the gaps. I need to be able to specify a distance or time gap.
One of many possible solutions is to create a falling action sequence which calls itself recursively until no more platform nodes are left. You can control the mean "gap time" and the range of its random variation. Here is a working example (assuming the iOS SpriteKit game template):
import SpriteKit
extension Double {
var cg: CGFloat { return CGFloat(self) }
}
extension Int {
var cg: CGFloat { return CGFloat(self) }
}
func randomInt(range: Range<Int>) -> Int {
return range.startIndex + Int(arc4random_uniform(UInt32(range.endIndex - range.startIndex)))
}
extension Array {
func randomElement() -> Element? {
switch self.count {
case 0: return nil
default: return self[randomInt(0..<self.count)]
}
}
func apply<Ignore>(f: (T) -> (Ignore)) {
for e in self { f(e) }
}
}
class GameScene: SKScene {
var screenWidth: CGFloat { return UIScreen.mainScreen().bounds.size.width }
var screenHeight: CGFloat { return UIScreen.mainScreen().bounds.size.height }
let PlatformName = "Platform"
let FallenPlatformName = "FallenPlatform"
func createRectangularNode(#x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) -> SKShapeNode {
let rect = CGRect(x: x, y: y, width: width, height: height)
let path = UIBezierPath(rect: rect)
let node = SKShapeNode(path: path.CGPath)
return node
}
func createPlatformNodes(numNodes: Int, atHeight: CGFloat) -> [SKShapeNode] {
var padding = 20.cg
let width = (screenWidth - padding) / numNodes.cg - padding
padding = (screenWidth - width * numNodes.cg) / (numNodes.cg + 1)
let height = width / 4
var nodes = [SKShapeNode]()
for x in stride(from: padding, to: numNodes.cg * (width + padding), by: width + padding) {
let node = createRectangularNode(x: x, y: atHeight, width: width, height: height)
node.fillColor = SKColor.blackColor()
node.name = PlatformName
nodes.append(node)
}
return nodes
}
func createFallingAction(#by: CGFloat, duration: NSTimeInterval, timeGap: NSTimeInterval, range: NSTimeInterval = 0) -> SKAction {
let gap = SKAction.waitForDuration(timeGap, withRange: range)
// let fall = SKAction.moveToY(toHeight, duration: duration) // moveToY appears to have a bug: behaves as moveBy
let fall = SKAction.moveByX(0, y: -by, duration: duration)
let next = SKAction.customActionWithDuration(0) { [unowned self]
node, time in
node.name = self.FallenPlatformName
self.fallNextNode()
}
return SKAction.sequence([gap, fall, next])
}
func fallNextNode() {
if let nextNode = self[PlatformName].randomElement() as? SKShapeNode {
let falling = createFallingAction(by: screenHeight * 0.7, duration: 1, timeGap: 2.5, range: 2) // mean time gap and random range
nextNode.runAction(falling)
} else {
self.children.apply { ($0 as? SKShapeNode)?.fillColor = SKColor.redColor() }
}
}
override func didMoveToView(view: SKView) {
self.backgroundColor = SKColor.whiteColor()
for platform in createPlatformNodes(7, atHeight: screenHeight * 0.8) {
self.addChild(platform)
}
fallNextNode()
}
}