Fix: Cannot Assign CGRect Property to Variable in Switch Statement

Fix: Cannot Assign CGRect Property to Variable in Switch Statement

Written by

in

Quick answer: You cannot assign CGRect instance properties like width or maxX to variables directly because they are not types. Use KeyPath<CGRect, CGFloat> to capture property references, then access values with rect[keyPath: keyPath].

Why this happens

The error Property 'width' is not a member type of 'CGRect' occurs because CGRect.width, CGRect.maxX, and similar properties are instance properties, not types. In your switch statement you wrote:

var span: CGRect.width

Swift interprets CGRect.width as a type annotation, but width is a computed property that returns a CGFloat. You cannot use a property name as a type.

What you actually need is a reference to the property that you can apply to any CGRect instance at runtime. That is exactly what a key path provides.

How to fix it

1. Use KeyPath to capture the property reference

Define a tuple of key paths in the switch expression. Each key path points to the corresponding CGFloat property on CGRect.

func findParallelSections<T: AreaContainer>(
    regions: [[T]],
    overlapTolerance: Double,
    axis: String
) throws -> [[Int]] {

    let (span, upperLimit, lowerLimit, middle) = switch axis {
    case "x", "X":
        (\CGRect.width, \CGRect.maxX, \CGRect.minX, \CGRect.midX)
    case "y", "Y":
        (\CGRect.height, \CGRect.maxY, \CGRect.minY, \CGRect.midY)
    default:
        throw AreaProcessingError.invalidAxis
    }

    var parallelRegionArray: [[Int]] = []

    for mainRegion in regions {
        guard let mainRegionFirstMember = mainRegion.first else { continue }
        let area = mainRegionFirstMember.area

        // Access the selected properties via key paths
        let upperBound = area[keyPath: upperLimit] + (area[keyPath: span] * overlapTolerance)
        let lowerBound = area[keyPath: lowerLimit] - (area[keyPath: span] * overlapTolerance)

        for otherRegion in regions {
            guard let otherRegionFirstMember = otherRegion.first else {
                throw AreaProcessingError.emptyRegion
            }
            let otherMiddle = otherRegionFirstMember.area[keyPath: middle]

            if lowerBound < otherMiddle && otherMiddle < upperBound {
                // record parallel indices
            }
        }
    }

    // cleanup and return
    return parallelRegionArray
}

Key points:
– \CGRect.width is a key path literal of type KeyPath<CGRect, CGFloat>.
– The switch returns a tuple of four key paths, one for each property you need.
– At the call site you read the value with rect[keyPath: keyPath].

2. Alternative: Use a small helper enum (if you prefer no key paths)

If you want to avoid key paths entirely, wrap the axis logic in an enum that supplies the needed getters.

enum Axis {
    case x, y

    var span: (CGRect) -> CGFloat {
        switch self {
        case .x: return \.width
        case .y: return \.height
        }
    }
    var upperLimit: (CGRect) -> CGFloat {
        switch self {
        case .x: return \.maxX
        case .y: return \.maxY
        }
    }
    var lowerLimit: (CGRect) -> CGFloat {
        switch self {
        case .x: return \.minX
        case .y: return \.minY
        }
    }
    var middle: (CGRect) -> CGFloat {
        switch self {
        case .x: return \.midX
        case .y: return \.midY
        }
    }
}

func findParallelSections<T: AreaContainer>(
    regions: [[T]],
    overlapTolerance: Double,
    axis: Axis
) throws -> [[Int]] {
    let span = axis.span
    let upperLimit = axis.upperLimit
    let lowerLimit = axis.lowerLimit
    let middle = axis.middle

    // same loop body, but call like:
    // let upperBound = upperLimit(area) + span(area) * overlapTolerance
}

This approach keeps the axis logic centralized and type-safe, and the call site reads like regular function calls.

How to prevent it

  • Remember that instance properties (width, maxX) are values, not types. You cannot write var x: CGRect.width.
  • When you need to choose a property dynamically, reach for key paths (\Type.property) or closures ({ $0.property }).
  • Prefer an enum or a small struct to group related key paths instead of passing raw strings like "x" / "y".

FAQ

### Can I use this technique with any struct, not just CGRect?

Yes. KeyPath<Root, Value> works with any type. Replace CGRect with your own struct and the property names you need.

### Is there a performance cost to using key paths?

Negligible. The compiler optimizes key path application to direct property access in almost all cases.

### Why did the original code throw “Property ‘width’ is not a member type”?

Because CGRect.width is a member (a computed property), not a member type (a nested type like CGRect.Edge). Type annotations expect a type, not a value.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.