Reputation: 1469
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?
Upvotes: 0
Views: 83
Reputation: 6600
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)
}
}
Upvotes: 1
Reputation: 5088
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 .
Upvotes: 0