WKWebView.loading returns unrecognized selector - ios

I run a timer that looks like this:
else if label == "Instagram" && defaults.boolForKey("instagramswitch") {
activeWebview.loadRequest(request)
let dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
self.i = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("instagram:"), userInfo: activeWebview, repeats: true)
})
}
and the function looks like this:
func instagram(webview: WKWebView) {
if webview.loading == false {
let code: String = "document.getElementsByClassName('2yal _csflf').item(3).click();"
webview.evaluateJavaScript(code, completionHandler: nil)
let dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
self.i = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("instagramloaded:"), userInfo: webview, repeats: true)
})
}
}
I get an error on this line - "if webview.loading == false {" and the error is:
2016-04-10 15:16:53.679 PF 0.5[878:241174] -[__NSCFTimer isLoading]: unrecognized selector sent to instance 0x145a03fe0
Any and all help would be much appreciated :) I think it is because I call webview.loading but I have no idea why that would cause a crash.

You should modify the method like below,
func instagram(timerObject: NSTimer) {
if let webview = timerObject.userInfo as! WKWebView{
if webview.loading == false {
let code: String = "document.getElementsByClassName('2yal _csflf').item(3).click();"
webview.evaluateJavaScript(code, completionHandler: nil)
let dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
self.i = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("instagramloaded:"), userInfo: webview, repeats: true)
})
}
}
}
Because the object in the argument is received as a NSTimer Object and you have to take out the data that you sent from the method call as the userInfo object. Hope this helps.

Selector("instagram:") mapping to func instagram(webview: WKWebView)
calls method with NSTimer argument. So method is receiving NSTimer instance that fire a method. Not WKWebView instance. To get WKWebView Instance. Use userInfo property of timer.
That's why crash is happening,
[__NSCFTimer isLoading]: unrecognized selector sent to instance 0x145a03fe0
__NSCFTimer is NSTimer instance and isLoading is not available with NSTimer.

Related

call function periodically at random intervals in Swift

I am trying to call a function 10 times at random intervals between them.
How can I achieve this?
I did come up with a method, but is is horribly ugly. It looks like this:
var counter = 0
NSTimer.scheduledTimerWithTimeInterval(arc4random_uniform(4)+2, target: self, selector: Selector("createNewTimer"), userInfo: nil, repeats: false)
func createNewTimer(){
// PERFORM STUFF YOU NEED TO
counter++
if counter <= 10{
NSTimer.scheduledTimerWithTimeInterval(arc4random_uniform(4)+2, target: self, selector: Selector("createNewTimer"), userInfo: nil, repeats: false)
}
}
Is there a nicer way of calling function at random intervals?
Try this in the playground. Hope it helps:
func after(delay: Double, block: () -> Void) {
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
block()
}
}
func repeatBlock(counter: Int = 1, times: Int, block: () -> Void) {
after(Double(arc4random_uniform(4) + 2)) {
block()
if counter < times {
repeatBlock(counter + 1, times: times, block: block)
}
}
}
//client's code
var counter = 0
repeatBlock(times: 10) {
//your code here
print(NSDate())
}
sleep(100)
If you need a random intervals, then I'm afraid, the above is the best solution. If the time interval was same, then you could have set it to repeats:true. But not in you case.

How can I create delay in iOS Swift so task A ends before task B begins?

Basically what I need is followings:
start task A (play sound)
wait until task A finish
start task B (listen to mic for 4 seconds)
stop task B
repeat 1-4
I use dispatch_after for step 3 and 4 but that didn't work. The simulator played all the sound files without listening to the mic. How can I fix this problem?
#IBAction func play(sender: AnyObject) {
var words = ["laud","puff","keen","death","knock"]
for item in words {
let path = NSBundle.mainBundle().pathForResource(item, ofType: "m4a")
let fileURL = NSURL(fileURLWithPath: path!)
do {
player = try AVAudioPlayer(contentsOfURL: fileURL)
player.delegate = self
player.play()
} catch {
print("Error AVAudioPlayer")
}
while player.playing {
print("Playing")
}
print("stop playing")
startListening()
let seconds = 4.0
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), { () -> Void in
self.stopListening()
})
}
}
Since you have already set player.delegate = self, you need to implement the method (See documentation here)
audioPlayerDidFinishPlaying(_:successfully:)
to catch the event that audio playing was finished
func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool) {
if flag {
startLitening()
}
}
Then, you can use #John 's answer which is NSTimer to do the remaining tasks
let seconds = 4.0
let timer = NSTimer.scheduledTimerWithTimeInterval(seconds * Double(NSEC_PER_SEC), target: self, selector: "stopListening:", userInfo: "", repeats: false)
stopListening() {
// do anything else
playAudioAgain()
}
Use a NSTimer:
let timer = NSTimer.scheduledTimerWithTimeInterval(seconds * Double(NSEC_PER_SEC), target: self, selector: "stopListening:", userInfo: "", repeats: false)

Change Label Text at a Time Interval based on Character Count of a String within String Array

