Display UIView behind SKScene in GameViewController - ios

I have a basic SpriteKit game.
What I basically want to do is function create_new_ui() to create a UIView and to hide it behind the scene.
override func viewDidLoad()
{
super.viewDidLoad()
create_new_ui()
if let view = self.view as! SKView?
{
// Load the SKScene from 'GameScene.sks'
let scene = MainScene()
// Set the scale mode to scale to fit the window
scene.scaleMode = .resizeFill
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
// Present the scene
view.presentScene(scene)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
func create_new_ui()
{
let ui_view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
ui_view.center = CGPoint(x: self.view.frame.size.width / 2, y: self.view.frame.size.height / 2)
ui_view.backgroundColor = UIColor.red
self.view.addSubview(ui_view)
}
Can you tell me how can I move the ui_view behind the SKScene?
I tried sendSubviewToBack(_:) but had no effect. Any ideas?

There are several methods to do it. One way is directly adding it to the window after view did appear.:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
addFakeView()
}
func addFakeView(){
let fview = UIView.init(frame: CGRect.init(x: 100, y: 400, width: 100, height: 100))
fview.backgroundColor = UIColor.green
view.window?.insertSubview(fview, at: 0)
}

Related

Set values greater than 1 or less than 0 for UIViewPropertyAnimator's fractionComplete

I'm setting propertyAnimator?.fractionComplete to change the position of a view, based on my pan gesture's translation. Later, I'm planning to animate it back to CGPoint.zero once the finger lifts. Here's my code:
class ViewController: UIViewController {
let squareContainerView = UIView(frame: CGRect(x: 100, y: 70, width: 230, height: 30))
let squareView = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
let targetSquareViewOrigin = CGFloat(200)
let label = UILabel(frame: CGRect(x: 25, y: 140, width: 350, height: 30))
var propertyAnimator: UIViewPropertyAnimator?
var panGesture: UIPanGestureRecognizer!
var totalTranslation = CGFloat(0) {
didSet {
label.text = "Translation: \(totalTranslation.rounded()), percentage: \(totalTranslation / squareContainerView.frame.width)"
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(squareContainerView)
squareContainerView.addSubview(squareView)
view.addSubview(label)
squareContainerView.backgroundColor = UIColor.red
squareView.backgroundColor = UIColor.blue
label.text = "Translation: \(totalTranslation.rounded()), percentage: \(totalTranslation / squareContainerView.frame.width)"
panGesture = UIPanGestureRecognizer(target: self, action:(#selector(self.handleGesture(_:))))
squareContainerView.addGestureRecognizer(panGesture)
propertyAnimator = UIViewPropertyAnimator(duration: 1, curve: .linear)
propertyAnimator?.addAnimations {
self.squareView.frame.origin.x = self.targetSquareViewOrigin
}
}
#objc func handleGesture(_ sender: UIPanGestureRecognizer) {
totalTranslation += sender.translation(in: squareContainerView).x
let fractionComplete = totalTranslation / squareContainerView.frame.width
propertyAnimator?.fractionComplete = fractionComplete
sender.setTranslation(.zero, in: squareContainerView) /// reset to zero
}
}
The square view is moving, but when the percentage goes above 1 or below 0, it stops. This makes sense, as its original origin was CGPoint.zero and I set a 200 end value with addAnimations().
But, I want it to continue moving, even when the fraction complete goes over. In the documentation it says:
When defining a custom animator, you may allow values greater than 1.0 or less than 0.0 if you support animations running past their beginning or end points.
How can I enable this? Should I create a subclass, and if so, what methods should I override?

How to re-draw line connected between moveable two UIView

I would like to re-draw the line between two movable UIView, depending on the position of UIView.
So I found this. However, this method uses "Pan Gesture" to re-draw lines, depending on the position of the gesture.
Also found are setNeedsDisplay(), but this is a request to re-draw, not a real-time event function.
The way I want to find it is not to use gestures to redraw lines, but to re-draw lines in real time.
In a little bit more detail, I applied "UIColisionBehavior" to all the UIVviews I created. The UIView applied changes position as they are struck, and depending on the changed position, the line is being redrawn.
As if the UIView were moving in this way, the connected line was redrawn according to where it was moved:
Below is the code I'm experimenting with in the Playground. When you execute the code, you can see that the first connected purple line is connected to the falling UIView and does not fall off:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MoveAbleView : UIView {
var outGoingLine : CAShapeLayer?
var inComingLine : CAShapeLayer?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func lineTo(connectedView: MoveAbleView) -> CAShapeLayer {
let path = UIBezierPath()
path.move(to: self.center)
path.addLine(to: connectedView.center)
let line = CAShapeLayer()
line.path = path.cgPath
line.lineWidth = 5
line.strokeColor = UIColor.purple.cgColor
connectedView.inComingLine = line
outGoingLine = line
return line
}
}
class MyViewController : UIViewController {
var dynamicAnimator = UIDynamicAnimator()
var collisionBehavior = UICollisionBehavior()
var gravityBehavior = UIGravityBehavior()
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
let viw = MoveAbleView(frame: CGRect(x: 100, y: 200, width: 50, height: 50))
viw.backgroundColor = UIColor.red
self.view.addSubview(viw)
let viw2 = MoveAbleView(frame: CGRect(x: 300, y: 100, width: 50, height: 50))
viw2.backgroundColor = UIColor.orange
self.view.addSubview(viw2)
let gravityViw = MoveAbleView(frame: CGRect(x: 100, y: 0, width: 50, height: 50))
gravityViw.backgroundColor = UIColor.green
self.view.addSubview(gravityViw)
let gravityViw2 = MoveAbleView(frame: CGRect(x: 300, y: -200, width: 50, height: 50))
gravityViw2.backgroundColor = UIColor.blue
self.view.addSubview(gravityViw2)
collisionBehavior.addItem(viw)
collisionBehavior.addItem(viw2)
collisionBehavior.addItem(gravityViw)
collisionBehavior.addItem(gravityViw2)
gravityBehavior.addItem(gravityViw)
gravityBehavior.addItem(gravityViw2)
dynamicAnimator.addBehavior(collisionBehavior)
dynamicAnimator.addBehavior(gravityBehavior)
self.view.layer.addSublayer(viw.lineTo(connectedView: viw2))
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
When UIView strikes and moves, how do you redraw a connected line in real time?
Actually, all what you need is another UIView to represent the lines and employ attachmentBehaviors. For instance, there is a line between two attached objects.
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
dynamicAnimator = UIDynamicAnimator(referenceView: view)
let viw = MoveAbleView(frame: CGRect(x: 100, y: 200, width: 50, height: 50))
viw.backgroundColor = UIColor.red
self.view.addSubview(viw)
let viw2 = MoveAbleView(frame: CGRect(x: 300, y: 200, width: 50, height: 50))
viw2.backgroundColor = UIColor.orange
self.view.addSubview(viw2)
let gravityViw = MoveAbleView(frame: CGRect(x: 100, y: 0, width: 50, height: 50))
gravityViw.backgroundColor = UIColor.green
self.view.addSubview(gravityViw)
let line1 = MoveAbleView(frame: CGRect(x: 125, y: 225, width: 200, height: 10))
line1.backgroundColor = UIColor.purple
self.view.addSubview(line1)
let l1 = UIAttachmentBehavior.init(item: viw, offsetFromCenter: UIOffset.zero, attachedTo: line1, offsetFromCenter: UIOffset.init(horizontal: -100, vertical: 0))
let l2 = UIAttachmentBehavior.init(item: viw2, offsetFromCenter: UIOffset.zero, attachedTo: line1, offsetFromCenter: UIOffset.init(horizontal: 100, vertical: 0))
collisionBehavior.addItem(viw)
collisionBehavior.addItem(viw2)
collisionBehavior.addItem(gravityViw)
gravityBehavior.addItem(gravityViw)
dynamicAnimator.addBehavior(l1)
dynamicAnimator.addBehavior(l2)
dynamicAnimator.addBehavior(collisionBehavior)
dynamicAnimator.addBehavior(gravityBehavior)
}
}

How to implement video controller like YouTube with rotation to landscape

I want to implement controller like YouTube. Where Top part of controller plays video and bottom remains static. And when user rotate device only Top gets rotates.
I tried to implement it with this code:
#IBOutlet weak var myView: UIView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(self.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
}
#objc func rotated() {
if UIDeviceOrientationIsLandscape(UIDevice.current.orientation) {
UIView.animate(withDuration: 0.9, animations: {
self.myView.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2)
let rect = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
self.myView.frame = rect
})
}
}
And I have this result:
Problem is controller still in portrait orientation and status bar at wrong side.
I think that there is better implementation for this thing.
Please, help
This is a simple solution for your problem,
import UIKit
class ViewController: UIViewController {
let RotatingView :UIView = UIView()
let TextLabel :UILabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.RotatingView.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: (self.view.frame.size.height/3))
self.RotatingView.backgroundColor = UIColor.black
self.TextLabel.text = "Rotating View"
self.TextLabel.textColor = UIColor.white
self.TextLabel.textAlignment = .center
self.TextLabel.frame = CGRect(x: 0, y: self.RotatingView.frame.size.height/2, width: self.RotatingView.frame.width, height: 20)
self.RotatingView.addSubview(self.TextLabel)
self.view.addSubview(self.RotatingView)
NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func rotated(){
switch UIDevice.current.orientation {
case .landscapeLeft, .landscapeRight:
self.RotatingView.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: (self.view.frame.size.height))
self.TextLabel.frame = CGRect(x: 0, y: self.RotatingView.frame.size.height/2, width: self.RotatingView.frame.width, height: 20)
default:
self.RotatingView.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: (self.view.frame.size.height/3))
self.TextLabel.frame = CGRect(x: 0, y: self.RotatingView.frame.size.height/2, width: self.RotatingView.frame.width, height: 20)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.setNeedsStatusBarAppearanceUpdate()
}
override var prefersStatusBarHidden : Bool {
return false
}
}

