I know that theoretically it's possible to create multiple instances of the same class with a property that would have a different value for each instance.
The thing is, I can't make it happen.
Each time I'm creating a new instance, it gets the property's value of the other instances, and when I'm changing one value for an instance, it changes the other's too.
So my guess is that I'm doing something wrong (obviously), like accessing the class property value instead of the instance property value... Here's the code.
class CustomUIImageView: UIImageView {
var someParameter: Bool = false // This is the property I want to be different in each version of the instance.
}
class ClassSiege: UIViewController, UIGestureRecognizerDelegate {
var myView: CustomUIImageView! //the instance declaration.
// I use this gesture recognizer to find out the value of the instance I'm tapping on.
func handleTap (sender: UITapGestureRecognizer) {
print("value of someParameter \(self.myView.someParameter)")
}
func handlePan(recognizer: UIPanGestureRecognizer) {
let iv: UIView! = recognizer.view
let translation = recognizer.translationInView(self.view)
iv.center.x += translation.x
iv.center.y += translation.y
recognizer.setTranslation(CGPointZero, inView: self.view)
var centerBoardX = BlackBoard.center.x // 'Blackboard' is a fixed image on the screen.
var centerBoardY = BlackBoard.center.y
var centerRondX = iv.center.x
var centerRondY = iv.center.y
if centerRondY - centerBoardY < 100 {
self.myView.someParameter = true // If the distance between myView and the blackboard is under 100 I want the instance's property to become true.
} else {
self.myView.someParameter = false // On the other hand, if the distance is greater than 100, I want it to be false.
}
}
// When the user pushes a button, it triggers this method that creates a new instance of myView and add it to the screen.
#IBAction func showContent(sender: AnyObject) {
// some code...
// Here I'm creating the instance of the view and I give it the gesture recognizer parameters. I don't think that relevant to the issue, so I'm not adding the code.
}
}
So clearly that's not the good way to do it, but what's wrong, and how can it be solved?
Basing my answer on your related question.
If what you want to achieve is initializing a property with a value that you provide, just add a new parameter to the initializer. If for instance you are using the initializer with a CGRect passed in, then you can implement an initializer like this:
class CustomUIImageView : UIImageView {
let someParameter : Bool
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(frame: CGRect, someParameter: Bool) {
self.someParameter = someParameter
super.init(frame: frame)
}
}
I hope that this is what you are looking for - let me know otherwise.
I've found the solution, and if you've been facing the same issu, here's how to deal with it.
The secret is to downcast the recognizer.view to take the parameter of the subclass CustomUIImageView.
here's how :
func handleTap (sender: UITapGestureRecognizer) {
println("value of someParameter \(self.myView.someParameter)") //I use this gesture recognizer to find out the value of the instance I'm tapping on.
}
func handlePan(recognizer:UIPanGestureRecognizer) {
let iv : UIView! = recognizer.view
let translation = recognizer.translationInView(self.view)
iv.center.x += translation.x
iv.center.y += translation.y
recognizer.setTranslation(CGPointZero, inView: self.view)
var centerBoardX = BlackBoard.center.x //blackboard is a fixed image on the screen.
var centerBoardY = BlackBoard.center.y
var centerRondX = iv.center.x
var centerRondY = iv.center.y
var myParameter = recognizer.view as CustomUIImageView //<- this is the key point. Downcasting let you access the custom subclass parameters of the object that is currently moved
if centerRondY - centerBoardY < 100 {
myParameter.someParameter = true //so now I'm really changing the parameter's value inside the object rather than changing a global var like I did before.
} else {
myParameter.someParameter = false
}
}
//when user pushes a button, it triggers this func that creates a new instance of myView and add it to the screen.
#IBAction func showContent(sender: AnyObject) {
some code...
//here I'm creating the instance of the view and I give it the gesture recognizer parameters. I don't think that relevant to the issue, so I'm not adding the code.
}
Related
I have gameCenterButton in VC1. Its purpose is to take the user to Game Center's Leaderboards where they can see High Scores. If the user decides to authenticate with Game Center, then I want to change gameCenterButton's state (un-grey and enable). In my GameKitHelper class I have these:
func authenticateLocalPlayer() {
GKLocalPlayer.local.authenticateHandler =
{ (viewController, error) in
self.gameCenterEnabled = false
if viewController != nil {
self.authenticationViewController = viewController
NotificationCenter.default.post(name: NSNotification.Name(
GameKitHelper.PresentAuthenticationViewController),
object: self)
} else if GKLocalPlayer.local.isAuthenticated {
self.gameCenterEnabled = true
}
}
}
extension GameKitHelper: GKGameCenterControllerDelegate {
func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) {
gameCenterViewController.dismiss(animated: true, completion: nil)
}
}
In VC1 I have this:
#IBOutlet weak var gameCenterButton: UIButton!
#IBAction func gameCenter(_ sender: UIButton) {
GameKitHelper.sharedInstance.showGKGameCenterViewController(viewController: self)
}
I'm thinking that inside of extension GameKitHelper I can do ...
if gameCenterEnabled == true {
gameCenterButton.isEnabled = true // How do I allow for this?
gameCenterButton.alpha = 1 // How do I allow for this?
How do I allow gameCenterButton state to change outside of it's class. Is there something I need to do in AppDelegate?
Put var gameCenterEnabled = false outside (above) of your GameKitHelper class, thus making it "global". You will likely be prompted to remove the self. in self.gameCenterEnabled = false and in self.gameCenterEnabled = true. Do so.
Now, you can reference gameCenterEnabled in VC1's class and change gameCenterButton's state like this:
// code to determine gameCenterButton's state based on gameCenterEnabled's status
if gameCenterEnabled == false {
self.gameCenterButton.isEnabled = false
self.gameCenterButton.alpha = 0.37
} else {
self.gameCenterButton.isEnabled = true
self.gameCenterButton.alpha = 1
}
Consider the following simplified code example. It's presented here in Swift, but the same behavior occurs in objective-c.
import Foundation
import Cocoa
class MainWindow : NSWindow {
#IBAction func onClick_openFile(sender : AnyObject?) {
let path = runOpenPanel(false);
NSLog(path as String)
}
#IBAction func onClick_crashyByeBye(sender : AnyObject?) {
let path = runOpenPanel(true);
NSLog(path as String)
}
private func runOpenPanel(useCrashyDelegate : Bool) -> NSString {
let openPanel = NSOpenPanel.init()
openPanel.canChooseDirectories = false
openPanel.canChooseFiles = true
openPanel.allowsMultipleSelection = false
let safeDelegate = MyOpenPanelDelegate.init() //same scope as openPanel.runModal()--works fine
if (useCrashyDelegate) {
let crashyDelegate = MyOpenPanelDelegate.init() //falls out of scope before openPanel.runModal() and crashes
openPanel.delegate = crashyDelegate
} else {
openPanel.delegate = safeDelegate
}
if (openPanel.runModal() == NSFileHandlingPanelOKButton && openPanel.URLs.count == 1) {
return openPanel.URLs[0].path!
}
return ""
}
}
class MyOpenPanelDelegate : NSObject, NSOpenSavePanelDelegate {
func panel(sender: AnyObject, shouldEnableURL url: NSURL) -> Bool {
var isDir : ObjCBool = false
if (NSFileManager.defaultManager().fileExistsAtPath(url.path!, isDirectory: &isDir)) {
return isDir || (url.path! as NSString).lastPathComponent.lowercaseString == "foo.txt"
}
return false
}
}
When the useCrashyDelegate argument to runOpenPanel is true, crashyDelegate is instantiated in a nested scope and falls out of scope before the call to openPanel.runModal(). Since the open panel assigns crashyDelegate as its delegate, I would expect crashyDelegate's reference count to be incremented. However, the application crashes with an EXC_BAD_ACCESS when useCrashyDelegate is true. If useCrashyDelegate is false, safeDelegate, which is instantiated in the same scope as the call to openPanel.runModal(), is assigned to the open panel and there is no EXC_BAD_ACCESS.
This is leading me to believe that NSOpenPanel is not incrementing its delegate's reference count. Is this the expected behavior, or might this be a bug?
This is leading me to believe that NSOpenPanel is not incrementing its delegate's reference count. Is this the expected behavior, or might this be a bug?
It is expected. Check the type of the property and you will see it is assign (Objective-C) or unsafe (Swift), a strong reference is not kept. This is a common design pattern for Cocoa delegates.
HTH
I'm trying to create an effect over my custom buttons (SKSpriteNodes) and other UI objects that are also SKSpriteNode subclasses so that when the mouse hovers over them, they expand slightly to indicate that the user is hovering over them. Once the mouse leaves the vicinity of the sprite, the sprite should go back to normal size.
I initially tried this with the mouseMoved method, but undoing the scaling effect is proving to be an issue. Is there a better way to handle this maybe in the subclasses themselves? Ideas?
One way to do this is to simply keep track of the currently hovered node (if any). Something like this.
I've added a method to recursively search a nodes parents to try and find a suitable hoverable parent. SKSpriteNode in my example, but Producer in your case unless I'm mistaken.
class GameScene: SKScene {
var hoverNode: SKSpriteNode?
//Setup
override func mouseMoved(theEvent: NSEvent) {
let location = theEvent.locationInNode(self)
if let node = findHoverable(self.nodeAtPoint(location)) {
if (node == hoverNode) { return }
node.removeAllActions()
node.runAction(SKAction.scaleTo(1.2, duration: 0.5))
hoverNode = node
} else {
if let hoverNode = hoverNode {
hoverNode.removeAllActions()
hoverNode.runAction(SKAction.scaleTo(1.0, duration: 0.5))
self.hoverNode = nil
}
}
}
private func findHoverable(node: SKNode) -> SKSpriteNode? {
if let node = node as? SKSpriteNode {
return node
}
if let parent = node.parent {
if let parent = parent as? SKSpriteNode {
return parent
} else {
return findHoverable(parent)
}
} else {
return nil
}
}
}
I have a instance with a property which I want to listen for updates from other instance.
For example class Menu has a property badgeCount, I want to listen for any updates for badgeCount for example when badgeCount is changed. I want my ViewController to have callback after badgeCount is modified to know actual data.
In objective was KVO that I can use for listed property, how can I use KVO in Swift. I am new in Swift.
If you want to use KVO in swift, there are two requirements :
The class you want to do KVO on must inherit from NSObject (or any NSObject subclass)
The property you need to observe must be marked as dynamic
a code example would be:
class Menu: NSObject {
dynamic var badgeCount: Int = 0
}
And then, you can use the usual menuInstance.addObserver(self, forKeyPath: "badgeCount", options: NSKeyValueObservingOptions(), context: nil)
But this solution is not very much swifty.
Better solutions are (not an exhaustive list):
Use swift's didSet/willSet to call some callback
class Menu {
var badgeCount: Int = 0 {
didSet {
badgeCountChangedListener(badgeCount)
}
}
init(badgeCountChangedListener: (Int -> Void)) {
self.badgeCountChangedListener = badgeCountChangedListener
}
private let badgeCountChangedListener: (Int -> Void)
}
Use RxSwift's Variable type
class Menu {
let badgeCount = Variable(0)
}
// and from where you observe
menuInstance.badgeCount.subscribeNext { badgeCount in
print(badgeCount)
}
I have two flag properties that should change when a text field contains an integer, and I have IBActions, when the text field editing ends, that change the flags. When both of the variables are true, those methods should enable a button. I ran the iOS simulator, but the button isn't enabling. I also declared the text field delegate for both the text fields.
I am new to swift, so please be clear with your answer. Also, I haven't set any breakpoints. Here's the code for what I have so far:
var yourWeightFilled = false
var calorieNumberFilled = false
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// Find out what the text field will be after adding the current edit
let text = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
if textField == yourWeightTextField {
yourWeightFilled = text.toInt() != nil
} else if textField == calorieNumberTextField {
calorieNumberFilled = text.toInt() != nil
}
return true
}
#IBAction func yourWeightEditingDidEnd(sender: AnyObject) {
if self.yourWeightFilled && self.calorieNumberFilled {
self.calculateButton.enabled = true
}
yourWeightTextField.resignFirstResponder()
}
#IBAction func calorieNumberEditingDidEnd(sender: AnyObject) {
if self.yourWeightFilled && self.calorieNumberFilled {
self.calculateButton.enabled = true
}
calorieNumberTextField.resignFirstResponder()
}
UITextField is a subclass of UIControl and thus needs to have action methods registered to be called in response to control events. You do that with the addTarget(_:action:forControlEvents:) method.
For example:
weightField.addTarget(self, action:"yourWeightEditingDidEnd:", forControlEvents:.EditingDidEnd);
Would work in your case to call your action method yourWeightEditingDidEnd() when the user finishes editing the text field. This assumes your field property is named weightField. A good place for this code is in your view controller's viewDidLoad() method.
There is one more important step. You appear to be implementing UITextFieldDelegate, which is good because you also need a textFieldShouldReturn(textField:) -> Bool method that returns true and resigns the text field as first responder. Example:
func textFieldShouldReturn(textField: UITextField) -> Bool
{
textField.resignFirstResponder();
return true;
}
This in turn causes the .EditingDidEnd control event to fire and the action method you registered to be called.