3

Is it possible for an instance of a UIView to call a method which executes a closure, and inside that closure referring to the same instance? This is the non-generic version:

import UIKit

public extension UIView {
    func layout(from: (UIView) -> ()) {
        from(self)
    }
}

When I call it with a UILabel for example, I do not have access to e.g. the text aligment. Is it possible that inside the closure I can refer to the UILabel? I would expect something like this would work:

func layout(from: (Self) -> ()) {
    from(self)
}

But it doesn't compile. Is there a workaround? This is what I want:

let label = UILabel(frame: .zero)

label.layout { $0.textAlignment = .natural } // Currenly not working, since $0 = UIView.
J. Doe
  • 12,159
  • 9
  • 60
  • 114

2 Answers2

3

Different approach: Protocol Extension with associated type.

protocol Layout {
    associatedtype View : UIView = Self
    func layout(from: (View) -> ())
}

extension Layout where Self : UIView {
    func layout(from: (Self) -> ()) {
        from(self)
    }
}

extension UIView : Layout {}

let label = UILabel(frame: .zero)
label.layout { $0.textAlignment = .natural }
vadian
  • 274,689
  • 30
  • 353
  • 361
  • 2
    You don't need to declare an `associatedtype` for this to work (or any protocol requirements for that matter). You could also declare the protocol as `protocol Layout : UIView` to restrict it to being conformed by `UIView` (and then remove the constraint from the extension). – Hamish Apr 06 '19 at 14:33
1

There are different ways to do it.

Firstly, you could use the closures' variable capturing system in order to directly use the variable inside the closure, without passing it as an argument.

public extension UIView {
    func layout(from: () -> ()) {
        from()
    }
}

label.layout { label.textAlignment = .natural }

Otherwise, if you want to pass a generic UIView and change the behaviour accordingly to the specific one - since it looks like you know for sure what type you are working on - you can use a downcast:

public extension UIView {
    func layout(from: (UIView) -> ()) {
        from(self)
    }
}

let label = UILabel(frame: .zero)

label.layout { ($0 as! UILabel).textAlignment = .natural }

Anyway, why are you doing:

label.layout { $0.textAlignment = .natural }

instead of:

label.textAlignment = .natural

Is there any particular reason not to do it? I imagine there's something bigger behind the scenes, I'm just curious.

  • 1
    Hey thanks for your response. The code in the question isn't the real code, but a simplified version. When using the closure method as you described at the top of your answer, I do not get an argument of type `self` (which is the question). – J. Doe Apr 06 '19 at 14:22
  • Hello! I see, what about the second piece of the answer? I mean, the downcast. Is it too *dirty*? – Pentracchiano Apr 06 '19 at 14:29
  • 2
    Yes, I do not want to downcast everytime I use it, but still thank you for your explaination and answer :) – J. Doe Apr 08 '19 at 12:57