I have an array of enums witch contains associated structs like so:
struct TypeOne {
let id = UUID()
var image:UIImage
}
struct TypeTwo {
let id = UUID()
var image:UIImage
var url:URL
}
enum Types {
case one(a: TypeOne)
case two(b: TypeTwo)
}
var array:[Types] = []
Both possible structs share the variables; id and image. Is there any way to retrieve those values without doing like below?
switch array[0] {
case .one(a: let a):
print(a.id)
case .two(b: let b):
print(b.id)
}
Since the two variables are present in both structs I'm looking for a solution like this (if it exists):
print(array[0].(xxx).id)
UPDATE: My answer may miss the mark. Do you really need the enum? In my solution the need for the enum was eliminated. But if you need the enum because it carries other significant info, then my solution is no good. If you are just using the enum so you can put both types in an array, then consider using an array of Typeable instead where Typeable is the protocol of my solution.
Yes, you could use protocols for something like this to define a common set of methods or fields:
protocol Typeable {
var id:UUID {get}
var image:UIImage {get}
}
struct TypeOne:Typeable {
let id = UUID()
var image:UIImage
}
struct TypeTwo:Typeable {
let id = UUID()
var image:UIImage
var url:URL
}
var array:[Typeable] = []
let typeOne:TypeOne = //
let typeTwo:TypeTwo = //
array.append(typeOne)
array.append(typeTwo)
print(array[0].id)
// to declare a function that takes a Typeable
func myMethod<T:Typeable>(val:T) {
print(val.id)
}
Note, my protocol name is no good and doesn't match naming guidelines. Without knowing more about your use case I'm not sure what a good name would be.
Someway or another you're going to have to switch over each case to extract the associated values. However, we can make it convenient for use. I'd start with a protocol so our types have a common interface:
protocol TypeProtocol {
var id: UUID { get }
var image: UIImage { get }
}
struct TypeOne: TypeProtocol {
let id = UUID()
var image: UIImage
}
struct TypeTwo: TypeProtocol {
let id = UUID()
var image: UIImage
var url: URL
}
Your enum can largely be the same but we can extend it with some convenient properties.
enum Types {
case one(a: TypeOne)
case two(b: TypeTwo)
}
extension Types: TypeProtocol {
var id: UUID {
type.id
}
var image: UIImage {
type.image
}
var type: TypeProtocol {
switch self {
case .one(let a):
return a
case .two(let b):
return b
}
}
}
Finally, given an array of types we can use the convenience properties to access the underlying data:
var array: [Types] = []
array.forEach { print($0.id) }
array.forEach { print($0.type.id) }
Related
I want to add two different types, Pig and Frog, to the same array. Normally this would work well as long as both conform to the same protocol. However, in swiftUI if you want to use that array for a List then those types also need to conform to Identifiable. Like so:
protocol Animal: Identifiable {
var id: String { get }
var name: String { get }
var count: Int { get }
var image: String { get }
}
struct Pig: Animal {
var id: String = UUID().uuidString
var name = "Pig"
var count = 2
var image = "🐷"
}
struct Frog: Animal {
var id: String = UUID().uuidString
var name = "Frog"
var count = 3
var image = "🐸"
}
struct ContentView: View {
var animals: [Animal] = [Pig(), Frog()]
var body: some View {
List(animals) { animal in
Row(animals: animal)
}
}
}
This gives me the error Protocol 'Animal' can only be used as a generic constraint because it has Self or associated type requirements.
So my questions are why is this a problem and how do I get around it?
I experimented a bit and tried using an associated type like this:
protocol ConstructAnimal: View where AnimalType: Animal {
associatedtype AnimalType
var animals: [AnimalType] { get set }
}
But this still only lets me have one type in the array at a time.
I'm trying to practice generics so I probably missed something obvious and could use some help.
enum CacheTime {
case short
case long
}
struct Cached<T: Equatable> {
let object: T
let type: CacheTime
init(_ object: T, type: CacheTime) {
self.object = object
self.type = type
}
}
I then want to use this type with an actual concrete type as a property. I thought I could do:
struct Holder {
var people: Cached<[Person]> = []
}
I get the error that cannot convert value of type [Any] to specified type Cached<[Person]>. Any thoughts?
To shorten your code, you can use propertyWrappers. Your Cached struct would be:
#propertyWrapper
struct Cached<T: Equatable> {
var wrappedValue: T
let type: CacheTime
init(wrappedValue: T, type: CacheTime) {
self.wrappedValue = wrappedValue
self.type = type
}
}
And use it like this:
struct Holder {
#Cached<[Person]>(type: .short) var people = []
}
You are initializing people as an array which is not. It is expecting a Cached object something like this
Cached([Person](), type: .short))
I'm going through a tutorial and I noticed that the author extended their protocol called Activity and wrote the function's body in their code. This does compile however I was under the impression that protocols only show method signatures or if it does implement the body then it'll be a mutating function. The code below doesn't use mutating on one of its functions but it still runs and WORKS! Can someone explain the phenomena or confirm that protocol extensions can have method bodies?
import CareKit
import SwiftyJSON
enum ActivityType: String {
case Intervention
case Assessment
}
enum ScheduleType: String {
case Weekly
case Daily
}
enum StepFormat : String {
case Scale
case Quantity
}
protocol Activity {
var identifier : String { get set}
var groupIdentifier : String { get set}
var title : String { get set}
var colour : UIColor? { get set}
var text : String { get set}
var startDate : Date { get set}
var schedule : [NSNumber] { get set}
var scheduleType : ScheduleType { get set}
var instructions : String? { get set}
var imageURL : NSURL? { get set}
var activityType: ActivityType { get set}
var medication : Medication? { get set}
init()
init(json: JSON)
func createCareKitActivity() -> OCKCarePlanActivity
}
extension Activity {
// A mutating function to allow Acticities or Assessments to intialiser base properties
mutating func parseActivityFields(json: JSON) {
self.identifier = json["identifier"].string!
self.groupIdentifier = json["group_identifier"].string!
self.title = json["title"].string!
self.text = json["text"].string!
let colourString = json["color"].string!
self.colour = UIColor.colorWithString(colourString)
if let instructionString = json["instructions"].string {
self.instructions = instructionString
}
if let imageString = json["imageURL"].string {
let componentsOfString = imageString.components(separatedBy: ".")
if let pathForResource = Bundle.main.path(forResource: componentsOfString[0], ofType: componentsOfString[1]){
self.imageURL = NSURL(fileURLWithPath: pathForResource)
}
}
self.startDate = dateFromString(string: json["startdate"].string!)!
self.scheduleType = ScheduleType(rawValue: json["scheduletype"].string!)!
self.schedule = json["schedule"].string!.components(separatedBy: ",").map ( {
NSNumber(value: Int32($0)!)
})
if let medication = json["medication"].string,
let medicationImageString = json["medicationimage"].string {
let componentsOfString = medicationImageString.components(separatedBy: ".")
let pathForResource = Bundle.main.path(forResource: componentsOfString[0], ofType: componentsOfString[1])
self.medication = Medication.init(medication: medication, imageURL: NSURL.init(fileURLWithPath: pathForResource!))
}
}
init(json: JSON) {
self.init()
self.parseActivityFields(json: json)
}
func createCareKitActivity() -> OCKCarePlanActivity{
//creates a schedule based on the internal values for start and end dates
let startDateComponents = NSDateComponents(date: self.startDate, calendar: NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian)! as Calendar)
let activitySchedule: OCKCareSchedule!
switch self.scheduleType {
case .Weekly :
activitySchedule = OCKCareSchedule.weeklySchedule(withStartDate: startDateComponents as DateComponents, occurrencesOnEachDay: self.schedule)
case .Daily:
activitySchedule = OCKCareSchedule.dailySchedule(withStartDate: startDateComponents as DateComponents, occurrencesPerDay: self.schedule[0].uintValue)
}
let activity = OCKCarePlanActivity.intervention(
withIdentifier: identifier,
groupIdentifier: nil,
title: title,
text: text,
tintColor: colour,
instructions: instructions,
imageURL: imageURL as? URL,
schedule: activitySchedule,
userInfo: ["medication": medication], optional: false)
return activity
}
}
In Swift, extensions allow you to provide default implementations for protocols.
According to the Swift documentation on Protocols,
Protocols can be extended to provide method, initializer, subscript,
and computed property implementations to conforming types. This allows
you to define behavior on protocols themselves, rather than in each
type’s individual conformance or in a global function.
Source: Swift Documentation
In Swift, does a protocol extension allow function bodies?
Yes. It's a really convenient way to add specific functionality only to instances of some protocol. Consider this:
protocol Flying {
func useWings()
}
extension Flying {
func fly() {}
}
class Animal {}
class Bird: Animal {}
extension Bird: Flying {
func useWings() {}
}
let bird = Bird()
bird.fly()
It also makes some logic here. If something can use wings, then it also probably can fly. So when we extend Bird to implement Flying as it has useWings - it also can fly now.
The code below doesn't use mutating on one of its functions but it still runs and WORKS! Can someone explain the phenomena
Mutating keyword says that function will mutate the value it's called onto. You have to say it explicitly if your protocol is not a class (protocol Some: class {}). The createCareKitActivity() doesn't mutate self, so you don't have to specify mutating
Class -> Protocol -> Extension
Swift's protocol extension allows your class operates by additional or default functions
I want to create enum in my struct. I tried:
struct ProfileTextfieldItem {
var txtfPlaceholder: String
var bottomTxt: String
var modelType : Int
enum modelType {
case phone
case skype
}
}
Then i did:
let tesEnum = ProfileTextfieldItem(txtfPlaceholder: "o.ivanova", bottomTxt: "Логин Skype", enumType: phone)
However compiler didn't let me run, it says - Use of unresolved identifier - phone.
Instead of this, here's what you should do:
struct ProfileTextfieldItem {
var txtfPlaceholder: String
var bottomTxt: String
var modelType: ModelType
enum ModelType {
case phone
case skype
}
}
In Swift, enums are Types, they can be used just like classes and structs. To make an instance of this, do:
let tesEnum = ProfileTextfieldItem(txtfPlaceholder: "o.ivanova", bottomTxt: "Логин Skype", modelType: .phone)
Note the third parameter has a . in front of it and is called modelType, not enumType.
You should define the struct like this:
struct ProfileTextfieldItem {
var txtfPlaceholder: String
var bottomTxt: String
var modelType : ModelType
enum ModelType {
case phone
case skype
}
}
and initial the var tesEnum like this:
let tesEnum = ProfileTextfieldItem(txtfPlaceholder: "o.ivanova", bottomTxt: "Логин Skype", modelType: .phone)
I want to map between any two objects which conform to the same protocol. It would be convenient to do so via a function with the signature:
func mapFrom<T>(objectA: T, to inout objectB: T)
Even better though (for immutable types) would be to have it in the form:
func map<T, U: T>(from source: T) -> U
where somehow it could initialize a U object from the values in T.
I would like to do this via Swift Reflection rather than using the Objective-C run-time, but I would settle for that if it was the only way. If somehow it could be done without reflection that would be amazing, but I don't see how.
The reason I want to do this is because I have mutable Realm classes which conform to their respective protocol, and I want to map them to the immutable struct types.
An example would be:
/**
The protocol.
*/
protocol Food {
var name: String { get }
var weight: Float { get }
var price: Float { get }
}
/**
The mutable Realm class representation.
*/
final class FoodEntity: Object, Food {
dynamic var name = ""
dynamic var weight = 0.0
dynamic var price = 0.0
}
/**
The final struct I want to map to from the Realm representation.
*/
struct FoodProduct: Food {
let name: String
let weight: Float
let price: Float
}
I would like to be able to have a generic function or method with which to map a FoodEntity to a FoodProduct without having to manually do something like:
FoodProduct(name: entity.name, weight: entity.weight, price: entity.price)
How can this be done, if it can be done at all?
I think you are looking for something like this.
func fetchAllFoodProducts() -> [FoodProduct]
{
var foodProducts : [FoodProduct] = []
// Fetch From Realm
let products = realm.objects(FoodEntity.self)
for product in products
{
foodProducts.append(FoodProduct(name: product.name, weight: product.weight, price: product.price))
}
return foodProducts
}
The thing is that there can't be a generic way to do this. Because you have to assign the values of name, weight & price somehow. This is the closest you can get, I think.
Or you can do something like this.
func fetchAllFoodProducts() -> [FoodProduct]
{
var foodProducts : [FoodProduct] = []
// Fetch From Realm
let products = realm.objects(FoodEntity.self)
for product in products
{
foodProducts.append(FoodProduct(entity: product))
}
return foodProducts
}
By altering your FoodEntity a little.
struct FoodProduct: Food {
let name: String
let weight: Float
let price: Float
init(entity : FoodEntity)
{
self.name = entity.name
self.weight = entity.weight
self.price = entity.price
}
}