Here is some sample code for what I am trying to do.
func firstFunction() {
var timer = NSTimer.scheduledTimerWithTimeInterval(0.6, target: self, selector: Selector("secondFunction:"), userInfo: self.data!.getInfo(), repeats: false);
println("Info: \(timer.userInfo)");
}
func secondFunction(value: Int) {
println("Called with \(value)");
}
The following is the output:
Info: Optional((
2
)) and Called with 140552985344960
Called with ############ is constantly changing too. Even if I use just a number in place of self.data!.getInfo I still get Info: Optional(2) as the output and the Called with output still changes. I'm thinking it's happening because the value being passed is an optional one, so how do I make it not optional if that is the problem?
NSTimer's scheduledTimerWithTimeInterval userInfo parameter is not a standard parameter in the sense that although you can set userInfo to hold AnyObject, you can't simply pass in a parameter as you would with most functions because scheduledTimerWithTimeInterval's selector can only be passed an NSTimer as its one and only parameter. So your secondFunction: must specify an NSTimer as its parameter if you'd like to access the value you have stored within the timer's userInfo, ex:
func firstFunction() {
var timer = NSTimer.scheduledTimerWithTimeInterval(0.6, target: self, selector: Selector("secondFunction:"), userInfo: self.data!.getInfo(), repeats: false);
println("Info: \(timer.userInfo)");
}
func secondFunction(timer: NSTimer) {
var value = timer.userInfo as Int
secondFunction(value)
}
// Function added based on your comment about
// also needing a function that accepts ints
func secondFunction(value: Int) {
println("Called with \(value)");
}
Related
Im my react native project's native module I need to send some data periodically from Objective-C to Swift so I am using NSNotificationCenter. I receive the data successfully in my Swift class, inside the function attached to the observer, and I store it in a property.
If I access this property from any instance method call I can see that the value has updated.
However if I access the same property in the selector function attached to the Timer it appears as if the value has not been updated and I cannot figure out why? It seems as if the timer selector function does not have access to anything except the initial value of the property - I have also tried passing the property as part of userInfo to the Timer but the issue is the same.
[[NSNotificationCenter defaultCenter] postNotificationName:#"stateDidUpdate" object:nil userInfo:state];
class StateController {
var state: Dictionary<String, Any> = Dictionary()
var timer: Timer = Timer()
func subscribeToNotifications() {
NotificationCenter.default.addObserver(
self, selector: #selector(receivedStateUpdate),
name: NSNotification.Name.init(rawValue: "stateDidUpdate"), object: nil)
}
#objc func receivedStateUpdate(notification: NSNotification) {
if let state = notification.userInfo {
self.state = (state as? Dictionary<String, Any>)!
print("\(self.state)") // I can see that self.state has been updated here
}
}
func runTimer() {
self.timer = Timer(timeInterval: 0.1, target: self, selector: #selector(accessState(timer:)), userInfo: nil, repeats: true)
self.timer.fire()
RunLoop.current.add(self.timer, forMode: RunLoop.Mode.default)
RunLoop.current.run(until: Date(timeIntervalSinceNow: 2))
}
#objc func accessState(timer: Timer) {
print("\(self.state)") // state is an empty Dictionary when accessed here
}
func printState() {
"\(self.state)" // value printed is the updated value received from the notification
}
}
I figured out that multiple instances of my Swift class being created was causing the issue. I assumed that React Native would create a singleton when calling a native module but it appears multiple instances are created as I could see how many times the init method was called. Switching to a Singleton pattern resolved the issue for me following this excellent video and this excellent gist on how to create a singleton in a react native project
class StateController {
static let shared = StateController()
private override init() {
}
}
Using Swift 3, Xcode 8.2.1
Method:
func moveToNextTextField(tag: Int) {
print(tag)
}
The lines below compile fine, but tag has an uninitialized value:
let selector = #selector(moveToNextTextField)
Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: selector, userInfo: nil, repeats: false)
However, I need to pass a parameter. Below fails to compile:
let selector = #selector(moveToNextTextField(tag: 2))
Swift Compile Error:
Argument of #selector does not refer to an #objc method, property, or initializer.
How can I pass an argument to a selector?
#selector describes method signature only. In your case the correct way to initialize the selector is
let selector = #selector(moveToNextTextField(tag:))
Timer has the common target-action mechanism. Target is usually self and action is a method that takes one parameter sender: Timer. You should save additional data to userInfo dictionary, and extract it from sender parameter in the method:
func moveToNextTextField(sender: Timer) {
print(sender.userInfo?["tag"])
}
...
let selector = #selector(moveToNextTextField(sender:))
Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: selector, userInfo: ["tag": 2], repeats: false)
You cannot pass a custom parameter through a Timer action.
Either
#selector(moveToNextTextField)
...
func moveToNextTextField()
or
#selector(moveToNextTextField(_:))
...
func moveToNextTextField(_ timer : Timer)
is supported, nothing else.
To pass custom parameters use the userInfo dictionary.
I am using an NSTimer to send a message on an interval. Here is the code :
{
// ....
var params : [String] = []
params.append(conversion)
params.append(message)
let timer = NSTimer(fireDate: date, interval: 60, target: self, selector: Selector("importTextMessage.sendMessage:"), userInfo: params, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
// ...
}
func sendMessage(params: [String]){ ...}
I have also tried changing to the Swift 2.2 syntax:
let timer = NSTimer(fireDate: date, interval: 60, target: self, selector: #selector(importTextMessage.sendMessage(_:)), userInfo: params, repeats: true)
but that does not change anything.
From every other question posted about the "Unrecognized selector sent to instance", the response is "include a colon in the Selector so that it knows to grab arguments from UserInfo", but I have included that, and can't figure out what is wrong.
What to Note:
The parameters for the function and the parameters passed in through NSTimer userInfo DO match up. They are both arrays of strings.
If it means anything, the code is failing while sendMessage is being called. It does not actually make it to sendMessage.
I am getting an odd warning saying that "String literal is not a valid objective-c selector"
I tried changing my code for my sendMessage to take the Timer as an argument as 1 user suggested : func sendMessage(timer: NSTimer){but that still gives the same error.
Thank you for your help in advance, I do appreciate it.
EDIT: here is the function that runs the timer: //Save all of the data
#IBAction func saveText(sender: AnyObject) {
var phone : Double
var active : Int
var frequency : Double
var message : String
var date : NSDate
phone = Double(currentNumber)!
active = 1
message = myTextView.text
date = myDatePicker.date
switch self.frequency.selectedRowInComponent(0) {
case 0:
frequency = 1
case 1:
frequency = 3
case 2:
frequency = 6
case 3:
frequency = 24
case 4:
frequency = 168
case 5:
frequency = 744
default:
frequency = 8760
}
importTextMessage.seedMessage(phone, active: active, frequency: frequency, message: message, date: date)
print(date)
let conversion : String = "+1" + String(Int(phone))
//importTextMessage.sendMessage("Ryan", to: conversion, message: message)
var params : [String] = []
params.append(conversion)
params.append(message)
//importTextMessage.sendMessage(params)
let timer = NSTimer(fireDate: date, interval: 60, target: self, selector: #selector(importTextMessage.sendMessage(_:)), userInfo: params, repeats: true)
//
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
}
EDIT: Exact error mssg:
unrecognized selector sent to instance 0x7ffe6b915460
2016-07-25 13:26:09.092 Harass Your Kate[53524:9000621] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Harass_Your_Kate.AddMessageViewController sendMessage:]: unrecognized selector sent to instance 0x7ffe6b915460
From doc:
The timer passes itself as the argument
so your timer expect other argument type, NSTimer. Try this code:
override func viewDidLoad() {
super.viewDidLoad()
var params : [String] = []
params.append(conversion)
params.append(message)
let timer = NSTimer(fireDate: date, interval: 60, target: self, selector: #selector(sendMessage(_:)), userInfo: params, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
}
func sendMessage(sender: NSTimer) {
print(sender.userInfo as? [String])
}
This code works fine in my standard view controller
The API of NSTimer is such that the receiver (target function) must take one and only one parameter, of type NSTImer.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSTimer_Class/#//apple_ref/occ/clm/NSTimer/scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
The message to send to target when the timer fires.
The selector should have the following signature: timerFireMethod:
(including a colon to indicate that the method takes an argument). The
timer passes itself as the argument, thus the method would adopt the
following pattern:
(void)timerFireMethod:(NSTimer *)timer
In Swift the signature must be
edit: updated to match your code exactly
Note: Swift 2.2 syntax
class TextMessageViewController: UIViewController {
var importTextMessage: AddMessageViewController // init'd somehow
override func viewDidLoad() {
super.viewDidLoad()
// ...
// Note how selector matches *the name of the class*
// As I understand it now, the sendMessage method
// is declared in the AddMessageViewController class,
// and importTextMessage is the name of the instance of that class.
// And we're in a different class now, TextMessageViewController,
// so using `self` does not make sense here.
let timer = NSTimer(fireDate: date, interval: 60, target: importTextMessage,
selector: #selector(AddMessageViewController.sendMessage(_:)), userInfo: params, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
}
}
class AddMessageViewController: UIViewController {
// func must not be private
func sendMessage(timer: NSTimer) {
// And now for example
print("params: \(timer.userInfo)")
}
}
As far as your userInfo dictionary, that is a property of the timer object, not a parameter to the target function itself.
The SECOND mistake is that the selector syntax is failing to use the name of the class as declared, not the name of some instance variable that has a reference to that class instance. Include more context in your question so it'll get solved faster!
The THIRD mistake is that you're using the wrong class name. If you use self, then the class name in the selector must match the name of the current class. Since you really want the message to go to a different class, you must use that reference instead. See updated code.
I think you still don't fully grasp object references, and their types, and how they must match, and how self is just the reference to the contextual object.
I checked the existing posts on this topic and also googled it, but I am not able to identify my mistake or make this work for me. I have a function iterativeDeepening() inside the class ChessPlayer. After say 15 seconds I want to stop further iterations within the function. In the code below, the function "flagSetter" is never invoked. If I use NSTimer.fire() the function is invoked immediately and not after 15 seconds. I tried placing the flagSetter function before or after iterativeDeepening(). Either case does not work. What have I done incorrectly?
class ChessPlayer {
var timeoutFlag = false
//Code
func iterativeDeepening() {
***//variables and constants***
let timer = NSTimer.scheduledTimerWithTimeInterval(15.0, target: self, selector: #selector(self.flagSetter), userInfo: nil, repeats: false)
***while minDepth <= maxDepth
{
// Loop iteration code
if timeoutFlag { break out of loop }
}***
}
#objc func flagSetter(timer: NSTimer) {
print("flag changed to true")
self.timeoutFlag = true
timer.invalidate()
}
}
The requirement:
computerThinking() is fired from GameScene from human move's action completion handler.
GameScene.computerThinking() invokes ChessPlayer.iterativeDeepening()
iterativeDeepening runs a while loop incrementing "depth". For each "depth" an optimal move at that depth is evaluated. Higher the depth, more detailed the evaluation.
after 15.0 seconds i want to break out of the while loop with the depth and optimal move available at that point of time.
I am a lover of Objective-c and never used Swift yet in my projects. Googling NSTimer Swift, I found out the following steps to implement NSTimer correctly.
we need to define our NSTimer. The first variable that we are going to need is a variable called timer of type NSTimer. We do this like:
var timer = NSTimer()
Start NSTimer timer:
timer = NSTimer.scheduledTimerWithTimeInterval(15.0, target:self, selector:#selector(ChessPlayer.flagSetter(_:)), userInfo: nil, repeats: false)
And your method flagSetter should be defined as this:
func flagSetter(timer: NSTimer) {
print("flag changed to true")
self.timeoutFlag = true
timer.invalidate()
}
This will now surely work as I've made my very first app, just for this question, made in Swift. Check how I put my selector. You're right with the warnings by the way.
If you need more information about Selectors, check this thread: #selector() in Swift?
Here is your solution: define timer outside of the function so you can invalidate it from another function. Right now, your timer is defined inside a function so it can only be altered inside that function, but that is not what you want. Fix it by doing the following: right below var timeoutFlag = false put var timer = NSTimer(). Then inside your function iterativeDeepening() get rid of the let. Then it will all work!!
Here's what your code should be, adapted from Hasya's answer and your provided code.
class ChessPlayer {
// Declare timer and timeoutFlag
var timer = NSTimer()
var timeoutFlag = false
func iterativeDeepening() {
self.timer = NSTimer.scheduledTimerWithTimeInterval(15.0, target: self, selector: “timerEventOccured”, userInfo: nil, repeats: true)
}
func timerEventOccured() {
print("timerEventOccured was called.")
timeoutFlag = true
self.timer.invalidate()
}
}
override func viewDidUnload() {
super.viewDidUnload()
self.timer.invalidate()
}
}
I am trying to build a UILabel animation. I want to add a variable string to the function, so that i can call it together with the variable.
I try to use the code below but it gives me error message.
#IBOutlet weak var Text1: UILabel!
let myText = Array("Hello World !!!".characters)
var myCounter = 0
var timer:NSTimer?
func fireTimer(){
timer = NSTimer.scheduledTimerWithTimeInterval(0.2, target: self, selector: "typeLetter", userInfo: nil, repeats: true)
}
override func viewDidLoad() {
super.viewDidLoad()
fireTimer()
// Do any additional setup after loading the view.
}
func typeLetter(myText :String){
if myCounter < myText.count {
Text1.text = Text1.text! + String(myText[myCounter])
let randomInterval = Double((arc4random_uniform(8)+1))/20
timer?.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(randomInterval, target: self, selector: "typeLetter", userInfo: nil, repeats: false)
} else {
timer?.invalidate()
}
myCounter++
}
The error message is shown below
May I know what's the problem and how to solve this problem? Thanks
Your code has various problems.
As #luk2302 pointed out, your function typeLetter takes a parameter of type string, but string does not have a count property. You need to use string.characters to convert it to an array of unichars. Arrays do have a count property.
As #LeoDabus pointed out, you have 2 different variables myText. One is (I think) and instance variable of your view controller class (type [Character]) and the other is a parameter to your typeLetter function (type String). The typeLetter function's parameter myText is going to hide the instance variable myText so you can't use it.
A bigger problem is that you've declared your typeLetter function as taking a parameter of type String, but you're calling it from a timer. The only parameter that a timer passes to a function is the timer itself. You can't pass arbitrary parameters to a timer function.
You could fix all of these problems if you rewrote your typeLetter function to take a timer as a parameter.
func typeLetter(timer: NSTimer)
{
if myCounter < myText.count {
Text1.text = Text1.text! + String(myText[myCounter])
let randomInterval = Double((arc4random_uniform(8)+1))/20
timer?.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(randomInterval,
target: self,
selector: "typeLetter",
userInfo: nil,
repeats: false)
}
else
{
timer?.invalidate()
}
myCounter++
}
When you do that, the instance variable myText is no longer hidden by the parameter of the same name, and the instance variable myText is the correct type, [unichar].
The problem there is that you need to choose a different name for your method parameter. Inside the method you cannot access your array count because your text parameter has the same name.
To get the characters count, you need to use
myText.characters.count
To solve the subscript error, you can try
myText[myText.startIndex.advancedBy(myCounter)]