Why omitting of structure property reports compile-time error - swift

I'm study the swift by the Apple's official book "The Swift Programming Language" and faced a compilation error. I create a structure which has two properties with default values. And attempt to initialise with only one parameter results in a compilation error. What a reason of it?
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
let zeroByTwo = Size(height: 2.0)
let zeroByZero = Size()

In Swift whenever we are going to create a structure. Swift automatically creates the default initializer for the particular structure. In your case, you will get a compile-time error in let zeroByTwo = Size(height: 2.0).
Because you are not passing all the parameters required by the default initializer. to fix that issue you can create your own init() functions in your structure as shown below.
struct Size {
var width:Double, height:Double
init(width:Double = 0.0, height:Double = 0.0){
self.width = width
self.height = height
}
}
let twoByTwo = Size(width: 2.0, height: 2.0)
let zeroByTwo = Size(width: 2.0)
let zeroByZero = Size()

Related

How store a tuple for NSGradient(colorsAndLocations:) in swift

To apply a gradient to a NSView, it seems best to make a class that inherits from NSView and override the draw method that sets a path and draws the gradient.
class GradientView: NSView {
var startColor = NSColor.red
var startPosition: CGFloat = 0
var endColor = NSColor.white
var endPosition: CGFloat = 1
var rotation: CGFloat = 0
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let bgGradient = NSGradient(colorsAndLocations: (startColor, startPosition), (endColor, endPosition))
let path = NSBezierPath.init(rect: self.bounds)
bgGradient?.draw(in: path, angle: rotation)
}
}
(Let me know if there's a better/easier way...)
It works great, but now I want to send a custom collection of colors & color-stops. Note: *With this above method, I am limited to defining two (the startColor & startPosition and endColor & endPosition) only.
I'd like to use (an array ? of) tuples (containing several colors and their stop values). The API to set a color and location is NSGradient(colorsAndLocations:) and uses the tuple (NSColor, CGFloat).
I can add a parameter like var colorStopArray: [(color: NSColor, stop: CGFloat)] = [] that creates an array of (such) tuples. Also I can append values with: colorStopArray.append((startColor, startPosition)).
But how can I assign this to the API?
I've tried many methods and they all cause errors (eg. tried NSGradient(colorsAndLocations: colorStopArray.flatMap{return ($0.color, $0.stop)}) )
So how can send many custom colors+locations to the above class (or hopefully a better suggestion) to create a custom gradient (which will then be drawn on my NSView)?
With this above method, I am limited to defining two (the startColor & startPosition and endColor & endPosition) only.
No, you're not. This is a variadic; you can add as many tuples as you wish. This works fine:
let startColor = NSColor.red
let startPosition: CGFloat = 0
let middleColor = NSColor.blue
let middlePosition: CGFloat = 0.5
var endColor = NSColor.white
var endPosition: CGFloat = 1
let bgGradient = NSGradient(colorsAndLocations:
(startColor, startPosition),
(middleColor, middlePosition),
(endColor, endPosition))
If the question is "can I turn an array into a variadic?", then the answer is, unfortunately no. This feature ("splatting") is missing from the Swift language. The only way to call a variadic in Swift is as a variadic.
The initializer that lets you supply an array of colors and stop locations is init(colors:atLocations:colorSpace:). So if that's what you want to supply, call that initializer instead.
Using matt's clues, I replaced the let line (using the more appropriate API init(colors:atLocations:colorSpace:)) :
let bgGradient = NSGradient(colorsAndLocations: (startColor, startPosition), (endColor, endPosition))
with
let bgGradient = NSGradient(colors: colorStopArray.map { $0.color }, atLocations: colorStopArray.map { $0.stop }, colorSpace: NSColorSpace.genericRGB)
Now it functions as I desired.

Swift protocol initializer precludes adding more stored properties to struct

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.

Smart KeyPaths in Swift 4 not working properly

I am trying to create an custom action block for an SKSpriteNode, I have the following code:
let sprite = SKSpriteNode(color: SKColor.red, size: CGSize(width: 50, height: 50))
sprite.position = CGPoint(x: 320, y: 240)
self.addChild(sprite)
let animation = SKAction.customAction(withDuration: 0, actionBlock: {
(node, elapsedTime) in
var initialValue : CGFloat?
initialValue = node[keyPath: \SKSpriteNode.position.x] //Extraneous argument label 'keyPath:' in subscript
node[keyPath: \SKSpriteNode.position.x] = 10 //Ambiguous reference to member 'subscript'
})
sprite.run(animation)
I am getting two errors, the first is that the compiler is thinking I have an Extraneous argument of 'keyPath', which is not the case, because if I were to remove it like it suggests, I get this error:
Cannot convert value of type 'ReferenceWritableKeyPath' to expected argument type 'String'
The second error I get is:
Ambiguous reference to member 'subscript'
I am not exactly sure why I am getting all of these errors, and I am not sure exactly what the errors mean. If somebody could explain them to me and propose a solution, that would be great. Thanks in advance.
The keyPath isn't working because node has type SKNode and not SKSpriteNode. You can use a conditional cast to establish that the node is an SKSpriteNode:
let animation = SKAction.customAction(withDuration: 0, actionBlock: {
(node, elapsedTime) in
var initialValue : CGFloat?
if let spritenode = node as? SKSpriteNode {
initialValue = spritenode[keyPath: \SKSpriteNode.position.x]
spritenode[keyPath: \SKSpriteNode.position.x] = 10
}
})

How to Create a CGSize in Swift?

How can I create a CGSize in Swift? This is what I have tried so far (but doesn't work):
var s:CGSize = {10,20}
var s:CGSize = CGMakeSize(10,20)
Your first attempt won't work because C structs don't exist in Swift. You need:
let size = CGSize(width: 20, height: 30)
Or (before Swift 3 only, and even then, not preferred):
let size = CGSizeMake(20,30)
(Not MakeSize).
As of Swift 3 you can no longer use CGSizeMake
The solution for Swift 3 is var size = CGSize(width: 20, height: 30)

In Xcode 6.1. 'UIImage?' does not have a member named 'size' Error

I'm getting an image's Size and used the below code. It was working perfectly fine with Xcode 6.0.1. After updating to Xcode 6.1, I'm getting Error Like :
'UIImage?' does not have a member named 'size'
var image = UIImage(named: imageName)
let sizeOfImage = image.size
Is it bug with my code or Apple ? Please help me in here. TIA.
That initializer is now a failable initializer, and as such it returns an optional UIImage.
To quickly fix your bug just unwrap the image:
let sizeOfImage = image?.size
but I presume you will reference the image variable more than just once in your code, in that case I suggest using optional binding:
if let image = image {
let sizeOfImage = image.size
/// ...
/// Use the image
}
In addition to #Antonio's answer you can use nil coalescing operator(??) to get a default size in case image initializer is failed. Here is how:
let size = image?.size ?? CGSizeZero
Once you’re sure that the optional does contain a value, you can access its underlying value by adding an exclamation mark (!) to the end of the optional’s name. In your case: image.size!
you can use like this:
let image = UIImage(named: "photo1.png")
let sizeOfImage = image?.size
imageView.image = image
imageView.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: sizeOfImage!)