iOS UI Testing cannot see the UILabel - xctest

I have a UI testing issue with some code I've got from another developer (Whose not with the project anymore). The code display a simple alert box at the top of the screen when there's an error and I cannot get my UI test to see the UILabel within it.
The structure of the box looks like this:
UIView
+ UILabel
+ UIButton
And in the custom class for the UIView there is this code:
// Original code
accessibilityIdentifier = "AlertBar" // Required for UI Testing.
// Bits I've added trying to make messageLabel visible to UI Test
isAccessibilityElement = true
accessibilityElements = [messageLabel]
messageLabel.accessibilityIdentifier = "AlertBarMessage"
messageLabel.isAccessibilityElement = true
}
If I breakpoint in the middle of my UI Test and dump the app into the log I can see the UIView:
Attributes: Application, pid: 60317, label: 'The App'
Element subtree:
→Application, 0x600001df8c40, pid: 60317, label: 'The App'
Window (Main), 0x600001dfa060, {{0.0, 0.0}, {390.0, 844.0}}
... app ui logged here!
Other, 0x600001dce220, {{4.0, 47.0}, {382.0, 107.7}}, identifier: 'AlertBar'
So it's clear that the alert is visible to XCTest, but nothing inside it. As you can tell from my added code I've been trying all sorts of things but so far I've not been able to make the UILabel (AlertBarMessage) visible.
What am I missing?

As per usual, after typing up a stackOVerFlow question I go back to the code and figure out the problem :-) In this case it was the isAccessibilityElement = true on the view. Making it accessible was effectively hiding the nested UILabel so turning it off fixed my test.
I know there are ways in Accessibility to setup the view correctly for accessibility so that screen readers see it as a single element and can read the message, but I'll have to work on that later for now.

Related

iOS Navigation Bar Prefers Large Titles Scroll Behaviour

In iOS 11 the system apps all compress the navigation bar as you scroll down if you enable prefersLargeTitles:
I can't figure out how to implement this in my own apps though, the bar stays the same by default:
The only thing I can see is Hide Bars On Swipe, but that hides the whole bar rather than compressing it:
This is just an empty project created in Xcode 9 beta and with a new storyboard added.
What do I need to do to get the same behaviour as the system apps?
Don't set anything regarding Large Titles in Interface Builder / Storyboard, only in code. That worked for me.
So in the navigation bar in storyboards, Prefers Large Titles unchecked.
In your view controller:
self.navigationController?.navigationBar.prefersLargeTitles = true
It seems like this issue is happening to people for different reasons. None of the above answers helped me, but here's what DID work...
I deconstructed my app to find the cause, which was the view hierarchy in the storyboard. It appears that the UITableView view HAS to the the first view in your view controller. I had a UITableView with two UIImageViews behind it and that's what was causing the issue. Once I removed those UIImageViews everything worked correctly.
My fix: I ended up creating a UIView in code, adding my two image views to that, THEN adding that UIView to the UITableview.backgroundView.
Hope this helps someone.
If you have to target older iOS versions, you’ll also have to wrap the assignment in an availability check:
if #available(iOS 11, *) {
self.navigationController?.navigationBar.prefersLargeTitles = true
}
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationBar.topItem?.title = "Hello"
navigationController?.navigationItem.largeTitleDisplayMode = .automatic
let attributes = [
NSAttributedStringKey.foregroundColor : UIColor.red,
]
navigationController?.navigationBar.largeTitleTextAttributes = attributes
} else {
// Fallback on earlier versions
}
http://iosrevisited.blogspot.in/2017/09/navigation-bar-with-large-titles-and.html

tvOS: Focus first button as preferred focus view when buttons are in horizontal order

I have been trying to focus the first button but focus engine takes the third button right below the tab bar as the first item to be focussed. I tried with preferred focus view but found that when i place the buttons in vertical order then preferred takes the preferred view to be focussed but when i placed all the buttons in horizontal plane it always takes the third button.The other approach i can think of if Focus Guide but i wonder how that will work in this scenario?
override weak var preferredFocusedView: UIView? {
get {
return thrirdButton
}
}
It happens because focus engine takes the nearest possible focus element as 1st element as you can see from the picture attached.I have attached the context screenshot for the view controller. Any help or clue to solve this will be appreciated.
Solution :
We need to add focus guide just above button 3 and redirect it to button one when the focus guide is focussed.
private var focusGuide = UIFocusGuide()
self.view.addLayoutGuide(focusGuide)
self.focusGuide.bottomAnchor.constraintEqualToAnchor(self.button3.topAnchor).active = true
self.focusGuide.leftAnchor.constraintEqualToAnchor(self.button3.leftAnchor).active = true
// Width and height
self.focusGuide.widthAnchor.constraintEqualToAnchor(self.button3.widthAnchor).active = true
self.focusGuide.heightAnchor.constraintEqualToAnchor(self.button3.heightAnchor).active = true
focusGuide.centerXAnchor.constraintEqualToAnchor(self.button3.centerXAnchor).active = true
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
self.focusGuide.preferredFocusedView = button1
}
Try adding a UIFocusGuide just above the button rows. In that case, before reaching the button, it will hit your focus guide in which you can redirect the preferredFocusedView to that particular button. The code to redirect is to be done by overriding didUpdateFocusInContext method.
Assuming you have setup the focus guide in viewDidload, the following is a sample,
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
super.didUpdateFocusInContext(context, withAnimationCoordinator: coordinator)
self.focusGuide.preferredFocusedView = self.mypreferredbutton
}
TO initialise a focus guide ( Do the addition of guide to view in viewDidLoad)
var focusGuide = UIFocusGuide()
self.view.addLayoutGuide(self.sidefocusGuide)
self.focusGuide.leftAnchor.constraintEqualToAnchor(self.placerLabel.leftAnchor).active = true
self.focusGuide.rightAnchor.constraintEqualToAnchor(self.placerLabel.rightAnchor).active = true
self.focusGuide.topAnchor.constraintEqualToAnchor(self.audioTable.topAnchor).active = true
I too faced a similar issue but somehow able to do it through this. The preferredFocusedView approach will also work, but you have to do lot of circus by updating some reference variable and then calling setneedsfocusupdate for that view. Try the focus guide way. Hope it helps.
EDITED:
I have added code how to setup the guide. In this case I have put the guide on the right side of my view. So, whenever your focus hits the guide, it redirects to the preferredfocusguide view that you want the focus to go to in the didUpdateFocusInContext. "There is only one view in focus at anytime.", remember thes ? So, the moment it hits the guide, the overriden method gets hit which in turn moves your focus to the guide's preferred view. For examples, you can refer to Apple's UIKitCatlog app for tvOS and here is one link explaining the same.
The best way to give the initial focus to your preferred view is by using preferredFocusEnivronments.
override var preferredFocusEnvironments: [UIFocusEnvironment] {
return [firstButton]
}
preferredFocusEnvironments will be called before didUpdateFocus, that way you can inform the system, where you want the focus to be redirected.

