So I'm making some small projects for a major game that I've been developing, but without knowing why, everytime I try to run my Game a SIGABRT error prompts with the following message:
* Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '* -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (GameScene) for key (root); the class may be defined in source code or a library that is not linked'
I've added the following extension to SKNode
extension SKNode {
class func unarchiveFromFile(_ file: NSString) -> SKNode? {
if let path = Bundle.main().pathForResource(file as String, ofType: "sks") {
do {
let sceneData = try Data(contentsOf: URL(fileURLWithPath: path), options: NSData.ReadingOptions.dataReadingMappedIfSafe)
let archiver = NSKeyedUnarchiver(forReadingWith: sceneData)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
let scene = archiver.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as! GameScene
archiver.finishDecoding()
return scene
} catch {
print("ERROR1002")
return nil
}
} else {
print("ERROR001 - pathForResource not found")
return nil
}
}
}
I've used this extension in other projects and everything ran like a charm, but now on this one.
When my game lanches, my GameViewController.swift File is called and on my viewDidLoad method I do the following:
import UIKit
import SpriteKit
import GameplayKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene.unarchiveFromFile(currentSKSFile) as? GameScene {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .aspectFill
skView.presentScene(scene)
}
}
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if UIDevice.current().userInterfaceIdiom == .phone {
return .landscape
} else {
return .all
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
print("didReceiveMemoryWarning ERRO - GameViewController.swift")
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
I've debugged the code and it crashes on the following line in the extension class I've added:
let scene = archiver.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as! GameScene
And it prompts the error described above.
I'm running Xcode 8 Beta + iOS 10, and no, it's not because of this, because I can run the exact same extension used in other projects on this kind of devices with Beta versions without any error.
Any hint on what might be causing this?
Regards - Ivan
Somehow cleaning my project solved my issues!
I have tried different stuff and the errors were there, so I've cleaned it and the error wasn't there anymore.
I don't know why that happened , but it's solved.
Thanks anyway :)
Related
I am wondering how I could detect key presses in Xcode - my goal is to be able to detect when keys are pressed, specifically the space, escape, enter and WASD keys.
What I have tried
I have tried pressesBegan, pressesEnded and its other 'variants', and I have also tried keyDown. After looking for 2 hours, the only ways I could find to detect key presses were from the two methods listed above, which don't seem to be working for me.
When using pressesBegan, I tried putting the following code in both the GameViewController.swift file and the AppDelegate.swift file, however it doesn't seem to work:
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard let key = presses.first?.key else { return }
print(key.keyCode)
}
The code shows no errors, and even when I put a print file at the very start of the function to see if it is being called it does not give any output.
After this, I tried a keyDown function (in the GameViewController.swift file), however it threw the error "Cannot find type NSEvent in scope". I fixed this error by adding ", NSObject" to the top of the file in the line "class GameViewController: UIViewController {" but that threw the error "Multiple inheritance from classes 'UIViewController' and 'NSObject'". I have looked for about an hour but there are no solutions which match my issue/have not worked for me.
import UIKit
import SpriteKit
import GameplayKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "SplashScreen") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
override var prefersStatusBarHidden: Bool {
return true
}
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard let key = presses.first?.key else { return }
switch key.keyCode {
case .keyboardR:
print("Roll dice")
case .keyboardH:
print("Show help")
default:
super.pressesBegan(presses, with: event)
}
}
override func keyDown(with event: NSEvent) {
print("a")
}
}
If you need any additional things I'll try to get back to you as quick as possible. I'm really sorry if I've missed something extremely obvious like putting it in a different file.
Thank you!
the pressesEnd() method works in the same way; Try to import UIKit first:
override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard let key = presses.first?.key else { return }
switch key.keyCode {
case .keyboardSpacebar:
print("Continue the quiz…")
default:
super.pressesEnded(presses, with: event)
}
}
More can be found at:
https://www.hackingwithswift.com/example-code/uikit/how-to-detect-keyboard-input-using-pressesbegan-and-pressesended
I have developed a game in Xcode using sprite kit, and scenes. Now I am trying to integrate the functionality to post high scores to twitter and Facebook. I've looked around, and most people say to use SLComposeServiceViewController which is fine, until I try and present it. Because my app really only uses scenes, they never have the member function "presentViewController(....)". Thus, I am unable to ever present it. Anyone know any way around this?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch:UITouch = touches.first!
let touchLocation = touch.location(in: self)
let touchedNode = self.atPoint(touchLocation)
if (touchedNode.name == "tryAgain") {
let nextScene = Scene_LiveGame(size: self.scene!.size)
nextScene.scaleMode = self.scaleMode
self.view?.presentScene(nextScene, transition: SKTransition.fade(withDuration: 0.5))
}
else if (touchedNode.name == "share") {
if SLComposeViewController.isAvailable(forServiceType: SLServiceTypeFacebook) {
let fShare = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
self.presentViewController(fShare!, animated: true, completion: nil)
//^This is where my problem is. Xcode is telling me that self has no member function presentViewController which I totally understand, because its a scene and thus doesn't share those functions. But every resource online has shown me this is the only way to do it
}
}
Your are getting this error because you need to present a UIViewController from another UIViewController. So
self.presentViewController(...)
will not work because self (SKScene) is not a UIViewController. To present from a SKScene you would have to say this
view?.window?.rootViewController?.presentViewController(fShare!, animated: true, completion: nil)
I would recommend that you do not use those APIs anymore. Its better to use a UIActivityViewController for your sharing needs. This way you only need one share button in your app and you can share to all sorts of services (email, Twitter, Facebook, iMessage, WhatsApp etc).
Create a new Swift file and add this code.
enum ShareMenu {
static func open(text: String, image: UIImage?, appStoreURL: String?, from viewController: UIViewController?) {
guard let viewController = viewController, let view = viewController.view else { return }
// Activity items
var activityItems = [Any]()
// Text
activityItems.append(text)
// Image
if let image = image {
activityItems.append(image)
}
/// App url
if let appStoreURL = appStoreURL {
let items = ActivityControllerItems(appStoreURL: appStoreURL)
activityItems.append(items)
}
// Activity controller
let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
// iPad settings
if UIDevice.current.userInterfaceIdiom == .pad {
activityController.modalPresentationStyle = .popover
activityController.popoverPresentationController?.sourceView = view
activityController.popoverPresentationController?.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
activityController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.init(rawValue: 0)
}
// Excluded activity types
activityController.excludedActivityTypes = [
.airDrop,
.print,
.assignToContact,
.addToReadingList,
]
// Present
DispatchQueue.main.async {
viewController.present(activityController, animated: true)
}
// Completion handler
activityController.completionWithItemsHandler = { (activity, success, items, error) in
guard success else {
if let error = error {
print(error.localizedDescription)
}
return
}
// do something if needed
}
}
}
// MARK: - Activity Controller Items
/**
ActivityControllerItems
*/
private final class ActivityControllerItems: NSObject {
// MARK: - Properties
/// App name
fileprivate let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String ?? "-"
/// App store web url
fileprivate let appStoreURL: String
// MARK: - Init
/// Init
fileprivate init(appStoreURL: String) {
self.appStoreURL = appStoreURL
super.init()
}
}
// MARK: - UIActivityItemSource
/// UIActivityItemSource
extension ActivityControllerItems: UIActivityItemSource {
/// Getting data items
/// Placeholder item
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return ""
}
/// Item for actity type
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivityType) -> Any? {
return URL(string: appStoreURL) ?? appName
}
/// Provide info about data items
/// Subject field for services such as email
func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivityType?) -> String {
return appName
}
}
Than when the share button is pressed you can call it like so
ShareMenu.open(
text: "Can you beat my score?",
image: UIImage(...), // set to nil if unused
appStoreURL: "your iTunes app store URL", // set to nil if unused
from: view?.window?.rootViewController
)
Bear in mind that the image and appStoreURL will not show up everywhere, it depends on the sharing service.
You can also use your score value from your scene and add it to the text e.g
ShareMenu.open(
text: "Can you beat my score \(self.score)?",
...
)
Hope this helps
I will not go into SLComposeViewController related code. I will just show you two techniques aside from what crashoverride777 proposed. So the first technique would be using notifications, like this:
GameScene:
import SpriteKit
let kNotificationName = "myNotificationName"
class GameScene: SKScene {
private func postNotification(named name:String){
NotificationCenter.default.post(
Notification(name: Notification.Name(rawValue: name),
object: self,
userInfo: ["key":"value"]))
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.postNotification(named: kNotificationName)
}
}
Here, you post a notification by tapping the screen. A desired view controller class can listen for this notification, like this:
import UIKit
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(self.handle(notification:)),
name: NSNotification.Name(rawValue: kNotificationName),
object: nil)
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = GameScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
}
}
func handle(notification:Notification){
print("Notification : \(notification)")
}
}
Here, we add self as an observer for this notification - means that when notification happens, an appropriate handling method will be called (and that is our custom handle(notification:) method. In that method, you should call your code:
if SLComposeViewController.isAvailable(forServiceType: SLServiceTypeFacebook) {
let fShare = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
self.presentViewController(fShare!, animated: true, completion: nil)
}
Actually, I will write another example for delegation, to keep things clean :)
As I said, this can be done using notifications, like in this answer, or you can go with delegation:
First you should declare the MyDelegate protocol which defines one method called myMethod().
protocol MyDelegate:class {
func myMethod()
}
That method is a requirement that every class must implement if it conforms to this protocl.
In our example, you can look at the scene as a worker, and a view controller as a boss. When the scene finishes its task, it notifies its boss (delegates responsibilities to him) about job completion, so that boss can decide what is next. I mean, I could say : "The scene is a boss, and it delegates responsibilities to his employee, the view controller..." But it doesn't really matter who you consider as a boss... The delegation pattern matters.
So, the view controller, should conform to this protocol, and it will implement the myMethod() (that will be called by the scene later):
class GameViewController: UIViewController, MyDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//MARK: Conforming to MyDelegate protocol
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = GameScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
scene.myDelegate = self
// Present the scene
view.presentScene(scene)
}
}
}
func myMethod(){
print("Do your stuff here")
}
}
And here is a code from the GameScene where you define the myDelegate property that we use to communicate with our view controller:
import SpriteKit
class GameScene: SKScene {
weak var myDelegate:MyDelegate?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.myDelegate?.myMethod()
}
}
To find out when to choose delegation over notifications and vice-versa take a look at this article (or just search SO, there are some nice posts about that).
I work on a SpriteKit Game which is connected to another Device via MultipeerConnectivity. I put all the Connection stuff in an extra ConnectionManager.swift file to clean up the code a bit.
When receiving data, a function in the ConnectionManager.swift class is called to convert the data into Float and pass the value to a function in the GameScene.swift class.
GameViewController.swift:
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)
}
}
GameScene.swift:
class GameScene: SKScene {
var scoreLabel = SKLabelNode()
let ConnectionsManager = ConnectionManager()
func dummyText(Spieler: Int, Anweisung: Float) {
print("dummyText")
scoreLabel.text = String(Anweisung)
}
ConnectionManager.swift : Here is the function that should pass values to GameScene().dummyText()
class ConnectionManager: NSObject, MCSessionDelegate, MCNearbyServiceBrowserDelegate, MCNearbyServiceAdvertiserDelegate {
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
print("Etwas erhalten!")
NSLog("%#", "Etwas erhalten!")
// Trying with dummy Value
GameScene().dummyText(Spieler: 1, Anweisung: 200 )
}
}
By using GameScene() I assume that I create a new instance instead of using the GameScene which is already shown and which I want to manipulate. Unfortunately I don't know how to work with different classes correctly. The best resolution for me would be to call the ConnectionManager class as some kind of ChildClass of GameViewController, so I can call functions like super.dummyText().
Besides learning swift for a few weeks I am still a newbie. I searched for this problem for a long time and I found some results, but no resolution worked for me. That's why I am posting a new topic here. Thanks in advance for your help!
I'm trying to stream two videos at the same time with swift on iphone. I already know that the AV Player can only stream one video at a time, but googling it I saw that it's still possible to stream different tracks at the same time. I also saw the picture in picture implementation. The real problem is that is all in objective-c and the code is quite old. I tried to understand it running the code as it is, but there are errors and some of the functions are deprecated.
Does someone know how to do that in swift? Also, I'm streaming video from the internet so merging them before playing is not an option.
Thank you!
Swift Version
The article you referenced is an interesting method of handling multiple video playback in iOS. The article appears to be related to Apple's Stitched Stream Player Sample Project. As an exercise in Swift 2.2, I've rewritten the code on the iOS Guy article.
You can find both the view and the view controller in Swift on my gist. I'm copying below as it is best practices to not use link only answers on SO.
Custom View Player
This custom view player allows one to swap out multiple AVPlayerItem(s).
import UIKit
import AVFoundation
class MNGVideoPlayerView: UIView {
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
}
*/
override class func layerClass () -> AnyClass {
return AVPlayerLayer.self
}
func player () -> AVPlayer {
return (self.layer as! AVPlayerLayer).player!
}
func setPlayer(player:AVPlayer) {
(self.layer as! AVPlayerLayer).player = player
}
func setVideoFillMode(fillMode:String) {
let playerLayer = (self.layer as! AVPlayerLayer)
playerLayer.videoGravity = fillMode
}
}
Multiple Video Player View Controller
This controller manages the distribution and presentation of the different AVPlayerItem(s). Check my gist repos for additional updates. (ported from original objc source #iOS Guy)
import UIKit
import AVFoundation
class MNGVideoPlayerViewController: UIViewController {
let kTracksKey = "tracks";
let kStatusKey = "status";
let kRateKey = "rate";
let kPlayableKey = "playable";
let kCurrentItemKey = "currentItem";
let kTimedMetadataKey = "currentItem.timedMetadata";
var _URL:NSURL? = nil
var player:AVPlayer? = nil
var playerItem:AVPlayerItem? = nil
var playerView:MNGVideoPlayerView? = nil
var AVPlayerDemoPlaybackViewControllerStatusObservationContext = UnsafeMutablePointer<Void>()
var AVPlayerDemoPlaybackViewControllerCurrentItemObservationContext = UnsafeMutablePointer<Void>()
var AVPlayerDemoPlaybackViewControllerStatusObservation = UnsafeMutablePointer<Void>()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Public methods
func setURL(url:NSURL) {
self._URL = url
let asset = AVURLAsset(URL: self._URL!)
let requestedKeys = [kTracksKey,kPlayableKey]
asset.loadValuesAsynchronouslyForKeys(requestedKeys) { () -> Void in
self.prepareToPlayAsset(asset, withKeys: requestedKeys)
}
}
func prepareToPlayAsset(asset:AVURLAsset, withKeys requestedKeys:NSArray) {
var error:NSErrorPointer = nil
for thisKey in requestedKeys {
let keyStatus = asset.statusOfValueForKey(thisKey as! String, error: error)
if keyStatus == .Failed {
return
}
}
if !asset.playable {
return
}
if (self.playerItem != nil) {
self.playerItem?.removeObserver(self, forKeyPath: kStatusKey)
NSNotificationCenter.defaultCenter().removeObserver(self, name: AVPlayerItemDidPlayToEndTimeNotification, object: self.playerItem)
}
self.playerItem = AVPlayerItem(asset: asset)
self.playerItem?.addObserver(self, forKeyPath: kStatusKey, options: [.Initial,.New], context: AVPlayerDemoPlaybackViewControllerStatusObservationContext)
if (self.player == nil) {
self.player = AVPlayer(playerItem: self.playerItem!)
self.player?.addObserver(self, forKeyPath: kCurrentItemKey, options: [.Initial,.New], context: AVPlayerDemoPlaybackViewControllerCurrentItemObservationContext)
}
if self.player?.currentItem! != self.playerItem! {
self.player?.replaceCurrentItemWithPlayerItem(self.playerItem!)
}
}
// MARK: - Key Value Observing
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if context == AVPlayerDemoPlaybackViewControllerStatusObservation {
let status:Int = (change![NSKeyValueChangeNewKey]?.integerValue)!
if status == AVPlayerStatus.ReadyToPlay.rawValue {
self.player?.play()
}
} else if context == AVPlayerDemoPlaybackViewControllerCurrentItemObservationContext {
let newPlayerItem = change![NSKeyValueChangeNewKey] as? AVPlayerItem
if newPlayerItem != nil {
self.playerView?.setPlayer(self.player!)
self.playerView?.setVideoFillMode(AVLayerVideoGravityResizeAspect)
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
It looks like http://iosguy.com/2012/01/11/multiple-video-playback-on-ios/ is the best solution at the moment. Nobody tried to convert it in swift yet.
I play multiple videos in my app at once, I just use multiple instances of AVPlayer.
Just started with coco2d/swift and am having trouble presenting a scene. I created a new one in spritebuilder, published it, and am loading it with CCReader as a scene. When I try to present it, it won't present.
class MainScene: CCNode {
override init(){
super.init()
let mainMenu = CCBReader.loadAsScene("ccbResources/MainMenu");
CCDirector.presentScene(mainMenu)
}
}
I get a build error saying:
"cannot invoke presentScene with an argument list of type CCScene".
So I can't really find any examples of this working for me or how to do it.
Only once you can use presentScene, then try replaceScene
class GameScene: CCScene {
class func scene() -> GameScene
{
return GameScene()
}
override init()
{
super.init()
}
}
class MainScene: CCScene {
class func scene() -> MainScene
{
return MainScene()
}
override init()
{
super.init()
//to replace scene
CCDirector.sharedDirector().replaceScene(GameScene.scene())
}
}