I'm writing an app building elements consisting of CGPoints. I have 2 buttons: makeRectangle and makeTriangle. For building/drawing stage I use three methods for rectangle and three methods for triangle inside drawRect.
I'm stuck with my code in drawRect. In if-else-statement each method swaps the building/drawing scheme for previous element every time a button pressed.
If I already have built rectangle and then I click makeTriangle button, I get new triangle but my rectangle turns into triangle with one unconnected point.
Is there a workaround or I shouldn't use drawRect method?
Here's an SO post on the drawRect topic: To drawRect or not to drawRect
Element declaration:
enum Element {
case point1(point: CGPoint)
case point2(point: CGPoint)
case point3(point: CGPoint)
case point4(point: CGPoint)
func coord() -> [CGPoint] {
switch self {
case .point1(let point): return [point]
case .point2(let point): return [point]
case .point3(let point): return [point]
case .point4(let point): return [point]
}
}
func buildQuadPath(path: CGMutablePath) {
switch self {
case .point1(let point): CGPathMoveToPoint(path, nil, point.x, point.y)
case .point2(let point): CGPathAddLineToPoint(path, nil, point.x, point.y)
case .point3(let point): CGPathAddLineToPoint(path, nil, point.x, point.y)
case .point4(let point): CGPathAddLineToPoint(path, nil, point.x, point.y)
CGPathCloseSubpath(path)
}
}
func buildTriPath(path: CGMutablePath) {
switch self {
case .point1(let point): CGPathMoveToPoint(path, nil, point.x, point.y)
case .point2(let point): CGPathAddLineToPoint(path, nil, point.x, point.y)
case .point3(let point): CGPathAddLineToPoint(path, nil, point.x, point.y)
default:
CGPathCloseSubpath(path)
}
}
}
Methods for building and drawing triangle and rectangle:
func buildTriPath() -> CGMutablePath {
let path = CGPathCreateMutable()
_ = array.map { $0.buildTriPath(path) }
return path
}
func drawTriPath() {
let path = buildTriPath()
GraphicsState {
CGContextAddPath(self.currentContext, path)
CGContextStrokePath(self.currentContext)
}
}
func drawTriFill() {
let fill = buildTriPath()
GraphicsState {
CGContextAddPath(self.currentContext, fill)
CGContextFillPath(self.currentContext)
}
}
///////////////////////////////////////////////////////
func buildQuadPath() -> CGMutablePath {
let path = CGPathCreateMutable()
_ = array.map { $0.buildQuadPath(path) }
return path
}
func drawQuadPath() {
let path = buildQuadPath()
GraphicsState {
CGContextAddPath(self.currentContext, path)
CGContextStrokePath(self.currentContext)
}
}
func drawQuadFill() {
let fill = buildQuadPath()
GraphicsState {
CGContextAddPath(self.currentContext, fill)
CGContextFillPath(self.currentContext)
}
}
Two variables help determine whether button is pressed:
var squareB: Int = 0
var triangleB: Int = 0
#IBAction func makeTriangle(sender: AnyObject?) {
....................
....................
triangleB += 1
squareB = 0
}
#IBAction func makeRectangle(sender: AnyObject?) {
....................
....................
triangleB = 0
squareB += 1
}
drawRect method:
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
drawBG()
GraphicsState { self.drawMyPoints() }
if squareB >= 1 && triangleB == 0 {
buildQuadPath()
drawQuadPath()
drawQuadFill()
needsDisplay = true
}
else if triangleB >= 1 && squareB == 0 {
buildTriPath()
drawTriPath()
drawTriFill()
needsDisplay = true
}
drawBorder()
}
...and at last a Context.swift file:
import Cocoa
import CoreGraphics
extension NSView {
var currentContext : CGContext? {
get {
let unsafeContextPointer = NSGraphicsContext.currentContext()?.graphicsPort
if let contextPointer = unsafeContextPointer {
let opaquePointer = COpaquePointer(contextPointer)
let context: CGContextRef = Unmanaged.fromOpaque(opaquePointer).takeUnretainedValue()
return context }
else { return nil }
}
}
func GraphicsState(drawStuff: () -> Void) {
CGContextSaveGState(currentContext)
drawStuff()
CGContextRestoreGState(currentContext)
}
}
//the end of code
Okay, since I could use the practice I created an example project to show what vikingosegundo and I mean.
Here's the gist of it:
For this example I kept all relevant code except the adding and removing of shapes in a GHShapeDemoView.
I used structs to define the shapes, but treat them as one "unit" of data that is handled during drawing, adding to the view, etc. All shapes are kept in an array and during drawing that is iterated and all found shapes are drawn using a simple NSBezierPath.
For simplicity's sake I just chose some random fixed points for each shape, in a real project that would obviously be determined in another way (I was too lazy to add input fields...).
Even here there are a lot of ways to refactor (probably). For example, one could even make each shape a class of its own (or use one class for shapes in general). Maybe even a subclass of NSView, that would then result in the "drawing area" itself not be a custom view, but a normal one and on button presses relevant shape views would be added as subviews. That would then probably also get rid of all this points-calculating stuff (mostly).
In a real project I would probably have gone for shapes as layer subclasses that I then add to the subview. I'm no expert, but I think that might have performance benefits depending on how many shapes there are and whether or not I would animate them.
(Obviously the highest performance would probably be gained from using OpenGL ES or something, but I have no clue about that and that's far beyond the scope of this question).
I hope this provides you with a good starting point to work on your drawing. As stated above I would strongly suggest restructuring your project in a similar way to properly define a flow of what you draw and how you draw it. If you somehow must rely on keeping points data in enums or structs or something, write adequate mappers to your drawing data structure.
if (makeTriangle != nil) { and if (makeRectangle != nil) { doesnt make much sense. according to your comment, makerRectangle and makeTriangle are buttons. By your statements you are checking their existence — and we can assume they always exists — the first if-clause will always be executed.
what you want instead: create method that will be executed by the buttons. Each of this method will set either a combination of bool values or a single enum value and then tell the view to redraw by calling setNeedsDisplay().
Here is a code written by Gero:
This code works well with Swift 2.2.
//
// GHShapeDemoView.swift
// GHShapeDrawingExample
//
// Created by Gero Herkenrath on 21/10/2016.
// Copyright © 2016 Gero Herkenrath. All rights reserved.
//
import Cocoa
class GHShapeDemoView: NSView {
struct Shape {
var p1:CGPoint = NSMakePoint(0.0, 0.0)
var p2:CGPoint = NSMakePoint(0.0, 0.0)
var p3:CGPoint = NSMakePoint(0.0, 0.0)
var p4:CGPoint?
}
var shapes:[Shape] = []
override internal var flipped: Bool {
return true
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
NSColor.whiteColor().setFill()
let updatedRect = NSBezierPath.init(rect: dirtyRect)
updatedRect.fill()
for shape in shapes {
drawShape(shape)
}
}
func drawShape(shape:Shape) {
let shapePath = NSBezierPath()
shapePath.moveToPoint(shape.p1)
shapePath.lineToPoint(shape.p2)
shapePath.lineToPoint(shape.p3)
if let lastPoint = shape.p4 {
shapePath.lineToPoint(lastPoint)
}
shapePath.closePath()
NSColor.blackColor().setStroke()
shapePath.stroke()
}
func addTrapezoid(p1:NSPoint, p2:NSPoint, p3:NSPoint, p4:NSPoint) {
var shape = Shape()
shape.p1 = p1
shape.p2 = p2
shape.p3 = p3
shape.p4 = p4
shapes.append(shape)
}
func addTriangle(p1:NSPoint, p2:NSPoint, p3:NSPoint) {
var shape = Shape()
shape.p1 = p1
shape.p2 = p2
shape.p3 = p3
shapes.append(shape)
}
func removeShapeAt(index:Int) {
if index < shapes.count {
shapes.removeAtIndex(index)
}
}
}
/////////////////////////////////////////////////
//
// ViewController.swift
// GHShapeDrawingExample
//
// Created by Gero Herkenrath on 21/10/2016.
// Copyright © 2016 Gero Herkenrath. All rights reserved.
//
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
if #available(OSX 10.10, *) {
super.viewDidLoad()
}
else {
// Fallback on earlier versions
}
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
// first Shape
let pointA1 = NSMakePoint(115.0, 10.0)
let pointA2 = NSMakePoint(140.0, 10.0)
let pointA3 = NSMakePoint(150.0, 40.0)
let pointA4 = NSMakePoint(110.0, 40.0)
// second Shape
let pointB1 = NSMakePoint(230.0, 10.0)
let pointB2 = NSMakePoint(260.0, 40.0)
let pointB3 = NSMakePoint(200.0, 40.0)
// thirdShape
let pointC1 = NSMakePoint(115.0, 110.0)
let pointC2 = NSMakePoint(140.0, 110.0)
let pointC3 = NSMakePoint(150.0, 140.0)
let pointC4 = NSMakePoint(110.0, 140.0)
#IBOutlet weak var shapeHolderView: GHShapeDemoView!
#IBAction func newTrapezoid(sender: AnyObject) {
if shapeHolderView.shapes.count < 1 {
shapeHolderView.addTrapezoid(pointA1, p2: pointA2, p3: pointA3, p4: pointA4)
}
else {
shapeHolderView.addTrapezoid(pointC1, p2: pointC2, p3: pointC3, p4: pointC4)
}
shapeHolderView.setNeedsDisplayInRect(shapeHolderView.bounds)
}
#IBAction func newTriangle(sender: AnyObject) {
shapeHolderView.addTriangle(pointB1, p2: pointB2, p3: pointB3)
shapeHolderView.setNeedsDisplayInRect(shapeHolderView.bounds)
}
#IBAction func removeLastShape(sender: AnyObject) {
if shapeHolderView.shapes.count > 0 {
shapeHolderView.removeShapeAt(shapeHolderView.shapes.count - 1)
shapeHolderView.setNeedsDisplayInRect(shapeHolderView.bounds)
}
}
}
Related
I have a swiftUI animation based on some state:
withAnimation(.linear(duration: 0.1)) {
self.someState = newState
}
Is there any callback which is triggered when the above animation completes?
If there are any suggestions on how to accomplish an animation with a completion block in SwiftUI which are not withAnimation, I'm open to those as well.
I would like to know when the animation completes so I can do something else, for the purpose of this example, I just want to print to console when the animation completes.
Unfortunately there's no good solution to this problem (yet).
However, if you can specify the duration of an Animation, you can use DispatchQueue.main.asyncAfter to trigger an action exactly when the animation finishes:
withAnimation(.linear(duration: 0.1)) {
self.someState = newState
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
print("Animation finished")
}
Here's a bit simplified and generalized version that could be used for any single value animations. This is based on some other examples I was able to find on the internet while waiting for Apple to provide a more convenient way:
struct AnimatableModifierDouble: AnimatableModifier {
var targetValue: Double
// SwiftUI gradually varies it from old value to the new value
var animatableData: Double {
didSet {
checkIfFinished()
}
}
var completion: () -> ()
// Re-created every time the control argument changes
init(bindedValue: Double, completion: #escaping () -> ()) {
self.completion = completion
// Set animatableData to the new value. But SwiftUI again directly
// and gradually varies the value while the body
// is being called to animate. Following line serves the purpose of
// associating the extenal argument with the animatableData.
self.animatableData = bindedValue
targetValue = bindedValue
}
func checkIfFinished() -> () {
//print("Current value: \(animatableData)")
if (animatableData == targetValue) {
//if animatableData.isEqual(to: targetValue) {
DispatchQueue.main.async {
self.completion()
}
}
}
// Called after each gradual change in animatableData to allow the
// modifier to animate
func body(content: Content) -> some View {
// content is the view on which .modifier is applied
content
// We don't want the system also to
// implicitly animate default system animatons it each time we set it. It will also cancel
// out other implicit animations now present on the content.
.animation(nil)
}
}
And here's an example on how to use it with text opacity animation:
import SwiftUI
struct ContentView: View {
// Need to create state property
#State var textOpacity: Double = 0.0
var body: some View {
VStack {
Text("Hello world!")
.font(.largeTitle)
// Pass generic animatable modifier for animating double values
.modifier(AnimatableModifierDouble(bindedValue: textOpacity) {
// Finished, hurray!
print("finished")
// Reset opacity so that you could tap the button and animate again
self.textOpacity = 0.0
}).opacity(textOpacity) // bind text opacity to your state property
Button(action: {
withAnimation(.easeInOut(duration: 1.0)) {
self.textOpacity = 1.0 // Change your state property and trigger animation to start
}
}) {
Text("Animate")
}
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
On this blog this Guy Javier describes how to use GeometryEffect in order to have animation feedback, in his example he detects when the animation is at 50% so he can flip the view and make it looks like the view has 2 sides
here is the link to the full article with a lot of explanations: https://swiftui-lab.com/swiftui-animations-part2/
I will copy the relevant snippets here so the answer can still be relevant even if the link is not valid no more:
In this example #Binding var flipped: Bool becomes true when the angle is between 90 and 270 and then false.
struct FlipEffect: GeometryEffect {
var animatableData: Double {
get { angle }
set { angle = newValue }
}
#Binding var flipped: Bool
var angle: Double
let axis: (x: CGFloat, y: CGFloat)
func effectValue(size: CGSize) -> ProjectionTransform {
// We schedule the change to be done after the view has finished drawing,
// otherwise, we would receive a runtime error, indicating we are changing
// the state while the view is being drawn.
DispatchQueue.main.async {
self.flipped = self.angle >= 90 && self.angle < 270
}
let a = CGFloat(Angle(degrees: angle).radians)
var transform3d = CATransform3DIdentity;
transform3d.m34 = -1/max(size.width, size.height)
transform3d = CATransform3DRotate(transform3d, a, axis.x, axis.y, 0)
transform3d = CATransform3DTranslate(transform3d, -size.width/2.0, -size.height/2.0, 0)
let affineTransform = ProjectionTransform(CGAffineTransform(translationX: size.width/2.0, y: size.height / 2.0))
return ProjectionTransform(transform3d).concatenating(affineTransform)
}
}
You should be able to change the animation to whatever you want to achieve and then get the binding to change the state of the parent once it is done.
You need to use a custom modifier.
I have done an example to animate the offset in the X-axis with a completion block.
struct OffsetXEffectModifier: AnimatableModifier {
var initialOffsetX: CGFloat
var offsetX: CGFloat
var onCompletion: (() -> Void)?
init(offsetX: CGFloat, onCompletion: (() -> Void)? = nil) {
self.initialOffsetX = offsetX
self.offsetX = offsetX
self.onCompletion = onCompletion
}
var animatableData: CGFloat {
get { offsetX }
set {
offsetX = newValue
checkIfFinished()
}
}
func checkIfFinished() -> () {
if let onCompletion = onCompletion, offsetX == initialOffsetX {
DispatchQueue.main.async {
onCompletion()
}
}
}
func body(content: Content) -> some View {
content.offset(x: offsetX)
}
}
struct OffsetXEffectModifier_Previews: PreviewProvider {
static var previews: some View {
ZStack {
Text("Hello")
.modifier(
OffsetXEffectModifier(offsetX: 10, onCompletion: {
print("Completed")
})
)
}
.frame(width: 100, height: 100, alignment: .bottomLeading)
.previewLayout(.sizeThatFits)
}
}
You can try VDAnimation library
Animate(animationStore) {
self.someState =~ newState
}
.duration(0.1)
.curve(.linear)
.start {
...
}
My background is Arduino and I have found Swift a bit opaque. There are a lot of outdated tutorials out there too. I am working on just getting an animated sprite on screen. I have a small animated .png sequence that I will use, saving it into a ball.atlas folder and copying it to Assets.xcassets.
I have found what I believe was posted by Knight0fDragon. Running it shows "worked" in the console, but I don't understand how to assign it to a player so to speak and thus show up on the simulator. If someone would be willing to share the additional code needed to do so, I could pull it apart.
Here is the code:
let textureAtlas = SKTextureAtlas(named: "example")
var textureArray = [SKTexture]()
var frames:[SKTexture] = []
for index in 1 ... 59 {
let textureName = "example_\(index)"
let texture = textureAtlas.textureNamed(textureName)
frames.append(texture)
textureArray = frames
print("worked")
I can't share a texture atlas with you but I help explain a little see below
let textureAtlas = SKTextureAtlas(named: "example")
var textureArray = [SKTexture]()
var frames:[SKTexture] = []
for index in 1 ... 59 {
let textureName = "example_\(index)"
let texture = textureAtlas.textureNamed(textureName)
frames.append(texture)
textureArray = frames
print("worked")
The line that gives "let textureName = "example_(index)" is where KoD gives you the naming convention for all the sprites in the Atlas. The atlas itself for this bit of code is called "example". All the sprites saved in the Atlas are assigned the following name "example_X" (X is an integer). example_1, example_2, example_3 etc... etc until you get to example_59. If you don't want to make 59 textures then you need to edit the for loop giving the new value.
hmmmmm OK have a look here
import SpriteKit
class GameScene: SKScene{
//MARK: PROPERTIES
var lastUpdateTime: TimeInterval = 0
var dt: TimeInterval = 0
var myAnimation = SKAction()
let myAnimaTimeInterval: TimeInterval = 0.1
let mySprite = SKSpriteNode(texture: SKTextureAtlas(named: "example").textureNamed("example_1"))
//MARK: INITIALISATION
override func sceneDidLoad() {
}
override func didMove(to view: SKView) {
addChild(mySprite)
mySprite.position = CGPoint.zero
setAnimation()
runAnimation(node: mySprite)
}
//MARK: GAME LOOP
override func update(_ currentTime: TimeInterval) {
updateGameLoopTimer(currentTime: currentTime)
}
override func didEvaluateActions() {
}
override func didFinishUpdate() {
}
//MARK: TOUCHES
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
//MARK: HELPER FUNCTIONS
private func setAnimation() {
let textureAtlas = SKTextureAtlas(named: "example")
var textureArray: [SKTexture] = []
for index in 2...59 {
let textureName = "example_\(index)"
let texture = textureAtlas.textureNamed(textureName)
textureArray.append(texture)
}
for index in (1...58).reversed() {
let textureName = "example_\(index)"
let texture = textureAtlas.textureNamed(textureName)
textureArray.append(texture)
}
let animation = SKAction.animate(with: textureArray, timePerFrame: myAnimaTimeInterval)
myAnimation = SKAction.repeatForever(animation)
}
private func runAnimation(node: SKSpriteNode) {
node.run(myAnimation, withKey: "myAnimation")
}
private func stopAnimation(node: SKSpriteNode) {
node.removeAction(forKey: "myAnimation")
}
public func updateGameLoopTimer(currentTime: TimeInterval) {
if lastUpdateTime > 0 {
dt = currentTime - lastUpdateTime
} else {
dt = 0
}
lastUpdateTime = currentTime
}
//MARK: CLEAN-UP
deinit {
}
}
I've taken a few liberties with the first sprite for the character which made me change the loop a little.
You can attach the animation to any sprite using the runAnimation method and stop the animation with the stopAnimation method. I've just added the animation to the didMoveToView call.
Try replacing your gameScene file with the above. OK now with the game loop timer ... maybe there are better ways to time the game.... but for you and me it is OK.
Try it again
I had created one project using ARKit and SceneKit framework. In which I am working with file extension .dae, the files are locally available in my project as shown in below screenshot.
Here I had applied many gestures on this virtual object such as Tap Gesture(When I tap on camera screen, it places the virtual object there), same way Pinch Gesture and Pan Gesture. All of these gestures are working perfectly fine. Now I wanted to apply rotation gesture, for which I got stuck how to do that, also I am not getting any such available sources to achieve this.
Below is my working code so far,
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
#IBOutlet var sceneView: ARSCNView!
private var movedObject: SCNNode?
private var hud :MBProgressHUD!
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView.autoenablesDefaultLighting = true
sceneView.delegate = self
sceneView.showsStatistics = true
let scene = SCNScene()
sceneView.scene = scene
registerGestureRecognizers()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
// Run the view's session
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
private func registerGestureRecognizers() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped(recognizer:)))
tapGestureRecognizer.numberOfTapsRequired = 1
self.sceneView.addGestureRecognizer(tapGestureRecognizer)
let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(pinched(recognizer:)))
self.sceneView.addGestureRecognizer(pinchGestureRecognizer)
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(moveObject(recognizer:)))
panGestureRecognizer.maximumNumberOfTouches = 1
panGestureRecognizer.minimumNumberOfTouches = 1
self.sceneView.addGestureRecognizer(panGestureRecognizer)
let rotationGestureRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(rotateObject(recognizer:)))
self.sceneView.addGestureRecognizer(rotationGestureRecognizer)
}
#objc func pinched(recognizer :UIPinchGestureRecognizer) {
if recognizer.state == .changed {
guard let sceneView = recognizer.view as? ARSCNView else {
return
}
let touch = recognizer.location(in: sceneView)
let hitTestResults = self.sceneView.hitTest(touch, options: nil)
if let hitTest = hitTestResults.first {
let chairNode = hitTest.node
let pinchScaleX = Float(recognizer.scale) * chairNode.scale.x
let pinchScaleY = Float(recognizer.scale) * chairNode.scale.y
let pinchScaleZ = Float(recognizer.scale) * chairNode.scale.z
chairNode.scale = SCNVector3(pinchScaleX,pinchScaleY,pinchScaleZ)
recognizer.scale = 1
}
}
}
#objc func moveObject(recognizer: UIPanGestureRecognizer) {
print("Move object")
if recognizer.state == .began {
print("Pan state began")
let tapPoint: CGPoint? = recognizer.location(in: sceneView)
let result = sceneView.hitTest(tapPoint ?? CGPoint.zero, options: nil)
if result.count == 0 {
return
}
let hitResult: SCNHitTestResult? = result.first
if (hitResult?.node.name == "free_car_1") {
movedObject = hitResult?.node
} else if (hitResult?.node.parent?.name == "free_car_1") {
movedObject = hitResult?.node.parent
}
if (movedObject != nil) {
print("Holding an Object")
}
}
if recognizer.state == .changed {
print("Pan State Changed")
if (movedObject != nil) {
let tapPoint: CGPoint? = recognizer.location(in: sceneView)
let hitResults = sceneView.hitTest(tapPoint ?? CGPoint.zero, types: .featurePoint)
let result: ARHitTestResult? = hitResults.last
let matrix: SCNMatrix4 = SCNMatrix4((result?.worldTransform)!)
//SCNMatrix4FromMat4((result?.worldTransform)!)
let vector: SCNVector3 = SCNVector3Make(matrix.m41, matrix.m42, matrix.m43)
movedObject?.position = vector
print("Moving object position")
}
}
if recognizer.state == .ended {
print("Done moving object homeie")
movedObject = nil
}
}
#objc func tapped(recognizer :UITapGestureRecognizer) {
guard let sceneView = recognizer.view as? ARSCNView else {
return
}
let touch = recognizer.location(in: sceneView)
let hitTestResults = sceneView.hitTest(touch)
guard let hitTest = hitTestResults.first?.node else {
let hitTestResultsWithExistingPlane = sceneView.hitTest(touch, types: .existingPlane)
let chairScene = SCNScene(named: "ShelbyWD.dae")!
guard let chairNode = chairScene.rootNode.childNode(withName: "ShelbyWD", recursively: true) else {
return
}
if let hitTestAvailable = hitTestResultsWithExistingPlane.first {
chairNode.position = SCNVector3(hitTestAvailable.worldTransform.columns.3.x,hitTestAvailable.worldTransform.columns.3.y,hitTestAvailable.worldTransform.columns.3.z)
self.sceneView.scene.rootNode.addChildNode(chairNode)
return
}
return
}
hitTest.removeFromParentNode()
}
#objc func rotateObject(recognizer :UIRotationGestureRecognizer)
{
}
}
Can anyone help me out to apply rotation gesture on my object?
Thank you!
In order to rotate an SCNNode, the 1st thing you need to do, is create a variable to store the rotationAngle around the YAxis or any other that you wish to perform the rotation on e.g:
var currentAngleY: Float = 0.0
Then have some way to have detected to node you wish to rotate, which in my example I am calling currentNode e.g.
var currentNode: SCNNode!
In my example I will just rotate around the YAxis.
You can use a UIPanGestureRecognizer like so:
/// Rotates An Object On It's YAxis
///
/// - Parameter gesture: UIPanGestureRecognizer
#objc func rotateObject(_ gesture: UIPanGestureRecognizer) {
guard let nodeToRotate = currentNode else { return }
let translation = gesture.translation(in: gesture.view!)
var newAngleY = (Float)(translation.x)*(Float)(Double.pi)/180.0
newAngleY += currentAngleY
nodeToRotate.eulerAngles.y = newAngleY
if(gesture.state == .ended) { currentAngleY = newAngleY }
print(nodeToRotate.eulerAngles)
}
Or if you wish to use a UIRotationGesture you can do something like this:
/// Rotates An SCNNode Around It's YAxis
///
/// - Parameter gesture: UIRotationGestureRecognizer
#objc func rotateNode(_ gesture: UIRotationGestureRecognizer){
//1. Get The Current Rotation From The Gesture
let rotation = Float(gesture.rotation)
//2. If The Gesture State Has Changed Set The Nodes EulerAngles.y
if gesture.state == .changed{
isRotating = true
currentNode.eulerAngles.y = currentAngleY + rotation
}
//3. If The Gesture Has Ended Store The Last Angle Of The Cube
if(gesture.state == .ended) {
currentAngleY = currentNode.eulerAngles.y
isRotating = false
}
}
Hope it helps...
I have a database in parse that i have pulled into a swift array. The custom parse object is called UserRecipe. The array is called recipes and is located in the viewDidLoad method. I am trying to set the imageview i have called recipeImage, to always access the image of the first element in the array. I do this in the updateImage function but am not sure if I have the correct syntax. Also the array seems to be stored only with the viewDidLoad method and is not accessible to my updateImage function. I'm wondering how to make it global so all functions can access it. Thanks in advance for any help.
The database looks like this:
import UIKit
import Parse
//import ParseFacebookUtilsV4
import FBSDKCoreKit
import FBSDKLoginKit
class ViewController: UIViewController {
let recipes = [PFObject]?.self
#IBOutlet var recipeImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
//load in all data from Parse custom Object UserRecipe and store it in variable recipes
var query = PFQuery(className:"UserRecipe")
query.findObjectsInBackgroundWithBlock {
(recipes: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let recipes = recipes {
for recipe in recipes {
print(recipe["recipeName"])
}
}
} else {
print("Error: \(error!) \(error!.userInfo)")
}
}
let gesture = UIPanGestureRecognizer(target: self, action: Selector("wasDragged:"))
recipeImage.addGestureRecognizer(gesture)
//let tapping = UITapGestureRecognizer(target: self, action: Selector("wasTapped:"))
//recipeImage.addGestureRecognizer(tapping)
recipeImage.userInteractionEnabled = true
//updateImage()
//getUserInfo()
}
func wasDragged(gesture: UIPanGestureRecognizer) {
//Dragging Animation
let translation = gesture.translationInView(self.view)
let imageDrag = gesture.view!
imageDrag.center = CGPoint(x: self.view.bounds.width / 2 + translation.x, y: self.view.bounds.height / 2 + translation.y - 153)
let xFromCenter = imageDrag.center.x - self.view.bounds.width / 2 + translation.x
let scale = min(100 / abs(xFromCenter), 1)
var rotation = CGAffineTransformMakeRotation(xFromCenter / 200)
var stretch = CGAffineTransformScale(rotation, scale, scale)
imageDrag.transform = stretch
//determines whether current user has accepted or rejected certain recipes
if gesture.state == UIGestureRecognizerState.Ended {
var acceptedOrRejected = ""
if imageDrag.center.x < 100 {
acceptedOrRejected = "rejected"
print("not chosen")
//print("not chosen" + object["recipeName"])
} else if imageDrag.center.x > self.view.bounds.width - 100 {
acceptedOrRejected = "accepted"
print("Chosen")
}
/*if acceptedOrRejected != "" {
PFUser.currentUser()?.addUniqueObjectsFromArray([displayedUserId], forKey: acceptedOrRejected)
PFUser.currentUser()?.saveInBackgroundWithBlock({
(succeeded: Bool, error: NSError?) -> Void in
if succeeded {
} else {
print(error)
}
})
}*/
//Resets image position after it has been let go of
rotation = CGAffineTransformMakeRotation(0)
stretch = CGAffineTransformScale(rotation, 1, 1)
imageDrag.transform = stretch
imageDrag.center = CGPoint(x: self.view.bounds.width / 2, y: self.view.bounds.height / 2 - 153)
updateImage()
}
}
func updateImage() {
recipeImage.image = recipes["image"][0]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I am not across Parse but if I were you these are things I would try,
Set a breakpoint in
func updateImage() {
// set breakpoint here and check whether recipes contains any data.
recipeImage.image = recipes["image"][0]
}
Replace the code as shown below,
//replace
recipes["image"][0]
//to
recipes[0]["image"]
I am using this code for my background in the gameviewcontroller:
let yourImage = UIImage(named: welkeLevel)
let imageview = UIImageView(image: yourImage)
self.view?.addSubview(imageview)
But I have a begin (menu) scene where I do not want this background and is still showing up.
How do I detect which scene I am (Gamescene / Menuscene etc.)?
Or is there another way?
SKScene is a subclass of SKNode. You can use the name property to do this. Just set the name of Gamescene or Menuscene to 'game' or 'menu' like this:
scene.name = "game"
And check the property like so:
if self.name == "game"{
//Do something.
println("game")
} else if self.name == "menu
//Do something else.
println("menu")
}
I have worked out the same issue with a replay button. I have to identify which scene the user is in and reload the same when user clicks replay button.
1) Add a extension function to detect current game level
extension SKScene {
static func sceneWithClassNamed(className: String, fileNamed fileName: String) -> SKScene? {
if let SceneClass = NSClassFromString("JumperCrab.\(className)") as? SKScene.Type,
let scene = SceneClass(fileNamed: fileName) {
return scene
}
return nil
}
func replayCurrentScene(currentLevelName: String,currentGameSceneIndex:Int)
{
GameViewController().getReplayCurrentSceneDetailsToVC(currentLevelName, currentGameSceneIndex: currentGameSceneIndex)
}
}
2) Call the above extension method in every game scene.
override func didMoveToView(view: SKView) {
self.replayCurrentScene( "Round 1", currentGameSceneIndex: 0)
}
3) Set up a similar method at the View Controller.
func getReplayCurrentSceneDetailsToVC(currentLevelName: String,currentGameSceneIndex:Int)
{
NSUserDefaults.standardUserDefaults().setInteger(currentGameSceneIndex, forKey: "LevelInt")
NSUserDefaults.standardUserDefaults().synchronize()
NSUserDefaults.standardUserDefaults().setObject(currentLevelName, forKey: "LevelName")
NSUserDefaults.standardUserDefaults().synchronize()
}
4) Create variables
//GAME LEVEL
var gameLevelSceneNameArray = NSArray()
var currentGameSceneAtVC:String?
var currentGameSceneIndexAtVC:Int?
5) Make my level list array
override func viewDidLoad() {
super.viewDidLoad()
//Game Scenes
gameLevelSceneNameArray = ["GameScene","SecondRound","ThirdRound", "FourthRound","FifthRound","SixthRound","SeventhRound", "EighthRound","NinethRound"]
}
6) Apply on the Replay Game button .
#IBAction func replayButtonClicked(sender: AnyObject) {
currentGameSceneIndexAtVC = NSUserDefaults.standardUserDefaults().integerForKey("LevelInt")
currentGameSceneAtVC = NSUserDefaults.standardUserDefaults().objectForKey("LevelName") as? String
self.moveToSelectedGameLevel(currentGameSceneAtVC!, gameSceneName: self.gameLevelSceneNameArray[currentGameSceneIndexAtVC!] as! String)
}
7) Set up a game scene navigation too (using switch case )
func moveToSelectedGameLevel(levelNameString:String, gameSceneName:String)
{
let skView = self.view as! SKView
switch(levelNameString)
{
case "Round 1":
if let gameScene = SKScene.sceneWithClassNamed(gameSceneName, fileNamed: gameSceneName) {
skView.presentScene(gameScene)
}
case "Round 2" :
if let gameScene = SKScene.sceneWithClassNamed(gameSceneName, fileNamed: gameSceneName) {
skView.presentScene(gameScene)
}
case "Round 3":
if let gameScene = SKScene.sceneWithClassNamed(gameSceneName, fileNamed: gameSceneName) {
skView.presentScene(gameScene)
default:
print("")
}
}