Sending app to background and re-launching it from recents in XCTest - ios

I was looking for a solution to my problem where in I need to send my app to background and re-launch it from the recents after a particular time interval.
deactivateAppForDuration() was used to achieve this in Instruments UIAutomation.
Does anybody know how to achieve that in XCTest?

Not positive if this will work, as I haven't tested it yet, but it's worth a shot. If nothing else it should give you a good idea on where to look.
XCUIApplication class provides methods to both terminate and launch your app programmatically: https://developer.apple.com/reference/xctest/xcuiapplication
XCUIDevice class allows you to simulate a button press on the device: https://developer.apple.com/reference/xctest/xcuidevicebutton
You can use these along with UIControl and NSURLSessionTask to suspend your application.
An example of this process using Swift 3 might look something like this (syntax may be slightly different for Swift 2 and below):
func myXCTest() {
UIControl().sendAction(#selector(NSURLSessionTask.suspend), to: UIApplication.shared(), for: nil)
Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(launchApp), userInfo: nil, repeats: false)
}
func launchApp() {
XCUIApplication().launch()
}
Another way may be simply executing a home button press, and then relaunching the app after a timer passes:
func myXCTest {
XCUIDevice().press(XCUIDeviceButton.Home)
Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(launchApp), userInfo: nil, repeats: false)
}
Neither of these ways may do what you're asking, or work perfectly, but hopefully, it will give you a starting point. You can play with it and find a solution that works for you. Good luck!

If you can use Xcode 8.3 and iOS 10.3 with your current tests, then you might want to try this:
XCUIDevice.shared().press(XCUIDeviceButton.home)
sleep(60)
XCUIDevice.shared().siriService.activate(voiceRecognitionText: "Open {appName}")
Be sure to include #available(iOS 10.3, *) at the top of your test suite file.
This will be relatively equivalent to deactivateAppForDuration(). Just change the sleep() to your desired duration.

Related

Application Refresh in Background

I'm trying to implement background refresh in a iOS App . I need to call a class call "updatedropinanddropout" based on the location.I have added timer class while the application is in foreground it works fine, When the application goes to background it's not working. I mean I cannot call the class.
Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(runCode), userInfo: nil, repeats: true)
I have gone through apple documentation func setMinimumBackgroundFetchInterval(_ minimumBackgroundFetchInterval: TimeInterval)
I don't know how to use this function

Pushing A New WatchKit Controller Immediately After Popping Another Always Fails

OK. It's easy enough to do this in classic iOS, but WatchKit doesn't give any blocks/closures, and there isn't a choice between with/without animation.
I have a root controller that has a list of options. Touching one of the options (on either the watch or the phone) will close any currently open controller (popToRootController), then immediately, push a new one.
More or less, like so:
self.popToRootController()
self.pushController(withName: "IKANHAZCHEEZEBURGR", context: nil)
The problem is that there isn't enough time between the calls, and there's no lambda for me to execute a semaphore or push the controller.
If I step through with the debugger, it happens, no problem.
If I just hit "run," it no work.
This is what is known as a "heisenbug".
I guess I could do a one-shot timer, but that seems to be such a hideous hack that it may actually cause a disruption of The Force.
Any better ideas? What am I missing?
I know there's a TON of answers for iOS. They don't do me a whole lot of good, here.
Well, I succumbed to The Dark Side, and did the timer hack. It works. I need to give it around 0.4 seconds per open controller.
Here's an approximation of what I did:
self.popToRootController()
let _ = Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(self.timerCallback(_:)), userInfo: nil, repeats: false)
func timerCallback(_ timer: Timer) {
if let timerIndex = timer.userInfo as? Int {
if 0 <= timerIndex {
DispatchQueue.main.async {self.pushController(withName: "IKANHAZCHEEZEBURGR", context: nil)}
}
}
}
UPDATE: I do want to mention that, even though this "solves" my issue, the issue that this issue is even an issue is an issue. My design was bad, and I am redesigning the basic navigation. I'll be using a page-based approach, instead of this hierarchical design.
On general principle, if I need to hack to make it work, I'm usually better off doing it a different way.

How can I run a method after n minutes I received a geofence event in iOS?

