2

I wanted add 2 CGPoints together, then I realised there is no function defined for that, so I made this down one, I want Pro's look this codes for any mistake or improvement, but then I got a big question in my head, Is that so much work and coding and customisation which Apple did not finished? am I missing something? everyone that use CGPoint in Swift in some point would be need to math on it, so why we do not have it at first place? also thanks for looking the code and correcting.

extension CGPoint {
    
    static func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
        
        return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
    }
    
}

shout out to LeoDabus : update for : +=

extension CGPoint {

    static func += (lhs: inout CGPoint, rhs: CGPoint)  {
        
        lhs = lhs + rhs
        
    }

}
ios coder
  • 1
  • 4
  • 31
  • 91
  • Your code looks fine, and why Apple does not supply a CGPoint `+` operator is largely a matter of opinion and thus is not a good candidate for a Stack Overflow question. Personally I have a _big_ library of methods Apple didn't supply and so, eventually, will you. – matt Feb 01 '21 at 21:06
  • thanks matt, I was not sure why apple did not coded literally just 1 line of coding, then I doubt maybe I am missing something, that is why asked for help – ios coder Feb 01 '21 at 21:13
  • Well, I agree with Rob Napier that the concept of adding points is nebulous at best. But, as I say, asking why Apple did or didn't do a thing is generally not a legal question here. – matt Feb 01 '21 at 21:17
  • you said "asking why Apple did or didn't do a thing is generally not a legal question here" maybe some one could explain or have information about why they did not coded this func in first place, and I wanted to know, – ios coder Feb 01 '21 at 21:19
  • In general, Apple employees can't talk and everyone else doesn't know. – matt Feb 01 '21 at 21:21
  • 1
    But I will say that in Swift, adding extra overloads to `+` has historically been the cause of massive compile-time slow-downs (because there are a lot of overloads to `+`). The compiler has gotten better, but there certainly was *some* pressure not to add more random `+` overloads. But the more common reason for Apple not doing something is "Apple didn't feel they needed it." There often is no clear "we decided, in an official, documented way, not to do that." (though sometimes there is…buried in a forum post somewhere…) Matt's point is good: guessing Apple's intent is often just guessing. – Rob Napier Feb 01 '21 at 21:34
  • @RobNapier: you mentioned and show me things that I need to know, about overloads to +, I just want a general understanding not exact explain, when we are extending something, like I did for CGPoint with +, this overloads is just in compling-time or even in run time? – ios coder Feb 01 '21 at 21:45
  • 1
    @swiftPunk You can implement the `+=` operator as well. `static func += (lhs: inout Self, rhs: Self) {` `lhs.x += rhs.x` `lhs.y += rhs.y` `}` – Leo Dabus Feb 01 '21 at 22:02
  • @LeoDabus: I would use that, that is very handy, probably I will need to + and +=, I can make extension for -, /, * but I think I would not use them and as Rob said they will became overloads, I am happy to made this question, because now I am more careful about my extensions, what should be there and not. – ios coder Feb 01 '21 at 22:13
  • 1
    The large number of overloads on `+` has historically been a source of compile-time performance problems. It doesn't have any impact on runtime, however. The main symptom is when you try to chain together several `+` like `x + y + z + a + b` (you get errors like "cannot type check in reasonable time"). This has gotten dramatically better over the years, but I still tend to be very careful about adding new overloads for `+`. :D – Rob Napier Feb 01 '21 at 22:33
  • @LeoDabus : I wanted be a little creative I came with this func, but does not work! :( can you look at plz? **static func += (lhs: inout CGPoint, rhs: CGPoint) -> CGPoint { return lhs + rhs }** – ios coder Feb 01 '21 at 22:41
  • 1
    @swiftPunk `+=` does not return any value it actually mutates the left side of the operator thats why you need to use `inout` keyword. I told you already how you should implement it. If you want to make use of the other implementation you are showing above `lhs = lhs + rhs` and remove the returning type. – Leo Dabus Feb 01 '21 at 23:36
  • Regarding extending CGPoint the most useful method IMO is to scale a point as you can see in this [post](https://stackoverflow.com/a/65071358/2303865) – Leo Dabus Feb 01 '21 at 23:41
  • 1
    @LeoDabus: you are the best! thanks! I actually focused on get use of + func and i missed out the mutates fact! – ios coder Feb 02 '21 at 00:21

3 Answers3

5

It is not strictly meaningful to add two CGPoints. It's not completely wrong; it's just not meaningful because points are coordinates, not offsets. "Chicago + New York" is not a new location.

Generally you would offset a CGPoint using a CGVector, CGSize/NSSize, or a UIOffset. Those also don't have a + operator, but it would make more sense to add the operator to CGPoint+CGVector rather than two CGPoints.

But you're free to add it the way you did if it's particularly convenient.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • I agree with this but then I got thinking about complex number math — and since complex numbers can be added and map to cartesian points, I decided it wasn't worth pressing. Similarly one might think of these as analogous to vectors. In any case I have to suggest this is not a real answer to the question and that in fact no answer should be attempted. :) – matt Feb 01 '21 at 21:15
  • 1
    I'd argue that complex numbers should just have their own type, like vectors do. Overloading CGPoint just because it has two CGFloat values is a light abuse of the type. (But also not horrible, if it makes folks happy.) I wouldn't argue the point strongly either way. – Rob Napier Feb 01 '21 at 21:17
  • 1
    This is where a `newtype`-like feature would be great: `newtype Complex = CGFloat`, and then add the `+` only on `Complex` instances, but not other `CGFloat`s – Alexander Feb 01 '21 at 21:18
  • 1
    I agree with the thought process, but this arguably overstates the case a bit. Consider [`translation(in:)`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer/1621207-translation) which returns a `CGPoint` (not a `CGVector`, which might have made more sense). This “translation” `CGPoint` practically begs for a `+` operator and I wouldn't contort myself to avoid it. – Rob Feb 02 '21 at 01:10
  • I agree that `translation(in:)` should have returned something other than a CGPoint, but it predates CGVector by several years. Given the era, it probably should have used CGSize like NSShadow does. IMO, it was just a mistake in an early API. But I definitely agree that I wouldn't go out of my way to avoid it, or even worry too much about it. The type-purist in me cares, but the professional programmer doesn't. – Rob Napier Feb 02 '21 at 13:05
  • For anyone still looking for this, check out https://github.com/koher/CGPointVector – agurtovoy Nov 17 '21 at 23:07
1

Rob Napier is correct, but unfortunately, most people don't know what he knows. So, as the other Rob said in the comments for his answer, a lot of APIs just use CGPoint for every floating point 2-tuple. So practically, you're going to want all of those from Apple to be synesthetic (i.e. dimensions of different quantities become tied together).

import CoreGraphics
import simd

public extension SIMD2 {
  init(_ scalars: (Scalar, Scalar)) {
    self.init(scalars.0, scalars.1)
  }

  init<Float2: CommonVectorOperable>(_ float2: Float2)
  where Float2.Operand == Self {
    self = float2.convertedToOperand
  }
}

public extension SIMD2 where Scalar == CGFloat.NativeType {
// MARK: - Initializers
  init(_ x: CGFloat, _ y: CGFloat) {
    self.init(x.native, y.native)
  }
}
import CoreGraphics
import simd

public protocol CGFloat2: CommonVectorOperable
where Operand == SIMD2<CGFloat.NativeType> {
  init(_: CGFloat.NativeType, _: CGFloat.NativeType)
}

// MARK: CommonOperable
public extension CGFloat2 {
  init(_ simd: Operand) {
    self.init(simd.x, simd.y)
  }
}

// MARK: CommonVectorOperable
public extension CGFloat2 {
  static var convertToOperandScalar: (CGFloat) -> Operand.Scalar { \.native }
}

extension CGPoint: CGFloat2 {
  public init(_ x: CGFloat.NativeType, _ y: CGFloat.NativeType) {
    self.init(x: x, y: y)
  }

  public var convertedToOperand: SIMD2<CGFloat.NativeType> { .init(x, y) }
}

extension CGSize: CGFloat2 {
  public init(_ width: CGFloat.NativeType, _ height: CGFloat.NativeType) {
    self.init(width: width, height: height)
  }

  public var convertedToOperand: SIMD2<CGFloat.NativeType> { .init(width, height) }
}

extension CGVector: CGFloat2 {
  public init(_ dx: CGFloat.NativeType, _ dy: CGFloat.NativeType) {
    self.init(dx: dx, dy: dy)
  }

  public var convertedToOperand: Operand { .init(dx, dy) }
}
/// A type that can operate with other types via intermediate conversion.
public protocol CommonOperable {
  /// The type to be converted to, for interoperability.
  associatedtype Operand

  init(_: Operand)
  var convertedToOperand: Operand { get }
}

public extension CommonOperable {
  init<Operable: CommonOperable>(_ operable: Operable)
  where Operand == Operable.Operand {
    self.init(operable.convertedToOperand)
  }
}

// MARK: internal
extension CommonOperable {
  /// Forwards  operators to converted operands.
  static func operate<Operable1: CommonOperable, Result: CommonOperable>(
    _ operable0: Self,
    _ operate: (Operand, Operand) -> Operand,
    _ operable1: Operable1
  ) -> Result
  where Operand == Operable1.Operand, Operand == Result.Operand {
    Result(
      operate(
        operable0.convertedToOperand,
        operable1.convertedToOperand
      )
    )
  }

  /// Forwards  `Operand` methods to converted operands.
  func performMethod<Parameters, Result>(
    _ method: (Operand) -> (Parameters) -> Result,
    _ parameters: Parameters
  ) -> Result {
    method(self.convertedToOperand)(parameters)
  }

  /// Forwards  `Operand` methods to converted operands.
  /// - Returns: A converted result.
  func performMethod<Parameters, Result: CommonOperable>(
    _ method: (Operand) -> (Parameters) -> Operand,
    _ parameters: Parameters
  ) -> Result
  where Operand == Result.Operand {
    Result(performMethod(method, parameters))
  }
}

/// A vector type that can operate with other types via intermediate conversion.
public protocol CommonVectorOperable: CommonOperable where Operand: SIMD {
  associatedtype Scalar

  static var convertToOperandScalar: (Scalar) -> Operand.Scalar { get }
}

public extension CommonVectorOperable {
  /// Forwards  operators to converted operands.
  static func operate(
    _ vector: Self,
    _ operate: (Operand, Operand.Scalar) -> Operand,
    _ scalar: Scalar
  ) -> Self {
    Self(
      operate(
        vector.convertedToOperand,
        convertToOperandScalar(scalar)
      )
    )
  }
}
public extension CommonOperable
where Operand: SIMD, Operand.Scalar: FloatingPoint {
  static func + <Operable1: CommonOperable, Result: CommonOperable>
  (operable0: Self, operable1: Operable1) -> Result
  where Operand == Operable1.Operand, Operand == Result.Operand {
    operate(operable0, +, operable1)
  }

  static func + (operable0: Self, operable1: Self) -> Self {
    operate(operable0, +, operable1)
  }

  static func += <Operable1: CommonOperable>
  (operable0: inout Self, operable1: Operable1)
  where Operand == Operable1.Operand {
    operable0 = operable0 + operable1
  }

// MARK: -

  static func - <Operable1: CommonOperable, Result: CommonOperable>
  (operable0: Self, operable1: Operable1) -> Result
  where Operand == Operable1.Operand, Operand == Result.Operand {
    operate(operable0, -, operable1)
  }

  static func - (operable0: Self, operable1: Self) -> Self {
    operate(operable0, -, operable1)
  }

  static func -= <Operable1: CommonOperable>
  (operable0: inout Self, operable1: Operable1)
  where Operand == Operable1.Operand {
    operable0 = operable0 - operable1
  }

// MARK: -

  static func / <Divisor: CommonOperable, Result: CommonOperable>
  (dividend: Self, divisor: Divisor) -> Result
  where Operand == Divisor.Operand, Operand == Result.Operand {
    operate(dividend, /, divisor)
  }

  static func / (dividend: Self, divisor: Self) -> Self {
    operate(dividend, /, divisor)
  }

  static func /= <Divisor: CommonOperable>
  (dividend: inout Self, divisor: Divisor)
  where Operand == Divisor.Operand {
    dividend = dividend / divisor
  }
}

public extension CommonOperable where Operand == SIMD2<CGFloat.NativeType> {
  func clamped<Result: CommonOperable>(within bounds: CGRect) -> Result
  where Operand == Result.Operand {
    performMethod(Operand.clamped(within:), bounds)
  }
}

public extension CommonVectorOperable where Operand.Scalar: FloatingPoint {
  static func * (vector: Self, scalar: Scalar) -> Self {
    operate(vector, *, scalar)
  }

  static func / (dividend: Self, divisor: Scalar) -> Self {
    operate(dividend, /, divisor)
  }
}
-1

May be you can consider importing my Vector2D package from GitHub, then you can use the following syntax to manipulate vectors:

import SwiftUI
import Vector2D

let u = CGPoint(1, 2)    // convenience init
let v = CGPoint(2,-1)
let w: CGPoint = [2, 3]  // expressible by array literal

// linear combinations
u + v          // ( 3, 1)
u - 2 * v      // (-3, 4)
3 * u - 2 * v  // (-1, 8)

// products
u * v          // (4, 3) : complex number multiplication
u • v          //  0.0   : inner product
u × v          // -5.0   : cross product
u ** v         // (2, -2): memberwise multiplication

// unit vectors: .i = (1,0), .j = (0,1)
let p = CGPoint(1,2) // (1,2)
p + 5 * .i           // (1,2) + 5(1,0) = (6,2)
2 * p - 3 * .j       // (2,4) - 3(0,1) = (2,1)
lochiwei
  • 1,240
  • 9
  • 16