unrecognized selector sent to instance - Swift 3 - ios

I created a class based on this code: https://gist.github.com/etartakovsky/06b8c9894458a3ff1b14
When I try to instantiate the class and call the tick methods, I get "unrecognized selector sent to instance" error. I reviewed the code over and over but don't understand why this is happening, any advice is appreciated:
StopWatch Class source:
import Foundation
import QuartzCore
class StopWatch: NSObject{
private var displayLink: CADisplayLink!
private let formatter = DateFormatter()
var callback: (() -> Void)?
var elapsedTime: CFTimeInterval!
override init() {
super.init()
self.displayLink = CADisplayLink(target: self, selector: "tick:")
displayLink.isPaused = true
displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes)
self.elapsedTime = 0.0
formatter.dateFormat = "mm:ss,SS"
}
convenience init(withCallback callback: #escaping () -> Void) {
self.init()
self.callback = callback
}
deinit {
displayLink.invalidate()
}
func tick(sender: CADisplayLink) {
elapsedTime = elapsedTime + displayLink.duration
callback?()
}
func start() {
displayLink.isPaused = false
}
func stop() {
displayLink.isPaused = true
}
func reset() {
displayLink.isPaused = true
elapsedTime = 0.0
callback?()
}
func elapsedTimeAsString() -> String {
return formatter.string(from: Date(timeIntervalSinceReferenceDate:elapsedTime))
}
}
And here is the ViewController Code:
import UIKit
class ActivityViewController: UIViewController {
let stopwatch = StopWatch()
#IBOutlet weak var elapsedTimeLabel: UILabel!
func tick() {
elapsedTimeLabel.text = stopwatch.elapsedTimeAsString()
}
override func viewDidLoad() {
super.viewDidLoad()
tick()
stopwatch.callback = self.tick
stopwatch.start()
// Do any additional setup after loading the view.
}
}

