This is a bit of a contrived situation, since it's a pared-down version of a bigger project, just to try to get the same results.
The gist is that I have a protocol, and some classes that conform to that protocol, and then a class that has a list of objects, and then a collection that has multiple of those lists. Simple, really.
Since I have a bunch of code (usually SwiftUI) that shouldn't care what the actual object is, just that it conform to protocols, I tried using both protocols and generics. And mostly it works, except when it comes to creating objects. That, I haven't been able to figure out how to do. I've tried using global functions with generics, overloaded functions with specific types, member functions, and on and on. And I assume I am missing something obvious, because it seems like such a common thing to do. (Side note: the reason I did not put an init method into the protocol was because I want to be able to use this with Core Data -- and I can't add initializers to NSManagedObject, which is frustrating because all I want to do is add an extension to CDBook that wraps around getting/setting the properties.)
Compiling this, I get the error:
/tmp/t.swift:87:37: error: type of expression is ambiguous without more context
let newObject = self.list.library.newEntry(self.newName)
~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~
So, am I missing something obvious? Or is this not really doable in Swift, and I have to completely change how I think to get a solution?
import Foundation
import SwiftUI
protocol Namable: ObservableObject {
associatedtype idType: Hashable
var id: idType { get }
var name: String { get set }
}
class Song: Namable, ObservableObject {
var id = UUID()
var name: String
var band: String
init(_ name: String, band: String = "<unknown>") {
self.name = name
self.band = band
}
}
struct ISBN: Hashable {
var id: String
static func ==(lhs: ISBN, rhs: ISBN) -> Bool {
return rhs.id == lhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.id)
}
init() {
self.id = UUID().uuidString
}
}
class Book: Namable, ObservableObject {
var id = ISBN()
var name: String
var author: String
var fiction: Bool
init(_ name: String, author: String = "Public Domain", fiction: Bool = false) {
self.name = name
self.author = author
self.fiction = fiction
}
}
class Collection<T: Namable>: ObservableObject {
@Published var objects = [T]()
var library: Library? = nil
func addObject(_ object: T) {
self.objects.append(object)
}
func findObject(_ name: String) -> T? {
return self.objects.first(where: { $0.name == name })
}
}
class Library: ObservableObject {
@Published var books = Collection<Book>()
@Published var music = Collection<Song>()
init() {
self.books.library = self
self.music.library = self
}
func newEntry<T: Book>(_ name: String) -> T where T: Namable {
return Book(name) as! T
}
func newEntry<T: Song>(_ name: String) -> T where T: Namable {
return Song(name) as! T
}
}
let library = Library()
library.books.addObject(Book("Huck Finn", author: "Sameul Clemens"))
library.music.addObject(Song("Sing"))
struct AddObjectView<T: Namable>: View {
@ObservedObject var list: Collection<T>
@State var newName: String = ""
var body: some View {
Form {
TextField("Name", text: self.$newName)
Button("Add") {
let newObject = self.list.library.newEntry(self.newName)
self.list.addObject(newObject)
}
}
}
}