Stop and remove a SKScene from a View - swift

a have subclassed a SKScene and implemented didMoveToView which did nothing at the moment. Empty function body. Let's name the class TestScene for a moment.
I also have a WindowController and a var named testScene:
var testScene: TestScene?
To functions are implemented showand hide
The show function looks like this:
func show(){
testScene = TestScene(fileNamed:"TestScene")
}
fileNamed:"TestScene" is of course the .sks file with a animation.
The hide function looks like this:
func hide(){
if let scene = testScene {
scene.paused = true
}
testScene?.removeFromParent()
testScene = nil
self.close()
}
This SpriteKit scene does appear on circumstances (button click) in my application. And after the execution is disappears.
Everything is working fine so far.
If I'll press the button the animation comes into the view and close after hide() is triggered, BUT: Since the button is pressed and
testScene = TestScene(fileNamed:"TestScene")
is executed, i got 0.5% CPU load. Even after hide. I can in commend the TestScene() and the CPU falls down to 0% after a little peak.
( i do some more stuff in that buttonPressed: function). So I am pretty sure that the TestScene is not removed after hide is called and maybe do some crazy shit in the background.
The Problem is, that the whole FinalScene will produce 5% of CPU and after the first appearance this load won't go away. :(
My Question is: How to remove the SKScene from the execution loop?
Thanks a lot for any advice, i debug this since hours by now and can't figure out how to dismiss the ended animation.
ps.

The solution is to explizit remove the SKView-subview:
animationView.removeFromSuperview()
This means that we have to build the view from the xib on every event, because the view is lost. The other possibility is to add the SKView programatically on show().

Related

Another UIMenuController won't display problem