SpriteKit: A gap between SKSpriteNodes

When the blue square falls down on the red one a gap will remain. But when I make the blue square fall from a lower position there is no gap. Also when I give the blue square a higher mass the gap (on the ground) under the red square disappears and when the mass is too high the red square is gone.
I also made the squares smaller because the amount of pixels didn't equal the pixels of the png file which led to an even bigger gap. And when I set "showsPhysics" to true there is also a gap to see and even bigger when the squares are not scaled down.
Gap between SKSpriteNodes (picture)
//: Playground - noun: a place where people can play
import UIKit
import SpriteKit
import PlaygroundSupport
class Scene: SKScene {
var square = SKSpriteNode(texture: SKTexture(imageNamed: "red"), size: SKTexture(imageNamed: "red").size())
var square2 = SKSpriteNode(texture: SKTexture(imageNamed: "blue"), size: SKTexture(imageNamed: "blue").size())
var label = SKLabelNode(fontNamed: "Chalkduster")
override func didMove(to view: SKView) {
self.addChild(square)
self.addChild(square2)
self.addChild(label)
}
override func didChangeSize(_ oldSize: CGSize) {
// Add Scene Physics
self.physicsBody?.usesPreciseCollisionDetection = true
self.physicsBody = SKPhysicsBody(edgeLoopFrom: CGRect(x: -1, y: -1, width: self.size.width + 2, height: self.size.height + 2))
self.backgroundColor = UIColor.white
self.physicsBody?.usesPreciseCollisionDetection = true
//Add square
square.texture?.usesMipmaps = true
square.zPosition = 11
square.physicsBody = SKPhysicsBody(rectangleOf: square.size)
square.physicsBody?.usesPreciseCollisionDetection = true
square.physicsBody?.mass = 0.1
square.physicsBody?.allowsRotation = false
square.physicsBody?.usesPreciseCollisionDetection = true
square.setScale(0.25)
square.position = CGPoint(x: self.frame.width / 2, y: self.square.size.height)
square.name = "square"
//Add square2
square2.texture?.usesMipmaps = true
square2.zPosition = 11
square2.physicsBody = SKPhysicsBody(rectangleOf: square2.size)
square2.physicsBody?.usesPreciseCollisionDetection = true
square2.physicsBody?.mass = 0.1
square2.physicsBody?.allowsRotation = false
square2.physicsBody?.usesPreciseCollisionDetection = true
square2.setScale(0.25)
square2.position = CGPoint(x: self.frame.width / 2, y: self.square.size.height * 6)
square2.name = "square2"
//Add label
label.fontSize = 30
label.text = "w: \(self.frame.size.width) h: \(self.frame.size.height)"
label.position = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height - 30)
label.fontColor = UIColor.black
label.zPosition = 100
}
}
class ViewController: UIViewController {
let scene = Scene()
var viewSize = CGSize(width: 0, height: 0)
override func loadView() {
self.view = SKView()
}
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
view.frame = CGRect(origin: CGPoint(x: -1, y: -1), size: viewSize)
view.presentScene(scene)
view.showsPhysics = false
view.showsFPS = true
view.showsNodeCount = true
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scene.size = CGSize(width: self.view.frame.size.width, height: self.view.frame.size.height)
}
}
PlaygroundPage.current.liveView = ViewController()
As proof from the print commands, there is no actual gap between the sprite nodes, and with second proof from a super-zoomed-in-screenshot:
The playground you provided does not replicate an issue, which makes me wonder... what is going on with your project?
pg #2:
The scene's width and height has to be multiplied by 2. So the scene has to be 4 times bigger than the ViewControllers view. It's also like that in the game template that Apple provides for Spritekit.
scene.size = CGSize(width: self.view.frame.size.width * 2, height: self.view.frame.size.height * 2)
https://github.com/simon2204/Playground

