Error on Static Array in Swift Class - ios

I am developing an app using swift. I create a subclass from SCNNode for this:
class Charge: SCNNode {
static var tagCounter = 0 //to give every charge a unique tag
static var charges = [Charge]() //to have a pointer that can access all charges
static var selectedCharge: Charge? //pointer to selected charge
override init() {
super.init()
super.geometry = Charge.carbonAtom()
Charge.tagCounter++
self.chargeTag = Charge.tagCounter
Charge.charges.append(self)
}
}
Then after initiating the class several times in ViewController, I want to access them by using Charge.charges. But for some unknown reason, only the last instance is available, and it occupies Charge.charges[0].
I tried to track the object movement by adding property in ViewController var test = [Charge]() and call test.append(charge) every time I initiate charge in ViewController. Then, when I want to access all the charges, Charge.charges loses most of its charges but test does not! Can anyone enlighten me on this? Or is it a bug in Xcode?
n.b. I use debugging tool to track this problem. It turns out that Charge.charges loses its first content as soon as the second initialization is finished, but the first content still exists right after the execution of Charge.charges.append(self)
edit: carbonAtom function
class func carbonAtom() -> SCNGeometry {
let carbonAtom = SCNSphere(radius: 0.8)
carbonAtom.firstMaterial!.diffuse.contents = UIColor.redColor()
carbonAtom.firstMaterial!.specular.contents = UIColor.whiteColor()
return carbonAtom
}

I have just tested, there is not any Xcode bug.
class Charge: NSObject {
static var tagCounter = 0 //to give every charge a unique tag
static var charges = [Charge]() //to have a pointer that can access all charges
override init() {
super.init()
// super.geometry = Charge.carbonAtom()
Charge.tagCounter++
// self.chargeTag = Charge.tagCounter
Charge.charges.append(self)
}
}
Create 3 Changes instance:
for var i = 0; i < 3; i++ {
_ = Charge()
}
print(Charge.charges.count)
The console prints 3.
Try to check your Charge.carbonAtom() static method. I doubt it clear value of the charges array.

OKAY GUYS I FOUND IT!
There are infos that I dont provide (I thought it was irrelevant) in Charge class:
override func removeFromParentNode() {
super.removeFromParentNode()
for var i = 0; i<Charge.charges.count; i++ {
let charge = Charge.charges[i]
if Charge.selectedCharge != nil {
if charge == Charge.selectedCharge! {
Charge.charges.removeAtIndex(i)
}
}
break
}
}
and in ViewController
#IBAction func addCharge(sender: AnyObject) {
let charge = Charge()
scene.rootNode.addChildNode(charge) //root of problem
Charge.selectedCharge = charge
print(Charge.charges.count)
}
what happen is, in the line scene.rootNode.addChildNode(charge), the method automatically calls removeFromParentNode() from charge. Because of that, the charge pointed by Charge.selectedCharge will be removed from Charge.charges when the second charge is initialized.
I try to search for this info in Apple documentation but no avail. If only they document it more comprehensively :/
Thank you for the help guys :) the divide and conquer method I use to replicate the bug really helps narrowing down the problem

Related

How to get data from one class to viewController?

Unfortunately I m newbie and cant solve this problem for days. the problem is I cant success to gather score from game engine class to view controller so that I can show it with a label.
here is I want to extract data from this func to global variables.
private func nextMove() {
current = .random(config)
if (well.hasCollision(current)) {
stopTimer()
scene.showGameOver(scores)
//extract score to another variable can be reached by another class
} else {
scene.show(current)
}
}
I tried many methods, creating a global class with init method, creating struct, creating protocol..etc that already written here, but never successed. any idea will be appriciated. thanks..
A Singleton could be what you're looking for.
Here's an example:
class ScoresSingleton {
static let sharedSession = ScoresSingleton()
var scores : Int = 0
}
You may then use globally by:
// Setting
ScoresSingleton.sharedSession.scores = 10
// Getting
var scores = ScoresSingleton.sharedSession.scores
Hope that helps.

How to update a UILabel's text based on a instance's property