In Swift 3 use the #selector syntax
self.displayLink = CADisplayLink(target: self, selector: #selector(tick))
In Swift 4 additionally you have to insert #objc at the beginning of the action
#objc func tick(...

Try two things. Use the new (and safer) selector syntax introduced by Swift 3:
CADisplayLink(target: self, selector: #selector(tick(sender:)))
and be sure to expose your tick method to Objective-C (the rules have changed in Swift 4):
#objc func tick(sender: CADisplayLink) {
...
}

To make it clear: unrecognized selector sent to instance is an error in MessagePassing scenario which means the desired selector which is:
func tick(sender: CADisplayLink) {...}
and has to receive the message is unrecognized.
It cannot be found because of the wrong way of addressing to it.
as other members said, you have to change your target selector by adding #selector(tick):
self.displayLink = CADisplayLink(target: self, selector: #selector(tick))
you can find more details about the error in this thread

Related

I want to make datePicker on IOS by swift

all
I am a new developer on IOS. nowdays I study Swift by books.
there are some trouble. I use swift3 but the book consists of swift2.
so I don't know what is wrong code.
could you help me?
thanks you for reading and helping me.
this is datacode.
import UIKit
class ViewController: UIViewController {
let timeSelector: Selector = #selector(ViewController.updateTime)
let interval = 1.0
var count = 0
#IBOutlet weak var IbICurrentTime: UILabel!
#IBOutlet weak var IbIPickTime: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
Timer.scheduledTimer(withTimeInterval: interval, repeats: true, block: timeSelector)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func changeDatePicker(_ sender: UIDatePicker) {
let datePickerView = sender
let formatter = DateFormatter()
formatter.dateFormat = "YYYY-MM-dd HH:mm:ss EEE"
IbIPickTime.text = "선택시간: " + formatter.string(from: datePickerView.date)
}
func updateTime() {
IbICurrentTime.text = String(count)
count = count+1
}
}
there are problem
Timer.scheduledTimer(withTimeInterval: interval, repeats: true, block: timeSelector)
}
i don't know what i have to input at "block"?
"Block" is the Objective-C term of Swift's closure. The "Blocks" variant works like this:
Timer.scheduledTimer(timeInterval: interval, repeats: true, block: { timer in
self.IbICurrentTime.text = String(self.count)
self.count += 1
})
You don't need to define a separate updateTime() function with the block syntax.
In Swift the above can be written more naturally as
Timer.scheduledTimer(timeInterval: interval, repeats: true) { _ in
self.IbICurrentTime.text = String(self.count)
self.count += 1
}
If you want to use selectors, use scheduled​Timer(time​Interval:​target:​selector:​user​Info:​repeats:​) instead:
Timer.scheduledTimer(time​Interval: interval,
target: self,
selector: #selector(updateTime),
userInfo: nil,
repeats: true)

How to get Notification from standalone class (Swift 3)

I want to call method in a class as soon as observer get Notification in another one. The problem is that I cannot call one class from another, because I will get recursion call then.
1) Controller class with Player instance:
// PlayerController.swift
// player
import UIKit
import MediaPlayer
class NowPlayingController: NSObject {
var musicPlayer: MPMusicPlayerController {
if musicPlayer_Lazy == nil {
musicPlayer_Lazy = MPMusicPlayerController.systemMusicPlayer()
let center = NotificationCenter.default
center.addObserver(
self,
selector: #selector(self.playingItemDidChange),
name: NSNotification.Name.MPMusicPlayerControllerNowPlayingItemDidChange,
object: musicPlayer_Lazy)
musicPlayer_Lazy!.beginGeneratingPlaybackNotifications()
}
return musicPlayer_Lazy!
}
//If song changes
func playingItemDidChange(notification: NSNotification) {
//somehow call updateSongInfo() method from 2nd class
}
//Get song metadata
func getSongData() -> (UIImage, String?, String?) {
let nowPlaying = musicPlayer.nowPlayingItem
//...some code
return (albumImage, songName, artistAlbum)
}
func updateProgressBar() -> (Int?, Float?){
let nowPlaying = musicPlayer.nowPlayingItem
var songDuration: Int?
var elapsedTime: Float?
songDuration = nowPlaying?.value(forProperty: MPMediaItemPropertyPlaybackDuration) as? Int
elapsedTime = Float(musicPlayer.currentPlaybackTime)
return(songDuration, elapsedTime)
}
}
2) View controller which should be updated when Player Controller get notification
// MainViewController.swift
// player
import UIKit
class MainViewController: UIViewController {
let playerController = PlayerController()
#IBOutlet weak var albumView: UIImageView!
#IBOutlet weak var songLabel: UILabel!
#IBOutlet weak var artistAlbum: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//Start updating progress bar
Timer.scheduledTimer(timeInterval: 0.5,
target: self,
selector: #selector(MainViewController.updateProgressBar),
userInfo: nil,
repeats: true)
}
private func updateSongInfo(){
(albumView.image!, songLabel.text, artistAlbum.text) = playerController.getSongData()
}
private func updateProgressBar(){
(progressBar.maximumValue, progressBar.value) = playerController.playingItemProgress()
}
}
Solution for Swift 3:
In NowPlayingController:
let newSongNotifications = NSNotification(name:NSNotification.Name(rawValue: "updateSongNotification"), object: nil, userInfo: nil)
func playingItemDidChange(notification: NSNotification) {
NotificationCenter.default.post(newSongNotifications as Notification)
}
And in other controller:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.updateSongInfo), name: NSNotification.Name(rawValue: "updateSongNotification"), object: nil)
}
You can post a notification from within your custom object where you need it:
let notification = NSNotification(name:"doSomethingNotification", object: nil, userInfo: nil)
NotificationCenter.defaultCenter.postNotification(notification)
And then in your other view controller in which you want to execute something in response to this notification, you tell it to observe the notification in viewDidLoad(). The selector you pass in is the method you want to be executed when the notification is received.
override func viewDidLoad(){
super.viewDidLoad()
NotificationCenter.addObserver(self, selector: #selector(self.doSomething), name: "doSomethingNotification", object: nil)
}
You can use delegate method to update MainViewController

Swift call timer from ViewController

I have a TimerManager class that I would like to access in multiple ViewControllers but I can't figure out a good way to do it. My code is as follows:
class TimerManager {
private var timer: NSTimer
private var timeRemaining: Int
init(initialTime: Int) {
self.timer = NSTimer()
self.timeRemaining = initialTime
}
func startTimer() {
self.timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(TimerManager.update), userInfo: nil, repeats: true)
}
func endTimer() {
self.timer.invalidate()
}
func getTimeRemaining() -> Int {
return self.timeRemaining
}
#objc func update() {
if self.timeRemaining > 0 {
self.timeRemaining = self.timeRemaining - 1
}
else {
endTimer()
}
}
}
In my ViewController I would like to be able to access my update() function to update a timer (which is a UILabel) on my actual page, but since my startTimer() function calls it every second, I don't know how to access update() every time it is called. I briefly looked into protocols but I'm not really sure how they work or if that would be useful in my case.
Any help would be appreciated!
As #sschale suggested, you can do this by using a singleton to ensure that you will be accessing the same instance anywhere in your code. To do this, you need to set the init to private and provide a static member variable to access your single instance.
class TimerManager
{
static let sharedInstance = TimerManager()
private var timer: NSTimer
private var timeRemaining: Int
private init()
{
let initialTime = 1
self.timer = NSTimer()
self.timeRemaining = initialTime
}
private init(initialTime: Int)
{
self.timer = NSTimer()
self.timeRemaining = initialTime
}
...
}
Then in your ViewControllers you can just call it like this:
TimerManager.sharedInstance.startTimer()
class TimerManager {
private var timer: NSTimer
private var timeRemaining: Int
private var intervalBlock: (TimerManager -> ())?
init(initialTime: Int) {
self.timer = NSTimer()
self.timeRemaining = initialTime
}
func startTimer(intervalBlock: (TimerManager -> ())? = nil) {
self.intervalBlock = self
self.timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(TimerManager.update), userInfo: nil, repeats: true)
}
func endTimer() {
self.intervalBlock = nil
self.timer.invalidate()
}
func getTimeRemaining() -> Int {
return self.timeRemaining
}
#objc func update() {
if self.timeRemaining > 0 {
self.timeRemaining = self.timeRemaining - 1
intervalBlock()
}
else {
intervalBlock()
endTimer()
}
}
}
Below is one of the best implementations of Timer on the background queue I found from this article
class RepeatingTimer {
let timeInterval: TimeInterval
init(timeInterval: TimeInterval) {
self.timeInterval = timeInterval
}
private lazy var timer: DispatchSourceTimer = {
let t = DispatchSource.makeTimerSource()
t.schedule(deadline: .now() + self.timeInterval, repeating: self.timeInterval)
t.setEventHandler(handler: { [weak self] in
self?.eventHandler?()
})
return t
}()
var eventHandler: (() -> Void)?
private enum State {
case suspended
case resumed
}
private var state: State = .suspended
deinit {
timer.setEventHandler {}
timer.cancel()
resume()
eventHandler = nil
}
func resume() {
if state == .resumed {
return
}
state = .resumed
timer.resume()
}
func suspend() {
if state == .suspended {
return
}
state = .suspended
timer.suspend()
}
}
Usage: -
In any of your ViewControllers
For example: -
class MyViewController: UIViewController {
// MARK: - Properties
var timer: RepeatingTimer!
// MARK: - ViewController LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
timer = RepeatingTimer(timeInterval: 1)
timer.eventHandler = {
print("Timer called")
}
}

