SceneKit Swift3 compiler-error - animation

I'm trying to run animated "DAE" model in SceneKit:
let url = Bundle.main.url(forResource: "art.scnassets/Player(walking)", withExtension: "dae")
let sceneSource = SCNSceneSource(url: url!, options: [SCNSceneSource.LoadingOption.animationImportPolicy: SCNSceneSource.AnimationImportPolicy.playRepeatedly] )
let animationIds: NSArray = sceneSource?.identifiersOfEntries(withClass: CAAnimation)
for eachId in animationIds {
let animation: CAAnimation = (sceneSource?.entryWithIdentifier(eachId as! String, withClass: CAAnimation.self))!
animations.add(animation)
}
character?._walkAnimations = animations
But compiler It throws on the line:
let animationIds: NSArray = sceneSource?.identifiersOfEntries(withClass: CAAnimation)
and writes an error:
Cannot convert value of type '[String]?' to specified type 'NSArray'
Please help me to fix that problem.
Thanks in advance.

Why you are converting [String]? to NSArray and then convert each element to String again, no need of it simply use Swift native Array and wrapped the optional value with if let or guard let.
guard let animationIds = sceneSource?.identifiersOfEntries(withClass: CAAnimation) else {
return
}
for eachId in animationIds {
if let animation = sceneSource?.entryWithIdentifier(eachId, withClass: CAAnimation.self) {
animations.add(animation)
}
}
character?._walkAnimations = animations

Related

using NSUserDefaults to save an array of objects