I'm working on a Geofence based iOS application and I would to know when the user stays more than 5 minutes inside a place.
Now, the geofence part is already done and working, I get the "enter" and "exit" events, but I want to execute some methods 5 minutes after I entered in a geofenced area, if I don't left it.
The main problem here is that NSTimers will not work with the app closed and I don't know how to focus this.
Any ideas?
Thank you for your time!
P.S: CLVisit class is not valid on this case because the events for this class are not "in real time" and we cannot set a time-inside.
You can use NSTimer when your app is Active like this :
let timer = NSTimer(timeInterval: 1.0, target: self, selector: #selector(self.printStr), userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
and also if your app did receive UIApplicationWillTerminateNotification event you can save the current date to e.g userDefaults and use it after the user run the application again. Another way is to use UILocalNotification after you receive AppWillTerminate event, you should schedule the UILocalNotificaiton and trigger it after 5 minutes.
Hope it helps you

Is there a bug with simulator when we trying to make a timer with timeInterval < 0.1 second?

I'm making a new stopwatch Application with Watchkit, first my application is very simple like this:
first of all i tried to make a playButtonPressed to start a timer:
#IBAction func playButtonPressed() {
println("playButton pressed")
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("updateTimer"), userInfo: nil, repeats: true)
startTime = NSDate()
}
with my updateTimer function like this:
func updateTimer() {
duration = NSDate().timeIntervalSinceDate(startTime)
println("updateTimer: \(dateStringFromTimeInterval(duration))")
timeLabel.setText(dateStringFromTimeInterval(duration))
}
the dateStringFromTimeInterval function can help me make a dateString with duration is TimeInterval variable.
every thing is ok with my output on Debug area, i can see the dateString at printOut. But my label is lagging for setting the timeLabel as you can see here:
I don't know why, can any one can help me fix that or may be it is a bug of apple watchkit right now? i don't know it'll be lag on real device or not.
Thanks
You have several good questions in here. Unfortunately I have nothing but bad news for you. I have been working extensively with WKInterfaceTimers over the past couple of weeks and they have severe limitations and bugs associated with them. I have a couple responses broken down here in detail.
Issue 1 - Using a WKInterfaceDate as a Timer
This is going to be really frowned upon by Apple and I wouldn't doubt this would be possible grounds for rejection. As #mattt mentions, you don't want to use an NSTimer to flip the date value. Each time you try to switch the date label, Apple lumps all those changes together and pushes them from the Extension on the iPhone to the Watch over WiFi or Bluetooth. It does this to try to optimize battery life.
Because of this, you will never be able to accurately display the time on the Watch in the way that you are currently doing. The proper way to do this is to use a WKInterfaceTimer.
Issue 2 - Using a WKInterfaceTimer
While WKInterfaceTimers are built to do exactly what you want, they have some limitations. The major one for your use case is that it only does up to second precision, not millisecond. Secondly, the timers are extremely inaccurate. I've seen them anywhere from 50ms to 950ms off. I've followed this radar to bring the issue to Apple's attention.
In summary, your current implementation is not going to be super accurate and will be frowned upon by Apple, and the WKInterfaceTimer is extremely inaccurate and can only perform second precision.
Sorry for the downer answer. :-(

UIScrollView pauses NSTimer until scrolling finishes

While a UIScrollView (or a derived class thereof) is scrolling, it seems like all the NSTimers that are running get paused until the scroll is finished.
Is there a way to get around this? Threads? A priority setting? Anything?
An easy & simple to implement solution is to do:
NSTimer *timer = [NSTimer timerWithTimeInterval:...
target:...
selector:....
userInfo:...
repeats:...];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
For anyone using Swift 3
timer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: aSelector,
userInfo: nil,
repeats: true)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
tl;dr the runloop is handing scroll related events. It can't handle any more events — unless you manually change the timer's config so the timer can be processed while runloop is handling touch events. OR try an alternate solution and use GCD
A must read for any iOS developer. Lots of things are ultimately executed through RunLoop.
Derived from Apple's docs.
What is a Run Loop?
A run loop is very much like its name sounds. It is a loop your thread
enters and uses to run event handlers in response to incoming events
How delivery of events are disrupted?
Because timers and other periodic events are delivered when you run
the run loop, circumventing that loop disrupts the delivery of those
events. The typical example of this behavior occurs whenever you
implement a mouse-tracking routine by entering a loop and repeatedly
requesting events from the application. Because your code is grabbing
events directly, rather than letting the application dispatch those
events normally, active timers would be unable to fire until after
your mouse-tracking routine exited and returned control to the
application.
What happens if timer is fired when run loop is in the middle of execution?
This happens A LOT OF TIMES, without us ever noticing. I mean we set the timer to fire at 10:10:10:00, but the runloop is executing an event which takes till 10:10:10:05, hence the timer is fired 10:10:10:06
Similarly, if a timer fires when the run loop is in the middle of
executing a handler routine, the timer waits until the next time
through the run loop to invoke its handler routine. If the run loop is
not running at all, the timer never fires.
Would scrolling or anything that keeps the runloop busy shift all the times my timer is going to fire?
You can configure timers to generate events only once or repeatedly. A
repeating timer reschedules itself automatically based on the
scheduled firing time, not the actual firing time. For example, if a
timer is scheduled to fire at a particular time and every 5 seconds
after that, the scheduled firing time will always fall on the original
5 second time intervals, even if the actual firing time gets delayed.
If the firing time is delayed so much that it misses one or more of
the scheduled firing times, the timer is fired only once for the
missed time period. After firing for the missed period, the timer is
rescheduled for the next scheduled firing time.
How can I change the RunLoops's mode?
You can't. The OS just changes itself for you. e.g. when user taps, then the mode switches to eventTracking. When the user taps are finished, the mode goes back to default. If you want something to be run in a specific mode, then it's up to you make sure that happens.
Solution:
When user is scrolling the the Run Loop Mode becomes tracking. The RunLoop is designed to shifts gears. Once the mode is set to eventTracking, then it gives priority (remember we have limited CPU cores) to touch events. This is an architectural design by the OS designers.
By default timers are NOT scheduled on the tracking mode. They are scheduled on:
Creates a timer and schedules it on the current run loop in the
default mode.
The scheduledTimer underneath does this:
RunLoop.main.add(timer, forMode: .default)
If you want your timer to work when scrolling then you must do either:
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self,
selector: #selector(fireTimer), userInfo: nil, repeats: true) // sets it on `.default` mode
RunLoop.main.add(timer, forMode: .tracking) // AND Do this
Or just do:
RunLoop.main.add(timer, forMode: .common)
Ultimately doing one of the above means your thread is not blocked by touch events.
which is equivalent to:
RunLoop.main.add(timer, forMode: .default)
RunLoop.main.add(timer, forMode: .eventTracking)
RunLoop.main.add(timer, forMode: .modal) // This is more of a macOS thing for when you have a modal panel showing.
Alternative solution:
You may consider using GCD for your timer which will help you to "shield" your code from run loop management issues.
For non-repeating just use:
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
// your code here
}
For repeating timers use:
See how to use DispatchSourceTimer
Digging deeper from a discussion I had with Daniel Jalkut:
Question: how does GCD (background threads) e.g. a asyncAfter on a background thread get executed outside of the RunLoop? My understanding from this is that everything is to be executed within a RunLoop
Not necessarily - every thread has at most one run loop, but can have zero if there's no reason to coordinate execution "ownership" of the thread.
Threads are an OS level affordance that gives your process the ability to split up its functionality across multiple parallel execution contexts. Run loops are a framework-level affordance that allows you to further split up a single thread so it can be shared efficiently by multiple code paths.
Typically if you dispatch something that gets run on a thread, it probably won't have a runloop unless something calls [NSRunLoop currentRunLoop] which would implicitly create one.
In a nutshell, modes are basically a filter mechanism for inputs and timers
Yes, Paul is right, this is a run loop issue. Specifically, you need to make use of the NSRunLoop method:
- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
You have to run another thread and another run loop if you want timers to fire while scrolling; since timers are processed as part of the event loop, if you're busy processing scrolling your view, you never get around to the timers. Though the perf/battery penalty of running timers on other threads might not be worth handling this case.
This is the swift version.
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
for anyone use Swift 4:
timer = Timer(timeInterval: 1, target: self, selector: #selector(timerUpdated), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .common)
Tested in swift 5
var myTimer: Timer?
self.myTimer= Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
//your code
}
RunLoop.main.add(self.myTimer!, forMode: .common)

Resources