NSUserActivity webpageURL becoming nil - ios

I have a universal link, say: https://universallink.com/account/settings
When my application is fully closed, this works perfectly. However, if app is open in background and the link is clicked from safari or from slack, or something like that, this is non functional.
What I have observed:
application(\_:willContinueUserActivityWithType:) is of type "NSUserActivityTypeBrowsingWeb"
application.userActivity is nil OR application.userActivity not nil but application.userActivity.webpageURL is nil during application(\_:willContinueUserActivityWithType:)
application(\_:continue:restorationHandler:) is never called.
application(_:didFinishLaunchingWithOptions:) and application(_:willFinishLaunchingWithOptions:) are returning true.
Checking for let url = launchOptions?[.url] as? URL in application(_:didFinishLaunchingWithOptions:) and application(_:willFinishLaunchingWithOptions:) finds nothing.
I am not exactly sure how to proceed in fixing this scenario. The user activity clearly knows that is was opened with a link, but the link is never preserved so as to be handled.
Does anyone know something else that may be causing this issue?

Related

GIDSignIn.sharedInstance.currentUser is always nil when app starts

I'm comming accross an issue similar to this one. Basically every time my app starts, I have to login with my Google Account.
Then, I have this property:
var isGoogleSessionOpen: Bool {
return GIDSignIn.sharedInstance().currentUser != nil
}
which is called as soon as the app starts to check if I have to show the LoginViewController or not.
My problem is that this call is always nil in first place, so I have to login every time my app starts.
also, as it's mentioned here, I'm configuring the scope like this:
if let signIn = GIDSignIn.sharedInstance() {
signIn.scopes = ["https://www.googleapis.com/auth/plus.login","https://www.googleapis.com/auth/plus.me"]
}
Any idea pls?
Regards
You need to sign in when the app starts.
GIDSignIn.sharedInstance().signInSilently()

Best practice for showing an error Alert