make vertical moving background

I'm beginner in Spritekit and I try to make background move vertically
I did know how
this what in the controller
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = GameScene(size: view.bounds.size)
let skView = view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
scene.scaleMode = .resizeFill
skView.presentScene(scene)
}
and this the scene
class GameScene: SKScene {
var background = SKSpriteNode()
override func didMove(to view: SKView) {
let backgroundTexture = SKTexture(imageNamed: "bbbgg.png")
let shiftBackground = SKAction.moveBy(x: 0, y: -backgroundTexture.size().height, duration: 5)
let replaceBackground = SKAction.moveBy(x: 0, y:backgroundTexture.size().height, duration: 0)
let movingAndReplacingBackground = SKAction.repeatForever(SKAction.sequence([shiftBackground,replaceBackground]))
for i in 1...3{
background=SKSpriteNode(texture:backgroundTexture)
background.position = CGPoint(x: 0, y: 0)
background.size.height = self.frame.height
background.run(movingAndReplacingBackground)
self.addChild(background)
}
}
}
the problem is the background move horizontally and after it arrives to the end of screen disappear, then begins move from the beginning of the screen again
I want it work vertically without disappearing
The background move horizontally because declarations of moveBy (shiftBackground, replaceBackground) works on the x axe (horizontally). You need to work on the y axe (vertically).
let shiftBackground = SKAction.moveBy(x: 0, y: -backgroundTexture.size().height, duration: 5)
let replaceBackground = SKAction.moveBy(x: 0, y: backgroundTexture.size().height, duration: 0)
At the first line, the background move from it position to it position - it height. At the second line, it return to it first position (I think moveBy is relative).
_
The background move again because you use the function SKAction.repeatForever (movingAndReplacingBackground). I think you can remove it.
let movingAndReplacingBackground = SKAction.sequence([shiftBackground,replaceBackground])
Edit :
I forget the loop.
In the loop, you give to the background an initial position.
background.position = CGPoint(x: backgroundTexture.size().width/2 + (backgroundTexture.size().width * CGFloat(i)), y: self.frame.midY)
Try to replace it by 0, middle if you want an immediately visible background
background.position = CGPoint(x: 0, y: self.frame.midY)
Or -height, 0 if you want the background appear by the top of the screen. In this case you need to replace shiftBackground and replaceBackground
background.position = CGPoint(x: -backgroundTexture.size().height, y: 0)
and
let shiftBackground = SKAction.moveBy(x: 0, y: backgroundTexture.size().height, duration: 5)
let replaceBackground = SKAction.moveBy(x: 0, y: 0, duration: 0)
I'm not sure, but I think, if you want only one mouvement, you can remove th sequence :
class GameScene: SKScene {
var background = SKSpriteNode()
override func didMove(to view: SKView) {
let backgroundTexture = SKTexture(imageNamed: "bbbgg.png")
let scrollByTopBackground = SKAction.moveBy(x: O, y: backgroundTexture.size().height, duration: 5)
background = SKSpriteNode(texture:backgroundTexture)
background.position = CGPoint(x: -backgroundTexture.size().height, y: self.frame.midY)
background.size.height = self.frame.height
background.run(scrollByTopBackground)
self.addChild(background)
}
}
Edit to answer you, try this...
class GameScene: SKScene {
var background = SKSpriteNode()
override func didMove(to view: SKView) {
let backgroundTexture = SKTexture(imageNamed: "bbbgg.png")
let shiftBackground = SKAction.moveBy(x: 0, y: -backgroundTexture.size().height, duration: 5)
let replaceBackground = SKAction.moveBy(x: 0, y:backgroundTexture.size().height, duration: 0)
let movingAndReplacingBackground = SKAction.repeatForever(SKAction.sequence([shiftBackground,replaceBackground]))
for i in 0...2 {
background=SKSpriteNode(texture:backgroundTexture)
background.position = CGPoint(x: 0, y: self.frame.midY + (backgroundTexture.size().height * CGFloat(i)))
background.size.height = self.frame.height
background.run(movingAndReplacingBackground)
self.addChild(background)
}
}

Resources