Calling multiple URLs in succession using UIApplication.shared.open - only opens first - ios

I'm trying to create a wifi tress test by opening multiple URLs in succession in Xcode/Swift for iPad. It seems to open only the first successfully. The "sleep(x)" call makes no difference. Code snippet:
func counter()
{
seconds -= 1
label.text = String(seconds) + " Seconds"
if (seconds == 0)
{
let url1 = URL(string: "http://www.wix.com")!
let url2 = URL(string: "http://www.activistpost.com")!
let url3 = URL(string: "http://www.time.com")!
let url4 = URL(string: "http://www.steemit.com")!
let url5 = URL(string: "http://www.youtube.com")!
let url6 = URL(string: "http://www.cptts.net/61m.jpg")!
if #available(iOS 10.0, *) {
UIApplication.shared.open (url1)
sleep (5)
UIApplication.shared.open (url2)
sleep (5)
UIApplication.shared.open (url3)
sleep (5)
UIApplication.shared.open (url4)
sleep (5)
UIApplication.shared.open (url5)
sleep (5)
UIApplication.shared.open (url6)
} else {
// Fallback on earlier versions
}
Xcode 10.0 Beta 2

I'm trying to create a wifi tress test
Well, that's not how to do it. Also, I wonder whether you really need to do it; the developer tools already allow you to simulate a busy network for testing purposes.

As i quote from Apple documentation, it appears to be it will only execute the first and the app will quit and it will launch the other app if found.
The URL you pass to this method can identify a resource in the app
that calls the method, or a resource to be handled by another app. If
the resource is to be handled another app, invoking this method might
cause the calling app to quit so the other can launch.
So eventually ones your app quits then your other calls won't by executed.

Related

iOS 13 - Using BGTaskScheduler doesn't work all the time

THE PROBLEM:
I want to run a simple function, 5 seconds after app goes into background.
I had to implement BGTaskScheduler, to support iOS 13.
The old implementation for BackgroundTask works for me on older iOS versions.
I added background modes as requested (BLE accessories is ticked because we perform a small BLE operation in this function):
Then, I prepared the Info.plist according to the docs (Identifier is fake just for StackOverflow question):
Before didFinishLaunchingWithOptions is ended, I register my BackgroundTask:
if #available(iOS 13.0, *) {
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.MyID", using: .global()) { (task) in
print("My backgroundTask is executed NOW!")
task.expirationHandler = {
task.setTaskCompleted(success: true)
}
}
}
Now, when the app run the didEnterBackground method, I submit a BackgroundTaskRequest:
if #available(iOS 13.0, *) {
do {
let request = BGAppRefreshTaskRequest(identifier: "com.example.MyID")
request.earliestBeginDate = Calendar.current.date(byAdding: .second, value: 5, to: Date())
try BGTaskScheduler.shared.submit(request)
print("Submitted task request")
} catch {
print("Failed to submit BGTask")
}
}
The problem here is that it is VERY inconsistent. Apple guarantee that the task will not be executed before the given date, but does not guarantee that it will be executed on the exact time (I'm fine with a small delay).
However, when I ran the app, it did not work 100% of the times, regardless if I provided the task request a delay (using earliestBeginDate) so it used to go first try 7 seconds (instead of 5), next time I submitted the task it took 26 seconds, third time never arrived the closure.
Am I implementing the BackgroundTask the wrong way? I've searched all over the internet some answer but did not encounter anyone having this problem.
As badhanganesh said, It seems like the task will be executed only when the system decides to do so.
Apple said that during WWWDC 2019, session #707.

Force user to update the app programmatically in iOS

In my iOS app I have enabled force app update feature. It is like this.
If there is a critical bug fix. In the server we are setting the new release version. And in splash screen I am checking the current app version and if its lower than the service version, shows a message to update the app.
I have put 2 buttons "Update now", "Update later"
I have 2 questions
If I click now. App should open my app in the appstore with the button UPDATE. Currently I use the link "http://appstore.com/mycompanynamepvtltd"
This opens list of my company apps but it has the button OPEN, not the UPDATE even there is a new update for my app. whats the url to go for update page?
If he click the button "Update Later" is it ok to close the app programmatically? Does this cause to reject my app in the appstore?
Please help me for these 2 questions
Point 2 : You should only allow force update as an option if you don't want user to update later. Closing the app programmatically is not the right option.
Point 1 : You can use a good library available for this purpose.
Usage in Swift:
Library
func applicationDidBecomeActive(application: UIApplication) {
/* Perform daily (.daily) or weekly (.weekly) checks for new version of your app.
Useful if user returns to your app from the background after extended period of time.
Place in applicationDidBecomeActive(_:)*/
Siren.shared.checkVersion(checkType: .daily)
}
Usage in Objective-C: Library
-(void)applicationDidBecomeActive:(UIApplication *)application {
// Perform daily check for new version of your app
[[Harpy sharedInstance] checkVersionDaily];
}
How it works : It used lookup api which returns app details like link including version and compares it.
For an example, look up Yelp Software application by iTunes ID by calling https://itunes.apple.com/lookup?id=284910350
For more info, please visit link
Don't close the app programmatically. Apple can reject the app. Better approach will be do not allow user to use the app. Keep the update button. Either user will go to app store or close the app by himself.
According to Apple, your app should not terminate on its own. Since the user did not hit the Home button, any return to the Home screen gives the user the impression that your app crashed. This is confusing, non-standard behavior and should be avoided.
Please check this forum:
https://forums.developer.apple.com/thread/52767.
It is happening with lot of people. In my project I redirected the user to our website page of downloading app from app store. In that way if the user is not getting update button in app store, at least the user can use the website in safari for the time being.
To specifically answer your question:
Use this URL to directly open to your app in the app store:
https://apps.apple.com/app/id########## where ########## is your app's 10 digit numeric ID. You can find that ID in App Store Connect under the App Information section. It's called "Apple ID".
I actually have terminate functionality built into my app if it becomes so out of date that it can no longer act on the data it receives from the server (my app is an information app that requires connectivity to my web service). My app has not been rejected for having this functionality after a dozen updates over a couple years, although that function has never been invoked. I will be switching to a static message instead of terminating the app, just to be safe to avoid future updates from being rejected.
I have found that the review process is at least somewhat subjective, and different reviewers may focus on different things and reject over something that has previously been overlooked many times.
func appUpdateAvailable() -> (Bool,String?) {
guard let info = Bundle.main.infoDictionary,
let identifier = info["CFBundleIdentifier"] as? String else {
return (false,nil)
}
// let storeInfoURL: String = "http://itunes.apple.com/lookupbundleId=\(identifier)&country=IN"
let storeInfoURL:String = "https://itunes.apple.com/IN/lookup?
bundleId=\(identifier)"
var upgradeAvailable = false
var versionAvailable = ""
// Get the main bundle of the app so that we can determine the app's
version number
let bundle = Bundle.main
if let infoDictionary = bundle.infoDictionary {
// The URL for this app on the iTunes store uses the Apple ID
for the This never changes, so it is a constant
let urlOnAppStore = NSURL(string: storeInfoURL)
if let dataInJSON = NSData(contentsOf: urlOnAppStore! as URL) {
// Try to deserialize the JSON that we got
if let dict: NSDictionary = try?
JSONSerialization.jsonObject(with: dataInJSON as Data, options:
JSONSerialization.ReadingOptions.allowFragments) as! [String:
AnyObject] as NSDictionary? {
if let results:NSArray = dict["results"] as? NSArray {
if let version = (results[0] as! [String:Any]).
["version"] as? String {
// Get the version number of the current version
installed on device
if let currentVersion =
infoDictionary["CFBundleShortVersionString"] as? String {
// Check if they are the same. If not, an
upgrade is available.
print("\(version)")
if version != currentVersion {
upgradeAvailable = true
versionAvailable = version
}
}
}
}
}
}
}
return (upgradeAvailable,versionAvailable)
}
func checkAppVersion(controller: UIViewController){
let appVersion = ForceUpdateAppVersion.shared.appUpdateAvailable()
if appVersion.0 {
alertController(controller: controller, title: "New Update", message: "New version \(appVersion.1 ?? "") is available")
}
}
func alertController(controller:UIViewController,title: String,message: String){
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Update", style: .default, handler: { alert in
guard let url = URL(string: "itms-apps://itunes.apple.com/app/ewap/id1536714073") else { return }
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(url)
}
}))
DispatchQueue.main.async {
controller.present(alertController, animated: true)
}
}
Use appgrades.io. Keep your app focus on delivering the business value and let 3rd party solution do their tricks. With appgrades, you can, once SDK integrated, create a custom view/alert to display for your old versions users asking them to update their apps. You can customize everything in the restriction view/alert to make it appear as part of your app.

