3

Within my app, I have multiple UIView subclasses that depend on a model. Each of the classes adopting 'Restorable' protocol which holds the superclass of the model. Each sub-model describes the specific UIView not-common properties.

// Super-model
public protocol StoryItem {
    var id: Int64? { get }
}

// Parent protocol
public protocol Restorable: AnyObject {
    var storyItem: StoryItem? { get set }
}

// Specific protocol
public struct TextItem: StoryItem {
    public var id: Int64?
    public var text: String?
}

// Not complling
class ResizableLabel: UILabel, Restorable {
    var storyItem: TextItem?
}

I'm getting the following compiler error:

*Type 'ResizableLabel' does not conform to protocol 'Restorable'*

The only way I can make it compile is by changing ResizableLabel to

// Works
class ResizableLabel: UILabel, Restorable {
    var storyItem: StoryItem?
}

Is there any way to conform to protocol subclass? it'll make the Init process much cleaner. Thank you for your help!

Roi Mulia
  • 5,626
  • 11
  • 54
  • 105
  • Hey @matt, thank you for replying. I've updated the code to be clearer. Also, I changed TextItem to be struct and not protocol. In my app, I can create labels, images. stickers etc'. Each of them being constructed using a different model. But all the models have a superclass-model that share the common properties. When I'm adding a model to the UIView-subclass, let's say label. I want to specify that this object gets his data from the TextModel (that's why I changed the storyItem class to TextItem. – Roi Mulia Jun 29 '19 at 01:24
  • @matt Right now, when I'm creating a label object, for example, I'm creating TextItem and than assign it to the storyItem (the most bottom code line which works), and every time I want to access it I need to cast it, which I'm trying to avoid – Roi Mulia Jun 29 '19 at 01:26

1 Answers1

6

Change

public protocol Restorable: AnyObject {
    var storyItem: StoryItem? { get set } // adopter must declare as StoryItem
}

to

public protocol Restorable: AnyObject {
    associatedtype T : StoryItem
    var storyItem: T? { get set } // adopter must declare as StoryItem adopter
}

Now your code compiles. Full example:

public protocol StoryItem {
    var id: Int64? { get }
}
public protocol Restorable: AnyObject {
    associatedtype T : StoryItem
    var storyItem: T? { get set }
}
public struct TextItem: StoryItem {
    public var id: Int64?
    public var text: String?
}
class ResizableLabel: UILabel, Restorable {
    var storyItem: TextItem? // ok because TextItem is a StoryItem adopter
}

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 1
    Added comments that explain the difference between what you were saying and what you wanted to say. – matt Jun 29 '19 at 01:52
  • Hey @matt, small follow-up question. Let's say I have a UIView which has stickers, images, labels as subviews among other classes. and I want to get only the subviews that adopt 'Restorable' subviews. so I tried something like: let restorableSubviews = view.subviews.compactMap({$0 as? (Restorable & UIView)}). But I'm gettings compile error: Protocol 'Restorable' can only be used as a generic constraint because it has Self or associated type requirements.|| Do I need to cast them differently? – Roi Mulia Jun 29 '19 at 01:57
  • Btw if you think I need to post another question, let me know and I'll do it immediately. – Roi Mulia Jun 29 '19 at 01:58
  • In order to satisfy your requirement in this question, we had to make Restorable a generic constraint. Now it cannot be used as a type. The simplest solution is to declare an empty protocol that only Restorable adopts, and use _that_ as your type name (though you'll then have to cast down later). But that isn't the only solution; search on that error and you'll find that this has been thoroughly discussed on Stack Overflow, so there's no point asking a new question - it will be a duplicate. – matt Jun 29 '19 at 02:15
  • Thank you for replying! Should I do that for each object (Label/Image/Sticker)? Or It's one protocol to rule them all? – Roi Mulia Jun 29 '19 at 02:22
  • Either way will work. But do research this first, as there are other options. I'm sorry we had to end up with a generic protocol, but that's the only way to express what you wanted to express. – matt Jun 29 '19 at 02:26
  • No problem! It's still much better to use generic protocol imh, mainly because you can work directly with mode-view and you can access the right 'item' every time without the fear of blind cast. Thank you for everything Matt! – Roi Mulia Jun 29 '19 at 02:31
  • Hey Matt! I worked all night trying to overcome this issue, but no results ah. I posted a new question detailing this specific issue. If you want to have a look: https://stackoverflow.com/questions/56817656/swift-filter-objects-that-conform-to-a-generic-protocol Thanks! – Roi Mulia Jun 29 '19 at 12:47
  • Well, as you can see, this is a duplicate of https://stackoverflow.com/questions/40387960/in-swift-how-to-cast-to-protocol-with-associated-type, and the answer Hamish is giving you is the same thing I told you. – matt Jun 29 '19 at 13:30