In my application, I am trying to access the array values but the array is return zero values. I have been stuck on this for a few hour now, and cannot seem to solve it. So any help would be appreciated:
#implementation MyScene{
NSMutableArray *_touchPoints;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint touchPoint = [[touches anyObject] locationInNode:self.scene];
[self clearTouchPoints];
[self addTouchPointToMove:touchPoint];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint touchPoint = [[touches anyObject] locationInNode:self.scene];
CGPoint targetPoint = CGPointMake(touchPoint.x , touchPoint.y - delta.y);
[self addTouchPointToMove:touchPoint];
NSLog(#"Value: %lf", touchPoint.y);
}
- (void)move:(NSNumber *)_tempTime{
CGPoint currentPoint = [_touchPoints[0] CGPointValue];
CGPoint targetPoint = [_touchPoints[1] CGPointValue];
NSLog(#"Check Value: %lf", targetPoint.y);
}
- (void)addTouchPointToMove:(CGPoint)point {
[_touchPoints addObject:[NSValue valueWithCGPoint:point]];
}
- (void)clearTouchPoints {
[_touchPoints removeAllObjects];
}
The array is never initialized (e.g., _touchPoints = [NSMutableArray new];) so it is nil. Attempting to invoke methods on a nil object does nothing, nor does it complain (i.e., does not throw an exception). So in the addTouchPointToMove method:
[_touchPoints addObject:[NSValue valueWithCGPoint:point]];
does nothing.
In the init method, initialize the array.
Related
I am encoding and decoding coordinates that are sometimes encoded as an object {"x":1,"y":2,"z":3} and sometimes as an array [1,2,3].
For decoding, this poses no problem. I'll add the code at the end, but it is rather trivial and uninteresting. However, for re-encoding, I absolutely must be certain the coordinates are encoded back into the exact same way I found them, either object or array.
What is the best way to achieve that ? Should I use a private variable that remembers which kind of source they came from ? Make Coordinates a protocol and have two different structs implement it ? Something else I haven't thought of ?
Please note that I know in advance which encoding is going to be used. The API I'm interfacing with just hasn't updated everything to use objects yet, which is their newest standard as far as I understand. The outcome is not random.
Here is my current decoding logic :
extension Coordinates : Codable {
private enum CodingKeys : String, CodingKey {
case x
case y
case z
}
init(from decoder : Decoder) throws {
if let container = try? decoder.container(keyedBy: CodingKeys.self) {
self.x = try container.decode(Element.self, forKey: .x)
self.y = try container.decodeIfPresent(Element.self, forKey: .y)
self.z = try container.decode(Element.self, forKey: .z)
} else if var container = try? decoder.unkeyedContainer() {
self.x = try container.decode(Element.self)
self.z = try container.decode(Element.self)
if let z = try? container.decode(Element.self) {
self.y = self.z
self.z = z
}
} else {
preconditionFailure("Cannot decode coordinates")
}
}
}
This is actually an XY problem. Instead of trying to be super smart, I just added an array computed property to Coordinates and will encode that instead of the Coordinates themselves when that is required :
/**
A spatial position with `XYZ` coordinates, `Y` being optional.
*/
struct Coordinates<Element : Coordinate> : Codable {
/**
West to East axis coordinate.
*/
var x : Element
/**
Down to Up axis coordinate.
*/
var y : Element?
/**
North to South axis coordinate.
*/
var z : Element
/**
The coordinates in the form `[x, z]` or `[x, y, z]`.
*/
var array : [Element] {
if let y {
return [x, y, z]
} else {
return [x, z]
}
}
init(x : Element, y : Element? = nil, z : Element) {
self.x = x
self.y = y
self.z = z
}
}
//Normal encoding
struct Something {
var coordinates : Coordinates<Int>
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(coordinates)
}
}
//Array encoding
struct SomethingElse {
var coordinates : Coordinates<Int>
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(coordinates.array)
}
}
I don't mind that this forces me to explicitly implement encoding methods, as everything in this file format / API is busted and requires me to custom encode/decode anyways.
You can preserve the structured data structure with a simple enum like:
enum Structured {
case array([Element])
case object(Coordination)
private enum CodingKeys : String, CodingKey {
case x
case y
case z
}
init(from decoder : Decoder) throws {
if let container = try? decoder.container(keyedBy: CodingKeys.self) {
let x = try container.decode(Element.self, forKey: .x)
let y = try container.decodeIfPresent(Element.self, forKey: .y)
let z = try container.decode(Element.self, forKey: .z)
self = .object( Coordination(x: x, y: y, z: z) )
} else if var container = try? decoder.unkeyedContainer() {
let array = try container.decode([Element].self)
self = .array( array )
} else {
preconditionFailure("Cannot decode coordinates")
}
}
}
So the only way i can think of achieving this is by putting the array inside mainArray into a variable and then indexing that. Is there an easier way?
mainArray = [ 3400, "Overwatch", [UIButton(), UIButton()]] // Some buttons already made
currentButtonArray = mainArray[mainArray.count - 1] as! NSArray
for i in 0..<currentButtonArray.count {
buttonArray.append( currentButtonArray[i] as! UIButton)
}
If there is one array containing only UIButton instances, just filter it.
let mainArray : [Any] = [3400, "Overwatch", [UIButton(), UIButton()]]
if let buttonArray = mainArray.filter({$0 is [UIButton]}).first as? [UIButton] {
print(buttonArray)
}
or
let buttonArray = Array(mainArray.flatMap{$0 as? [UIButton]}.joined())
The second approach returns a non-optional empty array if there is no array of UIButton in mainArray
If the subarray is of all one type, you can append all in one go:
var buttonArray = [UIButton]()
let mainArray:[Any] = [3400, "Overwatch", [UIButton(), UIButton()]] // Some buttons already made
if let currentButtonArray = mainArray.last as? [UIButton] {
buttonArray.append(contentsOf: currentButtonArray)
}
Or you could simply write:
guard let currentButtonArray = mainArray.last as? [UIButton] else {
// do something e.g. return or break
fatalError()
}
// do stuff with array e.g. currentButtonArray.count
If you didn't know the position in the array of the nested UIButton array or if there were multiple nested button arrays then this would work:
let buttonArray = mainArray.reduce([UIButton]()){ (array, element) in if let bArray = element as? [UIButton] {
return array + bArray
}
else {
return array
}
}
Note: this is Swift 3 code.
I am trying to save a copy of my custom class to a file, my class has 2 arrays of CGPoints which I append to every so often, they look like this:
class BlockAttributes: NSObject {
var positions:[CGPoint] = []
var spawns:[CGPoint] = []
}
Everything is working great as far as just as using and accessing the class goes, but archiving it does not work. I can archive arrays of Strings, Bools, and Ints just fine in my other classes but my game fails every time I try to use NSCoder to encode my arrays of CGPoints. Here is my code for archiving:
func encodeWithCoder(coder: NSCoder!) {
coder.encodeObject(positions, forKey: "positions")
coder.encodeObject(spawns, forKey: "spawns")
}
....
class ArchiveData: NSObject {
var documentDirectories:NSArray = []
var documentDirectory:String = ""
var path:String = ""
func saveData(data: BlockAttributes) {
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as! String
path = documentDirectory.stringByAppendingPathComponent("data.archive")
if NSKeyedArchiver.archiveRootObject(data, toFile: path) {
print("Success writing to file!")
} else {
print("Unable to write to file!")
}
}
func retrieveData() -> NSObject {
var dataToRetrieve = BlockAttributes()
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as! String
path = documentDirectory.stringByAppendingPathComponent("data.archive")
if let dataToRetrieve2 = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? BlockAttributes {
dataToRetrieve = dataToRetrieve2 as BlockAttributes
}
return(dataToRetrieve)
}
}
....
And to save:
let archiveData = ArchiveData()
archiveData.saveData(myBlockActionsObject)
I even tried creating my own custom class to save the CGPoints to, which I call MyCGPoint (I read somewhere on SO that creating custom classes for some data types resolves some NSCoder issues):
class MyCGPoint: NSObject {
var x: CGFloat = 0.0
var y: CGFloat = 0.0
init(X: CGFloat, Y: CGFloat) {
x = X
y = Y
}
override init() {
}
}
....
class BlockAttributes: NSObject {
var positions:[MyCGPoint] = []
var spawns:[MyCGPoint] = []
}
But alas, I am still getting this error:
[Game.MyCGPoint encodeWithCoder:]:
unrecognized selector sent to instance 0x137f1d1a0 Game[20953:5814436]
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[Game.MyCGPoint encodeWithCoder:]:
unrecognized selector sent to instance 0x137f1d1a0'
Any idea how I can use encodeObject to encode my array of CGPoints/MyCGPoints?
You can convert them to and from strings:
//To string
let point = CGPointMake(0, 0)
let string = NSStringFromCGPoint(point)
//Or if you want String instead of NSString
let string = String(point)
//From string
let point2 = CGPointFromString(string)
CGPoint (and its Cocoa's twin NSPoint) are structs, i.e. value type, so you can't encode them directly. Wrap them in NSValue:
let positionValues = positions.map { NSValue(point:$0) }
let spawnValues = spawns.map { NSValue(point:$0) }
coder.encodeObject(positionValues, forKey: "positions")
coder.encodeObject(spawnValues, forKey: "spawns")
// Decode:
positons = (coder.decodeObjectForKey("positions") as! [NSValue]).map { $0.pointValue }
spawns = (coder.decodeObjectForKey("spawns") as! [NSValue]).map { $0.pointValue }
When you write your custom wrapper class, you have to make it compliant with NSCoding too, which NSValeu had already done for you, for free.
I get this error message:-[MPConcreteMediaItem isEqualToString:]: unrecognized selector sent to instance…
I am creating an NSMutable array of songs from media picker into an array named "array". This array shows up properly in the tableview (myTable). HOWEVER when I try to segue to the detail view and send the song title to a label on the detail view following this tutorial. I get the crash and error message above.
Appreciate any help, thanks!
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"detailView"]) {
NSIndexPath *indexPath = [self.myTable indexPathForSelectedRow];
DetailViewController *vc = [segue destinationViewController];
vc.trackName = [array objectAtIndex:indexPath.row];
//crashes -[MPConcreteMediaItem isEqualToString:]: unrecognized selector sent to instance
}
}
- (void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection {
[array addObjectsFromArray:mediaItemCollection.items];
[myTable reloadData];
// mediaItemCollection = nil;
[mediaPicker dismissViewControllerAnimated:YES completion:nil];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//this method is called to fill out table with data
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (cell == nil) {
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"Cell"];
}
// Configure the cell
anItem = nil;
anItem = [array objectAtIndex:indexPath.row];
cell.textLabel.text = [anItem valueForProperty:MPMediaItemPropertyTitle];
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
return cell;
}
The error looks like that you aren't calling 'isEqualToString:' on a string object... Which superclass has MPConcreteMediaItem
SOffer[26229:c07] current Product: (
{
id = 20;
image = "img2.png";
name = "offer 2";
}
)
I have product which result the above when I print it through NSLog,, I need to print the these individually. Following code generate this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
NSString *currentProductName;
currentProductName = [productKeys objectAtIndex:indexPath.row];
currentProduct = [products objectForKey:[productKeys objectAtIndex:indexPath.row]];
NSLog(#"current Product: %#",currentProduct);
//currentProductName = [currentProduct objectForKey:#"image"];
//currentProduct = [currentProduct ];
[[cell textLabel] setText:currentProductName];
return cell;
}
currentProduct is declared as NSArray but when i try to print with objectForKey it say No visible interface for NSArray declares the selector ObjectForKey
objectForKey, I believe works with NSDictionary only.
NSArray does not have key value pairs. Unless currentProduct holds the JSON file, then you gonna have to first get out the Array Object of the first Product and assign it to a NSDictionary, where you can then use the objectForKey to get your image value.
Something like this:
// Get the first product object from the NSArray. Assuming you dropped your entire Product values into a NSArray
NSMutableDictionary *prodDict = [currentProduct objectAtIndex:indexPath.section];
NSString *prodID = [prodDict objectForKey:#"id"];
NSString *prodName = [prodDict objectForKey:#"name"];
...