addUIInterruptionMonitor(withDescription:handler:) not working on iOS 10 or 9

The following tests works fine on iOS 11. It dismisses the alert asking permissions to use the locations services and then zooms in in the map. On iOS 10 or 9, it does none of this and the test still succeeds
func testExample() {
let app = XCUIApplication()
var handled = false
var appeared = false
let token = addUIInterruptionMonitor(withDescription: "Location") { (alert) -> Bool in
appeared = true
let allow = alert.buttons["Allow"]
if allow.exists {
allow.tap()
handled = true
return true
}
return false
}
// Interruption won't happen without some kind of action.
app.tap()
removeUIInterruptionMonitor(token)
XCTAssertTrue(appeared && handled)
}
Does anyone have an idea why and/or a workaround?
Here's a project where you can reproduce the issue: https://github.com/TitouanVanBelle/Map
Update
Xcode 9.3 Beta's Changelogs show the following
XCTest UI interruption monitors now work correctly on devices and simulators running iOS 10. (33278282)
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let allowBtn = springboard.buttons["Allow"]
if allowBtn.waitForExistence(timeout: 10) {
allowBtn.tap()
}
Update .exists to .waitForExistence(timeout: 10), detail please check comments.
I had this problem and River2202's solution worked for me.
Note that this is not a fix to get the UIInterruptionMonitor to work, but a different way of dismissing the alert. You may as well remove the addUIInterruptionMonitor setup. You'll need to have the springboard.buttons["Allow"].exists test anywhere the permission alert could appear. If possible, force it to appear at an early stage of the testing so you don't need to worry about it again later.
Happily the springboard.buttons["Allow"].exists code still works in iOS 11, so you can have a single code path and not have to do one thing for iOS 10 and another for iOS 11.
Incidentally, I logged the base issue (that addUIInterruptionMonitor is not working pre-iOS 11) as a bug with Apple. It has been closed as a duplicate now, so I guess they acknowledge that it is a bug.
I used the #River2202 solution and it works better than the interruption one.
If you decide to use that, I strongly suggest that you use a waiter function. I created this one in order to wait on any kind of XCUIElement to appear:
Try it!
// function to wait for an ui element to appear on screen, with a default wait time of 20 seconds
// XCTWaiter was introduced after Xcode 8.3, which is handling better the timewait, it's not failing the test. It uses an enum which returns: 'Waiters can be used with or without a delegate to respond to events such as completion, timeout, or invalid expectation fulfilment.'
#discardableResult
func uiElementExists(for element: XCUIElement, timeout: TimeInterval = 20) -> Bool {
let expectation = XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == true"), object: element)
let result = XCTWaiter().wait(for: [expectation], timeout: timeout)
guard result == .completed else {
return false
}
return true
}