I'm trying to have a character count of a String element in an Array (buttonTappedOutput) determine the time before updating a UILabel (outputText.text). Immediately after the label is updated, I'd like the timer to start again based on the next String in the array, updating again and again for all String elements (in order from element 0 to X).
Why it's not working:
Right now, my for/in loop is doing it's thing, the timer is overwritten for each loop, and finally, the final set timer (based on length of the last String in the array) invokes updateOutputText and updates the output text to the last String in the array. I've also tried calling my updateOutputText function within the for/in loop and including a delay in my updateOutputText function, but have the same result. I basically need to "pause" the execution of the for/in after each loop until updateOutputText does it's thing, but is that really the best way to attack this issue? A pause?
Also, am I passing the local variable 'output' the right way (currentOutput = output)? Is there another/better way to access it outside of the for/in?
Here's my setup with the NSTimer:
#IBAction func eastPressed(sender: AnyObject) {
for output in trigger.currentDecisionPoint.buttonTappedOutput {
currentOutput = output
timer = NSTimer.scheduledTimerWithTimeInterval(Double(output.characters.count / 5), target: self, selector: "updateOutputText", userInfo: nil, repeats: false)
}
}
func updateOutputText() {
outputText.text = currentOutput
return
}
With a delay in the updateOutputText function:
#IBAction func eastPressed(sender: AnyObject) {
for output in trigger.currentDecisionPoint.buttonTappedOutput {
currentOutput = output
updateOutputText()
//timer = NSTimer.scheduledTimerWithTimeInterval(Double(output.characters.count), target: self, selector: "updateOutputText", userInfo: nil, repeats: false)
}
}
func updateOutputText() {
delay(Double(currentOutput.characters.count)) { () -> () in
self.outputText.text = currentOutput
}
return
}
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
The way I would approach it is something like that (didn't test it though):
var index = 0
#IBAction func eastPressed(sender: AnyObject) {
firstOutput = trigger.currentDecisionPoint.buttonTappedOutput[index]
timer = NSTimer.scheduledTimerWithTimeInterval(Double(firstOutput.count / 5), target: self, selector: "updateOutputText", userInfo: nil, repeats: false)
}
func updateOutputText() {
outputText.text = trigger.currentDecisionPoint.buttonTappedOutput[index]
if index < trigger.currentDecisionPoint.buttonTappedOutput.count - 1 {
nextOutput = trigger.currentDecisionPoint.buttonTappedOutput[index + 1]
timer = NSTimer.scheduledTimerWithTimeInterval(Double(nextOutput.count / 5), target: self, selector: "updateOutputText", userInfo: nil, repeats: false)
index++
} else {
index = 0
}
}

NSTimer - how to delay in Swift

I have a problem with delaying computer's move in a game.
I've found some solutions but they don't work in my case, e.g.
var delay = NSTimer.scheduledTimerWithTimeInterval(4, target: self, selector: nil, userInfo: nil, repeats: false)
I tried to use this with function fire but also to no effects.
What other possibilities there are?
Swift 3
With GCD:
let delayInSeconds = 4.0
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayInSeconds) {
// here code perfomed with delay
}
or with a timer:
func myPerformeCode() {
// here code to perform
}
let myTimer : Timer = Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(self.myPerformeCode), userInfo: nil, repeats: false)
Swift 2
With GCD:
let seconds = 4.0
let delay = seconds * Double(NSEC_PER_SEC) // nanoseconds per seconds
let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
// here code perfomed with delay
})
or with a timer:
func myPerformeCode(timer : NSTimer) {
// here code to perform
}
let myTimer : NSTimer = NSTimer.scheduledTimerWithTimeInterval(4, target: self, selector: Selector("myPerformeCode:"), userInfo: nil, repeats: false)
With Swift 4.2
With Timer You can avoid using a selector, using a closure instead:
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { (nil) in
// Your code here
}
Keep in mind that Timer is toll-free bridged with CFRunLoopTimer, and that run loops and GCD are two completely different approaches.... e
In swift we can delay by using Dispatch_after.
SWift 3.0 :-
DispatchQueue.main.asyncAfter(deadline: .now()+4.0) {
alert.dismiss(animated: true, completion: nil)
}
How about using Grand Central Dispatch?
https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html
Valfer has shown you how

Swift performSelector:withObject:afterDelay: is unavailable [duplicate]

This question already has answers here:
dispatch_after - GCD in Swift?
(26 answers)
Closed 8 years ago.
I have an app in Objective C that I'm transitioning to Swift. In Objective C, I have this method:
[self.view performSelector:#selector(someSelector) withObject:self afterDelay:0.1f];
I'm working with Swift and I can't figure out how to do this. I've tried:
self.view.performSelector(Selector("someSelector"), withObject: self, afterDelay: 0.1)
Here's the error that I get: 'performSelector' is unavailable: 'performSelector' methods are unavailable
What call would I use to call a method afterDelay?
UPDATE
Here's what I ended up with:
extension NSObject {
func callSelectorAsync(selector: Selector, object: AnyObject?, delay: NSTimeInterval) -> NSTimer {
let timer = NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: selector, userInfo: object, repeats: false)
return timer
}
func callSelector(selector: Selector, object: AnyObject?, delay: NSTimeInterval) {
let delay = delay * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue(), {
NSThread.detachNewThreadSelector(selector, toTarget:self, withObject: object)
})
}
}
Swift 4
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
// your function here
}
Swift 3
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(0.1)) {
// your function here
}
Swift 2
let dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
// your function here
})
You could do this:
var timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("someSelector"), userInfo: nil, repeats: false)
func someSelector() {
// Something after a delay
}
SWIFT 3
let timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(someSelector), userInfo: nil, repeats: false)
func someSelector() {
// Something after a delay
}
Swift is statically typed so the performSelector: methods are to fall by the wayside.
Instead, use GCD to dispatch a suitable block to the relevant queue — in this case it'll presumably be the main queue since it looks like you're doing UIKit work.
EDIT: the relevant performSelector: is also notably missing from the Swift version of the NSRunLoop documentation ("1 Objective-C symbol hidden") so you can't jump straight in with that. With that and its absence from the Swiftified NSObject I'd argue it's pretty clear what Apple is thinking here.

Resources