How can I detect the what the last scene was? - ios

So I have multiple scenes and what I want is that the transitions between the scenes are different depending on what the last scene was. So basically I need a way to tell what the last scene was? How could I do this? Maybe make a number that changes depending on what the last scene was that's accessible from all the scenes. How could I do this?

I had this same problem before and implemented it by creating a subclass of SKView. The cut down version is:
#objc protocol NavigationViewDelegate {
optional func willMoveToScene(scene: SKScene, fromScene previousScene: SKScene?)
}
class NavigationView: SKView {
weak var delegate: NavigationViewDelegate?
weak var previousScene: SKScene?
override func presentScene(scene: SKScene?) {
super.presentScene(scene)
if let scene = scene {
delegate?.willMoveToScene?(scene, fromScene: previousScene)
self.previousScene = scene
}
}
}
All you need to do now is set (in IB) NavigationView as your Custom Class of the view of your UIViewController and set the delegate.

Related

Swift: how to finish a UIView draw animation before changing a label

I have a little problem regarding the timing of an animation.
The sequence should be:
Button is pressed
Animation is displayed
Label is changed
In reality the sequence is:
Button is pressed
Label is changed
Animation is displayed
Here is the code of the main VC:
import Foundation
import UIKit
class MainVC: UIViewController {
var counter: Int = 0
#IBOutlet weak var counterLabel: UILabel!
#IBOutlet weak var nextButton: UIButton!
#IBOutlet weak var animationView: AnimationView!
#IBAction func nextButtonPressed(_ sender: UIButton) {
counter += 1
animationView.progress = 1
//this view draws a progress bar and animates it from 0 to 100% via CABasicAnimation.
counterLabel.text = "\(counter)"
animationView.progress = 0
//the progress bar is reset
}
override func viewDidLoad() {
super.viewDidLoad()
nextButton.setTitle("Next")
counterLabel.text = "\(counter)"
}
}
I've experimented with the dispatch queue, but I just can't get it to work right. Any ideas on how to solve this?
You don't show enough of your code for us to be able to help you specifically.
In general, you would set some object to be the delegate of your CAAnimation, and implement the method func animationDidStop(_ anim: CAAnimation, finished flag: Bool).
In that method you'd then change the label text (assuming finished == true.)
I don't really like the delegate pattern for animations. If you have multiple animations it can be painful to handle custom completion code for each one. What I've done is use the fact that you can attach objects to CAAnimation objects, and attach a closure to the animation. I then set the view controller to be the delegate, and in the view controller's animationDidStop(), I look for a closure attached to the animation, and execute it if there is one.
Edit:
See this article for a discussion on using key-value coding to attach objects to CAAnimation and CALayer objects:
Core Animation Key-value coding extensions
It's old, and written using Objective-C, but the concepts are the same in Swift, and very useful. For a more general discussion on key-value coding, which is updated for Swift, see this article: https://developer.apple.com/documentation/objectivec/nsobject/nskeyvaluecoding
(The two functions you need to know in Swift are setValue(_:forKeyPath:) and value(forKey:).)

I want my view controllers to have the same combination of custom UIViews which communicate with each other

I want my several view controllers to have a player in the bottom. This player consists of 2 views: the player and a button which toggles it (can be hidden or expanded).
Now I use the code below in each view controller to add this player.
#IBOutlet weak var broadcastView: BroadcastView!
#IBOutlet weak var broadcastViewBottomConstraint: NSLayoutConstraint!
#IBOutlet weak var avatarImageView: UIImageView!
#IBAction func toggleBroadcastMode(_ sender: ToggleBroadcastButton) {
if sender.isExpanded {
broadcastViewBottomConstraint.hideBroadcastView()
} else {
broadcastViewBottomConstraint.expandBroadcastView()
}
animateBroadcastToggle()
sender.toggle()
broadcastView.toggleBroadcastView()
}
Is there a way not to duplicate the code over and over? Maybe I can create parent VC or View to do it? If so, then how?
I personally would subclass a UINavigationController and have it in there, that way you can navigate through the flow while the player stays looking good at the bottom, if you need a VC to interact with it then you can
if let nav = navigationController as? MyPlayerNavController {
nav.PlayThis()
}
you can have it change size and everything from there and you wont lose it during transitions and stuff like the music app when playing music.
Add it as a subview of the keyWindow. That way it will always stay in all the viewControllers.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var overlayViewFrame = UIScreen.main.bounds
overlayViewFrame.origin.y = overlayViewFrame.height - 200
overlayViewFrame.size.height = 200
let overlayView = UIView(frame:overlayViewFrame)
overlayView.backgroundColor = UIColor.cyan
UIApplication.shared.keyWindow?.addSubview(overlayView)
}
This code is a sample generic code. If you want certain VCs to not show this, keep this overlay view in a singleton, and hide in appropriate VCs.
Output screenshots:

