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 writevar 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.
Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.