I am new to swift, and I am trying to build a game which will include a top 5 players name and score, if i just add the players to an array and than restart the game it "deletes" the players, so i am trying to use NSUserDefaults(i need to store just 5 strings and 5 integers), it does not work no matter what,
the code is:
class scoreController: UITableViewController {
var playersArray:[Player] = [Player]()
var nameFromGame = "" //name from game vc
var timeFromGame = 0 //time from game vc
let tmpPlayer = Player(playerName: "", scoreTime: 0)
let playerDefaults = NSUserDefaults.standardUserDefaults()
override func viewDidLoad() {
super.viewDidLoad()
let player1 = Player(playerName: "Bil", scoreTime: 50)
let player2 = Player(playerName: "Bob", scoreTime: 100)
playersArray.append(player1)
playersArray.append(player2)
tmpPlayer.playerName = nameFromGame
tmpPlayer.scoreTime = timeFromGame
playersArray.append(tmpPlayer)
playerDefaults.setObject(playersArray[11], forKey: "players")
print(playersArray )
}
}
I am just trying to save this for now and it crashes, does anyone know why? and also how can i store this in my app so it will save the data?
thank you!
Your player objects probably are the problem here. You must implement two methods in your player class (I am no swift master but it's probaby the same mistake):
- (void)encodeWithCoder:(NSCoder *)encoder;
- (id)initWithCoder:(NSCoder *)decoder;
That should work for you.
Hope it helps!!
PD: check this answer
How to store custom objects in NSUserDefaults
Your Player class needs to conform to NSCoding, and you'll need to store an archived data of your players array and unarchive it when extracting out the data.
Class:
class Player: NSObject, NSCoding {
var playerName: String
var scoreTime: Int
init(playerName: String, scoreTime: Int) {
self.playerName = playerName
self.scoreTime = scoreTime
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeInteger(scoreTime, forKey: "score_time")
aCoder.encodeObject(playerName, forKey: "player_name")
}
required convenience init?(coder decoder: NSCoder) {
guard let playerName = decoder.decodeObjectForKey("player_name") as? String else {
return nil
}
self.init(playerName: playerName, scoreTime: decoder.decodeIntegerForKey("score_time"))
}
}
NSUserdefaults & Archiving/Unarchiving :
let player1 = Player(playerName: "Bil", scoreTime: 50)
let player2 = Player(playerName: "Bob", scoreTime: 100)
let playersArray = [player1, player2]
let playersData = NSKeyedArchiver.archivedDataWithRootObject(playersArray)
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(playersData, forKey: "players")
defaults.synchronize()
if let playersArrayData = defaults.objectForKey("players") as? NSData {
let unarchivedPlayers = NSKeyedUnarchiver.unarchiveObjectWithData(playersArrayData) as! [Player]
print(unarchivedPlayers)
}
Hope this helps, please remember to choose answer and up-vote if this solves your question.
You need to could call
playerDefaults.synchronize()
to save the data in the defaults.
EDIT
As facumenzella says, your other problem is storing custom class objects in the defaults. To solve that you need to implement methods in the custom Player class that tell it how to be converted to data. Then you should serialize the object before you store it in the defaults.

How to fix `Ambiguous use of 'subscript'` every time?

I'm using this class that was written in Swift 1.2 and now I want to use it with Swift 2.0.
I get an error: Ambiguous use of 'subscript' # let artist = result["name"] as! String
} else if let jsonArtists = jsonResult["artists"] as? NSDictionary {
if let results:NSArray = jsonArtists["items"] as? NSArray {
dispatch_async(dispatch_get_main_queue(), {
self.searching = false
var suggestionResults: [spotifySearchResult] = []
for result in results {
let artist = result["name"] as! String
var sugresult = spotifySearchResult()
sugresult.artist = artist
if !suggestionResults.contains(sugresult) {
suggestionResults.append(sugresult)
}
}
handler(suggestionResults)
})
}
}
}
I tried different fixes such as let artist = (result["name"] as! String) or let artist = (result["name"] as! String) as! String
But nothing worked.
I know that the question was already post 4 times but, I can't find anyone explaining how to fix it in every case, only case by case.
Can someone explain me how to investigate to fix it? Not just only a fix for my case. I would prefer fix it by myself with your hints!
BTW what does mean subscript? Is subscript the thing between quotation mark? IMHO the error message is a bit vague...
EDIT:
class spotifySearchResult : NSObject {
var artist=""
var track=""
var duration=0
var spotifyURL = NSURL()
var spotifyURI = NSURL()
override func isEqual(theObject: AnyObject?) -> Bool {
if let myObject = theObject as? spotifySearchResult {
return (myObject.artist.uppercaseString == self.artist.uppercaseString && myObject.track.uppercaseString == self.track.uppercaseString)
}
return false
}
}
Subscription means to use the shorter syntax item["key"] for item.objectForKey["key"]
results seems to be an array of dictionaries so I suggest to cast down to a more specific type
if let results = jsonArtists["items"] as? [[String:AnyObject]] {
or even, if all values are guaranteed to be strings
if let results = jsonArtists["items"] as? [[String:String]] {

Updated from Swift 1 to Swift 2

I wrote this code in the previous version of Swift and Xcode and now when I updated xcode to 7.0.1 and updated swift to another version I got a lot of errors. This is something I can't solve and it would be so nice if you could help me. The fat text is the error. The part from >>> to <<< is the part that I need help with so the > and < is not in the real code.
extension SKNode {
class func unarchiveFromFile(file : String) -> SKNode? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
>>> var sceneData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)! <<<
var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as! GameScene
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
Here, I made your code nicer and safe. This code (in theory) cannot crash whatever happens:
extension SKNode {
class func unarchiveFromFile(file : String) -> SKNode? {
guard let
path = NSBundle.mainBundle().pathForResource(file, ofType: "sks"),
sceneData = try? NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe) else {
return nil
}
let archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
guard let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as? GameScene else {
return nil
}
archiver.finishDecoding()
return scene
}
}
If you don't know how the new error handling and guard works, you should check online.
Also if you really want to be fancy, you can use the new defer:
extension SKNode {
class func unarchiveFromFile(file : String) -> SKNode? {
guard let
path = NSBundle.mainBundle().pathForResource(file, ofType: "sks"),
sceneData = try? NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe) else {
return nil
}
let archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
defer { archiver.finishDecoding() }
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
return archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as? GameScene
}
}
In swift 2.0 you have to manage errors thrown:
do {
var sceneData = try NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe)
} catch let error as NSError {
// manage error case
}
There is no longer an NSData.init(contentsOfFile:options:error:) constructor. Most Cocoa calls that returned an error this way are now throwing. The new way to do what you've done here is:
var sceneData = try! NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe)
Rather than getting back an optional and then force-unwrapping it, this uses a force-try to do the same thing. In both cases, this will crash if the read fails. Generally a normal try inside of a do/catch is preferred (just like you generally should not force-unwrap optionals).
You need to download the new version of the Swift language book. Enums that consist of bits that should be combined are now represented as sets, not as integer values. Everything is nicely explained in Apple's documentation.
Reading release notes is also very, very useful. At this point in time, Apple doesn't have the slightest scruples to modify the language in a way that breaks existing code. You need to keep up with it yourself.

