Swift protocol initializer precludes adding more stored properties to struct - swift

TL;DR:
I want a protocol to provide default init behavior, but the compiler resists adopters adding more stored properties. I solved this with composition instead of inheritance, but what's wrong with my original approach?
Motivation
I want to automate the transformation of objects from design specifications to runtime specs. I use the example of scaling a CGSize but the intent is more general than just geometric layout. (IOW e.g. my solution won't be to adopt/reject/rewrite autolayout.)
Code
You can paste this right into a Playground, and it will run correctly.
protocol Transformable {
var size : CGSize { get } // Will be set automatically;
static var DESIGN_SPEC : CGSize { get } // could be any type.
init(size: CGSize) // Extension will require this.
}
// A simple example of transforming.
func transform(_ s: CGSize) -> CGSize {
CGSize(width: s.width/2, height: s.height/2)
}
// Add some default behavior.
// Am I sinning to want to inherit implementation?
extension Transformable {
init() { self.init(size: transform(Self.DESIGN_SPEC)) }
// User gets instance with design already transformed. No muss, fuss.
}
// Adopt the protocol...
struct T : Transformable {
let size: CGSize
static let DESIGN_SPEC = CGSize(width: 10, height: 10)
}
// ...and use it.
let t = T()
t.size // We get (5,5) as expected.
But every Eden must have its snake. I want a Transformable with another property:
struct T2 : Transformable {
// As before.
let size: CGSize
static let DESIGN_SPEC = CGSize(width: 10, height: 10)
let i : Int // This causes all sorts of trouble.
}
Whaa? Type 'T2' does not conform to protocol 'Transformable'
We have lost the synthesized initializer that sets the size member.
So... we put it back:
struct T3 : Transformable {
// As before.
let size: CGSize
static let DESIGN_SPEC = CGSize(width: 10, height: 10)
let i : Int
init(size: CGSize) {
self.size = size
self.i = 0 // But this is a hard-coded value.
}
}
But now our new member is statically determined. So we try adding another initializer:
struct T4 : Transformable {
// As before.
let size: CGSize
static let DESIGN_SPEC = CGSize(width: 10, height: 10)
let i : Int
init(size: CGSize) { self.size = size ; self.i = 0 }
// Try setting 'i':
init(i: Int) {
self.init() // Get the design spec properly transformed.
self.i = i // 'let' property 'i' may not be initialized directly;
} // use "self.init(...)" or "self = ..." instead
}
Declaring i as var shuts the compiler up. But i is immutable, and I want i that way. Explain to me why what I want is so wrong... This page is too small to include all the variations I tried, but perhaps I have missed the simple answer.

Related

Getting a `Type of expression is ambiguous without more context` when trying to returning a `some View` with a `ZStack` object

I'm trying to use CoreGraphics to draw some musical notes from a struct that is defined elsewhere in a swiftui App
func drawSheetMusic(in size: CGSize) -> some View {
return ZStack {
Color.clear.drawingGroup { ctx in
for note in self.musicData.notes {
let rect = CGRect(x: note.position.x - note.radius, y: note.position.y - note.radius, width: 2 * note.radius, height: 2 * note.radius)
ctx.cgContext.addEllipse(in: rect)
ctx.cgContext.setFillColor(Color.black.cgColor)
ctx.cgContext.fillPath()
}
return Rectangle().fill(Color.clear)
}
}
}
But this is returning an error Type of expression is ambiguous without more context on Line 2 there. What exactly am I doing wrong here?
func drawingGroup(opaque: Bool = false, colorMode: ColorRenderingMode = .nonLinear) -> some View
doesn't take a closure parameter. The problem becomes obvious if you remove the ZStack.
You seem to be confusing this with
UIGraphicsImageRenderer.image(actions: (UIGraphicsImageRendererContext) -> Void) -> UIImage

How to return early from an init in swift 3

I have an app in which I would like to create an object based on whether there is already an equivalent object saved to user defaults. I would like to detect this in the class init and return early, if an object is detected. This is what I am trying to do:
init() {
/* There are two possibilities when creating a hero:
1. The hero is brand new and needs to be built from scratch
2. The hero is loaded from defaults */
// Check to see if there is existing game data:
if defaultExistsForGameData() {
// This means there is a hero to load and no need to create a new one
self = extractHeroFromDefaults() // This just loads from UserDefaults
print("Loading hero from defaults with name of: \(hero.heroName).")
return self
}
// These actions are for creating a brand new hero
let size = CGSize(width: 32, height: 32)
let heroTexture = SKTexture(imageNamed: "hero2.ico")
super.init(texture: heroTexture, color: .clear, size: size)
self.isUserInteractionEnabled = true
self.name = "hero"
self.zPosition = 50
}
There are a couple errors in the console, self is immutable, etc. What I want to know if this is a valid pattern or if I should be taking an entirely different approach.
In Swift (unlike ObjC), init can't return a different object than itself. A common way you achieve what you're trying to do here is with a class factory method (optionally making init private if you don't want other objects to be able to call it directly).
For example, something along these lines:
class func loadOrCreate() -> Hero {
if defaultExistsForGameData() {
// This means there is a hero to load and no need to create a new one
print("Loading hero from defaults with name of: \(hero.heroName).")
return extractHeroFromDefaults() // This just loads from UserDefaults
} else {
return Hero()
}
}
private init() {
let size = CGSize(width: 32, height: 32)
let heroTexture = SKTexture(imageNamed: "hero2.ico")
super.init(texture: heroTexture, color: .clear, size: size)
self.isUserInteractionEnabled = true
self.name = "hero"
self.zPosition = 50
}
Another approach that is closer to your current API is to create a separate (possibly private) designated initializer like this:
private init(name: String, zPosition: Int) {
let size = CGSize(width: 32, height: 32)
let heroTexture = SKTexture(imageNamed: "hero2.ico")
super.init(texture: heroTexture, color: .clear, size: size)
self.isUserInteractionEnabled = true
self.name = name
self.zPosition = zPosition
}
public convenience init() {
let name: String
let zPosition: Int
if defaultExistsForGameData() {
name = defaultName() // Read it out of user defaults
zPosition = defaultZPosition
} else {
name = "hero"
zPosition = 50
}
self.init(name: name, zPosition: zPosition)
}
The one problem with this approach is that it can be a little surprising. It's not precisely clear what's supposed to happen if you create multiple Hero objects. something like loadOrCreate() makes it very clear that there are external influences.

Adding New Parameters to a Child Class in Swift

I've been writing a game that has a subclass of the SKSpriteNode with some extra functions and variables. I'd like to set some of the variables when the object is created eg
let mySprite = MySubclass (width: 24, height 33)
I'm not sure this is possible which means I'll probably have to call a methos of the subclass to set the vars in a separate stage which is a bit clunky:
let mySprite = MySubclass ()
mySprite.setSize(24, height: 33)
Any ideas how I can do this in a more elegant way?
Many Thanks,
Kw
This is very fundamental OOP. Here is how you do it in Swift:
class MySubClass: SKSpriteNode {
var width: CGFloat // Declare your own properties
var height: CGFloat // ...
init(width: CGFloat, height: CGFloat) {
self.width = width // set up your own properties
self.height = height // ...
super.init() // call up to the super-class's init to set up its properties
}
}
Have you read Apple's book The Swift Programming Language? It's free and clearly covers this...

How can I handle different types using generic type in swift?

I'm trying to write a class which allows me to easily interpolate between two values.
class Interpolation
{
class func interpolate<T>(from: T, to: T, progress: CGFloat) -> T
{
// Safety
assert(progress >= 0 && progress <= 1, "Invalid progress value: \(progress)")
if let a = from as? CGFloat, let b = to as? CGFloat
{
}
if let a = from as? CGPoint, let b = to as? CGPoint
{
}
if let from = from as? CGRect, let to = to as? CGRect
{
var returnRect = CGRect()
returnRect.origin.x = from.origin.x + (to.origin.x-from.origin.x) * progress
returnRect.origin.y = from.origin.y + (to.origin.y-from.origin.y) * progress
returnRect.size.width = from.size.width + (to.size.width-from.size.width) * progress
returnRect.size.height = from.size.height + (to.size.height-from.size.height) * progress
return returnRect // Cannot convert return expression of type 'CGRect' to return type 'T'
}
return from
}
}
Unfortunately, it gives me an error at return returnRect: Cannot convert return expression of type 'CGRect' to return type 'T'. Maybe I'm not understanding how generics are used...I just want to have one function that will handle interpolating between various types, rather than having a bunch of functions like func interpolate(from: Int, to: Int), func interpolate(from: CGPoint, to: CGPoint), etc.
The problem is that T is a generic placeholder – meaning that you cannot know what the actual concrete type of T is from within the function. Therefore although you are able to conditionally cast from and to to a CGRect (thus establishing that T == CGRect), Swift is unable to infer this information and therefore prohibits attempting to return a CGRect when it expects a return of T.
The crude solution therefore is to force cast the return result back to T in order to bridge this gap in information with the type-system:
if let from = from as? CGRect, let to = to as? CGRect {
// ...
return returnRect as! T
}
However, this kind of type-casting is really a sign that you're fighting the type-system and not taking advantage of the static typing that generics offer, and therefore is not recommended.
The better solution, as #Wongzigii has already said, is to use a protocol. For example, if you define an Interpolate protocol as he shows in his answer – you can then use this protocol in order to constrain your generic placeholder T in your interpolate function:
class Interpolation {
class func interpolate<T:Interpolate>(from: T, to: T, progress: CGFloat) -> T {
// Safety
assert(progress >= 0 && progress <= 1, "Invalid progress value: \(progress)")
return T.interpolate(from: from, to: to, progress: progress)
}
}
This solves many of your problems – it does away with the runtime type-casting and instead uses the protocol constraint in order to call the specialised interpolate function. The protocol constraint also prevents you from passing any types that don't conform to Interpolate at compile-time, and therefore also solves the problem of what to do when your type-casting fails.
Although that being said, I actually quite like the solution that #JoshCaswell suggested in his answer to your other question – overloading operators in order to achieve this functionality. As with the previous solution, the key is to define a protocol that encapsulates the functionality you're defining on each type, and then constrain your generic function to this protocol.
A simple implementation may look like this:
protocol Interpolatable {
func +(lhs:Self, rhs:Self) -> Self
func -(lhs:Self, rhs:Self) -> Self
func *(lhs:Self, rhs:CGFloat) -> Self
}
func +(lhs:CGRect, rhs:CGRect) -> CGRect {
return CGRect(x: lhs.origin.x+rhs.origin.x,
y: lhs.origin.y+rhs.origin.y,
width: lhs.size.width+rhs.size.width,
height: lhs.size.height+rhs.size.height)
}
func -(lhs:CGRect, rhs:CGRect) -> CGRect {
return CGRect(x: lhs.origin.x-rhs.origin.x,
y: lhs.origin.y-rhs.origin.y,
width: lhs.size.width-rhs.size.width,
height: lhs.size.height-rhs.size.height)
}
func *(lhs:CGRect, rhs:CGFloat) -> CGRect {
return CGRect(x: lhs.origin.x*rhs,
y: lhs.origin.y*rhs,
width: lhs.size.width*rhs,
height: lhs.size.height*rhs)
}
extension CGRect : Interpolatable {}
extension CGFloat : Interpolatable {}
class Interpolation {
class func interpolate<T:Interpolatable>(from: T, to: T, progress: CGFloat) -> T {
assert(progress >= 0 && progress <= 1, "Invalid progress value: \(progress)")
return from + (to - from) * progress
}
}
It would be nice if you use Protocol to scale your generic type.
protocol Interpolate {
associatedtype T
static func interpolate(from: T, to: T, progress: CGFloat) -> T
}
Then let CGRect extension conform to your protocol:
extension CGRect: Interpolate {
typealias T = CGRect
static func interpolate(from: T, to: T, progress: CGFloat) -> CGRect.T {
var returnRect = CGRect()
returnRect.origin.x = from.origin.x + (to.origin.x-from.origin.x) * progress
returnRect.origin.y = from.origin.y + (to.origin.y-from.origin.y) * progress
returnRect.size.width = from.size.width + (to.size.width-from.size.width) * progress
returnRect.size.height = from.size.height + (to.size.height-from.size.height) * progress
return returnRect
}
}
var from = CGRect(x: 0, y: 0, width: 1, height: 1) // (0, 0, 1, 1)
var to = CGRect(x: 1, y: 1, width: 0, height: 0) // (1, 1, 0, 0)
CGRect.interpolate(from, to: to, progress: 1) // (1, 1, 0, 0)
Also, this would make NSString conform to protocol Interpolate easily, like:
extension NSString: Interpolate {
typealias T = NSString
static func interpolate(from: T, to: T, progress: CGFloat) -> NSString.T {
//...
return ""
}
}

Swift subclass' init must set all properties before calling super but property requires super's init to complete first

I'm trying to write two Swift classes, one is a subclass of the other and needs to take one of the superclass' properties and use that to configure its self.
class BaseClass {
let someValue: Double
let size: CGSize
init (size: CGSize) {
self.size = size
self.someValue = size.width / 2.0 //Doesn't really matter how this is calculated
}
}
class Subclass: BaseClass {
let someNewValue: Double
override init(size: CGSize) {
super.init(size: size)
self.someNewValue = self.someValue * 2
}
}
The problem is that the subclass requires that its call to super.init is after it sets initial values for all of its properties. However the self.someNewValue = self.someValue * 2 call which does this, relies on the super's init having been called first to set self.someValue. I guess I could get around this by turning let someValue: Double to var someValue: Double on the BaseClass, and then set its value in the subclass's init as well as the base class's init, but that just seems bad.
I see two solutions here:
Make someNewValue implicitly unwrapped optional
class Subclass: BaseClass {
let someNewValue: Double!
override init(size: CGSize) {
super.init(size: size)
self.someNewValue = self.someValue * 2
}
}
or give someNewValue some default value before calculating its final value
class Subclass: BaseClass {
let someNewValue: Double = 0.0
override init(size: CGSize) {
super.init(size: size)
self.someNewValue = self.someValue * 2
}
}
Both approches shouldn't cause any problems