I'm capturing randomly-sized/shaped sections of a UIView and saving to disk using UIImagePNGRepresentation. The files save as expected, but when I run on a device, I get the dreaded "Received memory warning", even though all I'm doing is saving files in a loop and not displaying them:
UIGraphicsBeginImageContextWithOptions(bbox.size, false, 0)
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.25).CGColor)
CGContextAddRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, bbox.width, bbox.height))
CGContextFillPath(UIGraphicsGetCurrentContext())
let strokeImage = UIGraphicsGetImageFromCurrentImageContext()
let strokeFile = "stroke_\(strokeBezierArrayMemory.count).png"
let dataPng = UIImagePNGRepresentation(strokeImage)
let fileURL = folderURL!.URLByAppendingPathComponent(strokeFile) //println("\(fileURL)")
if (dataPng.writeToURL(fileURL, options: .AtomicWrite, error: &error) ) {
//println ("WROTE \(fileURL)")
} else {
print (error)
}
I've seen similar posts, but no answers that solve my problem. This post suggests using AVFoundation, but I'm hoping I can stick to UIImagePNGRepresentation.
I also tried wrapping the NSData part of the code in an autoreleasepool closure to no avail:
autoreleasepool({ () -> () in
let strokeFile = "stroke_\(strokeBezierArrayMemory.count).png"
let data = UIImagePNGRepresentation(strokeImage)
let fileURL = folderURL!.URLByAppendingPathComponent(strokeFile) //println("\(fileURL)")
if (data.writeToURL(fileURL, options: .AtomicWrite, error: &error) ) {
//println ("WROTE \(fileURL)")
} else {
print (error)
}
})
Is there a way to force NSData to release each dataPng once written to disk?
Related
I'm trying to decode a JSON result in Swift but sometimes one of the keys is missing in the JSON which is causing my attempt at decoding to crash. I cannot figure out a way to replace a missing key with a value to prevent it from crashing and to keep my data structured and in line with other arrays and their positions.
It works perfectly unless there is a missing key from the API, in which case I'm getting a crash.
Here is my code:
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
struct PortHW: Codable {
let EventType: String
let DateTime: String
let Height: Double
}
func getPortHW(portID: String) -> (eventArray: [String], timeArray: [String], heightArray: [Double]) {
var json: Data? = nil
let semaphore = DispatchSemaphore (value: 0)
var request = URLRequest(url: URL(string: "https://admiraltyapi.azure-api.net/uktidalapi/api/V1/Stations/\(portID)/TidalEvents?duration=7")!,timeoutInterval: Double.infinity)
request.addValue("HIDDEN", forHTTPHeaderField: "Ocp-Apim-Subscription-Key")
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
semaphore.signal()
return
}
json = data
semaphore.signal()
}
task.resume()
semaphore.wait()
let decoder = JSONDecoder()
let tideResult = try? decoder.decode([PortHW].self, from: json!)
var eventArray: [String] = []
var timeArray: [String] = []
var heightArray: [Double] = []
for item in tideResult! {
eventArray.append(item.EventType)
timeArray.append(item.DateTime)
heightArray.append(item.Height)
}
return (eventArray, timeArray, heightArray)
}
The JSON I'm getting my data from looks like this most of the time:
[
{
"EventType": "HighWater",
"DateTime": "2022-03-15T01:29:00",
"IsApproximateTime": false,
"Height": 4.2845989326570688,
"IsApproximateHeight": false,
"Filtered": false,
"Date": "2022-03-15T00:00:00"
},
But sometimes the key Height is missing, not empty, but missing... I've struggled with finding a way to replace the missing key with a value of 0.0 when it's not there.. I'd really appreciate some guidance.
Thank you.
whichever is missing, you can make it optional
like this:
struct PortHW: Codable {
let EventType: String
let DateTime: String
let Height: Double?
}
extension PortHW {
func getHeight() -> Double {
if let height = self.Height {
return height
}
return 0.0
}
}
or you can just add defaultValue ==> 0.0
for item in tideResult! {
eventArray.append(item.EventType)
timeArray.append(item.DateTime)
heightArray.append(item.Height ?? 0.0)
}
I have a very similar question to:
Swift: Store arrays of custom classes in Core Data
The difference is that question was looking to add to a custom class array in Core Data one element at a time. I am looking to add the entire array at once. When I try to, I get the following error:
[CDTester.MassOfSubpart entity]: unrecognized selector sent to instance.
I made a simple app to demonstrate the error and hopefully figure out where I am going wrong. I uploaded it to GitHub here: https://github.com/Yrban/Core-Data-Test-App
The code where it crashes is here:
func saveWidget(){
print("saveWidget() entered")
if self.massOfSubpartMeasurementDict.count > 0,
self.massOfSubpartMeasurementDict.count == self.numberOfSubparts,
self.manufacturer != "",
self.title != "" {
var massPerSubpartMeasurementArray: [MassOfSubpart]
massPerSubpartMeasurementArray = massOfSubpartMeasurementDict.map {
return MassOfSubpart(subpart: $0.key, mass: Measurement(value: Double($0.value)!, unit: self.massUnit))
}
let massOfSubpartSet = Set(massPerSubpartMeasurementArray)
let widget = Widget(context: self.managedObjectContext)
widget.id = UUID()
widget.madeBy?.name = self.manufacturer
widget.hasMassOfPart = massOfSubpartSet // FIXME: The crash is here
widget.title = self.title
do {
print("SaveWidget() attempted/n")
try self.managedObjectContext.save()
} catch {
print("SaveWidget() failed/n")
// handle the Core Data error
// Show alert as to status
}
}
}
My Core Data model is:
MassOfSubpart is a custom object:
import SwiftUI
public class MassOfSubpart: NSObject, Codable {
var subPart: Int
var mass: Measurement<UnitMass>
override init() {
self.subPart = 1
self.mass = Measurement(value: 0.0, unit: UnitMass.grams)
}
init(subpart: Int, mass: Measurement<UnitMass>){
self.subPart = subpart
self.mass = mass
}
}
I have read everything I could find, including Apple's documentation. I feel I am close, but I am obviously missing something important. Any help would be appreciated.
I finally figured out my error. I have not used relationships before, so it didn't dawn on me what I was doing. When I was accessing MassOfPartEntity through its relationship with Widget, I was sending a Set, my custom object through. However, I had to send a Set, the actual entity through. Therefore, I changed saveWidget to this:
func saveWidget(){
print("saveWidget() entered")
if self.massOfSubpartMeasurementDict.count > 0,
self.massOfSubpartMeasurementDict.count == self.numberOfSubparts,
self.manufacturer != "",
self.title != "" {
let massOfPartEntity = MassOfPartEntity(context: self.managedObjectContext)
var massPerSubpartEntityArray: [MassOfPartEntity]
massPerSubpartEntityArray = massOfSubpartMeasurementDict.map {
massOfPartEntity.subPart = Int16($0.key)
if let massDouble = Double($0.value) {
massOfPartEntity.mass = Measurement(value: massDouble, unit: massUnit)
} else {
massOfPartEntity.mass = Measurement(value: 0.0, unit: massUnit)
}
return massOfPartEntity
}
let massOfSubpartSet = Set(massPerSubpartEntityArray) as NSSet
let widget = Widget(context: self.managedObjectContext)
widget.id = UUID()
widget.madeBy?.name = self.manufacturer
widget.addToHasMassOfPart(massOfSubpartSet) // FIXME: The crash is here
widget.title = self.title
do {
print("SaveWidget() attempted/n")
try self.managedObjectContext.save()
} catch {
print("SaveWidget() failed/n")
// handle the Core Data error
// Show alert as to status
}
}
}
changing the Map function return so that it returned MassOfPartEntity's that eventually became a Set. Essentially, it was a type mismatch.
Thanks to those who responded as talking it out definitely helped resolve this.
I'm trying to convert a string into a double in swift. I managed to extract the string from a website (www.x-rates.com) into an array but I cannot convert it after in a double in order to make some work around this number. Can anyone tell me what I'm supposed to do or what I did wrong? I know that my label don't update now but I will do it later, the first thing that I'm trying to do is the conversion.
thx a lot!
Here is the code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var resultLabel: UILabel!
#IBOutlet weak var moneyTextField: UITextField!
#IBAction func convert(_ sender: Any) {
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = URL(string: "https://www.x-rates.com/calculator/?from=EUR&to=USD&amount=1")!
let request = NSMutableURLRequest(url : url)
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
var message = ""
if let error = error {
print(error)
} else {
if let unwrappedData = data {
let dataString = NSString(data: unwrappedData, encoding: String.Encoding.utf8.rawValue)
var stringSeperator = "<span class=\"ccOutputRslt\">"
if let contentArray = dataString?.components(separatedBy: stringSeperator){
if contentArray.count > 0 {
stringSeperator = "<span"
let newContentArray = contentArray[1].components(separatedBy: stringSeperator)
if newContentArray.count > 0 {
message = newContentArray[0]
var message = Float(newContentArray[0])! + 10
}
}
}
}
}
DispatchQueue.main.sync(execute: {
self.resultLabel.text = "the value of the dollar is " + message
}
)}
task.resume()
func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I will talk about convert an Array of String to Array of Double.
In swift Array has a method called map, this is responsable to map the value from array, example, in map function you will receive an object referent to your array, this will convert this object to your new array ex.
let arrOfStrings = ["0.3", "0.4", "0.6"];
let arrOfDoubles = arrOfStrings.map { (value) -> Double in
return Double(value)!
}
The result will be
UPDATE:
#LeoDabus comments an important tip, this example is considering an perfect datasource, but if you have a dynamic source you can put ? on return and it will work, but this will return an array with nil
like that
let arrOfStrings = ["0.3", "0.4", "0.6", "a"];
let arrOfDoubles = arrOfStrings.map { (value) -> Double? in
return Double(value)
}
Look this, the return array has a nil element
If you use the tips from #LeoDabus you will protect this case, but you need understand what do you need in your problem to choose the better option between map or compactMap
example with compactMap
let arrOfStrings = ["0.3", "0.4", "0.6", "a"];
let arrOfDoubles = arrOfStrings.compactMap { (value) -> Double? in
return Double(value)
}
look the result
UPDATE:
After talk with the author (#davidandersson) of issue, this solution with map ou contactMap isn't his problem, I did a modification in his code and work nice.
first I replaced var message = "" per var rateValue:Double = 0.0 and replacedFloattoDouble`
look the final code
let url = URL(string: "https://www.x-rates.com/calculator/?from=EUR&to=USD&amount=1")!
let request = NSMutableURLRequest(url : url)
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
var rateValue:Double = 0.0;
if let error = error {
print(error)
} else {
if let unwrappedData = data {
let dataString = NSString(data: unwrappedData, encoding: String.Encoding.utf8.rawValue)
var stringSeperator = "<span class=\"ccOutputRslt\">"
if let contentArray = dataString?.components(separatedBy: stringSeperator){
if contentArray.count > 0 {
stringSeperator = "<span"
let newContentArray = contentArray[1].components(separatedBy: stringSeperator)
if newContentArray.count > 0 {
rateValue = Double(newContentArray[0])! + 10
}
}
}
}
}
//
print("Rate is \(rateValue)"); //Rate is 11.167
}
task.resume()
Hope to help you
The reason your code doesn’t work in my opinion is that you have two variables with the same name that are defined in different scopes and you use the wrong one at the end.
At the beginning you define
var message = ""
And then when converting to a number further down
var message = Float(newContentArray[0])! + 10
So change the last line to something like
var number = Float(newContentArray[0])! + 10
And use number in your calculations. Although I think
var number = Double(message)
should work equally fine since you have assigned newContentArray[0] to message already and Double is more commonly used than Float (I don’t understand + 10)
Currently I'm trying to troubleshoot the use of the projection matrix and framemarker pose when rendering in SceneKit. The model in the scene and the camera image background appear without problems. However, once I change the projection matrix and framemarker pose matrix to match Vuforia everything is pushed offscreen.
func didUpdateProjectionMatrix(projectionMatrix: matrix_float4x4)
{
let extrinsic = SCNMatrix4FromMat4(projectionMatrix)
let camera = self.cameraNode?.camera
camera?.setProjectionTransform(extrinsic)
}
func didUpdateFramemarkers(framemarkers: [Framemarker]?)
{
guard let framemarkers = framemarkers else {
return
}
for framemarker in framemarkers {
let pose = SCNMatrix4Invert(SCNMatrix4FromMat4(framemarker.pose))
self.objectNode?.transform = pose
}
}
Is this a correct way to setup the SCNCamera and object node and is there anything else required to setup Vuforia framemarkers to work with SceneKit?
It just works!
The hard part is determining what pieces of SceneKit are necessary to make this work. Originally I read the article Making Augmented Reality app easily with Scenekit + Vuforia which outlined how to rejigger the sample app for user-defined targets. The downsides to that article include that it isn't always clear what the author changed, no sample project is provided, and it is based upon an older version of Vuforia. Ultimately, I found it unnecessary to invert the pose matrix.
Draw camera image and set projection matrix and update marker pose
override func viewDidLoad()
{
super.viewDidLoad()
let scene = SmartScanScene()
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
scene.rootNode.addChildNode(cameraNode)
_cameraNode = cameraNode
let view = self.view as! SCNView
view.backgroundColor = UIColor.blackColor()
view.showsStatistics = true
// view.debugOptions = SCNDebugOptions.ShowBoundingBoxes.union(.ShowWireframe)
view.autoenablesDefaultLighting = true
view.allowsCameraControl = false
}
func didUpdateProjectionMatrix(projectionMatrix: matrix_float4x4)
{
let extrinsic = SCNMatrix4FromMat4(projectionMatrix)
_cameraNode?.camera?.setProjectionTransform(extrinsic)
}
func didUpdateFramemarkers(framemarkers: [Framemarker]?)
{
guard let framemarkers = framemarkers else {
return
}
for framemarker in framemarkers {
let pose = SCNMatrix4FromMat4(framemarker.pose)
self.objectNode?.transform = pose
}
}
func didUpdateCameraImage(image: UIImage?)
{
if let image = image {
_scene?.background.contents = image
}
}
I am working with Parse and would like to download images for offline use. I understand that this is not possible with Local Datastore so I have decided to add them to Core Data.
I have successfully downloaded the PFFiles and put them in to an Array. I am then trying to create an Array for the NSData, but the Array count is always 0 when I use the code below
class DealsDownloadViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var trailID = [Int]()
var trailStep = [Int]()
var dealNumber = [Int]()
var imageFile = [PFFile]()
var imagesArray = [UIImage]()
var imageDataArray = [NSData]()
var number = 0
override func viewDidLoad() {
super.viewDidLoad()
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext
let dealsQuery = PFQuery(className: ("Deals"))
dealsQuery.orderByAscending("TrailId")
dealsQuery.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if let objects = objects {
for object in objects {
self.trailID.append(object["TrailID"] as! Int)
self.trailStep.append(object["TrailStep"] as! Int)
self.dealNumber.append(object["dealNumber"] as! Int)
self.imageFile.append(object["dealImage"] as! PFFile!)
}
for file in self.imageFile {
let dealImage = file
dealImage.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in
if error == nil {
let image = UIImage(data: imageData!)
self.imageDataArray.append(imageData!)
self.imagesArray.append(image!)
} else {print("error here")}
})
print(self.trailID.count)
print(self.trailStep.count)
print(self.dealNumber.count)
print(self.imageDataArray.count)
print(self.imagesArray.count)
}
} else {print("problem making arrays")}
}
}
If I move the Print statement up, I just get it printing every iteration of the loop.
for file in self.imageFile {
let dealImage = file
dealImage.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in
if error == nil {
let image = UIImage(data: imageData!)
self.imageDataArray.append(imageData!)
self.imagesArray.append(image!)
} else {print("error here")}
print(self.trailID.count)
print(self.trailStep.count)
print(self.dealNumber.count)
print(self.imageDataArray.count)
print(self.imagesArray.count)
})
}
} else {print("problem making arrays")}
}
}
In this case I can see that the data is added to both the imagesArray and imageDataArray.
This seems like such a simple issue but I am going crazy over it. What am I doing wrong, and is this the most efficient way of adding this data to Core Data? Am I overlooking something obvious?
I am new to programming so please do point out any mistakes I have made, and I am especially new as a questioner to stackoverflow (you have been indispensable while learning) so please let me know if you need any information that I have missed.
Thanks for your help.
Update 1
I have tried editing the code as explained in the comments and I am still getting the same result. I have moved the Print statement around on this code and it is still giving me the same results as above.
for file in self.imageFile {
let dealImage = file
dealImage.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in
if error == nil {
weak var aBlockSelf = self
let image = UIImage(data: imageData!)
aBlockSelf!.imageDataArray.append(imageData!)
self.imagesArray.append(image!)
}
print(self.trailID.count)
print(self.trailStep.count)
print(self.dealNumber.count)
print(self.imageDataArray.count)
print(self.imagesArray.count)
})
}
} else {print("problem making arrays")}
}
}
Am I missing something very simple? Thanks again for your help.
Update 2
This is the same code with (I think) the print statements moved outside of the For Loop. This is giving me counts of 9,9,9,0,0 from the print statements, whereas I think I should be expecting 9,9,9,9,9.
for file in self.imageFile {
let dealImage = file
dealImage.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in
if error == nil {
weak var aBlockSelf = self
let image = UIImage(data: imageData!)
aBlockSelf!.imageDataArray.append(imageData!)
self.imagesArray.append(image!)
}
})
}
print(self.trailID.count)
print(self.trailStep.count)
print(self.dealNumber.count)
print(self.imageDataArray.count)
print(self.imagesArray.count)
} else {print("problem making arrays")}
}
}
There is no issue here!
You are being deceived by the way asynchronous block works. Asynchronous block gets queued up to get executed some point later in next run loop.
Your first print statement is just after you pass the code to block which is yet to be executed. Which is why you do see your image array empty.