Type 'AnyObject' does not conform to protocol 'SequenceType'

func loadThumbnails() {
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let documentsDirectory:NSString = paths[0] as NSString
var error:NSError?
let fileManager = NSFileManager()
let directoryContent:AnyObject = fileManager.contentsOfDirectoryAtPath(documentsDirectory, error: &error)!
thumbnails = [QSPhotoInfo]()
for item:AnyObject in directoryContent {
let fileName = item as NSString
if fileName.hasPrefix(kThumbnailImagePrefix) {
let image = loadImageFromDocumentsDirectory(fileName)
var photoInfo = QSPhotoInfo()
photoInfo.thumbnail = image;
photoInfo.thumbnailFileName = fileName
thumbnails += photoInfo
}
}
}
the compile error is below:
Type 'AnyObject' does not conform to protocol 'SequenceType'
what does this menas?
who can help me ,thks a lot!!!!
Apple states in The Swift Programming Language:
The for-in loop performs a set of statements for each item in a range,
sequence, collection, or progression.
Right now, directoryContent is just conforming to protocol AnyObject, so you can't use for loops over it. If you want to do so, you have to do something similar to the following:
for item in directoryContent as [AnyObject] {
//Do stuff
}
contentsOfDirectoryAtPath returns an NSArray, whereas you are casting it to AnyObject. The solution is to cast it to either [AnyObject]? or NSArray:
let directoryContent: [AnyObject]? = fileManager.contentsOfDirectoryAtPath(documentsDirectory, error: &error)
or
let directoryContent: NSArray? = fileManager.contentsOfDirectoryAtPath(documentsDirectory, error: &error)
Then use an optional binding before the for loop:
if let directoryContent = directoryContent {
for item:AnyObject in directoryContent {
Looking at the contentsOfDirectoryAtPath documentation, it states it always returns an array - so what said above can be reduced to unwrapping the return value to either a swift or objc array, with no need to use the optional binding:
let directoryContent: [AnyObject] = fileManager.contentsOfDirectoryAtPath(documentsDirectory, error: &error)!
or
let directoryContent: NSArray = fileManager.contentsOfDirectoryAtPath(documentsDirectory, error: &error)!

Creating NSData from NSString in Swift

I'm trying to ultimately have an NSMutableURLRequest with a valid HTTPBody, but I can't seem to get my string data (coming from a UITextField) into a usable NSData object.
I've seen this method for going the other way:
NSString(data data: NSData!, encoding encoding: UInt)
But I can't seem to find any documentation for my use case. I'm open to putting the string into some other type if necessary, but none of the initialization options for NSData using Swift seem to be what I'm looking for.
In Swift 3
let data = string.data(using: .utf8)
In Swift 2 (or if you already have a NSString instance)
let data = string.dataUsingEncoding(NSUTF8StringEncoding)
In Swift 1 (or if you have a swift String):
let data = (string as NSString).dataUsingEncoding(NSUTF8StringEncoding)
Also note that data is an Optional<NSData> (since the conversion might fail), so you'll need to unwrap it before using it, for instance:
if let d = data {
println(d)
}
Swift 4 & 3
Creating Data object from String object has been changed in Swift 3. Correct version now is:
let data = "any string".data(using: .utf8)
In swift 5
let data = Data(YourString.utf8)
Here very simple method
let data = string.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
Swift 4
let data = myStringVariable.data(using: String.Encoding.utf8.rawValue)
// Checking the format
var urlString: NSString = NSString(data: jsonData, encoding: NSUTF8StringEncoding)
// Convert your data and set your request's HTTPBody property
var stringData: NSString = NSString(string: "jsonRequest=\(urlString)")
var requestBodyData: NSData = stringData.dataUsingEncoding(NSUTF8StringEncoding)!
To create not optional data I recommend using it:
let key = "1234567"
let keyData = Data(key.utf8)
Convert String to Data
extension String {
func toData() -> Data {
return Data(self.utf8)
}
}
Convert Data to String
extension Data {
func toString() -> String {
return String(decoding: self, as: UTF8.self)
}
}
Swift 4.2
let data = yourString.data(using: .utf8, allowLossyConversion: true)

Resources