Strong references and UIView memory issues

I'm dealing with some deallocation issue and perhaps strong or circular referencing that can't figure out. I have three UIViews instantiating like below:
There is one main ViewController which I have added a UIView inside it in storyboard and the UIView has a weak outlet inside the class like:
class ViewController : UIViewController {
//MARK: - outlets
#IBOutlet weak var firstView: FirstUiview!
}
second UIView is added as a subview to the first view programmatically like:
class FirstUiview : UIView {
//creating an instance of secondUiView
lazy var mySecondView: SecondViewClass = {
let dv = SecondViewClass()
dv.backgroundColor = UIColor.red
return dv
}()
//sometime later by clicking on a button
self.addSubview(mySecondView)
//a button will be tapped to remove mySecondView;
//later will be called at some point upon tapping:
func removingSecondViewByTapping() {
if mySecondView.isDescendant(of: self) {
mySecondView.removeFromSuperview()
}
}
}
Now the SecondViewClass is :
class SecondViewClass : UIView {
//in this class I create bunch of uiview objects like below:
lazy var aView : UIView = {
let hl = UIView()
hl.tag = 0
hl.backgroundColor = UIColor.lightGray
return hl
}()
self.addSubview(aView) //... this goes on and I add other similar views the same way.
//creating an instance of thirdView
var let thirdView = UIView()
self.addSubview(thirdView)
}
Now if user taps the button to remove mySecondView and then add it again at some other time (still in the same ViewController) I expect all the subviews of mySecondView to have been released and gone but they are all there. I would appreciate it a lot if someone can point it to me where am I keeping a strong reference or if there is a circular referencing issue? or perhaps something else?
You have two strong references to your views, your custom property and the view hierarchy reference established when you call addSubview. When you remove the view from the view hierarchy, your class, itself, still has its strong reference to it.
You could solve this by making your reference optional, and when you call removeFromSuperview, also manually set your reference to nil. Or, perhaps easier, you might resolve this by using weak references, letting the view hierarchy maintain the strong references for you. And because your custom property is weak, when you remove it from the view hierarchy (thus eliminating the only strong reference to it), your weak reference will automatically become nil:
class FirstView: UIView {
weak var secondView: SecondView? // note the `weak` reference, which is obviously an optional
//sometime later by clicking on a button
func doSomething() {
let subview = SecondView()
subview.backgroundColor = .red
self.addSubview(subview)
secondView = subview
}
// a button will be tapped to remove secondView;
// later will be called at some point upon tapping ...
func removingSecondViewByTapping() {
secondView?.removeFromSuperview()
}
}

Setting preferred focus in TVOS on an SKSpriteNode?

