1

Description

I have a protocol containing an associated type that needs to be inferred later and it called UIViewRepresentable

Then another protocol to add extra:

protocol UIViewRepresentableHelper: UIViewRepresentable {
    var configuration: (UIViewType) -> () { get set }
}

extension UIViewRepresentableHelper {
    func makeUIView(context: UIViewRepresentableContext<Self>) -> UIViewType { UIViewType() }
    func updateUIView(_ uiView: UIViewType, context: UIViewRepresentableContext<Self>) { configuration(uiView) }
}

So any types conforming to the second one should be able to implement the first and second protocols together.

struct LabelView: UIViewRepresentableHelper {
    typealias UIViewType = UILabel // <- Commenting  out this line confuses the Xcode about TheTypeThatShouldBeInferred
    var configuration = { (view: UILabel) in }
}

struct ButtonView: UIViewRepresentableHelper {
    typealias UIViewType = UIButton // <- Commenting  out this line confuses the Xcode about TheTypeThatShouldBeInferred
    var configuration = { (view: UIButton) in }
}

The Issue:

But as comments in the code are saying, seems like Swift can not infer UIViewType from the context of the closure. Also, Xcode confuses about the context of the already written configuration.

The Question:

What could be done to get rid of at least one of the type annotations, or one line of code, or anything else to make it more elegant and less coded?


One of the expectations:

I hoped atleast something like this would work:

struct TextFieldView: UIViewRepresentableHelper {
    var configuration: (UITextField)->() = { _ in }
}
AnderCover
  • 2,488
  • 3
  • 23
  • 42
Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
  • "What could be done to get rid of at least one of them" What's "them" referring to? The two error messages? – Sweeper Jun 21 '20 at 09:29
  • Oh wait, you mean "one of the type annotations", don't you? – Sweeper Jun 21 '20 at 09:33
  • Yep, type annotations :D. Or maybe one line of code. Or anything else to make it more DRY. @Sweeper – Mojtaba Hosseini Jun 21 '20 at 09:55
  • this reminds of this [question](https://stackoverflow.com/questions/62383640/swift-generic-overloads-definition-of-more-specialized/62405174#62405174) or maybe this [one](https://stackoverflow.com/questions/62355933/swift-specialize-method-of-generic-class-for-function-types/62405388#62405388) but the it seems to me your code fails for the same reasons, ie: the compiler does not support destructuring function types like `(TheTypeThatShouldBeInferred) -> ()` and same solutions could be applied. – AnderCover Jun 21 '20 at 15:09
  • It didn't help @AnderCover. Also, I have updated my question to make the situation more realistic – Mojtaba Hosseini Jun 22 '20 at 06:50
  • Realistic or not, as I said the compiler does not support what you want ie. when **you** see `(TheTypeThatShouldBeInferred) -> ()` **you** know that the parameter type. But the compiler does not, at least not when it’s evaluating constraints. It may be a bug or just a limitation. – AnderCover Jun 22 '20 at 10:13
  • Actually I was in error, I don't think the problem come froe the closure :) – AnderCover Jun 22 '20 at 13:49
  • After playing too much with closure and generics these last days I was **sure** it was the same issue, but I guess that in fact the compiler **needs** to have the associatedtype/typealias and the protocol requirement in the same protocol. I edited my answer, does it work for you ? – AnderCover Jun 22 '20 at 14:06
  • funny, I could reproduce it – AnderCover Jun 22 '20 at 14:10
  • 1
    (deleted comments; my reproduction was incorrect.) – Rob Napier Jun 22 '20 at 14:15
  • I can't find out what is the issue to make it more clear @RobNapier – Mojtaba Hosseini Jun 22 '20 at 14:16
  • 1
    You're clear; I just reproduced it incorrectly. – Rob Napier Jun 22 '20 at 14:17
  • maybe but I'm puzzled now, because i don't see how it was incorrect – AnderCover Jun 22 '20 at 14:18
  • Oh got it, the extension ! – AnderCover Jun 22 '20 at 14:20
  • In the extension that implemented `updateUIView`, I wrote `uiView: UILabel` rather than `uiView: UIViewType`. That nailed down UIViewType to UILabel, so everything else worked. My suspicion is that the type inference engine just can't handle this. – Rob Napier Jun 22 '20 at 14:21
  • I think so. But that will force me to implement `updateUIView` everywhere instead. – Mojtaba Hosseini Jun 22 '20 at 14:23
  • I arrived at the same conclusion, I had the `UITextfield` in the extension – AnderCover Jun 22 '20 at 14:24
  • 1
    @RobNapier in the end, you deleted comment was useful ;) – AnderCover Jun 22 '20 at 14:25
  • 1
    Yeah, I'd say this is all just a bridge too far for the inference engine. It needs to work out the type in order to make sure it conforms, and it needs to conform in order to get the extensions, and I just don't think it can figure it out. I don't see an obvious mistake; it just is probably more than the compiler can figure out. I rewrote it with generics rather than closures, and it has the same problem. I suggest opening a defect at bugs.swfit.org. https://gist.github.com/rnapier/529a0d56df41854f5cc4f1a45f14cedb – Rob Napier Jun 22 '20 at 14:34
  • @RobNapier I actually tried using generics and managed to make it work based on the first version of my answer but I'm not sure that's what OP wants – AnderCover Jun 23 '20 at 10:58