and thanks in advance for your help. I assure you that I've read most everything on here about UIMenuController problems. I really think I've covered it all. Clearly I've missed something.
In a nutshell, I'm trying to replicate the "Replace ..." edit menu behavior (but with my own function different than Replace). (If you're not familiar, when a word is selected, the Replace... option in the edit menu will bring up a second menu which shows possible alternate spellings for the word.)
In a UITextView (sub-classed), I select some text. The default gesture recognizer causes the edit menu to come up with the expected items, including my added "Translate..." option. When I click on "Translate..." in the menu, the menu closes and invokes my selector code. That code changes the menu items to the sub-choices I want. I call
UIMenuController.shared.showMenu(from: self, rect: textBounds).
I see the calls to canPerformAction() to verify that the "sub-menu" items I've added are recognized, but the menu never shows up. The notification for willShowWindowNotification (which occurs when the first menu is opened) does not happen for this sub-menu.
Here is the code:
#objc func translateSelectionMenu()
{
let sharedMC = UIMenuController.shared
// Create menu choices for the translate sub-menu.
let charChoice = UIMenuItem(title: "To Chars", action: #selector(translateChars))
let byteChoice = UIMenuItem(title: "Byte Decimal", action: #selector(translateByte))
let halfChoice = UIMenuItem(title: "2-Byte Decimal", action: #selector(translateHalf))
savedMenuItems = sharedMC.menuItems
sharedMC.menuItems = [charChoice, byteChoice, halfChoice]
... for brevity, I've omitted the code here which determines the bounds of the user's
text selection. The resulting numbers are shown below.
let textBounds = CGRect(x: 114.1, y: 73, width: 48, height: 55)
// let windowBounds = convert(textBounds, to: nil)
// sharedMC.update() not needed
self.becomeFirstResponder() // TextView is already the first responder. This does nothing.
sharedMC.showMenu(from: self, rect: textBounds)
}
Note that the TextView IS and must remain first-responder. (Changing it loses the users selection.) So I've implemented all of this in the subclass of the UITextView that is showing the user's text. I have tried using the UITextView-referenced bounds and the window-referenced bounds but neither works.
If I move one of the end-points of the selected text or just click in the selection, this causes the menu to be shown again, and it has my sub-menu items in it as expected. I know this should work because "Replace..." does it all the time.
Things I've verified:
My sub-class of UITextView is a UIView.
UserInteractionIsEnabled is true (since I can select the text).
There is only one window, but I am calling self.window.makeKeyAndVisible() at the point where canBecomeFirstResonder is called.
I have implemented canBecomeFirstResponder() (returning True). (It is called right before the gesture recognizer brings up the first menu but not after that.)
I do call self.becomeFirstResponder() (even though it already is).
I have implemented canPerformAction(). This is called a lot both with first-menu and sub-menu items. I return True for the items I want to use.
What else? Thanks!!
I asked Apple for help on this. The fix is to add
sharedMC.hideMenu()
right before the call to showMenu().
I think the issue is that my code is not what had presented the Menu originally and so I had to hide it before my code could show it. I note (from notifications) that the menu was not officially "hidden" at all (even though it was no longer visible after pressing my Translate... button).
I also tried just changing the menuItems and calling update(), but that also didn't work, probably again for the same reason.

CAShapeLayer.isHidden automatically applying an animation in swift

I'm new to CAShapeLayers so forgive me if this is an obvious fix. Im trying to move a line in sync with a UIScrollView when the view is moving up but keep the line still when the scroll view is moving down. I am using a delegate to detect when the scroll view is moving and this is my code for changing between the visible line.
func scrollViewDidScroll(_ scrollView: UIScrollView){
if(mainScrollView.contentOffset.y < CGFloat(0.0)){
topMainContentViewLine!.isHidden = false
largeRocketImageView!.isHidden = true
topViewLine!.isHidden = true
}else if(mainScrollView.contentOffset.y == CGFloat(0)){
topMainContentViewLine!.isHidden = false
topViewLine!.isHidden = false
}else{
largeRocketImageView!.isHidden = false
topMainContentViewLine!.isHidden = true
topViewLine!.isHidden = false
}
}
When I run my app I can easily see that the two lines are fading in and out instead of appearing and disappearing instantly. For comparison the image has no animation. Any ideas on how to get rid of the animation? Thanks again!
From the documentation:
https://developer.apple.com/documentation/quartzcore/calayer/1410838-ishidden
isHidden
A Boolean indicating whether the layer is displayed. Animatable.
See that word "animatable"? That is a rather tight shorthand; it means that when you set this property on a layer that is not the underlying backing layer of a UIView (e.g. any standalone sublayer), animation is the default. That's called implicit layer animation.
If you do not want animation of an animatable property under those circumstances, you can turn animation off by calling
CATransaction.setDisableActions(true)
You can do that either for a specific explicit transaction block (i.e. everything between matching begin() and commit() commands), or for the entire implicit transaction in which all your code runs.
More commonly, if you have a layer for which you always or mostly don't want animation, you'd host it as the underlying layer of a custom UIView. In that case, setting an animatable property would not animate, and if you needed animation, you would use an explicit CAAnimation (or UIView animation) to get it.
For comparison the image has no animation
Because largeRocketImageView is not, itself, a layer; it's a view.

Buttons obstructed due to UIView created due to custom table view style code (Xcode8)

I use the following code to change the background colour and border radius of a UITableView:
let containerView:UIView = UIView(frame:self.gamesTableView.frame)
gamesTableView.layer.cornerRadius = 5
gamesTableView.layer.masksToBounds = true
gamesTableView.backgroundColor = UIColor(red:1, green:1, blue:1, alpha:0.40)
self.view.addSubview(containerView)
containerView.addSubview(gamesTableView)
This worked fine in xcode 7 and produced the result I expected. However in Xcode 8 this code seems to cause the buttons on the screen not to work, using the debug view hierarchy tool I discovered that with this code there is an extra UIView created that seems to obstruct the buttons. Removing the code in question does solve the problem but it also takes away the extra traits I gave the table view. I would like to know whether there is an alternative method to achieve the same result. I have included images of the view hierarchy with and with out the code.
The buttons in question are: "Start new game" and "Settings"
Side view without styling code:
Side view with sling code, notice the highlighted layer in front:
The fix for this seems to be as simple as putting the code in the question above in viewDidLayoutSubviews
Like this:
override func viewDidLayoutSubviews(){
let containerView:UIView = UIView(frame:self.gamesTableView.frame)
gamesTableView.layer.cornerRadius = 5
gamesTableView.layer.masksToBounds = true
gamesTableView.backgroundColor = UIColor(red:1, green:1, blue:1, alpha:0.40)
self.view.addSubview(containerView)
containerView.addSubview(gamesTableView)
}

insertSubVIew taking a "long" time

In my container controller, the user can pan the views to switch to different views. When the pan gesture begins, it's add the view of the new view controller to the view with: view.insertSubView(view:, atIndex:)
After researching a bit, I noticed this step takes about 0.03 sec (while the other things are all 0.001-0.002 sec). This causes the transition to stotter a bit which is kind of annoying.
The view controller is created at the beginning of the app as a global, using the storyboard.
Also, this only happens when the view is loaded for the first time. The transitions are all fluently after.
What can I do to preload the views so it won't take so "long" when its loaded for the first time?
EDIT:
SURROUNDING CONTEXT:
var pendingViewController: UIViewController! {
didSet {
if let pending = pendingViewController {
addChildViewController(pending)
let index = view.subviews.count - 1
NSLog("start insert view test")
view.insertSubview(pending.view, atIndex: index)
NSLog("end insert view test")
pending.didMoveToParentViewController(self)
}
}
}
Because it only happens when the view is loaded for the first time I was thinking the problem could be somewhere with the viewDidLoad and viewWillAppear. The results are shown below. Only viewDidLoad took a small amount of time (0.005 seconds). There's a gap of 0.02 sec before getting to viewDidLoad though, but I have no idea what it could be.
2015-12-17 15:15:57.116 Valor[777:232799] start insertView view test
2015-12-17 15:15:57.136 Valor[777:232799] start viewDidLoad test
2015-12-17 15:15:57.141 Valor[777:232799] end viewDidLoad test
2015-12-17 15:15:57.142 Valor[777:232799] start viewWillAppear test
2015-12-17 15:15:57.144 Valor[777:232799] end viewWillAppear test
2015-12-17 15:15:57.146 Valor[777:232799] end insertView view test
Use instruments to find out where slow code is happening, not log statements with timestamps. This will show you (including system calls) exactly where the time is being spent.
Inserting subviews can be slow because of layout. However, your trace (such as it is) suggests the time is being spent creating and loading the view of the view controller. You say this view comes from a storyboard. What is in there? How many other things get triggered when this view loads? Use the time profiler and you will be able to tell. It could be something as simple as a property you're giving a default value to that could instead be a lazy value.
The view controller is created at the beginning of the app as a global, using the storyboard.
If this is the case then you can force loading of the view controller's view by doing something like
let hack = viewcontroller.view
Accessing the view property of the view controller causes it to load up the view from the storyboard.
At first sight, I see one strange thing: the index for inserting pending.view. If the view count on your view is 5 for example, that means that the indices of its subviews vary from 0 to 4. And then, you want to put the new subview at index 4 (= 5 - 1) while there's one already on your view which has index = 4. AFAICS, you meant 5 instead of 4. So, you should ether stick to let index = view.subviews.count and then insert the new view at that index, or just use view.addSubview(pending.view). I hope this will help you out.

Keeping SKNodes upright with GameplayKit

I'm working on a SpriteKit game with animal characters. The animals move left and right across the screen using agents from GameplayKit. They follow an invisible agent that goes from one side of the screen to the other. This works ok except that the sprites are moving upside down when I go from right to left (they're ok when they go from left to right). I've tried changing the xScale to get a mirror image but it looks like the agent is overriding the SKAction I've made to change this (the characters are jumpy on the screen for an instant). Does anyone know how to get the sprite characters to remain upright? My code is triggered in func didBeginContact.
if contact1 is RightWall {
if contact2 is AnimalNode {
invisibleNode.position = CGPointMake(0, CGFloat(10 + arc4random_uniform(UInt32(size.height - 20))))
invisibleNode.agent.position = float2(x: Float(invisibleNode.position.x), y: Float(invisibleNode.position.y))
contact2.turnLeft() // this triggers the xScale change I've tried directly on the SKNode
}
Any ideas how to fix this?
Update: the code is shown in the original question.
The moving node is prepared in a child of SKNode (class: AnimalNode) with the following function:
override func turnLeft() {
removeActionForKey("rightTurn") let turnRight2Left = SKAction.sequence([SKAction.scaleXTo(0.3, duration: 0.1), SKAction.scaleXTo(-0.3, duration: 0.1), SKAction.scaleXTo(-1.0, duration: 0.1)]) runAction(turnRight2Left, withKey: "leftTurn")
}
RightWall is an SKShapeNode set up in GameScene. The contact works ok as I added a print statement to check if the if statements are working. The call to func turnLeft also shows no problems, just nothing changes in the node. Maybe this isn't the best way to flip the character. I'm open to other approaches. The turnLeft func is set-up to make the change in character's orientation look more seamless when it transitions from moving right to moving left (that's why I change the sprite's xScale in stages to give it a more 'animated' feel).
thank you both for your help. It took some digging but I found another call to xScale which I was subclassed to. This was over-riding my call and causing the jumping action I'd noticed. I can combine the parent method and solve the problem. Thanks very much for the tips and feedback.