How I can access to one Struct property that implements two protocols? - swift

Im just learning Swift 4 and I have some troubles trying to access a property of an struct that have to implement 2 protocols
here is my struct
struct FigureA {
static func load() -> Model {
return Model.make(
name: "FigureName",
status: "Painted",
image: UIImage(named: "FigureA"),
description: "Good figure")
}
}
here the protocol 1
protocol ListModel: class {
var name: String { get }
var status: String { get }
var image: UIImage? { get }
}
here the protocol 2
protocol DetailModel: ListModel {
var categoryName: String { get }
var modelDescription: String? { get }
}
And I want to get the access to the description of the Struct but I don't know how at all.
Can someone give me a bit of light.

Here is good start for you:
protocol BaseProtocol {
var id: Int { get set }
}
protocol PersonProtocol: BaseProtocol {
var firstName: String { get set }
var lastName: String { get set }
var name: String { get }
}
struct Person: PersonProtocol {
var id: Int
var firstName: String
var lastName: String
var name: String { return firstName + " " + lastName }
}
//≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠≠
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Create Struct Instance & Print properties.
let person = Person(id: 1001, firstName: "Manish", lastName: "Rathi")
print(person.id)
print(person.firstName)
print(person.lastName)
print(person.name)
}
}
#HappyCoding 😊

Related

Using a protocol array with ForEach and bindable syntax