Is it okay to download data directly to watch OS from server

So I'm trying to make a watchOS app for a music streaming app, and I found an example pretty much close to what I'm going to make.
(https://github.com/belm/BaiduFM-Swift)
But It seems like the project is kinda outdated. According to the codes below, watch extension is getting required datas like sound, images via HttpRequest. From what I read, watchOS 3 supports Background Connectivity, (which enables app to transfer data more efficiently) and Apple encourages developers to process and get data from the main app.
What is right way to do it? Is there any good example to see?
// play song method in interface controller
HttpRequest.getSongLink(info.id, callback: {(link:SongLink?) -> Void in
if let songLink = link {
DataManager.shareDataManager.curSongLink = songLink
DataManager.shareDataManager.mp.stop()
var songUrl = Common.getCanPlaySongUrl(songLink.songLink)
DataManager.shareDataManager.mp.contentURL = NSURL(string: songUrl)
DataManager.shareDataManager.mp.prepareToPlay()
DataManager.shareDataManager.mp.play()
DataManager.shareDataManager.curPlayStatus = 1
Async.main{
self.songTimeLabel.setText(Common.getMinuteDisplay(songLink.time))
}
HttpRequest.getLrc(songLink.lrcLink, callback: { lrc -> Void in
if let songLrc = lrc {
DataManager.shareDataManager.curLrcInfo = Common.praseSongLrc(songLrc)
//println(songLrc)
}
})
}
})

NSURLSession with WatchOS 2.2 very slow

I try to download some JSON data from a web server with NSURLSession in a WatchOS 2.2 app. The same code that runs flawlessly on the iPhone itself, takes forever on the watch (using the simulator).
I checked out this example: https://github.com/shu223/watchOS-2-Sampler, which has a function to download and display an image via NSURLSession, and it has the same problem.
The code of this example is:
let url = NSURL(string:"https://pbs.twimg.com/profile_images/3186881240/fa714ece16d0fabccf903cec863b1949_400x400.png")!
let conf = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: conf)
task = session.dataTaskWithURL(url) { (data, res, error) -> Void in
if let e = error {
print("dataTaskWithURL fail: \(e.debugDescription)")
return
}
if let d = data {
let image = UIImage(data: d)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if self.isActive {
self.image.setImage(image)
}
})
}
}
task!.resume()
Playing around with it, I figured out that if I change the code to
let session = NSURLSession.sharedSession()
it works fine.
However, I cannot use this in my app because I need to setup delegates.
What can I do to get it working?
I'm still experiencing the same issue in watchOS3. Slow network request. Especially when the watch app was freshly installed, the first network request is usually very slow. It can take up to 30s and timeout, but instantly in iOS.
But I find immediate improvement by using the iOS app to make the network request and send it to the watch app.
I use WCSession sendMessage:replyHandler:errorHandler in watchOS to notify the iOS app to make a specific network request. Then I send back the response from the network request via the replyHandler in session:didReceiveMessage:replyHandler. There's only a short lag.
As a fallback, whenever sendMessage fails, I make the same request via the watch app, so it should still work if iPhone is not nearby.

Resources