Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mathematical operations on CGPoint in Swift

Tags:

swift

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
        
    }

}
like image 428
ios coder Avatar asked Oct 26 '25 02:10

ios coder


2 Answers

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.

like image 129
Rob Napier Avatar answered Oct 29 '25 00:10

Rob Napier


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)
  }
}