Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define an empty, "uninitialized" keypath and set it later?

Tags:

swift

keypaths

I made a program to calculate the total width/height of views (sometimes I want the total width, sometimes I want the total height). The only catch is: If I'm calculating the width, I want to add an extra 10 to the total. Here's my current code:

func calculateLengthOfAllViews(calculatingWidth: Bool) {
    let views = [
        UIView(frame: CGRect.zero),
        UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50)),
        UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
    ]

    var totalLength: CGFloat = 0
    
    if calculatingWidth {
        totalLength += 10 /// add extra 10 if calculating width
    } else {
        totalLength += 0
    }
    
    for view in views { /// add each view's width/height
        let length: CGFloat
        if calculatingWidth {
            length = view.frame.width
        } else {
            length = view.frame.height
        }
        totalLength += length
    }
    
    print("Total length is \(totalLength)")
}

calculateLengthOfAllViews(calculatingWidth: true) /// Total length is 160.0
calculateLengthOfAllViews(calculatingWidth: false) /// Total length is 100.0

This works fine. But, I'm repeating if calculatingWidth { in 2 places, to determine:

  1. Whether to add extra padding 10 padding
  2. Whether to use view.frame.width or view.frame.height as the length

The second if-statement is unnecessary, because it always evaluates to the same thing in every iteration.

So, I think keypaths are the way to go -- I can store a reference to either .width or .height from the first if-statement. However, how do I define a keypath without "initializing" it? I would like to do something like this:

let keyPath: KeyPath /// Reference to generic type 'KeyPath' requires arguments in <...>

if calculatingWidth {
    totalLength += 10
    keyPath = \UIView.frame.width
} else {
    totalLength += 0
    keyPath = \UIView.frame.height
}

for view in views {
    let length = view[keyPath: keyPath] /// Type of expression is ambiguous without more context
    totalLength += length
}

However, this gives me Reference to generic type 'KeyPath' requires arguments in <...>. How can I fix this?

like image 432
aheze Avatar asked Aug 15 '21 20:08

aheze


Video Answer


2 Answers

class KeyPath<Root, Value>

is a generic type with two type parameters: the root type and the result value type, and those must be specified in the declaration of the variable. In your case it would be

let keyPath: KeyPath<UIView, CGFloat>

With that definition, the remaining code compiles as well.

like image 136
Martin R Avatar answered Sep 22 '22 12:09

Martin R


You are using if at the start of the function, and if on every iteration of the loop. I think a better way to do this would be to use reduce(into:_:), to reduce the number of conditional branches.

Code:

func calculateLengthOfAllViews(calculatingWidth: Bool) {
    let views = [
        UIView(frame: CGRect.zero),
        UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50)),
        UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
    ]

    let totalLength: CGFloat

    if calculatingWidth {
        totalLength = views.reduce(10, { $0 + $1.frame.width })
    } else {
        totalLength = views.reduce(0, { $0 + $1.frame.height })
    }

    print("Total length is \(totalLength)")
}
like image 42
George Avatar answered Sep 26 '22 12:09

George