I am having trouble displaying an Alert in case of an Error properly.
My idea is: Everytime I download data from my backend with an completion block, I present an Alert if an error occurs.
query?.findObjectsInBackground(block: { (objects, error) -> Void in
if error != nil {
createAlert(error)
return
} else if let objects = objects {
}
Since I got more than one call in a ViewController at the same time, it may happen to find myself having more than 2 or 3 Alerts presenting at the same time saying e.g. "No Connection to the Internet".
It will constantly reload the Alert and it is a pain in terms of UI.
What is best practice to solve this issue?
My solution idea would be to put everything in a Singleton pattern and make sure no other other Alert is currently displayed.
Are there any better ways?
Instead of using a singleton pattern, you might prefer having an optional property (var noConnectivityAlert) in the class currently responsible for creating the alert.
Instead of the createAlert() method you would have a informUserAboutConnectivity() method.
func informUserAboutConnectivity() {
// If noConnectivityAlert is nil
// the method creates an alert and shows it.
// If the property is NOT nil
// do nothing (since the user is already informed).
}
When the internet connection would come back and then disappear again, some apps in the App Store would show an alert once again.
In that case, when the internet connection comes back you can directly set noConnectivityAlert = nil so that when the connection is lost, things will be handled nicely (a new alert will be created and shown).
By the way, in the iOS SDK, singletons are not used often. There are mostly used for providing a default and most common use case of a class (think of UserDefaults), or (of course) a shared manager/provider.

UserDefaults being read incorrectly sometimes

I have been developing an app that stores user data (username, etc.) between launches of the app; I stored this data in UserDefaults. However, I have recently noticed a problem: Sometimes, when I run the app, I will get some value back from UserDefaults.standard.object(forKey:), but other times I will get nil when I know for a fact that there is something there.
This makes no sense to me.
I have searched for the answer to this question on SO but only found this, which did not help.
In order to test this, I made an empty app and put the following in viewDidAppear, and then I ran the app once:
UserDefaults.standard.setValue("aValue", forKey: "aKey")
The above line is just to ensure that there is in fact a value stored. I then deleted the above line from viewDidAppear, and then I put this in didFinishLaunchingWithOptions:
print(UserDefaults.standard.object(forKey: "aKey") as? String)
I then ran the app 20 times. Here is my data for if the print(...) printed nil or "aValue":
βœ“ 𐄂 𐄂 𐄂 βœ“ 𐄂 βœ“ βœ“ βœ“ βœ“ βœ“ βœ“ 𐄂 βœ“ βœ“ βœ“ 𐄂 βœ“ βœ“ 𐄂
It printed nil 35% of the time and it seems to me fairly random too.
I have two questions:
Why would this happen, and how can I fix/prevent it?
It is crazy to put the set in viewDidAppear but the get in didFinishLaunching, because they are unrelated and you don't know anything about the order in which they will happen or even whether viewDidAppear will ever happen (not every view controller ever appears, after all).
Put them in the same place to run your test. For example, let's put them both in didFinishLaunching:
let val = UserDefaults.standard.object(forKey: "aKey") as? String
if val != nil {
print("got it")
} else {
print("didn't get it, setting it")
UserDefaults.standard.set("aValue", forKey: "aKey")
}
You'll find that this works just as you would expect.
EDIT Okay, thanks to the movie you posted, we see that there's a complication: you're testing this on the device. You are testing by repeatedly hitting the Run button in Xcode without stopping the app in a coherent way, and this possibly confuses Xcode so that it does a complete replacement, or so that it never gets a chance to write the defaults to disk in the first place. Instead, Run, switch to the device and hit the Home button, now switch back to Xcode and Stop. Now Run again. I think you'll find that you'll get a more consistent result.
It could happen if didFinishLaunchingWithOptions in AppDelegate is called after viewDidAppear in the view controller.
The recommended way (by Apple) is to register all key / value pairs as soon as possible (awakeFromNib or applicationWillFinishLaunching)
let defaultValues = ["aKey" : "aValue"]
UserDefaults.standard.register(defaults: defaultValues)
The default value "aValue" is considered until it is overwritten the first time.
If you reset the data of the app, the default value is read again.
This is the most reliable way.
PS: Never use valueForKey: and setValue:forKey: with UserDefaults

0_ os_lock_corruption_abort in NSURLProtocol

I've built a custom NSURLProtocol which is used by a WebView whilst it browses. But at seemingly random times (between a 20 seconds or a few minutes into browsing) I am getting an EXC_BREAKPOINT and the app stops running in my NSURLProtocol.
The relevant part of my NSURLProtocol is below, it's the last line which is showing the EXC_BREAKPOINT
self.mutableData = NSMutableData(data: data!)
self.response = response
self.client?.URLProtocol(self, didReceiveResponse: response!, cacheStoragePolicy: NSURLCacheStoragePolicy.Allowed)
self.client?.URLProtocol(self, didLoadData: data!)
self.client?.URLProtocolDidFinishLoading(self)
The Xcode error is visible below:
I'm totally bamboozled on this one. Does anyone have an idea what might be causing this, and how to fix it?
Thank you!
Sam
There's not enough context for me to fully understand the code here, much less guess what's wrong, but basically what's happening is that there's a lock (mutex) that has been deallocated, but is still being used somewhere down in the NSURL* stack.
This probably points to something not getting retained properly, but it is anybody's guess what or where. It might even be that your protocol is not getting retained properly, in which case you might be able to fix it by assigning your protocol object to a property on itself until after you call your last delegate method, then nulling it out.
With that said, there's reason to believe that this is a bug in the OS itself, so while you try to work around it, you should also file a bug. It will get duped to the other dozen or so bugs from other people who have asked this same question both here and on the Apple developer forums. :-)

Xcode 7 UI Testing: how to dismiss a series of system alerts in code

I am writing UI test cases using the new Xcode 7 UI Testing feature. At some point of my app, I ask the user for permission of camera access and push notification. So two iOS popups will show up: "MyApp Would Like to Access the Camera" popup and "MyApp Would Like to Send You Notifications" popup. I'd like my test to dismiss both popups.
UI recording generated the following code for me:
[app.alerts[#"cameraAccessTitle"].collectionViews.buttons[#"OK"] tap];
However, [app.alerts[#"cameraAccessTitle"] exists] resolves to false, and the code above generates an error: Assertion Failure: UI Testing Failure - Failure getting refresh snapshot Error Domain=XCTestManagerErrorDomain Code=13 "Error copying attributes -25202".
So what's the best way of dismissing a stack of system alerts in test? The system popups interrupt my app flow and fail my normal UI test cases immediately. In fact, any recommendations regarding how I can bypass the system alerts so I can resume testing the usual flow are appreciated.
This question might be related to this SO post which also doesn't have an answer: Xcode7 | Xcode UI Tests | How to handle location service alert?
Thanks in advance.
Xcode 7.1
Xcode 7.1 has finally fixed the issue with system alerts. There are, however, two small gotchas.
First, you need to set up a "UI Interuption Handler" before presenting the alert. This is our way of telling the framework how to handle an alert when it appears.
Second, after presenting the alert you must interact with the interface. Simply tapping the app works just fine, but is required.
addUIInterruptionMonitorWithDescription("Location Dialog") { (alert) -> Bool in
alert.buttons["Allow"].tap()
return true
}
app.buttons["Request Location"].tap()
app.tap() // need to interact with the app for the handler to fire
The "Location Dialog" is just a string to help the developer identify which handler was accessed, it is not specific to the type of alert.
I believe that returning true from the handler marks it as "complete", which means it won't be called again. For your situation I would try returning false so the second alert will trigger the handler again.
Xcode 7.0
The following will dismiss a single "system alert" in Xcode 7 Beta 6:
let app = XCUIApplication()
app.launch()
// trigger location permission dialog
app.alerts.element.collectionViews.buttons["Allow"].tap()
Beta 6 introduced a slew of fixes for UI Testing and I believe this was one of them.
Also note that I am calling -element directly on -alerts. Calling -element on an XCUIElementQuery forces the framework to choose the "one and only" matching element on the screen. This works great for alerts where you can only have one visible at a time. However, if you try this for a label and have two labels the framework will raise an exception.
Objective - C
-(void) registerHandlerforDescription: (NSString*) description {
[self addUIInterruptionMonitorWithDescription:description handler:^BOOL(XCUIElement * _Nonnull interruptingElement) {
XCUIElement *element = interruptingElement;
XCUIElement *allow = element.buttons[#"Allow"];
XCUIElement *ok = element.buttons[#"OK"];
if ([ok exists]) {
[ok tap];
return YES;
}
if ([allow exists]) {
[allow tap];
return YES;
}
return NO;
}];
}
-(void)setUp {
[super setUp];
self.continueAfterFailure = NO;
self.app = [[XCUIApplication alloc] init];
[self.app launch];
[self registerHandlerforDescription:#"β€œMyApp” would like to make data available to nearby Bluetooth devices even when you're not using app."];
[self registerHandlerforDescription:#"β€œMyApp” Would Like to Access Your Photos"];
[self registerHandlerforDescription:#"β€œMyApp” Would Like to Access the Camera"];
}
Swift
addUIInterruptionMonitorWithDescription("Description") { (alert) -> Bool in
alert.buttons["Allow"].tap()
alert.buttons["OK"].tap()
return true
}
Gosh.
It always taps on "Don't Allow" even though I deliberately say tap on "Allow"
At least
if app.alerts.element.collectionViews.buttons["Allow"].exists {
app.tap()
}
allows me to move on and do other tests.
For the ones who are looking for specific descriptions for specific system dialogs (like i did) there is none :) the string is just for testers tracking purposes. Related apple document link : https://developer.apple.com/documentation/xctest/xctestcase/1496273-adduiinterruptionmonitor
Update : xcode 9.2
The method is sometimes triggered sometimes not. Best workaround for me is when i know there will be a system alert, i add :
sleep(2)
app.tap()
and system alert is gone
God! I hate how XCTest has the worst time dealing with UIView Alerts. I have an app where I get 2 alerts the first one wants me to select "Allow" to enable locations services for App permissions, then on a splash page the user has to press a UIButton called "Turn on location" and finally there is a notification sms alert in a UIViewAlert and the user has to select "OK". The problem we were having was not being able to interact with the system Alerts, but also a race condition where behavior and its appearance on screen was untimely. It seems that if you use the alert.element.buttons["whateverText"].tap the logic of XCTest is to keep pressing until the time of the test runs out. So basically keep pressing anything on the screen until all the system alerts are clear of view.
This is a hack but this is what worked for me.
func testGetPastTheStupidAlerts() {
let app = XCUIApplication()
app.launch()
if app.alerts.element.collectionViews.buttons["Allow"].exists {
app.tap()
}
app.buttons["TURN ON MY LOCATION"].tap()
}
The string "Allow" is completely ignored and the logic to app.tap() is called evreytime an alert is in view and finally the button I wanted to reach ["Turn On Location"] is accessible and the test pass
~Totally confused, thanks Apple.
The only thing I found that reliably fixed this was to set up two separate tests to handle the alerts. In the first test, I call app.tap() and do nothing else. In the second test, I call app.tap() again and then do the real work.
On xcode 9.1, alerts are only being handled if the test device has iOS 11. Doesn't work on older iOS versions e.g 10.3 etc. Reference: https://forums.developer.apple.com/thread/86989
To handle alerts use this:
//Use this before the alerts appear. I am doing it before app.launch()
let allowButtonPredicate = NSPredicate(format: "label == 'Always Allow' || label == 'Allow'")
//1st alert
_ = addUIInterruptionMonitor(withDescription: "Allow to access your location?") { (alert) -> Bool in
let alwaysAllowButton = alert.buttons.matching(allowButtonPredicate).element.firstMatch
if alwaysAllowButton.exists {
alwaysAllowButton.tap()
return true
}
return false
}
//Copy paste if there are more than one alerts to handle in the app
#Joe Masilotti's answer is correct and thanks for that, it helped me a lot :)
I would just like to point out the one thing, and that is the UIInterruptionMonitor catches all system alerts presented in series TOGETHER, so that the action you apply in the completion handler gets applied to every alert ("Don't allow" or "OK"). If you want to handle alert actions differently, you have to check, inside the completion handler, which alert is currently presented e.g. by checking its static text, and then the action will be applied only on that alert.
Here's small code snippet for applying the "Don't allow" action on the second alert, in series of three alerts, and "OK" action on the remaining two:
addUIInterruptionMonitor(withDescription: "Access to sound recording") { (alert) -> Bool in
if alert.staticTexts["MyApp would like to use your microphone for recording your sound."].exists {
alert.buttons["Don’t Allow"].tap()
} else {
alert.buttons["OK"].tap()
}
return true
}
app.tap()
This is an old question but there is now another way to handle these alerts.
The system alert isn't accessibly from the app context of the app you are launched in, however you can access the app context anyway. Look at this simple example:
func testLoginHappyPath() {
let app = XCUIApplication()
app.textFields["Username"].typeText["Billy"]
app.secureTextFields["Password"].typeText["hunter2"]
app.buttons["Log In"].tap()
}
In a vacuum with a simulator already launched and permissions already granted or denied, this will work. But if we put it in a CI pipeline where it gets a brand new simulator, all of the sudden it won't be able to find that Username field because there's a notification alert popping up.
So now there's 3 choices on how to handle that:
Implicitly
There's already a default system alert interrupt handler. So in theory, simply trying to typeText on that first field should check for an interrupting event and handle it in the affirmative.
If everything works as designed, you won't have to write any code but you'll see an interruption logged and handled in the log, and your test will take a couple seconds more.
Explicitly via interruptionmonitor
I won't rewrite the previous work on this, but this is where you explicitly set up an interruptionmonitor to handle the specific alert being popped up - or whatever alerts you expect to happen.
This is useful if the built-in handler doesn't do what you want - or doesn't work at all.
Explicitly via XCUITest framework
In xCode 9.0 and above, you can switch between app contexts fluidly by simply defining multiple XCUIApplication() instances. Then you can locate the field you need via familiar methods. So to do this explicitly would look like the following:
func testLoginHappyPath() {
let app = XCUIApplication()
let springboardApp = XCUIApplication(bundleidentifier: "com.apple.springboard")
if springboardApp.alerts[""FunHappyApp" would like permission to own your soul."].exists {
springboardApp.alerts.buttons["Allow"].tap()
}
app.textFields["Username"].typeText["Billy"]
app.secureTextFields["Password"].typeText["hunter2"]
app.buttons["Log In"].tap()
}
Sounds like the approach to implementing camera access and notifications are threaded as you say, but not physically managed and left to chance when and how they are displayed.
I suspect one is triggered by the other and when it is programatically clicked it wipes out the other one as well (which Apple would probably never allow)
Think of it you're asking for a users permission then making the decision on their behalf? Why? Because you can't get your code to work maybe.
How to fix - trace where these two components are triggering the pop up dialogues - where are they being called?, rewrite to trigger just one, send an NSNotification when one dialogue has been completed to trigger and display the remaining one.
I would seriously discourage the approach of programatically clicking dialogue buttons meant for the user.

Resources