Since I'm working in SpriteKit, my buttons are SKSpriteNodes...however I find myself in a situation where I need to set focus in my viewController via overriding preferredFocusedView. Is there a way to downcast an SKSpriteNode to a UIView? If so I haven't been able to figure out yet...any alternative?
let playButton = SKSpriteNode(imageNamed: "PlayButton")
playButton.position = CGPoint(x: scene.size.width * 0.25, y: scene.size.height * 0.25)
playButton.zPosition = Layer.UI.rawValue
scene.worldNode.addChild(playButton)
override var preferredFocusedView: UIView? {
get {
return //playButton how?
}
}
Focus navigation is only now supported with tvOS 10 and SpriteKit, prior to that you had to do it manually using your own focus system. For that reason preferred focus view is deprecated because it only supports UIViews. You should now use preferred focus environments instead.
First thing you do is in your GameViewController set the preferred focus environment to the currently presented SKScene. This essentially means that your SKScenes will handle the preferred focus instead of GameViewController. In a SpriteKit game the SKScenes should handle the UI such as buttons using SpriteKit APIs such as SKLabelNodes, SKSpriteNodes etc. Therefore you need to pass the preferred focus to the SKScene.
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// default code to present your 1st SKScene.
}
}
#if os(tvOS)
extension GameViewController {
/// Tell GameViewController that the currently presented SKScene should always be the preferred focus environment
override var preferredFocusEnvironments: [UIFocusEnvironment] {
if let scene = (view as? SKView)?.scene {
return [scene]
}
return []
}
}
#endif
Your playButton should be a subclass of SKSpriteNode that you will use for all your buttons in your game. Use enums and give them different names/ identifiers to distinguish between them when they are pressed (checkout Apples sample game DemoBots).
class Button: SKSpriteNode {
var isFocusable = true // easy way to later turn off focus for your buttons e.g. when overlaying menus etc.
/// Can become focused
override var canBecomeFocused: Bool {
return isFocusable
}
/// Did update focus
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
if context.previouslyFocusedItem === self {
// SKAction to reset focus animation for unfocused button
}
if context.nextFocusedItem === self {
// SKAction to run focus animation for focused button
}
}
}
Than in your SKScenes you can set the focus environment to your playButton or other UI.
e.g Start Scene
class StartScene: SKScene {
....
}
#if os(tvOS)
extension StartScene {
override var preferredFocusEnvironments: [UIFocusEnvironment] {
return [playButton]
}
}
#endif
e.g GameScene (e.g transfer focus to game menu when needed)
class GameScene: SKScene {
....
}
#if os(tvOS)
extension GameScene {
override var preferredFocusEnvironments: [UIFocusEnvironment] {
if isGameMenuShowing { // add some check like this
return [gameMenuNode]
}
return []
}
}
#endif
You will also have to tell your GameViewController to update its focus environment when you transition between SKScenes (e.g StartScene -> GameScene). This is especially important if you use SKTransitions, it took me a while to figure this out. If you use SKTransitions than the old and new scene are active during the transition, therefore the GameViewController will use the old scenes preferred focus environments instead of the new one which means the new scene will not focus correctly.
I do it like this every time I transition between scenes. You will have to use a slight delay or it will not work correctly.
...
view?.presentScene(newScene, transition: ...)
#if os(tvOS)
newScene.run(SKAction.wait(forDuration: 0.1)) { // wont work without delay
newScene.view?.window?.rootViewController?.setNeedsFocusUpdate()
newScene.view?.window?.rootViewController?.updateFocusIfNeeded()
}
#endif
You should read this article
https://medium.com/folded-plane/tvos-10-getting-started-with-spritekit-and-focus-engine-53d8ef3b34f3#.x5zty39pc
and watch the 2016 apple keynote called "Whats New in SpriteKit" where they talk about it half way through.
Hope this helps

Passing data between views in ONE ViewController in Swift

All of the searches I've done focus on passing data between view controllers. That's not really what I'm trying to do. I have a ViewController that has multiple Views in it. The ViewController has a slider which works fine:
var throttleSetting = Float()
#IBAction func changeThrottleSetting(sender: UISlider)
{
throttleSetting = sender.value
}
Then, in one of the Views contained in that same ViewController, I have a basic line that (for now) sets an initial value which is used later in the DrawRect portion of the code:
var RPMPointerAngle: CGFloat {
var angle: CGFloat = 2.0
return angle
}
What I want to do is have the slider's value from the ViewController be passed to the View contained in the ViewController to allow the drawRect to be dynamic.
Thanks for your help!
EDIT: Sorry, when I created this answer I was having ViewControllers in mind. A much easier way would be to create a method in SomeView and talk directly to it.
Example:
class MainViewController: UIViewController {
var view1: SomeView!
var view2: SomeView!
override func viewDidLoad() {
super.viewDidLoad()
// Create the views here
view1 = SomeView()
view2 = SomeView()
view.addSubview(view1)
view.addSubview(view2)
}
#IBAction func someAction(sender: UIButton) {
view1.changeString("blabla")
}
}
class SomeView: UIView {
var someString: String?
func changeString(someText: String) {
someString = someText
}
}
Delegate:
First you create a protocol:
protocol NameOfDelegate: class { // ": class" isn't mandatory, but it is when you want to set the delegate property to weak
func someFunction() // this function has to be implemented in your MainViewController so it can access the properties and other methods in there
}
In your Views you have to add:
class SomeView: UIView, NameOfDelegate {
// your code
func someFunction() {
// change your slider settings
}
}
And the last step, you'll have to add a property of the delegate, so you can "talk" to it. Personally I imagine this property to be a gate of some sort, between the two classes so they can talk to each other.
class MainViewController: UIViewController {
weak var delegate: NameOfDelegate?
#IBAction func button(sender: UIButton) {
if delegate != nil {
let someString = delegate.someFunction()
}
}
}
I used a button here just to show how you could use the delegate. Just replace it with your slider to change the properties of your Views
EDIT: One thing I forgot to mention is, you'll somehow need to assign SomeView as the delegate. But like I said, I don't know how you're creating the views etc so I can't help you with that.
In the MVC model views can't communicate directly with each other.
There is always a view controller who manages the views. The views are just like the controllers minions.
All communication goes via a view controller.
If you want to react to some view changing, you can setup an IBAction. In the method you can then change your other view to which you might have an IBOutlet.
So in your example you might have an IBAction for the slider changing it's value (as in your original question) from which you could set some public properties on the view you would like to change. If necessary you could also call setNeedsDisplay() on the target view to make it redraw itself.

Resources