Thumbnail of previous image instead of cancel button while camera is open

i want to show the thumbnail of the previous image taken by camera instead of the cancel button while camera is running ...
Is that possible ?? Need help ..
Yep. Just capture the last image, keep it in memory (or save it to disk), then use it as one of the controls. We can do this using the overlay property of the Titanium.Media.showCamera function. Here is a quick example:
First we need an overlay view to show the image.
var overlayView = Ti.UI.createView();
var imageView = Ti.UI.createImageView({
width:44,
height:44,
left : 5
});
overlayView.add(imageView);
Now this is the function we use to open the camera with the overlay view. Note that we don't have controls so you need to add those (for closing etc). All we do right now is set the overlays image.
Titanium.Media.showCamera({
success:function(event) {
// called when media returned from the camera
imageView.image = event.media;
},
cancel:function() {},
error:function(error) {},
saveToPhotoGallery:true,
allowEditing:true,
mediaTypes:[Ti.Media.MEDIA_TYPE_PHOTO],
overlay : overlayView,
showControls: false // This is important!
});
To really make this work, you may need to save the event.media in a global variable, or use a similiar technique make sure overlayView will not be nulled out / garbage collected.
Also this is a barebones solution, not very robust, but this is the basic method I would use!

ActivityIndicator without dialog box on android?

Im new to titanium and i'm trying to create an a indeterminate preloader (or activity indicator as it is called in titanum). The problem is that on android, the activty indicator is automatically placed in a dialog box, preventing users from interacting with the app until the dialog is dismissed.
Is there any way to just add a simple indetermindate preloader without using a dialog box in android?
Thanks.
According to Appcelerator Docs
Activity indicators must be used differently on Android and iOS:
On Android, the activity indicator is a modal dialog that blocks the UI. Calling show displays the indicator, and calling hide removes it.
One option that you can use is setting cancelable property to true which let the user to cancel the activity indicator dialog by pressing the BACK button.
Appcelerator docs says :
An activity indicator can be used to show the progress of an operation
in the UI to let the user know that some action is taking place. An
activity indicator consists of a spinning animation and an optional
text message, and is used to indicate an ongoing activity of
indeterminate length. To show progress, use Titanium.UI.ProgressBar
instead.
Titanium.App.addEventListener('show_indicator', function(e) {
showIndicator(e.title_msg, e.sub_msg);
});
function showIndicator(title_msg, sub_msg) {
var actIndG = Titanium.UI.createActivityIndicator({
style : Titanium.UI.iPhone.ActivityIndicatorStyle.BIG,
top :10
left : 130,
height : 60,
width : 60,
height : screenheigth,
width : screenwidth
});
indView.add(actIndG);
indWin.open();
}
Up vote or Mark best if it helps you.

Check if user finished sliding on a continuous UISlider?

In my app, I have a couple of UISlider instances to change various values. The values are displayed right next to the slider, as well as rendered in a 3d space in another visible part of the app.
The 3d part includes some rather heavy calculations, and right now it doesn't seem possible to update it live as the slider changes. That would imply that I'd have to set the slider's continuous property to NO, therefore only getting updates when the slider has finished changing.
I'd prefer to have the displayed value update live, however. Is there a way to have a slider that is continuous (so I can update my value-label in real time) and still sends some kind of message once the user has finished interacting with it? My gut feeling right now is to subclass UISlider and override the touchesEnded: method. Is that feasible?
You can do this with simple target/actions.
Set a target and action for the UIControlEventValueChanged event, and then another target and action for the UIControlEventTouchUpInside event. With the continuous property set to YES, the value changed event will fire as the slider changes value, while the touch up inside event will only fire when the user releases the control.
I just had to do this, so I looked up touch properties, and used the full IBAction header.
This should be a viable alternative for people who want some extra control, though Jas's is definitely easier on the code side.
- (IBAction)itemSlider:(UISlider *)itemSlider withEvent:(UIEvent*)e;
{
UITouch * touch = [e.allTouches anyObject];
if( touch.phase != UITouchPhaseMoved && touch.phase != UITouchPhaseBegan)
{
//The user hasn't ended using the slider yet.
}
}
:D
Also note you should connect the UIControlEventTouchUpOutside event as well in case the user drags his finger out of the control before lifting it.
In Swift 3:
#IBAction func sliderValueChanged(_ slider: UISlider, _ event: UIEvent) {
guard let touch = event.allTouches?.first, touch.phase != .ended else {
// ended
return
}
// not ended yet
}