Calling uilabel animation with string variables swift - ios

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)]

Related

Swift: Passing a parameter to selector

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.

Sending an NSTimer target message to a different class; extracting its userInfo parameter

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.

NSTimer not firing propery

I have set up a timer class to firer when the viewDidLoad(). I want to have a timer on multiple view controllers thoughout the app. If you have a better solution to a accurate timer on multiple views please suggest.
Viewcontroller -> One of the views that needs a timer
override func viewDidLoad() {
super.viewDidLoad()
func setupTimer() {
// Setupt the timer, this will call the timerFired method every second
var timer = NSTimer(
timeInterval: 1.0,
target: self,
selector: #selector(TestTimer.timerFired()),//<- Error Code
userInfo: nil,
repeats: true)
}
The Error Code: Use of instance member 'timerFired' on type 'TestTimer',did you mean to use a value of type 'TestTimer' instead?
Timer Class -> Checks start date compared to current date/time for a accurate timer
class TestTimer: NSTimer {
var timer = NSTimer()
// Converter changes String into NSDate
var startDate = converter("Tue, 26 Apr 2016 09:01:00 MDT")
// Function to be fired
func timerFired() {
let now = NSDate()
let difference = now.timeIntervalSinceDate(self.startDate)
// Format the difference for display
// For example, minutes & seconds
let dateComponentsFormatter = NSDateComponentsFormatter()
dateComponentsFormatter.stringFromTimeInterval(difference)
print(difference)
}
}
The error you're getting is pretty obscure. What it's trying to tell you is you should remove the () from the end of your timerFired in the #selector.
var timer = NSTimer(
timeInterval: 1.0,
target: self,
selector: #selector(TestTimer.timerFired),
userInfo: nil,
repeats: true)
However, this isn't going to make your code how you want it to work – as self in the timer declaration refers to the view controller, not the timer. I would recommend you create a wrapper class for NSTimer, along with a delegate pattern in order to achieve what you want.
You should note that the documentation states that you shouldn't attempt to subclass NSTimer, so you could do something like this instead:
// the protocol that defines the timerDidFire callback method
protocol TimerDelegate:class {
func timerDidFire(cumulativeTime:NSTimeInterval)
}
// your timer wrapper class
class TimerWrapper {
// the underlying timer object
weak private var _timer:NSTimer?
// the start date of when the timer first started
private var _startDate = NSDate()
// the delegate used to implement the timerDidFire callback method
weak var delegate:TimerDelegate?
// start the timer with a given firing interval – which could be a property
func startTimer(interval:NSTimeInterval) {
// if timer already exists, make sure to stop it before starting another one
if _timer != nil {
stopTimer()
}
// reset start date and start new timer
_startDate = NSDate()
_timer = NSTimer.scheduledTimerWithTimeInterval(interval,
target: self,
selector: #selector(timerDidFire),
userInfo: nil, repeats: true)
}
// invalidate & deallocate the timer,
// make sure to call this when you're done with the timer
func stopTimer() {
_timer?.invalidate()
_timer = nil
}
// make sure to stop the timer when the wrapper gets deallocated
deinit {
stopTimer()
}
// called when the timer fires
#objc func timerDidFire() {
// get the change in time, from when the timer first fired to now
let deltaTime = NSDate().timeIntervalSinceDate(_startDate)
// do something with delta time
// invoke the callback method
delegate?.timerDidFire(deltaTime)
}
}
You can then use it like this:
// your view controller class – make sure it conforms to the TimerDelegate
class ViewController: UIViewController, TimerDelegate {
// an instance of the timer wrapper class
let timer = TimerWrapper()
override func viewDidLoad() {
super.viewDidLoad()
// set the timer delegate and start the timer – delegate should be set in viewDidLoad,
// timer can be started whenever you need it to be started.
timer.delegate = self
timer.startTimer(1)
}
func timerDidFire(cumulativeTime: NSTimeInterval) {
// do something with the total time
let dateComponentsFormatter = NSDateComponentsFormatter()
let text = dateComponentsFormatter.stringFromTimeInterval(cumulativeTime)
label.text = text
}
}
As far as the appropriateness of using an NSTimer here goes, as you're only using a time interval of 1 second, an NSTimer is suitable. By taking the time interval over the total timer duration, you can average out any small firing inaccuracies.
This is how a timer is initialized
var timer = NSTimer.scheduledTimerWithTimeInterval(1.0 , target: self, selector: #selector(TestTimer.timerFired()), userInfo: nil, repeats: true)

Selector is not fired from scheduledTimerWithTimeInterval

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()
}
}

UserInfo in NSTimer not passing correct information - Swift

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)");
}

Resources