I'm learning how to implement great CollectionViewPagingLayout templates in my project.
This one: https://github.com/amirdew/CollectionViewPagingLayout
First I specify which template to use (now I use "invertedCylinder") - and this part works well:
extension MovieCollectionViewCell: ScaleTransformView {
var scaleOptions: ScaleTransformViewOptions {
.layout(.invertedCylinder)
}
}
The problem appears when I try to modify the template. There is an extension, written by the creator of the Layout:
extension YourCell: ScaleTransformView {
var scaleOptions = ScaleTransformViewOptions(
minScale: 0.6,
scaleRatio: 0.4,
translationRatio: CGPoint(x: 0.66, y: 0.2),
maxTranslationRatio: CGPoint(x: 2, y: 0
)
}
I have tried to get rid of stored properties error and modify the code:
extension MovieCollectionViewCell: ScaleTransformView {
var scaleOptionsDetailed: ScaleTransformViewOptions {
minScale: 0.6,
scaleRatio: 0.4,
translationRatio: CGPoint(x: 0.66, y: 0.2),
maxTranslationRatio: CGPoint(x: 2, y: 0)
}
}
But this gives me more errors:
Redundant conformance of 'MovieCollectionViewCell' to protocol 'ScaleTransformView'
Cannot find 'minScale' in scope
Consecutive statements on a line must be separated by ';'
I understand that this is a question about basics. But it is already the second day im trying to solve the issue and would be very grateful for a guidance.
I don’t know why that repo includes stored properties on an extension. That is illegal and will likely always be illegal.
You could convert your variable scaleOptions to a computed property. To do that get rid of the equals sign. Then every time you reference that property it will run the code in the closure and generate a new value.
It is also possible to fake stored properties for extensions using associated values from the Objective-C runtime, but that is considered a hack and probably not a great idea.
Related
Looking for some direction
I'm working with PDFKit. Everything is going fine but having trouble finding the methods (documentation / WWDC / elsewhere ) on how to stop text from drawing at a certain y position and start the next page. Any saved references or a snippet of code would be a great help.
I dont know, wether this is the best way to implement your use case, but it worked for me.
When adding a new line into the PDF_Context, I recalculate a variable the keeps tracking of the current content height of my PDF_Page. When it exceeds a certain value, I create a NEW page, set the content height to zero and go on filling, ...
And you might wanna find some good answers, practices HERE -> RayWenderlich.
// my initial value
private var myCurrentPageContentHeight: CGFloat = 20
// in your PDF fill procedures add something like
myCurrentPageContentHeight += 40
// I also have a struct the encapsulates
// PageSize with padding, ...
enum PDF_PageSize {
case din_A4_Portrait
case din_A4_Landscape
// -------------------------------
// getPDF_TotalRect
// -------------------------------
func getPDF_TotalRect() -> CGRect {
switch self {
case .din_A4_Portrait : return CGRect(x: 0, y: 0, width: 595, height: 842)
case .din_A4_Landscape : return CGRect(x: 0, y: 0, width: 842, height: 595)
}
}
// ....
}
I’m looking for a way of chaining SceneKit animations.
I’m adding x number of nodes to a scene, I’d like to add the first node, make it fade in and once it’s visible then we go to the next node until all nodes are visible. So I need the animation on the first node to end before we start on the second.
Obviously I tried a for loop with SCNActions but the animation is batched together so all nodes appear at the same time.
I don’t know how many nodes will be added, so I can’t make a sequence.
What would be the best way to handle this?
Edit:
Okay, I've figured that if I add a sequence to the node before I add it to the paused scene (that includes an incremented wait interval)
let sequence = SCNAction.sequence([.wait(duration: delay), .fadeIn(duration: 0.5)])
node.runAction(sequence)
then I un-pause the scene once all the nodes are added it achieves the effect that I'm looking for. But it seems hacky.
Is there a better way?
Pause/unpause - hmm, yeah it works, but just feels like that might cause problems down the road if you start doing more things.
I like the completionHandler route per (James P). You could set up multiple (and different) animations or movements with this method. Like move to (5,0,0), rotate, animate, and when it gets there, call it again and move to (10,0,0), etc.
You could do it with a timer if they all work the same way and that would give you some consistency if that's what you are looking for. If you go this route, please ensure to put timers in the main thread.
You can also create some pre-defined sequences, depending on your needs:
let heartBeat = SCNAction.sequence([
SCNAction.move(to: SCNVector3(-0.5, 0.0, 0.80), duration: 0.4),
SCNAction.unhide(),
SCNAction.fadeIn(duration: 1.0),
SCNAction.move(to: SCNVector3(-0.3, 0.0, 0.80), duration: 0.4),
SCNAction.move(to: SCNVector3(-0.2, 0.2, 0.80), duration: 0.4),
SCNAction.move(to: SCNVector3(-0.1, -0.2, 0.80), duration: 0.4),
SCNAction.move(to: SCNVector3( 0.0, 0.0, 0.80), duration: 0.4),
SCNAction.move(to: SCNVector3( 0.1, 0.5, 0.80), duration: 0.4),
SCNAction.move(to: SCNVector3( 0.3, -0.5, 0.80), duration: 0.4),
SCNAction.move(to: SCNVector3( 0.5, 0.0, 0.80), duration: 0.4),
SCNAction.fadeOut(duration: 0.1),
SCNAction.hide()
])
node.runAction(SCNAction.repeatForever(heartBeat))
I ended up using a Timer, I did think about this but couldn't get it to work, probably because I wasn't doing it on the main thread.
Here's the code I'm using so far in case anyone else is in the same boat.
func animateNodesInTo(scene: SCNScene, withDuration: TimeInterval) {
DispatchQueue.main.async {
let nodes = scene.rootNode.childNodes
let acion = SCNAction.fadeIn(duration: withDuration)
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
let opaqueNode = nodes.first(where: {$0.opacity == 0})
opaqueNode?.runAction(acion)
if opaqueNode == nil {
timer.invalidate()
}
}
}
}
Swift 4 has a new NSRect.fill() function to replace NSRectFill(). When attempting to clear a bitmap using NSColor.clear.setFill(), the bitmap remains unchanged using NSRect.fill().
A workaround is to use NSRect.fill(using:) and specifying .copy.
Here is what the code does in Playgrounds in Swift 4:
Sample code:
// Duplicating a bug with Xcode 9 and Swift 4
import Cocoa
var image = NSImage(size: NSSize(width: 32, height: 32))
let rect = NSRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
image.lockFocus()
NSColor.red.setFill()
rect.fill()
image.unlockFocus()
image.lockFocus()
NSColor.clear.setFill()
// calling fill() with a clear color does not work
rect.fill()
image.unlockFocus()
// Image above should not be red
image.lockFocus()
NSColor.clear.setFill()
// using fill(using:) does work
rect.fill(using: .copy)
image.unlockFocus()
// image above is properly cleared
Is this a bug that I should file with Apple or am I missing something? This sequence worked in Swift 3 using NSRectFill().
Here is Swift 3 using NSRectFill:
They have simply different default logic:
Old syntax was always .copy.
New syntax is context if present or .sourceOver.
void NSRectFill(NSRect rect);
Fills aRect with the current color using the compositing mode NSCompositingOperationCopy
(source: https://developer.apple.com/documentation/appkit/1473652-nsrectfill)
/// Fills this rect in the current NSGraphicsContext in the context's fill
/// color.
/// The compositing operation of the fill defaults to the context's
/// compositing operation, not necessarily using `.copy` like `NSRectFill()`.
/// - precondition: There must be a set current NSGraphicsContext.
#available(swift 4)
public func fill(using operation: NSCompositingOperation = NSGraphicsContext.current?.compositingOperation ?? .sourceOver)
(source: Xcode comments for https://developer.apple.com/documentation/corefoundation/cgrect/2903486-fill/)
So if you're converting old code to newer format, then rect.fill(using: .copy) is the syntax to keep identical behaviour.
In my program I have a method called addObstacle, which creates a rectangular SKShapeNode with an SKPhysicsBody, and a leftward velocity.
func addObstacle(bottom: CGFloat, top: CGFloat, width: CGFloat){
let obstacleRect = CGRectMake(self.size.width + 100, bottom, width, (top - bottom))
let obstacle = SKShapeNode(rect: obstacleRect)
obstacle.name = "obstacleNode"
obstacle.fillColor = UIColor.grayColor()
obstacle.physicsBody = SKPhysicsBody(edgeLoopFromPath: obstacle.path!)
obstacle.physicsBody?.dynamic = false
obstacle.physicsBody?.affectedByGravity = false
obstacle.physicsBody?.contactTestBitMask = PhysicsCatagory.Ball
obstacle.physicsBody?.categoryBitMask = PhysicsCatagory.Obstacle
obstacle.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(obstacle)
obstacle.runAction(SKAction.moveBy(obstacleVector, duration: obstacleSpeed))
}
In a separate method, called endGame, I want to fade out all the obstacles currently in existence on the screen. All the obstacle objects are private, which makes accessing their properties difficult. If there is only one on the screen, I can usually access it by its name. However, when I say childNodeWithName("obstacleNode")?.runAction(SKAction.fadeAlphaBy(-1.0, duration: 1.0)), only one of the "obstacles" fades away; the rest remain completely opaque. Is there a good way of doing this? Thanks in advance (:
You could probably go with:
self.enumerateChildNodesWithName("obstacleNode", usingBlock: {
node, stop in
//do your stuff
})
More about this method can be found here.
In this example I assumed that you've added obstacles to the scene. If not, then instead of scene, run this method on obstacle's parent node.
And one side note...SKShapeNode is not performant solution in many cases because it requires at least one draw pass to be rendered by the scene (it can't be drawn in batches like SKSpriteNode). If using a SKShapeNode is not "a must" in your app, and you can switch them with SKSpriteNode, I would warmly suggest you to do that because of performance.
SpriteKit can render hundreds of nodes in a single draw pass if you are using same atlas and same blending mode for all sprites. This is not the case with SKShapeNodes. More about this here. Search SO about this topic, there are some useful posts about all this.
I'm new to objective c and swift and I created a small app where small circles are rendered and once the player collides with a circle, the game ends. I managed to get everything to work, but how do I remove the nodes after they collide. I tried removeAllChildren(), but none of them disappear. When I use removeFromParent(), only 1 disappears. I want a way to remove all 3 nodes that will be rendered in the code below
//addEvilGuys() is called first
func addEvilGuys()
{
addEvilGuy(named: "paul", speed: 1.3, xPos: CGFloat(self.size.height/3))
addEvilGuy(named: "boris", speed: 1.7, xPos: frame.size.width/4 + 50)
addEvilGuy(named: "natasha", speed: 1.5, xPos: frame.size.width/4 + 150)
}
func addEvilGuy(#named:String, speed:Float, xPos: CGFloat)
{
evilGuyNode = SKSpriteNode(imageNamed: named)
evilGuyNode.zPosition = 10
evilGuyNode.physicsBody = SKPhysicsBody(circleOfRadius: 16)
evilGuyNode.physicsBody!.affectedByGravity = false
evilGuyNode.physicsBody!.categoryBitMask = ColliderType.BadGuy.rawValue
evilGuyNode.physicsBody!.contactTestBitMask = ColliderType.Hero.rawValue
evilGuyNode.physicsBody!.collisionBitMask = ColliderType.Hero.rawValue
evilGuyNodeCount++
var evilGuy = EvilGuy(speed: speed, eGuy: evilGuyNode)
evilGuys.append(evilGuy)
resetEvilGuy(evilGuyNode, xPos: xPos)
evilGuy.xPos = evilGuyNode.position.x
addChild(evilGuyNode)
}
func resetEvilGuy(evilGuyNode:SKSpriteNode, xPos:CGFloat)
{
evilGuyNode.position.y = endOfScreenBottom
evilGuyNode.position.x = xPos
}
It looks like in addEvilGuy you are recreating a stored property (i.e. that is visible for the entire class + whatever the access level allows) to create the SKSpriteNode that you're adding. This means that you are orphaning the previously created EvilGuy.
In addEvilGuy, replace
evilGuyNode = SKSpriteNode(imageNamed: named)
with
let evilGuyNode = SKSpriteNode(imageNamed: named)
and remove the property from your class (it doesn't seem like you have a need for in in a larger scope).
It also looks like you're creating EvilGuys and storing them in an array, which is good. So when you can remove all of them from the screen with a function like:
func removeAllEvilGuys(evilGuys: [EvilGuy]) {
for evilGuy in evilGuys {
evilGuy.eGuy.removeFromParent()
}
}
As a best practice advice, since you mentioned you're a beginner:
I'd recommend defining the characteristics of the evil guys in a .plist and then use the file to create an array of evil guys. This way you can easily make changes to the evil guys in that file without having to change anything in your code.
The code that creates an EvilGuy object should be separated from the one that adds the evil guy to the screen. As long as you are storing the SKNode of each one, you'll be able to add/remove without unnecessarily recreating the entire object.