I'm attempting to update my "Lives Remaining: " UILabel, and I'm having trouble getting it to update based on a class's or instances current variable value- in this case lives. I'm using didSet to do so in my following code:
The Ship class:
class Ship:SKSpriteNode{
...
var lives:Int = 0{
didSet{
shipLivesLabel?.text = self.lives.description
}
}
Instantiating the label in GameScene:
class GameScene: SKScene, SKPhysicsContactDelegate {
private var shipLives = 0 {
didSet{
self.shipLivesLabel?.text = aShip.lives.description
}
}
private var shipLivesLabel:SKLabelNode?
and where I'm adding it to the scene:
override func didMoveToView(view: SKView) {
let shipLivesLabel = SKLabelNode(fontNamed: "Times New Roman")
shipLivesLabel.text = shipLives.description
shipLivesLabel.fontSize = 14
shipLivesLabel.position = CGPoint(x:CGRectGetMidX(self.frame)*1.3,y:CGRectGetMidY(self.frame)*0.1)
self.addChild(shipLivesLabel)
self.shipLivesLabel = shipLivesLabel
I'm not sure if this is the proper way to go about this, and I'm also not sure how to reference the shipLivesLabel within the Ship class- I receive the error: Instance member shipLivesLabel cannot be used on type GameScene. Any help would be awesome.
Your issue is in :
private var shipLives = 0 {
didSet{
self.shipLivesLabel?.text = aShip.lives.description
}
}
where your self.shipLivesLabel could be not ready during this assignment.
So what happened?
You try to assign to a not initialized class, a value to his property.
Bad syntax:
MyClass.variable = 'Foo'
// error: Instance member 'variable' cannot be used on type 'MyClass'
Good syntax:
instanceOfMyClass.variable = 'Foo'
About your case:
GameScene.shipLivesLabel assignment used before GameScene is fully initialized.
I don't like your approach to write important game properties like the lives counter of your characters. Please take a look to this answer to better understand what I mean: answer
Another advice gived by Tibrogargan in question comments is to don't use description: it's a bad attitude, it's used for debugging, live is an Int and if you want to assign this one to a String do:
shipLivesLabel.text = "\(shipLives)"

Presenting ViewController anew does not re-initialize it

I have a viewController (videocallVC) that I want to initialize every time it comes into view/loads.
Currently, the videocallVC only initialize the first time. If I leave the videocallVC, go to another viewController and come back to the videocallVC it holds the last session in memory and does not "refresh".
How can I make sure that every time I present the videocallVC it is initialized anew ?
import OpenTok
class videocallVC: UIViewController, OTSessionDelegate, OTSubscriberKitDelegate, OTPublisherDelegate {
#IBOutlet var subscribeView: UIView!
#IBOutlet var publishView: UIView!
let apiKey = "xxxxxxx"
var session : OTSession?
var publisher : OTPublisher?
var subscriber : OTSubscriber?
var connection : OTConnection?
override func viewDidLoad() {
super.viewDidLoad()
session = OTSession(
apiKey: apiKey,
sessionId: variableInstance.sessionID,
delegate: self)
}
override func viewWillAppear(animated: Bool) {
doConnect()
}
// MARK: - OpenTok Methods
/**
* Asynchronously begins the session connect process. Some time later, we will
* expect a delegate method to call us back with the results of this action.
*/
func doConnect() {
if let session = self.session {
var maybeError : OTError?
session.connectWithToken(variableInstance.tokboxToken, error: &maybeError)
if let error = maybeError {
showAlert(error.localizedDescription)
}
}
}
/**
* Sets up an instance of OTPublisher to use with this session. OTPubilsher
* binds to the device camera and microphone, and will provide A/V streams
* to the OpenTok session.
*/
func doPublish() {
publisher = OTPublisher(delegate: self)
var maybeError : OTError?
session?.publish(publisher, error: &maybeError)
if let error = maybeError {
showAlert(error.localizedDescription)
}
view.addSubview(publisher!.view)
publisher!.view.frame = publishView.frame
}
There is more code to the videocallVC but I gues this is sufficient to understand the problem.
Any help would be very much appreciated ! Thank you.
The method viewDidLoad is only called once. The method viewWillAppear is called every time the view will appear. So if you need to do things every time the view will appear, move it to that method (you may also use viewDidAppear if the UI behaviour makes sense). This may apply to the session = OTSession( ... ) snippet.
It may also be possible, that in your code things get initialized, then stored in a variable and later, if the variable is not nil no new initialization is done. You may solve that by setting those variables to nil in viewWillAppear.
However, without knowing all details, this may be a likely solution but still a shot in the dark :-)
how are your delegates store in OTSession and OTPublisher?
Are they weak?
If they are not weak, the View Controller can not be deinitialized, because there are still references to it. Maybe this can cause your problems.

What if I want to assign a property to itself?

If I attempt to run the following code:
photographer = photographer
I get the error:
Assigning a property to itself.
I want to assign the property to itself to force the photographer didSet block to run.
Here's a real-life example: In the "16. Segues and Text Fields" lecture of the Winter 2013 Stanford iOS course (13:20), the professor recommends writing code similar to the following:
#IBOutlet weak var photographerLabel: UILabel!
var photographer: Photographer? {
didSet {
self.title = photographer.name
if isViewLoaded() { reload() }
}
}
override func viewDidLoad() {
super.viewDidLoad()
reload()
}
func reload() {
photographerLabel.text = photographer.name
}
Note: I made the following changes: (1) the code was switched from Objective-C to Swift; (2) because it's in Swift, I use the didSet block of the property instead of the setPhotographer: method; (3) instead of self.view.window I am using isViewLoaded because the former erroneously forces the view to load upon access of the view property; (4) the reload() method (only) updates a label for simplicity purposes, and because it resembles my code more closely; (5) the photographer IBOutlet label was added to support this simpler code; (6) since I'm using Swift, the isViewLoaded() check no longer exists simply for performance reasons, it is now required to prevent a crash, since the IBOutlet is defined as UILabel! and not UILabel? so attempting to access it before the view is loaded will crash the application; this wasn't mandatory in Objective-C since it uses the null object pattern.
The reason we call reload twice is because we don't know if the property will be set before or after the view is created. For example, the user might first set the property, then present the view controller, or they might present the view controller, and then update the property.
I like how this property is agnostic as to when the view is loaded (it's best not to make any assumptions about view loading time), so I want to use this same pattern (only slightly modified) in my own code:
#IBOutlet weak var photographerLabel: UILabel?
var photographer: Photographer? {
didSet {
photographerLabel?.text = photographer.name
}
}
override func viewDidLoad() {
super.viewDidLoad()
photographer = photographer
}
Here instead of creating a new method to be called from two places, I just want the code in the didSet block. I want viewDidLoad to force the didSet to be called, so I assign the property to itself. Swift doesn't allow me to do that, though. How can I force the didSet to be called?
Prior to Swift 3.1 you could assign the property name to itself with:
name = (name)
but this now gives the same error: "assigning a property to itself".
There are many other ways to work around this including introducing a temporary variable:
let temp = name
name = temp
This is just too fun not to be shared. I'm sure the community can come up with many more ways to do this, the crazier the better
class Test: NSObject {
var name: String? {
didSet {
print("It was set")
}
}
func testit() {
// name = (name) // No longer works with Swift 3.1 (bug SR-4464)
// (name) = name // No longer works with Swift 3.1
// (name) = (name) // No longer works with Swift 3.1
(name = name)
name = [name][0]
name = [name].last!
name = [name].first!
name = [1:name][1]!
name = name ?? nil
name = nil ?? name
name = name ?? name
name = {name}()
name = Optional(name)!
name = ImplicitlyUnwrappedOptional(name)
name = true ? name : name
name = false ? name : name
let temp = name; name = temp
name = name as Any as? String
name = (name,0).0
name = (0,name).1
setValue(name, forKey: "name") // requires class derive from NSObject
name = Unmanaged.passUnretained(self).takeUnretainedValue().name
name = unsafeBitCast(name, to: type(of: name))
name = unsafeDowncast(self, to: type(of: self)).name
perform(#selector(setter:name), with: name) // requires class derive from NSObject
name = (self as Test).name
unsafeBitCast(dlsym(dlopen("/usr/lib/libobjc.A.dylib",RTLD_NOW),"objc_msgSend"),to:(#convention(c)(Any?,Selector!,Any?)->Void).self)(self,#selector(setter:name),name) // requires class derive from NSObject
unsafeBitCast(class_getMethodImplementation(type(of: self), #selector(setter:name)), to:(#convention(c)(Any?,Selector!,Any?)->Void).self)(self,#selector(setter:name),name) // requires class derive from NSObject
unsafeBitCast(method(for: #selector(setter:name)),to:(#convention(c)(Any?,Selector,Any?)->Void).self)(self,#selector(setter:name),name) // requires class derive from NSObject
_ = UnsafeMutablePointer(&name)
_ = UnsafeMutableRawPointer(&name)
_ = UnsafeMutableBufferPointer(start: &name, count: 1)
withUnsafePointer(to: &name) { name = $0.pointee }
//Using NSInvocation, requires class derive from NSObject
let invocation : NSObject = unsafeBitCast(method_getImplementation(class_getClassMethod(NSClassFromString("NSInvocation"), NSSelectorFromString("invocationWithMethodSignature:"))),to:(#convention(c)(AnyClass?,Selector,Any?)->Any).self)(NSClassFromString("NSInvocation"),NSSelectorFromString("invocationWithMethodSignature:"),unsafeBitCast(method(for: NSSelectorFromString("methodSignatureForSelector:"))!,to:(#convention(c)(Any?,Selector,Selector)->Any).self)(self,NSSelectorFromString("methodSignatureForSelector:"),#selector(setter:name))) as! NSObject
unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setSelector:")),to:(#convention(c)(Any,Selector,Selector)->Void).self)(invocation,NSSelectorFromString("setSelector:"),#selector(setter:name))
var localVarName = name
withUnsafePointer(to: &localVarName) { unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setArgument:atIndex:")),to:(#convention(c)(Any,Selector,OpaquePointer,NSInteger)->Void).self)(invocation,NSSelectorFromString("setArgument:atIndex:"), OpaquePointer($0),2) }
invocation.perform(NSSelectorFromString("invokeWithTarget:"), with: self)
}
}
let test = Test()
test.testit()
There are some good workarounds but there is little point in doing that.
If a programmer (future maintainer of the code) sees code like this:
a = a
They will remove it.
Such a statement (or a workaround) should never appear in your code.
If your property looks like this:
var a: Int {
didSet {
// code
}
}
then it's a not a good idea to invoke the didSet handler by assignment a = a.
What if a future maintainer adds a performance improvement to the didSet like this?
var a: Int {
didSet {
guard a != oldValue else {
return
}
// code
}
}
The real solution is to refactor:
var a: Int {
didSet {
self.updateA()
}
}
fileprivate func updateA() {
// the original code
}
And instead of a = a directly call updateA().
If we are speaking about outlets, a suitable solution is to force the loading of views before assigning for the first time:
#IBOutlet weak var photographerLabel: UILabel?
var photographer: Photographer? {
didSet {
_ = self.view // or self.loadViewIfNeeded() on iOS >= 9
photographerLabel?.text = photographer.name // we can use ! here, it makes no difference
}
}
That will make the code in viewDidLoad unnecessary.
Now you might be asking "why should I load the view if I don't need it yet? I want only to store my variables here for future use". If that's what you are asking, it means you are using a view controller as your model class, just to store data. That's an architecture problem by itself. If you don't want to use a controller, don't even instantiate it. Use a model class to store your data.
I hope one day #Swift developers will fix this miscuzzi :)
Simple crutch:
func itself<T>(_ value: T) -> T {
return value
}
Use:
// refresh
style = itself(style)
image = itself(image)
text = itself(text)
(optionals including)
Make a function that the didSet calls then call that function when you want to update something? Seems like this would guard against developers going WTF? in future
#vacawama did a great job with all those options. However in iOS 10.3, Apple banned some of these ways and most likely will be doing it in the future again.
Note: To avoid the risk and future errors, I will use a temporary variable.
We can create a simple function for that:
func callSet<T>(_ object: inout T) {
let temporaryObject = object
object = temporaryObject
}
Would be used like: callSet(&foo)
Or even a unary operator, if there is a fitting one ...
prefix operator +=
prefix func +=<T>(_ object: inout T) {
let temporaryObject = object
object = temporaryObject
}
Would be used like: +=foo

UITapGestureRecognizer Causing Crash With Differing Error Messages

I have a major issue that I am stuck with at work and would REALLY appreciate some help on. This has cost me 2 days already.
What I am trying to do is have a special image class fire it's assigned callback when it's touched. Thats it.
But when I touch the image it crashes, often without an error message just (lldb). Sometimes it says garbage like "[__NSCFData tapped:]: unrecognized selector sent to instance 0x1780af360". Sometimes it says "message sent to deallocated object".
I can run the app 10 times in a row and get one of these random messages just from tapping the same object on the screen each time.
The code is very simple:
//view controller
var k:K_PreviewImage!
override func viewDidLoad()
{
var image:iImage = iImage(imageName: "ja3.jpg")
k = K_PreviewImage(image: image)
k.touchCallback = nestedTap
_view.addSubview(k.image)
}
func nestedTap(k:K_PreviewImage)
{
println("Successs")
}
And here is the code inside K_PreviewImage (my clickable image). This does NOT inherit from anything, including NSObject
var touchCallback:((K_PreviewImage)->Void)
{
set{
if(_touchCallback == nil)
{
var tap:UIGestureRecognizer = UITapGestureRecognizer(target: self, action:"tapped:")
_image.addGestureRecognizer(tap)
}
_touchCallback = newValue
}
get{
return _touchCallback
}
func tapped(tap:UITapGestureRecognizer)
{
println("here")
if(_touchCallback != nil)
{
touchCallback(self)
}
}
The above code causes a crash 100% of the time.
If I add #objc too the tap event listener in the K_PreviewImage like this
#objc func tapped(tap:UITapGestureRecognizer)
{
println("here")
if(_touchCallback != nil)
{
touchCallback(self)
}
}
Then the code WORKS and the touch event is fired (both inside K_PreviewImage and the controllers callback function 'nestedTap'.
Not sure why that works, but it does. However I'm still up a creek without a paddle because when I make the K_PreviewImage a function variable instead of a class member variable, I get a crash again
override func viewDidLoad()
{
var image:iImage = iImage(imageName: "ja3.jpg")
var k:K_PreviewImage = K_PreviewImage(image: image)
k.touchCallback = nestedTap
_view.addSubview(k.image)
}
So my first question is why did I need to add #objc to get the callback to fire instead of giving me an unclear 'deallocated' memory crash?
My second question is why is it when I moved the variable from a member variable to a function variable does it cause the crash all over again. If the object was deallocated it couldnt hear the tap event in the first place could it? Why would it still be on screen? And why is it getting dealloacted just because it's not a member variable!
How the heck can I create a custom object dynamically and have it fire click events!
UPDATE
Here is the full code block involving how my K_PreviewImage was being added with a touch event
var _array:Array<iImage>!
override func viewDidLoad()
{
_array = Array<iImage>()
var image:iImage = iImage(imageName: "ja3.jpg")
var k:K_PreviewImage = K_PreviewImage(image: image)
k.touchCallback = nestedTap
_view.addSubview(k.image)
_array.append(k.image)
}
func nestedTap(k:K_PreviewImage)
{
println("Successs")
}
Solved:
override func viewDidLoad()
{
_array = Array<K_PreviewImage>()
var image:iImage = iImage(imageName: "ja3.jpg")
var k:K_PreviewImage = K_PreviewImage(image: image)
k.touchCallback = nestedTap
_view.addSubview(k.image)
_array.append(k)
}
func nestedTap(k:K_PreviewImage)
{
println("Successs")
}
Because I was not storing a reference to the K_PreviewImage 'k' even though the subview was added, even though it belongs to k, k was not being retained. By making the array store a reference to K_PreviewImage instead of iImage the compiler now retains a reference to the K_Preview Image
First off, it looks like you're trying to add an image as a subview. I don't know Swift all that well, but _view.addSubview(k.image) looks wrong to me and would make more sense as _view.addSubview(k).
Secondly, you're not keeping your reference to your K_PreviewImage. You are assigning k.image as the subview, which causes k to be detected as "no longer used" by the system. It will clean it up and when you try to access it, you'll crash.

Resources