How to fix/debug a VoiceOver bug? - ios

What's my problem with the VO?
It gets stuck. It focuses only on the status bar (so it doesn't see the app at all) or it focuses temporarily on a header bar in the app (so I can change the focus between about 3 element in that field). Neither switching the VO off and on nor standard gestures can help a user in such a situation.
When exactly does it happen?
The app is quite big so the exact reason is hard to determine. That's why I want to know how you would debug it. But generally the app has login screens and inside screens. When the app starts and immediately goes to one of inside screens, the VO works perfectly. When the app starts and goes through login screens, the VO gets stuck after logging in.
Any code?
To change between the login screens and the inside screens I use UIApplicationDelegate
let storyboard = UIStoryboard(name: "StoryboardName", bundle: nil)
let vc: WantedViewController = storyboard.instantiateViewController(withIdentifier: "WantedViewControllerId") as! WantedViewController
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
Maybe it's too big change for the VO. The app uses also JGProgressHUD but I think I checked quite well that it couldn't be a reason.
Any suggestions and questions are welcome!

If you are using native UIKit widgets, then accessibility should be built in and Voiceover should find the widget. If you have a custom widget, you'll need to set isAccessibilityElement.

The presented code caused the issue indeed. I advise people with a similar problem to perform sergue instead:
performSegue(withIdentifier: "SegueToWantedViewControllerId", sender: nil)
Define it in storyboard for example with Kind as Present Modally and Presentation as Full Screen to get the same result but with VoiceOver working.

Related

Xcode UI Tests Multiple Buttons Found

I'm having a problem where my UI Test says multiple buttons found when using the following code.
app.buttons["Upgrade"].tap()
So I reran my unit test and set a breakpoint right before running that line and hit the record button and clicked the button and it generated the following code.
app.children(matching: .window).element(boundBy: 0).children(matching: .other).element(boundBy: 1).buttons["Upgrade"].tap()
Of course at the top of the test I have let app = XCUIApplication().
Any idea why this would be happening?
Sometimes when running p UIApplication.shared.windows in the debugger it has 2 values in the array. I'm not sure why as I never have multiple windows. The only interactions I have with windows is setting UIApplication.shared.keyWindow?.rootViewController to different view controllers sometimes, and the following code in didFinishLaunchingWithOptions.
// Get view controllers ready
self.window = UIWindow(frame: UIScreen.main.bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let mainViewController: ViewController = mainStoryboard.instantiateViewController(withIdentifier: "FirstView") as! ViewController
// Show view controller
self.window?.rootViewController = mainViewController
self.window?.makeKeyAndVisible()
That is within an if statement and within the else statement I have pretty much the same code except instead of FirstView it's SecondView.
This message appears because there is more than one button on the screen with the accessibilityIdentifier, accessibilityLabel or value, "Upgrade", so it can't work out which one to tap.
The reason that it works when you use the recorded version is because the recording tool has identified that the search needs to be narrowed down to search inside the element of type .other at index 1, in order to be sure of which "Upgrade" button to interact with.
It's not a problem with your window(s), but with the uniqueness of your buttons' identifiers, and how you're handling them in your test.
In a situation where the button is only used once on the page of your app in question, it's best to set a unique accessibilityIdentifier on the UIButton. Its value should be unique within that page of your app, so make sure you aren't using the same string anywhere else. Then you can access the button unambiguously:
// app code
let upgradeButton: UIButton!
upgradeButton.accessibilityIdentifier = "upgradeButton"
// test code
let app = XCUIApplication()
let upgradeButton = app.buttons["upgradeButton"]
upgradeButton.tap()
In the situation where there are multiple instances of the same upgrade button on screen at the same time (e.g. where the button is part of a repeated pattern on the screen, like if there are lots of products for sale), it's OK for them each to have the same accessibilityIdentifier, but you should change the way you access the element in your test, using element(boundBy:) to access items at specified indices:
// app code
let upgradeButton: UIButton!
upgradeButton.accessibilityIdentifier = "upgradeButton"
// test code
let app = XCUIApplication()
let upgradeButton = app.buttons["upgradeButton"].element(boundBy: 1) // second upgrade button
upgradeButton.tap()
In this situation, you could also take the approach of finding the correct container view, and searching for the upgrade button inside it.
// test code
let app = XCUIApplication()
let upgradeButtonContainer = app.descendants(matching: .any).containing(.button, identifier: "upgradeButton").element(boundBy: 1) // second upgrade button-containing element
let upgradeButton = upgradeButtonContainer.buttons["upgradeButton"]
upgradeButton.tap()
I faced same issue and tried multiple things to get out of it. Ultimately i found out out that the issue started happening when i updated my Xcode to 8.3.3 and simulator os to iOS 10.3.
Switched my simulator os back to iOS 10.2 and it worked perfectly.
I am seeing similar problems since updating to Xcode 8.3 where the XCUIElementQuery is returning multiple results when there should be only one. There may be a bug introduced in this build.
https://forums.developer.apple.com/thread/75546
describes the bug and the radar is here:
https://openradar.appspot.com/31498932
I've tried the posted workaround by trying to coerce my queries to return the desired object as an XCUIElementQuery and then using element(boundBy:) and it worked for me.
It's upsetting how unstable the UI Tests are.

Swift - Deeplink within app to launch popup viewcontroller

I'm trying to launch a deeplink from within a Label in Swift, the intention is that the deeplink launches a popup view controller with some additional information, these deeplinks will not be available externally in the app.
I have followed the following youtube video to setup the new view controller, https://www.youtube.com/watch?v=FgCIRMz_3dE, but the example here is on a button click, and i would like this to be dynamic as the content is driven from a database, i was hoping to use the following (https://github.com/TTTAttributedLabel/TTTAttributedLabel) to dyanmically add deeplinks into various words within a label.
The part i'm stuck on is how to convert the following snippet of code, into a format that can be run when the deeplink is selected, my understanding is this needs to be executed within the app delegate.
let popOverVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("sbPopUpID") as! PopUpViewController
self.addChildViewController(popOverVC)
popOverVC.view.frame = self.view.frame
self.view.addSubview(popOverVC.view)
popOverVC.didMoveToParentViewController(self)
Can anybody help here?
please rewrite in plain english with more details, but anyway is a bit obscure why use a deep link to "show a popup view controller with some additional information".
Passing info between controllers via deep links (custom URL is the correct name..) is wrong.
iOS is not web based.
So read other stackoverflow detailed answers to get the right approach.

iOS 9 Segue Causes App To Freeze (no crash or error thrown)

I have been working on this app for months now and from as far back as I can remember I have never had an issue with segues. The code is unchanged in terms of calling performSegueWithIdentifier but since my recent update to Xcode 7 and iOS 9 I have not been able to tack this issue.
I have tried:
Deleting button and creating new button w/ segue link
Using a direct segue from button to view, without the use of performSegueWithIdentifier
Connecting button to new blank viewController
When I press the button, no initial load functions are called on the destination VC (Ex: ViewDidLoad, ViewWillAppear, etc). When I connect it to a blank view, the segue works fine with the same code in place.
Since the code never stops, or breaks, and just seems to "freeze" in place while still running on Xcode I can't seem to even narrow this down to whats causing the issue. I have a similar segue that is also called from another button on the same ViewController that has no issues whatsoever.
Any thoughts on the matter are greatly appreciated!
EDIT: I have narrowed the issue down to the UITextView's causing the problem. Once the Text Views were removed the page loads fine via segue. I wonder what changed between iOS 8 and iOS 9 in terms of UITextView as I will have to remove the text views and completely re add new text views.
So basically the segue was freezing because of the UITextView's I was using in the destinationViewController. The following fixed the issue:
Delete all UITextView's
Add new UITextView's
you must leave the default lorem imposed text and change this programmatically in the viewDidLoad()
This was the fix for me, and from the research I have done on the issue it seems this is a bug in iOS 9 and Xcode 7.
Cheers!
NOTE: Removing the text in the UITextView (or making it longer then ~12 characters) is sufficient to work around it, no need to delete and recreate them. This is fixed in Xcode 7.1.1 and later.
I ran into the same issue and the fixes in this post (Xcode 7 crash: [NSLocalizableString length] 30000) solved the issue for me.
The first is to enable a localisation other than the base for the storyboard (see https://stackoverflow.com/a/32688815/3718974)
The second is to turn off the base localisation (see https://stackoverflow.com/a/32719247/3718974)
I think I have the same problem: I have a UITabelView with cells created from a nib file, when a user tap a cell this method is called:
and when I have the following method prepareForSegue:: the application crashes:
if I delete the line 129 Everything is ok , the method prepareForSegue:: open the right view and the label contactName is shown with its default text.
If I modify the method as follows prepareForSegue:: get exactly what you expect, without having any type of error:
let me know if you also get the same result
Any one who is facing this issue, i solved it by turning off the "Optimize rendering for windows scale" option in Debug of simulator window. I already had tried all of the above answers but could not solve the issue.
In the method in the first viewController where you activate the segue, do you have beginIgnoringInteractionEvents anywhere? If so the screen you segue to will be frozen and will ignore interaction events like you describe. If this is the case you can fix this by adding an endIgnoringInteractionEvents method before your segue method:
UIApplication.sharedApplication().endIgnoringInteractionEvents()
self.performSegueWithIdentifier("editItemToMyGearSegue", sender: self)
I realize this is an old topic, but appears to be still relevant. I was facing the same problem in Xcode 9, iOS11. My UITextViews are embedded inside UITableViewCells. Same symptoms as described here. The tricks with default text and placeholders did nothing for me, but I solved it by turning off the scrolling indicators for the text view in the xib. They were on by default, I guess, though unused.
Edit: this is probably an important detail... the views that were hanging all had an image NSTextAttachment in the attributed string of the text view. I think the image was wider than the available table cell content. With scrolling turned off, they appear to downscale.

How would one save end user changes to UITabBarController (Swift)?

I am a developer in training and have been working extensively with storyboard in order to create a fairly simple TabBarController based application. I have assigned tags (values 0-10) to 11 different views in the tab, and am trying to figure out how to make it so that the position of the views in the tabBar are saved when edited by the user. Right now, every time the app is started the, position of the tabs resets, even if changed by the user under the "more" tab automatically added by Xcode. I have started out by adding
func tabBarController(_tabBarController: UITabBarController!, didEndCustomizingViewControllers viewcontrollers: [AnyObject]!, changed: Bool){
if changed{
}
to my app delegate. I am not sure how I would implement such a seemingly simple feature, and would appreciate any help. I am programming in Xcode 6.1.1, on Mac OS 10.10.2, and coding in swift.
Thanks

iOS Duration of Splash Screen (Default.png)

im using a Default.png file for my iPad-app. It appears correctly but i could'nt find a way to modify the duration of the splash screen. Has somebody any suggestions? Google has many sites that show how to setup the startscreen but could'nt find a solution for my problem.
The first rule of Human Interface Guidelines for Splash Screens is: don't use splash screens. The second rule is: don't use splash screens!:
Supply a launch image to improve user experience.
Avoid using your launch image as an opportunity to provide:
An “application entry experience,” such as a splash screen
An About window
Branding elements, unless they are a static part of your application’s first screen
If you absolutely must include a long-duration splash screen, and have darn good reasons for doing so, the usual approach is to throw up a UIImageView containing a copy of you launch image in, e.g., application:didFinishLaunchingWithOptions: - which should provide the illusion of a lengthy splash screen.
But please don't.
Using a "splash screen" (Logo, etc) is not the idea of the Default.png!
Read the HIG from Apple.
The (splash) screen (named loading screen) is not for a Logo showing or something like this.
When having multitasking enabled, the "splash screen" shows up really rare.
The splash screen should, like the apple apps does, only show the interface coming up in the first application screen without any localized strings, etc.
Also keep in mind:
The faster the iOS Device get, the shorter you can see the Default.png. So avoid using it for any important CI/CD content.
The Default image is displayed while the app is loading and will be dismissed as soon as the app is ready. And there is no API to control that duration.
You can't technically modify the duration that the "Default" image stays there; it is designed to just be a temporary image "foreshadowing" the app actually starting up and isn't specifically designed as a splash screen.
I recommend that you keep the "splash screen effect" by adding an image view to the screen as the app starts in the -application:didFinishLaunchingWithOptions: method. You can then set a timer which calls a method to animate the splash off after the designated time you want it to be. It will be there for a little longer than you specify depending on how long it actually took the app to load up, but it will give the effect you're after.
You can set the image view's image to [UIImage imageNamed:#"Default"] and it will access that Default artwork for you.
You can't change the duration. If you want it be shown longer though, you can add the same image to a view that you show while you're loading your data!
There is a good blog post here on how to create a splash screen using a UIImageView with a timer:
http://nullpointr.wordpress.com/2012/02/19/iphone-dev-how-to-implement-a-splash-screen/
Useful for beginners, who are still learning the best way to do things in iOS.
As #Conrad Shultz answered, splash screen should be used only via the supplied LaunchScreen.storyboard file by Xcode.
However, in rare situations you do want to prolong the splash screen:
Download A LOT of files before the app starts since the app depends on them.
Other reason...
This is the way to do it:
Inside AppDelegate, under didFinishLaunchingWithOptions you should:
Create a VC that has the same splash image and the same constraints
Present it
Dismiss it after a given time
The code:
let splashVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "splash")
window?.makeKeyAndVisible()
if let root = window?.rootViewController
{
root.present(splashVC, animated: false, completion: nil)
let dispatchTime = DispatchTime.now() + 3
// didFinishLaunchingWithOptions will return and this block will be executed afterwards, hence, async..
DispatchQueue.main.asyncAfter(deadline: dispatchTime, execute: {
root.presentedViewController?.dismiss(animated: false, completion: nil)
})
}
}

Resources