0

I want to have an array of different page (HTML and PDF pages, may add more later on).

I thought this is a good chance to practice programming with protocols.

This is what I came up with:

protocol PageDataProtocol {
    var data: String? {get}
}

protocol PageTitleProtocol {
    var title: String {get}
    var pageType: Page {get}
}

struct PDFData: PageTitleProtocol {
    var title: String
    var pageType: Page
}

struct HTMLData: PageDataProtocol, PageTitleProtocol {
    var title: String
    var data: String?
    var pageType: Page
}

enum Page {
    case html
    case pdf
    case links
}


var pageArray : [PageTitleProtocol]? = []

let test = PDFData(title: "title1", pageType: .html)
pageArray?.append(test)

let testtwo = HTMLData(title: "title2", data: "hello", pageType: .pdf)
pageArray?.append(testtwo)


for page in pageArray! {
    print (page.title)
    if let pg = page as? HTMLData {
        print (pg.data)
    }

    if page.pageType == .pdf {
        print ( (page as? HTMLData)?.data )
    }
}

Everything seems fine, but as in my print statements there is an obvious problem.

Edit: I don't claim the code to be perfect above. But in answer to a question below pageArray is optional as before downloading from an API there may be no pages, but that is different from no pages pulled from the API.

There is no point in storing the enum, as I have to cast to the correct type in any case.

The idea behind doing this is so I can store the pages in the single array, get them as needed and then display in my view controller (formatted by my view model) rather than drawing from 2+ arrays with different types in them.

I imagined writing a nice case statement to be able to display the information properly (there will be other fields in my finished structs) but this seems impossible with my implementation.

How can I refactor the code to be able to switch on the type of pages as declared above?

5
  • 1
    Why is pageArray optional? That just seems to add unnecessary unwrapping. Commented Sep 20, 2019 at 11:39
  • 1
    Why does pageType in your example not match the class type. Are you just trying to confuse us? Commented Sep 20, 2019 at 11:40
  • 2
    The idea of protocoling is not having to cast down to a specific type when accessing properties Commented Sep 20, 2019 at 11:45
  • keep composition in mind and then see protocols :) messing with protocols without knowing the purpose of protocols will just confuse you nothing more. Commented Sep 20, 2019 at 12:42
  • pageArray is optional as before downloading from an API there may be no pages, but that is different from no pages pulled from the API. Commented Sep 21, 2019 at 3:38

2 Answers 2

1

If your page datas are predefined and sealed from the scratch you don't even need a protocol at all(it would even be counter-productive), just expand your enum:

enum Page {
    case html(title: String, data: String?)
    case pdf(title: String)
    case links(title: String, array: [URL]) // or something else, it wasn't used in the example

    var title: String {
        switch(self) {
        case .html(let title, _), .pdf(let title), .links(let title, _):
            return title
        }
    }
}

var pageArray : [Page] = [] // no need to make it optional, btw

let test = Page.pdf(title: "title1")
pageArray.append(test)

let testtwo = Page.html(title: "title2", data: "hello")
pageArray.append(testtwo)

for page in pageArray {
    print (page.title)

    if case .html(_, let data) = page {
        print(data)
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

pageArray will be optional, as the data is pulled from an API, as I want to know if we have zero pages from the backend or have not made the call.
0

Ok, I don't get the idea of what you're trying to do, but here is a tip on what you doing, if that is what you looking for,

protocol PageDataProtocol {
    var data: String? {get}
}

protocol PageTitleProtocol {
    var title: String {get}
}

struct PDFData: PageTitleProtocol {
    var title: String
}

struct HTMLData: PageDataProtocol, PageTitleProtocol {
    var title: String
    var data: String?
}

var pageArray : [Any]? = []

let test = PDFData(title: "title1")
pageArray?.append(test)

let testtwo = HTMLData(title: "title2", data: "hello")
pageArray?.append(testtwo)


for page in pageArray! {
   if let page = page as? PageDataProtocol {
        print(page.data)
    }
    if let page = page as? PageTitleProtocol {
        print(page.title)
    }
}

Check this out, there is no need to enum, and casting to type protocol to use the property is always better because you could have 100 conforming struct and class to it, but you don't care about them you care about the properties inside if they were of type of this specific protocol, this is a proper use of protocoling .

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.