SceneKit: save scn file to disk - scenekit

Goal: save scn file to disk.
What I did:
Trying to use this API:
https://developer.apple.com/documentation/scenekit/scnscene/1523577-write
Problem:
Get this error:
AttributeGraph: cycle detected through attribute 248096 ===
The operation couldn’t be completed. (MDLErrorDomain error 0.)
Any help is much appreciated!
let scnScene = SCNScene(named: "Art.scnassets/Ship")!
//get documents URL
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
//save scn to disk
func saveSCNFileToDisk() {
let url = getDocumentsDirectory()
scnScene.write(to: url, options: nil, delegate: nil) { float, error, pointer in
if let error = error {
print(error.localizedDescription)
return
}
self.scene = url.absoluteString
}
}

Your code should specify the file scheme as described in the docs
//save scn to disk
func saveSCNFileToDisk() {
let url = getDocumentsDirectory().appendingPathComponent("someFleName.scn)
scnScene.write(to: url, options: nil, delegate: nil) { float, error, pointer in
if let error = error {
print(error.localizedDescription)
return
}
self.scene = url.absoluteString
}
}

Related

How to Json decode API data with an array?

I am learning Swift and trying to get elevation data based on coordinates from the Open Elevation API.
I found a code to make the request and decode the data using structs.
My problem is that the API result includes the information in an array:
{"results": [{"latitude": 41.161758, "longitude": -8.583933, "elevation": 117}]}
What I have been able to program so far does save the data as an array in json.results, but only with one index including all of the data:
[API.MyResult(latitude: 41.16176, longitude: -8.583933, elevation: 117)]
("API" is the name of the file)
Here is my code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = "https://api.open-elevation.com/api/v1/lookup?locations=41.161758,-8.583933"
getData(from: url)
}
private func getData(from url: String){
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: {data, response, error in
guard let data = data, error == nil else {
print("error")
return
}
var result: Response?
//print(result)
do{
result = try JSONDecoder().decode(Response.self, from: data)
}
catch{
print(error.localizedDescription)
}
guard let json = result else {
return
}
print(json.results)
//print(json.results.latitude)
//print(json.results.longitude)
//print(json.results.elevation)
})
task.resume()
}
}
struct Response: Codable {
let results: [MyResult]
}
struct MyResult: Codable {
let latitude: Float
let longitude: Float
let elevation: Int
}
Trying to print out json.results.latitude leads to the error
"Value of type '[MyResult]' has no member 'latitude'"
I assume at some point, a variable has to be defined as an array.
What needs to be changed here?
result is indeed a single object, but the property results is an array (multiple objects).
A slightly different naming avoids the confusion.
Notes:
Never print literal "error" or error.localizedDescription in a Decoding context, always print the error instance.
Proceed to parse the result in the do scope
private func getData(from url: String){
guard let url = URL(string: url) else { print("Bad URL", url); return }
let task = URLSession.shared.dataTask(with: url) {data, _, error in
if let error = error { print(error); return }
do {
let response = try JSONDecoder().decode(Response.self, from: data!)
for result in response.results {
print(result.latitude)
print(result.longitude)
print(result.elevation)
}
}
catch {
print(error)
}
}
task.resume()
}

Could not get JSONArray in variable Swift

So basically I want to make a TableList from my REST service. The REST service can be decoded by this code block:
func getAllParkeergarages(_ completion: #escaping ([Parkeergarage]) -> ()) {
if let url = URL(string: "http://localhost:8080/parkeergarages") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
let res = try JSONDecoder().decode([Parkeergarage].self, from: data)
print(res)
completion(res)
return
} catch let error {
print(error)
}
}
}.resume()
}
}
By using this codeblock I can print the whole JSON in my terminal:
getAllParkeergarages { (array) in
print(array)
}
To get the data in a TableView I need to have the data in a variable. But here is where I get stuck. I tried some different methodes like:
private var data: [Parkeergarage] = getAllParkeergarages { (array) in
return array
}
but is gives me an error: 'Cannot convert value of type '()' to specified type '[Parkeergarage]'. Can someone help me get the result of the function in the variable?
you should do
private var data: [Parkeergarage] = []
in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
getAllParkeergarages { (array) in
self.data = array
self.tableView.reloadData()
}
}
I cannot explain any more.