Xcode NSTimer error: Unrecognized selector sent to class

I have a file called Util.swift with the following
class foo: NSObject {
func sayHello() {
print("hello")
}
}
var globalTimer = NSTimer()
func startTheTimer() {
globalTimer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: foo.self, selector: Selector("sayHello"), userInfo: nil, repeats: true)
}
When I call startTheTimer() in the viewDidLoad of any of my viewControllers, I get an error saying "unrecognized selector sent to class". I don't know what I have done wrong here.
class func sayHello() {
print("hello")
}
make this a class method please.
I have updated your code here:
class foo: NSObject {
class func sayHello() {
print("hello")
}
}
var globalTimer = NSTimer()
func startTheTimer() {
globalTimer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: foo.self, selector: Selector("sayHello"), userInfo: nil, repeats: true)
}

Updating UILabel.text with a variable belonging to a Singleton

I have the following Singleton
class SharingManager{
var smallBigText : String = "KLANG!"
static let sharedInstance = SharingManager()
}
I use it to set the text of the following UILabel
#IBOutlet weak var kleinGrossLabel: UILabel!
I initialize its text here, in my ViewController:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.kleinGrossLabel.text = SharingManager.sharedInstance.smallBigText
}
I reset the SharingManager.sharedInstance.smallBigText in an instance method of my SoundEvent class:
class SoundEvent {
var text:String
var duration:Double
init(text: String, duration: Double){
self.text = text
self.duration = duration
}
func startEvent(){
SharingManager.sharedInstance.smallBigText = self.text
}
func getDuration() -> Double{
return self.duration
}
}
When I run the app, the UILabel text remains as "KLANG" and is never changed.
It should be changed when I call startEvent in the following function:
func playEvent(eventIndex : Int){
if (eventIndex < 2){
let currEvent = self.eventArray[eventIndex]
currEvent?.startEvent()
let nextIndex = eventIndex + 1
//NSTimer.scheduledTimerWithTimeInterval(0.4, target: SomeClass.self, selector: Selector("someClassMethod"), userInfo: nil, repeats: true)
NSTimer.scheduledTimerWithTimeInterval((currEvent?.duration)!, target: self, selector: Selector("playEvent:"), userInfo: NSNumber(integer: nextIndex), repeats: false)
}
else if (eventIndex==2){
self.eventArray[eventIndex]?.startEvent()
NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: Selector("sentenceDidFinish"), userInfo: nil, repeats: false)
}
else{
//Do Nothing
}
}
Which I call here in my ViewController
var s1:Sentence = Sentence(type: "S3")
s1.start()
Which in the Sentence class does this:
func start(){
self.playEvent(0)
}
Somehow it breaks this flow of logic, or if the expected sequence of events IS executing, then it follows that I am not actually changing the UILabel's text when I change the shared Singleton resource var smallBigText
For clarity here are all the main .swift
https://gist.github.com/anonymous/07542f638fc5b9a3c4e9
https://gist.github.com/anonymous/10f5f0deb03f9adc354c
https://gist.github.com/anonymous/94fda980836dc057b05b

Resources