I've got an #Published protocol array that I am looping through with a ForEach to present the elements in some view. I'd like to be able to use SwiftUI bindable syntax with the ForEach to generate a binding for me so I can mutate each element and have it reflected in the original array.
This seems to work for the properties that are implemented in the protocol, but I am unsure how I would go about accessing properties that are unique to the protocol's conforming type. In the example code below, that would be the Animal's owner property or the Human's age property. I figured some sort of type casting might be necessary, but can't figure out how to retain the reference to the underlying array via the binding.
Let me know if you need more detail.
import SwiftUI
protocol Testable {
var id: UUID { get }
var name: String { get set }
}
struct Human: Testable {
let id: UUID
var name: String
var age: Int
}
struct Animal: Testable {
let id: UUID
var name: String
var owner: String
}
class ContentViewModel: ObservableObject {
#Published var animalsAndHumans: [Testable] = []
}
struct ContentView: View {
#StateObject var vm: ContentViewModel = ContentViewModel()
var body: some View {
VStack {
ForEach($vm.animalsAndHumans, id: \AnyTestable.id) { $object in
TextField("textfield", text: $object.name)
// if the object is an Animal, how can I get it's owner?
}
Button("Add animal") {
vm.animalsAndHumans.append(Animal(id: UUID(), name: "Mick", owner: "harry"))
}
Button("Add Human") {
vm.animalsAndHumans.append(Human(id: UUID(), name: "Ash", age: 26))
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This is a thorny problem with your data types.
If you can change your data types, you can make this easier to solve.
For example, maybe you can model your data like this instead, using an enum instead of a protocol to represent the variants:
struct Testable {
let id: UUID
var name: String
var variant: Variant
enum Variant {
case animal(Animal)
case human(Human)
}
struct Animal {
var owner: String
}
struct Human {
var age: Int
}
}
It will also help to add accessors for the two variants' associated data:
extension Testable {
var animal: Animal? {
get {
guard case .animal(let animal) = variant else { return nil }
return animal
}
set {
guard let newValue = newValue, case .animal(_) = variant else { return }
variant = .animal(newValue)
}
}
var human: Human? {
get {
guard case .human(let human) = variant else { return nil }
return human
}
set {
guard let newValue = newValue, case .human(_) = variant else { return }
variant = .human(newValue)
}
}
}
Then you can write your view like this:
class ContentViewModel: ObservableObject {
#Published var testables: [Testable] = []
}
struct ContentView: View {
#StateObject var vm: ContentViewModel = ContentViewModel()
var body: some View {
VStack {
List {
ForEach($vm.testables, id: \.id) { $testable in
VStack {
TextField("Name", text: $testable.name)
if let human = Binding($testable.human) {
Stepper("Age: \(human.wrappedValue.age)", value: human.age)
}
else if let animal = Binding($testable.animal) {
HStack {
Text("Owner: ")
TextField("Owner", text: animal.owner)
}
}
}
}
}
HStack {
Button("Add animal") {
vm.testables.append(Testable(
id: UUID(),
name: "Mick",
variant: .animal(.init(owner: "harry"))
))
}
Button("Add Human") {
vm.testables.append(Testable(
id: UUID(),
name: "Ash",
variant: .human(.init(age: 26))
))
}
}
}
}
}
Simple way to solve is extending your Testable protocol. Something likes
protocol Testable {
var id: UUID { get }
var name: String { get set }
var extra: String? { get }
}
struct Human: Testable {
let id: UUID
var name: String
var age: Int
var extra: String? { nil }
}
struct Animal: Testable {
let id: UUID
var name: String
var owner: String
var extra: String? { return owner }
}
Your ForEach block doesn't need to know the concrete type: Animal or Human, just check the extra of Testable to decide to add new element or not.

Type 'American' does not conform to protocol 'Food'

have been fighting the code for some time now, and can't resolve the issue of: Type 'American' does not conform to protocol 'Food'
protocol Food {
var type: String { get }
var ingredient1: String { get }
var price: Int { get set}
func showHistory()
mutating func transfer()
init(type: String)
init(ingredient1: String)
}
struct American: Food {
let type: String
let ingredient1: String
var price: Int = 125
init(type: String, ingredient1: String) {
self.type = type
self.ingredient1 = ingredient1
}
func showHistory() {
print("American history")
}
mutating func transfer() {
print("transfering burgers")
}
}
I doubt you intended to separate your inits into two separate calls which is causing your error. You could solve this by implementing two separate inits as well but then you'd have to initialize both properties in separate inits which will give you an error
protocol Food {
var type: String { get }
var ingredient1: String { get }
var price: Int { get set}
func showHistory()
mutating func transfer()
init(type: String, ingredient1: String)
}
struct American: Food {
let type: String
let ingredient1: String
var price: Int = 125
init(type: String, ingredient1: String) {
self.type = type
self.ingredient1 = ingredient1
}
func showHistory() {
print("American history")
}
mutating func transfer() {
print("transfering burgers")
}
}

Generic UITableViewCell with different type of data

Data keys change for different types and I want to use just this tableview cell for different types. I don't want to create new tableview cell for each type. Is it possible? I guess I should use generics but how can I implement for this problem?
I have a custom UITableViewCell that includes
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var departmentLabel: UILabel!
#IBOutlet weak var genderLabel: UILabel!
{
"data": [
{
"type": "employee",
"data": {
"name": "Michael",
"department": "HR",
"gender": "Male"
}
},
{
"type": "employer",
"data": {
"name": "Julia",
"division": "Finance",
"sex": "Female"
}
}
]
}
If I'm understanding your question, you want data that takes different forms but returned in the same array to be representable in a single table view cell. This is definitely doable, the challenge being parsing the variable JSON response into something consistent that you can send to your cells to be displayed.
This sort of thing would be possible using a custom implmentation of Codable. The caveat being that you'll need to know what sort of options this data could be coming down in (ie will gender always be either 'sex' or 'gender'?)
An example would be:
struct CompanyPersonContainer: Decodable {
var data: [CompanyRelatedPerson]
enum CodingKeys: String, CodingKey {
case data
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
data = []
if var dataContainer = try? values.nestedUnkeyedContainer(forKey: CodingKeys.data) {
while !(dataContainer.isAtEnd) {
let companyPerson: CompanyRelatedPerson
if let employee = try dataContainer.decodeIfPresent(Employee.self) {
companyPerson = employee
} else if let employer = try dataContainer.decodeIfPresent(Employer.self) {
companyPerson = employer
} else {
fatalError() // You need to know the total possible values you'll be decoding
}
data.append(companyPerson)
}
}
}
}
protocol CompanyRelatedPerson: Codable {
var name: String { get set }
var department: String { get set }
var gender: String { get set }
}
struct Employee: Codable, CompanyRelatedPerson {
var name: String
var department: String
var gender: String
}
struct Employer: Codable, CompanyRelatedPerson {
var name: String
var department: String
var gender: String
enum CodingKeys: String, CodingKey {
case name
case department = "division"
case gender = "sex"
}
}
I think you can create a protocol ViewModel then create different view models who inherit this protocol.
protocol ViewModel {
var name: String { get set }
var department: String { get set }
var gender: String { get set }
}
struct EmployeeViewModel: ViewModel {
var name: String
var department: String
var gender: String
// Suppose you already have this data model from data.
init(employee: Employee) {
self.name = employee.name
self.department = employee.department
self.gender = employee.gender
}
}
struct Employer: ViewModel {
var name: String
var department: String
var gender: String
init(employer: Employer) {
self.name = employer.name
self.department = employer.division
self.gender = employer.sex
}
}
Then, create a function in your tableview cell to assign the values to your properties.
// In your table view cell
func setViewModel(_ viewModel: ViewModel) {
self.nameLabel.text = vm.name
// do the same for the rest of labels...
}
For me, the advantage of using a protocol view model is that you can pass any view models inherited from protocol to your view to assign the value, that avoids to expose your data in your view (tableview cell in this case).

Function accepting generic parameters

I have a subclass of NSManagedObject. I'm using a protocol to create a "wrapper" class. In my controller the data can be either: Items or Item1. To be able to use my function I'll have to add the protocol ItemInfo to Items but that means I'll have to add
var items: Items { return self }
in Items, which seems a bit redundant. I've tried creating a base class but that didn't work.
Question:
Is there a better way to let my function accept both Items and Item1 as parameter like using generics?
NSManagedObject:
class Items: NSManagedObject {
#NSManaged var name: String
#NSManaged var code: String
}
Protocol:
protocol ItemInfo {
var item: Items { get }
}
extension ItemInfo {
var name : String { return item.name }
var code : String { return item.code }
}
Wrapper:
class Item1: ItemInfo {
let item: Items
init(item: Items) { self.item = item }
}
function:
func handleItem(item: ItemInfo) {
print(item.name)
print(item.code)
}
I could use:
func handleItem<T>(item: T) {
if let a = item as? Items {
print(a.name)
print(a.code)
}
if let a = item as? ItemInfo {
print(a.name)
print(a.code)
}
}
But this doesn't seem the right way ...
If I understand correctly what you are trying to achieve (function accepting two kind of items), I would use protocol as type accepted by function, refer the code below
class Items: NSManagedObject, ItemInfo {
#NSManaged var name: String
#NSManaged var code: String
}
class Item1: NSManagedObject, ItemInfo {
#NSManaged var name: String
#NSManaged var code: String
}
protocol ItemInfo {
var name: String {get set}
var code: String {get set}
}
and function would look like this
func handle(item: ItemInfo) {
print(item.code)
print(item.name)
}

How to create my game architecture in a protocol oriented way?

I haven't found any good ways to design a protocol oriented item architecture for games.
Heres the first version with Structs:
protocol Usable {
func useItem()
}
protocol Item {
var name: String { get }
var amount: Int { get }
var image: String { get }
}
struct Sword: Item, Usable {
var name = ""
var amount = 0
var image = ""
func useItem() {
}
}
struct Shield: Item, Usable {
var name = ""
var amount = 0
var image = ""
func useItem() {
}
}
The problem with this is I have to copy paste the variables which are A LOT of code across items.
Heres the second version with Classes:
protocol Usable {
func useItem()
}
class BaseItem {
var name = ""
var amount = 0
var image = ""
}
class SwordClass: BaseItem, Usable {
func useItem() {
}
}
This looks pretty good, but the problem is these are reference types and I would prefer them to be value types.
What is the right way to solve this problem?
You should create a generic struct which conforms to your protocols and which requires initialisation with default values and a 'use' closure:
protocol Usable {
func useItem()
}
protocol Item {
var name: String { get }
var amount: Int { get }
var image: String { get }
}
struct UsableItem: Item, Usable {
var name = ""
var amount = 0
var image = ""
let use: (Void -> Void)
init(name: String, image: String, use: (Void -> Void)) {
self.name = name
self.image = image
self.use = use
}
func useItem() {
self.use()
}
}
Then your JSON processing would create instances with the appropriate logic:
var sword = UsableItem(name: "sword", image: "sword") {
print("cut")
}