How to retrieve array of images from array of url swift

I'm trying to retrieve images from array of url..
I have this function that do the same as I won't but it doesn't work so I tried to use URLSession but didn't know how exactly to make it >>
func downloadImages(imageUrls: [String], completion: #escaping (_ images: [UIImage?]) -> Void) {
var imageArray: [UIImage] = []
var downloadCounter = 0
for link in imageUrls {
let url = NSURL(string: link)
let downloadQueue = DispatchQueue(label: "imageDowmloadQueue")
downloadQueue.sync {
downloadCounter += 1
let data = NSData(contentsOf: url! as URL)
if data != nil {
//image data ready and need to be converted to UIImage
imageArray.append(UIImage(data: data! as Data)!)
if downloadCounter == imageArray.count {
DispatchQueue.main.async {
completion(imageArray)
}
}
} else {
print("couldnt download image")
completion(imageArray)
}
}
}
}
The function I work on :
public func imagesFromURL(urlString: [String],completion: #escaping (_ images: [UIImage?]) -> Void) {
var imageArray: [UIImage] = []
var downloadCounter = 0
let downloadQueue = DispatchQueue(label: "imageDowmloadQueue")
for link in urlString {
downloadQueue.sync {
downloadCounter += 1
let dataTask = URLSession.shared.dataTask(with: NSURL(string: link)! as URL, completionHandler: { (data, response, error ) in
if error != nil {
print(error ?? "No Error")
return
}
if data != nil {
imageArray.append(UIImage(data: data! as Data)!)
if downloadCounter == imageArray.count {
completion(imageArray)
}
} else {
print("couldnt download image")
completion(imageArray)
}
} dataTask.resume()
}
}
}
i want to call the function in the collection cell and get the display the first image only from each artwork array..
//download the first image only to display it:
if artwork.ImgLink != nil && artwork.ImgLink.count > 0 {
downloadImages(imageUrls: [artwork.ImgLink.first!]) { (images) in
self.artworkImage.image = images.first as? UIImage
}
}
If you intend to use only the first available UIImage from an array of urls, you do not design a function trying to download all of them. Instead, try to download from the first url, return the downloaded UIImage if it succeeds, or continue with the second url if it fails, repeat until you get an UIImage.
Creating a DispatchQueue in a local function looks dangerous to me. A more common practice is to maintain a queue somewhere else and pass it to the function as a parameter, or reuse one of the predefined global queues using DispatchQueue.global(qos:) if you don't have a specific reason.
Be careful with sync. sync blocks the calling thread until your block finishes in the queue. Generally you use async.
Use a Int counter to control when to finish multiple async tasks (when to call the completion block) works but can be improved by using DispatchGroup, which handles multiple async tasks in a simple and clear way.
Here's two functions. Both work. firstImage(inURLs:completion:) only return the first UIImage that it downloads successfully. images(forURLs:completion:) tries to download and return them all.
func firstImage(inURLs urls: [String], completion: #escaping (UIImage?) -> Void) {
DispatchQueue.global().async {
for urlString in urls {
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url), let image = UIImage(data: data) {
DispatchQueue.main.async {
completion(image)
}
return
}
}
}
DispatchQueue.main.async {
completion(nil)
}
}
}
// Use it.
firstImage(inURLs: artwork.ImgLink) { image in
self.artworkImage.image = image
}
func images(forURLs urls: [String], completion: #escaping ([UIImage?]) -> Void) {
let group = DispatchGroup()
var images: [UIImage?] = .init(repeating: nil, count: urls.count)
for (index, urlString) in urls.enumerated() {
group.enter()
DispatchQueue.global().async {
var image: UIImage?
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url) {
image = UIImage(data: data)
}
}
images[index] = image
group.leave()
}
}
group.notify(queue: .main) {
completion(images)
}
}
// Use it.
images(forURLs: artwork.ImgLink) { images in
self.artworkImage.image = images.first(where: { $0 != nil }) ?? nil
}

Cloud Firestore Reading an Array

