Search code examples
iosswiftclassgenericsprotocols

Use Generic Protocol to filter array of subviews


In our app, we have a canvas. The canvas could contain Stickers, Images, Texts, etc. We have a protocol CanvasItem that implement the common properties between these items:

protocol CanvasItemProtocol{
  var scale : CGFloat { get set }
}

class CanvasItem : CanvasItemProtocol {
   public var scale : CGFloat = 1.0
}

Then each class-model (Stickers, Text etc) conform to CanvasItem, adding class-specific properties:

public class StickerItem: CanvasItem {
  public var stickerName: String
}

public class ShapeItem: CanvasItem {
  public var shapeColor: UIColor
}

To show those, we first created a base generic (I think) UIView class that can be inited only with CanvasItem:

class ViewItem <T: CanvasItemProtocol>: UIView {
let canvasItem: T
init (t: T) {
    self.canvasItem = t
    super .init(frame: .zero)
  }
}

and then for each of the models, we create specific UIView<CanvasItem> class:

class CanvasShapeView: ViewClass<ShapeItem> { }

class CanvasStickerView: ViewClass<StickerItem> { }

Then I'm trying to do the following:

let superview = UIView()
let shapeView = CanvasStickerView(StickerItem())
let stickerView = CanvasShapeView(ShapeItem())

superview.addSubview(shapeView)
superview.addSubview(stickerView)

for canvasItemView in superview.subviews.compactMap({$0 as? ViewItem<CanvasItem>}) {
   print(canvasItemView.canvasItem.scale) // **access only the common properties**
}

It compiles, but return zero results (the casting not working)... I'm trying to access only to the CanvasItem common properties.

Gist link: Playground.swift

Any suggestions? We stuck on this for a few good days now.

Any help would be highly appreciated.


Solution

  • Protocols and generics in conjunction with subclassing don't work together very well

    Actually you don't need neither the protocol nor the view wrapper class. Subclassing UIView is sufficient.

    class CanvasItem : UIView
    {
        public var scale : CGFloat = 1.0
    }
    
    class StickerItem: CanvasItem
    {
        public var stickerName : String = ""
    }
    
    class ShapeItem: CanvasItem
    {
        public var color: UIColor = .red
    }
    
    let shapeItem = ShapeItem()
    let stickerItem = StickerItem()
    let superview = UIView()
    superview.addSubview(shapeItem)
    superview.addSubview(stickerItem)
    
    for case let canvasItemView as CanvasItem in superview.subviews {
        print(canvasItemView.scale)
    }