1 Answers1

0

EDIT #3

Using a single generic class :

public protocol UIViewRepresentableHelper: UIViewRepresentable {
    var configuration: (UIViewType) -> () { get set }
}

public extension UIViewRepresentableHelper {
    func makeUIView(context: UIViewRepresentableContext<Self>) -> UIViewType { UIViewType() }
    func updateUIView(_ uiView: UIViewType, context: UIViewRepresentableContext<Self>) { configuration(uiView) }
}

public struct ViewConfigurator<UIViewType: UIView>: UIViewRepresentableHelper {
    public var configuration: (UIViewType) -> ()
    public init(configuration: @escaping (UIViewType) -> ()) {
        self.configuration = configuration
    }
}

You can either use it by explicitly specifying the type of the closure argument :

let labelConfigurator = ViewConfigurator { (label: UILabel) in
    print(label.text ?? "empty")
}

var label = UILabel()
labelConfigurator.configuration(label) // Optional("")
label.text = "some text"
labelConfigurator.configuration(label) // Optional("some text")

or by explicitly specifiying the generic argument :

typealias ButtonConfigurator = ViewConfigurator<UIButton>
let buttonConfigurator = ButtonConfigurator { button in
    print(button.title(for: .normal) ?? "empty")
}

var button = UIButton()
buttonConfigurator.configuration(button) // Optional("")
button.setTitle("some title", for: .normal)
buttonConfigurator.configuration(button) // Optional("some title")

either way you have to "help" the compiler at the calling site but you do it only once.

I think it did not work when you tried my first answer because you did not declare the ViewConfigurator like that :

public struct ViewConfigurator<UIViewType: UIView>: UIViewRepresentableHelper

EDIT #2

It worked because I declared :

func updateUIView(_ uiView: UITextField, context: Context) {
    
}

but once I put UIViewType the error came back, I think my first thought was correct, the compiler does not do well with associated types and closures

EDIT #1

So the problem was actually that UIViewType and configuration were not declared in the same protocol.

I could work around that by creating a typealias of UIViewType in UIViewRepresentableHelper

like that :

protocol UIViewRepresentableHelper: UIViewRepresentable {
    typealias ViewType = UIViewType
    var configuration: (ViewType) -> () { get set }
}

struct TextFieldView: UIViewRepresentableHelper {
    var configuration: (UITextField)->()
    
}

usage :

let textFieldView = TextFieldView { textField in
    print(textField.text)
}

var textField = UITextField()
textFieldView.configuration(textField) // Optional("")
textField.text = "some text"
textFieldView.configuration(textField) // Optional("some text")


AnderCover
  • 2,488
  • 3
  • 23
  • 42
  • Although this lets me remove the type from the original location, it forces me to add it somewhere else. I'm seeking for a way that swift can automatically infer the type as. I'm telling it to once and I'm avoiding to doing it again. (even in as a generic constraint or etc.). Also, same issue happens with this code too. I have updated my question with the real world information. – Mojtaba Hosseini Jun 22 '20 at 06:47
  • You have to specify the type at the calling point. The generic solution is a “less coded” one. I think what you want to do *should* work for closures, but it just doesn’t. What you could though is just declaring a method that takes the parameter type that you want. I’ll check if it works when I have the time – AnderCover Jun 22 '20 at 10:18
  • I have tried this edit with no luck. Unfortunately. – Mojtaba Hosseini Jun 22 '20 at 14:14
  • 1
    yes and I understand why thanks to @RobNapier I had mistakenly declared `func updateUIView(_ uiView: UITextField, context: Context)` in my extention – AnderCover Jun 22 '20 at 14:23