I've got a an array of alarms (as you'll see below of type Alarm) that I'm trying to store and read from the Cloud Firestore. I'm able to upload the array of alarms but I'm not able to read/decode it. When I use the code below it crashes as I try to decode the alarmArray with the error:
Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [], debugDescription: "Not a dictionary", underlyingError: nil))
SaveData function:
static func saveData(alarmArray: [Alarm]) {
let db = Firestore.firestore()
let firebaseAlarms = try! FirebaseEncoder().encode(alarmArray)
db.collection(K.FStore.alarmCollection).addDocument(data: [
K.FStore.userAlarms : firebaseAlarms
]) { (error) in
if let e = error {
print("Error saving: \(e)")
} else {
print("Successfully Saved")
}
}
}
LoadData function:
static func loadData() -> [Alarm] {
let db = Firestore.firestore()
var alarmArray: [Alarm] = []
db.collection(K.FStore.alarmCollection).getDocuments { (querySnapshot, error) in
if let e = error {
print("error retrieving from Firestore, \(e)")
} else {
if let snapshotDocuments = querySnapshot?.documents {
for firebaseAlarms in snapshotDocuments {
alarmArray = try! FirebaseDecoder().decode(Alarm.self, from: firebaseAlarms) //the app crashes here!
}
}
}
}
return alarmArray
}
Alarm model:
struct Alarm: Codable {
var uuid: String
var time: Time
var label: String
var repeatStatus: [DetailInfo.DaysOfWeek]
var isOn: Bool
var onSnooze: Bool
}
I'm using the CodableFirebase pod's documentation here and Firebase's documentation here, but struggling to put them together.
Please declare your model's all property as optional.
Also property name should be same as firebase.
if you need different name use coding keys.
Serialize your firebase data before decoding.
Put your decoding line on do catch block.
Also do not use firebase decoder use Swift decoder to decode the data.
guard let JSONData = try? JSONSerialization.data(withJSONObject: data, options: .prettyPrinted) else { return }
guard let alarm = try? JSONDecoder().decode(Alarm.self, from: JSONData) else { return }
If the error shows again use manual mapping.
It worked by changing the loadData() function to this:
static func loadData() -> [Alarm] {
let db = Firestore.firestore()
var alarmArray: [Alarm] = []
db.collection(K.FStore.alarmCollection).getDocuments { (querySnapshot, error) in
if let e = error {
print("error retrieving from Firestore, \(e)")
} else {
if let snapshotDocuments = querySnapshot?.documents {
for doc in snapshotDocuments {
let data = doc.data()
if let alarm = data[K.FStore.userAlarms] {
alarmArray = try! FirebaseDecoder().decode([Alarm].self, from: alarm)
print(alarmArray)
}
}
}
}
}
return alarmArray
}

swift iterating webfolder for putting filenames in a array

I want to put the filenames of a web folder "mywwwaddress" into an array
but the println gives me an empty array: []
func files(){
var urls : [NSURL] = []
let dirUrl = NSURL(string: "mywwwadres")
let fileManager = NSFileManager.defaultManager()
let enumerator:NSDirectoryEnumerator? = fileManager.enumeratorAtURL(dirUrl!, includingPropertiesForKeys: nil, options: nil, errorHandler: nil)
while let url = enumerator?.nextObject() as! NSURL? {
urls.append(url)
}
println(urls)
}
When I try your code with a URL of a directory on my local file system, it works OK for me, so you may want to put more error handling in to see if there's a problem reaching the URL you're using.
Also, since NSEnumerator conforms to SequenceType, you can use for...in or other sequence-processing operations like map on it instead, which can simplify the code a little.
Here's a version with more error handling to try:
func files() {
let fileManager = NSFileManager.defaultManager()
let url = NSURL(string: "mywwwadres")
assert(url != nil, "Invalid URL")
let enumerator = url.flatMap { fileManager.enumeratorAtURL($0,
includingPropertiesForKeys: nil,
options: nil)
{ url, error in
println("error with url \(url): \(error)")
return true // true to keep going
}
}
assert(enumerator != nil, "Failed to create enumerator")
let urls = enumerator.map { enumerator in
map(enumerator) { url in
url as! NSURL
}
}
println(urls ?? [])
}

Resources